原贴:http://wangfulong888.spaces.live.com/Blog/cns!1prHQ8LyFhaJkVAwBy_o8DHw!126.entry
一、常见字符串hash函数
1、ASCII码相加
基本思想:将字符串的每一个字符的ASCII码相加,得出的值即为hash函数值。
代码举例:
num:=0;
for I:=1 to length(s) do inc(num,ord(s[I]));
冲突:例如’abc’与’bca’等元素相同而排列不同的字符串,显然会造成冲突。
改进:可以将字符的ASCII码乘上一个位权,然后再相加,这样可以避免一部分冲突。还可采取另外类似的附加权值方法。
代码举例:
num:=0;
for I:=1 to length(s) do inc(num,ord(s[I])*I);
2、ElfHash函数
基本思想:利用位运算,产生一个hash函数值。
代码举例:
num:=0;
for I:=1 to length(s) do begin
num:=(num shl 4)+ord(s[I]);
g:=num and ($f0000000);
if g>0 then num:=num xor (g shr 24);
num:=num and (not g);
end;
优点:压缩极为密集,是一个很好用的产生冲突极少的函数。
压缩方法
经过处理之后生成的hash函数可能比较零散,或者跨越区间比较大。此时可以采取生成整型数hash函数的经典方法。比如可以将生成的hash函数mod一个数而产生新的hash函数值。但这样会使产生冲突的概率增加。
冲突的解决
(1)二维数组静态拉链(自己第一次用,发明的垃圾算法)
基本思想:
生成hash:计算hash值后,将未被改变的hash赋为true,并将字符串存入list[对应hash值]中。
查找:读入关键字,如果对应的hash值为true,则在list[对应hash值]中进行查找。
代码举例:
list:array[1..maxn,1..maxl] of string;
hash:array[1..maxn] of boolean; (maxn为hash表长度,maxl为估计最大冲突个数)
缺点:浪费大量空间且紧缩时无法准确把握限度,效果不大。
2、动态链表拉链
基本思想:
对于每一个hash地址,产生一个链表存储冲突的字符串。
优点:可动态安排存储冲突的空间,防止空间浪费。
缺点:属于动态数据结构的操作,对于不熟悉的操作者容易造成失误。
3、静态一维数组模拟链表
基本思想:类似于动态链表拉链法。
代码举例:
list:array[1..maxn] of string;
next:array[1..maxn] of integer;
hash:array[1..maxn] of integer; (maxn为hash表长度)
其中,list记录所有的字符串。Hash中存储的为每个hash函数值所对应的第一个字符串的存储地址。Next(i)表示字符串list(I)的后继字符串为next(I)。经过next连接的所有字符串均为冲突字符串。
二、程序举例
[文明的复兴(From TJU)]
Problem
战 神Prince&Gush回归了,但许多原先的上层精灵越来越不安分。他们无法忍受失去权力的空虚感,开始重新寻找新的途径获取权利。他们直率急 躁的领导人King_Bette开始公开抨击权威,并散布谣言。 权利的统治需要统一,需要强硬,被逼无奈下正义的领袖开始收缴反动的资料,清除世界的毒瘤,借以踏上快速发展之路。
不良信息指的是一组单词,每个单词均为不良信息。
不良信息文本是指包含一系列的单词,且其中包含有不良信息。发布信息者经常在单词中加些字母以外的字符以搅乱正义的视线,于是Prince想请你为他写一个能够将这些不良信息屏蔽掉的工具。但是为了尽量降低误删率,他提出了下面一个要求
你只需要将字母完全匹配的单词屏蔽掉即可。
例如:
sex为不良信息时,
sex8,sex$,se#x均为不良信息
sexx 则不属于不良信息.
Input
第一行为一整数n,表示有n组测试数据。
每组测试数据第一行为一个正数k <= 10000,表示有k个需要被屏蔽的词语,均为小写字母。
以下k行每行一个单词。
最后一行为输入需要处理的文本(文本长度<=100000),单词与单词之间空格分开且所有字母为小写字母。
Output
每组测试数据输出一行和输入格式一致的文本,被屏蔽的单词的字母以*代替原字母。
字符串Hash散列表设计
By WangFL
By WangFL
一、常见字符串hash函数
1、ASCII码相加
基本思想:将字符串的每一个字符的ASCII码相加,得出的值即为hash函数值。
代码举例:
num:=0;
for I:=1 to length(s) do inc(num,ord(s[I]));
冲突:例如’abc’与’bca’等元素相同而排列不同的字符串,显然会造成冲突。
改进:可以将字符的ASCII码乘上一个位权,然后再相加,这样可以避免一部分冲突。还可采取另外类似的附加权值方法。
代码举例:
num:=0;
for I:=1 to length(s) do inc(num,ord(s[I])*I);
2、ElfHash函数
基本思想:利用位运算,产生一个hash函数值。
代码举例:
num:=0;
for I:=1 to length(s) do begin
num:=(num shl 4)+ord(s[I]);
g:=num and ($f0000000);
if g>0 then num:=num xor (g shr 24);
num:=num and (not g);
end;
优点:压缩极为密集,是一个很好用的产生冲突极少的函数。
压缩方法
经过处理之后生成的hash函数可能比较零散,或者跨越区间比较大。此时可以采取生成整型数hash函数的经典方法。比如可以将生成的hash函数mod一个数而产生新的hash函数值。但这样会使产生冲突的概率增加。
冲突的解决
(1)二维数组静态拉链(自己第一次用,发明的垃圾算法)
基本思想:
生成hash:计算hash值后,将未被改变的hash赋为true,并将字符串存入list[对应hash值]中。
查找:读入关键字,如果对应的hash值为true,则在list[对应hash值]中进行查找。
代码举例:
list:array[1..maxn,1..maxl] of string;
hash:array[1..maxn] of boolean; (maxn为hash表长度,maxl为估计最大冲突个数)
缺点:浪费大量空间且紧缩时无法准确把握限度,效果不大。
2、动态链表拉链
基本思想:
对于每一个hash地址,产生一个链表存储冲突的字符串。
优点:可动态安排存储冲突的空间,防止空间浪费。
缺点:属于动态数据结构的操作,对于不熟悉的操作者容易造成失误。
3、静态一维数组模拟链表
基本思想:类似于动态链表拉链法。
代码举例:
list:array[1..maxn] of string;
next:array[1..maxn] of integer;
hash:array[1..maxn] of integer; (maxn为hash表长度)
其中,list记录所有的字符串。Hash中存储的为每个hash函数值所对应的第一个字符串的存储地址。Next(i)表示字符串list(I)的后继字符串为next(I)。经过next连接的所有字符串均为冲突字符串。
二、程序举例
[文明的复兴(From TJU)]
Problem
战 神Prince&Gush回归了,但许多原先的上层精灵越来越不安分。他们无法忍受失去权力的空虚感,开始重新寻找新的途径获取权利。他们直率急 躁的领导人King_Bette开始公开抨击权威,并散布谣言。 权利的统治需要统一,需要强硬,被逼无奈下正义的领袖开始收缴反动的资料,清除世界的毒瘤,借以踏上快速发展之路。
不良信息指的是一组单词,每个单词均为不良信息。
不良信息文本是指包含一系列的单词,且其中包含有不良信息。发布信息者经常在单词中加些字母以外的字符以搅乱正义的视线,于是Prince想请你为他写一个能够将这些不良信息屏蔽掉的工具。但是为了尽量降低误删率,他提出了下面一个要求
你只需要将字母完全匹配的单词屏蔽掉即可。
例如:
sex为不良信息时,
sex8,sex$,se#x均为不良信息
sexx 则不属于不良信息.
Input
第一行为一整数n,表示有n组测试数据。
每组测试数据第一行为一个正数k <= 10000,表示有k个需要被屏蔽的词语,均为小写字母。
以下k行每行一个单词。
最后一行为输入需要处理的文本(文本长度<=100000),单词与单词之间空格分开且所有字母为小写字母。
Output
每组测试数据输出一行和输入格式一致的文本,被屏蔽的单词的字母以*代替原字母。
Sample Input
2
1
sex
sex sex8 sex$ s&&ex sexx aaa bbb
2
sex
sexy
sex sexy&
Sample Output
*** ***8 ***$ *&&** sexx aaa bbb
*** ****&
粗略分析题目,可知本题的意思就是输入一些“不良信息”的单词,建立一个等待查找的数据结构,然后对于随后输入的每个单词,去除除了字母之外的无关信息之外,在建立好的查找表中进行查找。如果找到,则将该单词中所有的字母改变为’*’即可。
1、直接顺序查找
最简单的思路就是存储所有不良信息于一个线性表,然后每读入一个单词,就遍历一遍这个线性表。假设不良信息个数为n,单词的个数为m,则时间复杂度为O (m*n)(仅考虑查找算法,而不考虑字符串的处理)。由于n<=10000,m<=100000,时间复杂度上限O(10^9),显然是要 超时的。而且经过代码验证,确实超时(-_-):
program Information;
const
maxn=10000;
var
hash:array[1..maxn] of ansistring;
len,n,inp,data:integer;
h,g,i,j:longint;
s,st:ansistring;
c:char;
function judge(x:ansistring):boolean;
var
ii:integer;
begin
judge:=false;
for ii:=1 to len do
if hash[ii]=x then begin
judge:=true;exit;
end;
end;
begin
readln(data);
for inp:=1 to data do begin
readln(n);len:=0;
for i:=1 to n do begin
readln(s);
inc(len);
hash[len]:=s;
end;
readln(s);h:=1;st:='';
for i:=1 to length(s) do begin
if s[i]<>' ' then begin
if (ord(s[i])>=97)and(ord(s[i])<=122) then st:=st+s[i];
end;
if (s[i]=' ')or(i=length(s)) then begin
if judge(st) then
for j:=h to i do
if (ord(s[i])>=97)and(ord(s[i])<=122) then s[j]:='*';
h:=i;st:='';
end;
end;
writeln(s);
end;
end.
2、快速排序+二分查找
3、使用堆结构或Trie结构
4、字符串Hash
在查找算法中,Hash的算法为最高。每次查找只需要O(1)的时间。对于这道题,时间复杂度可以粗略的估计为O(m+n)。但是Hash的一个弊端是容 易产生冲突。虽然ElfHash函数产生冲突的概率极小,但也是不可避免的。所以,这道题如果使用hash表,就必须处理冲突。我第一次看这题,首先映射 的就是hash。马上编了一个不处理冲突的采用ASCII码相加产生hash函数的方法的hash查找,并逐渐加大hash表的容量,结果还是1ms就 WA掉。随后加以改进,采用了ElfHash函数(仍然不处理冲突),到29ms的时候WA。可见,冲突是不可避免的。而处理冲突适宜运用拉链法:
program Information;
const
maxn=40000;
var
list:array[1..maxn] of ansistring;
next:array[1..maxn] of integer;
hash:array[1..maxn] of integer;
loc,n,inp,data,len:integer;
num,h,g,i,j:longint;
s,st:ansistring;
c:char;
d:boolean;
begin
readln(data);
for inp:=1 to data do begin
readln(n);loc:=0;
fillchar(hash,sizeof(hash),0);
fillchar(next,sizeof(next),0);
for i:=1 to n do begin
readln(s);
num:=0;
for j:=1 to length(s) do begin
num:=(num shl 4)+ord(s[j]);
g:=num and ($f0000000);
if g>0 then num:=num xor (g shr 24);
num:=num and (not g);
end;
if hash[num mod maxn]=0 then begin
inc(loc);
hash[num mod maxn]:=loc;
list[loc]:=s;
end
else begin
j:=hash[num mod maxn];
while next[j]<>0 do j:=next[j];
inc(loc);
list[loc]:=s;
next[j]:=loc;
end;
end;
readln(s);h:=1;num:=0;st:='';
for i:=1 to length(s) do begin
if s[i]<>' ' then begin
if (ord(s[i])>=97)and(ord(s[i])<=122) then begin
st:=st+s[i];
num:=(num shl 4)+ord(s[i]);
g:=num and ($f0000000);
if g>0 then num:=num xor (g shr 24);
num:=num and (not g);
end;
end;
if (s[i]=' ')or(i=length(s)) then begin
if hash[num mod maxn]<>0 then begin
d:=false;j:=hash[num mod maxn];
repeat
if list[j]=st then begin
d:=true;break;
end;
j:=next[j];
until next[j]=0;
if d then
for j:=h to i do
if (ord(s[i])>=97)and(ord(s[i])<=122) then s[j]:='*';
end;
h:=i;num:=0;st:='';
end;
end;
writeln(s);
end;
end.
这个程序用的是静态数组模拟链表进行的拉链。我曾经用的是第一种二维数组的拉链法,自己在fp下测试没有问题。但到了toj上面就Re216,可能是这样占用的空间太大,而toj对数组大小的要求比较严格。
四、小结
运用Hash表是时间效率最高的查找算法(基本可以说是用空间换的),但每个hash函数都不可能是完美无缺的。尤其对于数据范围比较大的题,我们必须找 到一个最合适的hash函数。对于字符串来说,ElfHash函数无疑是最合适的选择(是否有更好的hash函数,当然还要因题而异)。
但是,如果处理冲突比较繁琐,或者hash表+冲突处理所占用的空间超过了题目限制,就不如改用其它的数据结构(例如堆、Trie、平衡二叉树等)了。总 之,应对类似类型的题目,都要学会合理将时间与空间分配均匀、达到最佳效果。无论是采用哪种数据结构,(引用一下jyy的名言)AC就是硬道理。
2
1
sex
sex sex8 sex$ s&&ex sexx aaa bbb
2
sex
sexy
sex sexy&
Sample Output
*** ***8 ***$ *&&** sexx aaa bbb
*** ****&
粗略分析题目,可知本题的意思就是输入一些“不良信息”的单词,建立一个等待查找的数据结构,然后对于随后输入的每个单词,去除除了字母之外的无关信息之外,在建立好的查找表中进行查找。如果找到,则将该单词中所有的字母改变为’*’即可。
1、直接顺序查找
最简单的思路就是存储所有不良信息于一个线性表,然后每读入一个单词,就遍历一遍这个线性表。假设不良信息个数为n,单词的个数为m,则时间复杂度为O (m*n)(仅考虑查找算法,而不考虑字符串的处理)。由于n<=10000,m<=100000,时间复杂度上限O(10^9),显然是要 超时的。而且经过代码验证,确实超时(-_-):
program Information;
const
maxn=10000;
var
hash:array[1..maxn] of ansistring;
len,n,inp,data:integer;
h,g,i,j:longint;
s,st:ansistring;
c:char;
function judge(x:ansistring):boolean;
var
ii:integer;
begin
judge:=false;
for ii:=1 to len do
if hash[ii]=x then begin
judge:=true;exit;
end;
end;
begin
readln(data);
for inp:=1 to data do begin
readln(n);len:=0;
for i:=1 to n do begin
readln(s);
inc(len);
hash[len]:=s;
end;
readln(s);h:=1;st:='';
for i:=1 to length(s) do begin
if s[i]<>' ' then begin
if (ord(s[i])>=97)and(ord(s[i])<=122) then st:=st+s[i];
end;
if (s[i]=' ')or(i=length(s)) then begin
if judge(st) then
for j:=h to i do
if (ord(s[i])>=97)and(ord(s[i])<=122) then s[j]:='*';
h:=i;st:='';
end;
end;
writeln(s);
end;
end.
2、快速排序+二分查找
3、使用堆结构或Trie结构
4、字符串Hash
在查找算法中,Hash的算法为最高。每次查找只需要O(1)的时间。对于这道题,时间复杂度可以粗略的估计为O(m+n)。但是Hash的一个弊端是容 易产生冲突。虽然ElfHash函数产生冲突的概率极小,但也是不可避免的。所以,这道题如果使用hash表,就必须处理冲突。我第一次看这题,首先映射 的就是hash。马上编了一个不处理冲突的采用ASCII码相加产生hash函数的方法的hash查找,并逐渐加大hash表的容量,结果还是1ms就 WA掉。随后加以改进,采用了ElfHash函数(仍然不处理冲突),到29ms的时候WA。可见,冲突是不可避免的。而处理冲突适宜运用拉链法:
program Information;
const
maxn=40000;
var
list:array[1..maxn] of ansistring;
next:array[1..maxn] of integer;
hash:array[1..maxn] of integer;
loc,n,inp,data,len:integer;
num,h,g,i,j:longint;
s,st:ansistring;
c:char;
d:boolean;
begin
readln(data);
for inp:=1 to data do begin
readln(n);loc:=0;
fillchar(hash,sizeof(hash),0);
fillchar(next,sizeof(next),0);
for i:=1 to n do begin
readln(s);
num:=0;
for j:=1 to length(s) do begin
num:=(num shl 4)+ord(s[j]);
g:=num and ($f0000000);
if g>0 then num:=num xor (g shr 24);
num:=num and (not g);
end;
if hash[num mod maxn]=0 then begin
inc(loc);
hash[num mod maxn]:=loc;
list[loc]:=s;
end
else begin
j:=hash[num mod maxn];
while next[j]<>0 do j:=next[j];
inc(loc);
list[loc]:=s;
next[j]:=loc;
end;
end;
readln(s);h:=1;num:=0;st:='';
for i:=1 to length(s) do begin
if s[i]<>' ' then begin
if (ord(s[i])>=97)and(ord(s[i])<=122) then begin
st:=st+s[i];
num:=(num shl 4)+ord(s[i]);
g:=num and ($f0000000);
if g>0 then num:=num xor (g shr 24);
num:=num and (not g);
end;
end;
if (s[i]=' ')or(i=length(s)) then begin
if hash[num mod maxn]<>0 then begin
d:=false;j:=hash[num mod maxn];
repeat
if list[j]=st then begin
d:=true;break;
end;
j:=next[j];
until next[j]=0;
if d then
for j:=h to i do
if (ord(s[i])>=97)and(ord(s[i])<=122) then s[j]:='*';
end;
h:=i;num:=0;st:='';
end;
end;
writeln(s);
end;
end.
这个程序用的是静态数组模拟链表进行的拉链。我曾经用的是第一种二维数组的拉链法,自己在fp下测试没有问题。但到了toj上面就Re216,可能是这样占用的空间太大,而toj对数组大小的要求比较严格。
四、小结
运用Hash表是时间效率最高的查找算法(基本可以说是用空间换的),但每个hash函数都不可能是完美无缺的。尤其对于数据范围比较大的题,我们必须找 到一个最合适的hash函数。对于字符串来说,ElfHash函数无疑是最合适的选择(是否有更好的hash函数,当然还要因题而异)。
但是,如果处理冲突比较繁琐,或者hash表+冲突处理所占用的空间超过了题目限制,就不如改用其它的数据结构(例如堆、Trie、平衡二叉树等)了。总 之,应对类似类型的题目,都要学会合理将时间与空间分配均匀、达到最佳效果。无论是采用哪种数据结构,(引用一下jyy的名言)AC就是硬道理。
参考文献:李羽修冬令营论文