1 字符串hash
思考一个问题:如何将一个二维整点P的坐标映射为一个整数,使得整点P可以由该整数唯一地代表。
假设一个点P的坐标为(x,y),那么可以令hash函数为H(P)=x*Range + y,这样对数据范围内的任意两个整点P1和P2,H(P1)都不会等于H(P2),就可以用H(P)唯一地代表该整点P,接着便可以通过整数hash的方法来进一步映射较小的范围。
字符串hash是指将一个字符串S映射为一个整数,使得该整数可以尽可能地唯一地代表字符串S。
例如假设字符串由大写字母A~Z构成,在这个基础上,令A=0,B=1…Z=25,这样就把26个大写字母对应到了二十六进制中,接着按照二十六进制转换为十进制的方法,就可以唯一地得到某个十进制数字。
int hashFunc(char S[],int len)
{
int id=0;
for(int i=0;i<len;i++)
{
id=id*26+(S[i]-'A');
}
return id;
}
显然,如果字符串S的长度比较长,那么转换得到的整数会很大。因此注意len的大小不能太大。
同理,如果又出现了小写字母a~z那么可以用五十二进制转换十进制,但这样导致得到的整数更大。
2 字符串hash进阶
对于上面的二十六进制转换为十进制的方法:
id=id*26+(S[i]-'A');
在这个转换中,虽然字符串与整数是一一对应的,但是由于没有适当处理,可能产生极大的整数,因此为了应对这种情况,我们可以舍弃一些“唯一性”,将产生的结果对应一个整数mod取模,也就是使用下列函数:
id = (id*26+(S[i]-'A'))%mod
通过这种方式可以把字符串转换成范围内可以接受的整数。但这产生了重复问题,不过幸运的是在int范围内,如果进制数设置为一个10^7级别的素数(例如10000019),同时把mod设置为一个10的9次方级别的素数(如1000000007),那么冲突的概率会变得很小。
2 KMP算法
如果给出两个字符串text和pattern,需要判断字符串pattern是否是字符串text的子串,一般把字符串text称为文本串,而把字符串pattern称为模式串,例如给定文本串text=“caniwaitforyourheart”,那么模式串pattern=‘wait’是它子串,而pattern=‘sorry’就不是。
暴力破解当然简单,但是复杂度达到了O(mn)级别,是无法承受的。
下面利用KMP算法解决问题,复杂度仅仅为O(n+m)。
2.1 next数组
假设有一个字符串s,那么它以i号位作为结尾的子串就是s[0,1,…i]。对该子串来说,长度为k+1的前缀和后缀为s[0,1,…k],s[i-k…i],现在定义一个int型的数组next,其中next[i]表示使子串s[0,1,2…i]的前缀s[0,1,2,3…k]等于后缀s[i-k…i]的最大的k,如果找不到相等的前后缀,那么令next[i]=-1,。显然,next[i]就是所求最长相等前后缀中前缀最后一位的下标。
具体过程可以看下图:
举个例子,例如next[2]=0,此时字符串为abc,当k=0时,前缀是s[0]=a,后缀为s[2-0…2],所以后缀是s[2]=a,前后缀相等,得到k=0;
解决思路如下:
- 初始化数组next,令j=next[0]=-1;
- 让i在1~len-1范围遍历,对每个i,执行3和4
- 不断令j=next[j],知道j回退为-1,或者s[i]=s[j+1]成立
- 如果s[i]==s[j+1],则next[i]=j+1,否则next[i]=j;
void getNext(char s[],int len)
{
int j=-1;
next[0]=-1;
for(int i=1;i<len;i++)
{
while(j!=-1&&s[i]!=s[j+1])
{
j = next[j];
}
if(s[i]==s[j+1])
{
j++ ;
}
next[i]=j;
}
}
2.2 KMP算法
在此前的基础上,KMP就是依样画葫芦。
更定文本串text=‘abababaabc’,模式串pattern=‘ababaab’,如下图所示:
令i指向text的当前欲比较位,令j指向pattern中当前已被匹配的最后位,这样只要text[i]==pattern[j+1],成立就说嘛pattern[j+1]也被匹配成功,让i和j同时加一继续比较,知道j达到m-1说明pattern是text的子串。
接着继续匹配,此时i指向text[5],j指向pattern[4],表面pattern[0…4]已经匹配成功。但是显然text[5]跟pattern[4+1]匹配失败,难道我们需要从0开始继续比较么?当然不需要
为了不让j退回-1,应找到离当前j最近的j`,使得text[i]==pattern[j`+1]成立,并且pattern[0…j`]仍然与text的相应位置处于匹配状态。从这里可以看到,next数组的含义就是但j+1位匹配失败时,j应该回退到的位置。
因此KMP算法的思路就是:
- 初始化j=-1,表示pattern当前已被匹配的最后位
- 让i遍历文本text,对每个i,执行3,4来视图匹配text[i]和pattern[j+1]
- 不断令j=next[j],知道j回退为-1,或者是text[i]==pattern[j+1]成立
- 如果text[i]==pattern[j+1],则令j++,如果j达到m-1,说明pattern是text的子串,返回true