当存在 [1] 这样的现象,[2] 这样的做法,是否合理以及正确,或者有 [1] 这样的现象,为什么就可以有 [2] 这样的做法 ?
算法思想
[1] 当主串的第i个字符与子串的第j个字符失配时,若子串的前(k-1)个字符和子串的后(k-1)个字符匹配,‘p1……pk-1’=‘pj-k+1……pj-1’,则只需主串 S 的第 i 个字符与子串 P 的第 k 个字符开始向后比较即可,i 不必回溯。
[2] k的值只与子串的组成有关,而与主串无关,即 k 值与 j 失配位置之前的子串的结构相关,每一个 j 位置对应一个k值,用next[j]存储,表示当模式串中第 j 个字符与主串中第 i 个字符失配时( Si ≠ Pj) ,下一次模式串 P 中第next[j]个字符与主串 S 的第i个字符比较。
简而言之
若子串中,‘p1……pk-1’=‘pj-k+1……pj-1’,则在 j 处的失配,i 不回溯,直接和子串的 k 位置比较即可,k位置用 next[j] 存储。
问题提出
其实,算法思想中[1]是现象,也就是子串中需要存在这样的前后缀相等时,[2]是做法,表示在失配的位置 j ,模式串应该向右滑动 k 距离。那么当存在这样的现象,这样的做法,是否合理以及正确?或者有 [1] 这样的现象,为什么就可以有 [2] 这样的做法 ?很多地方的讲解都是利用这样的做法去解释现象,或者因为有这样的现象,就可以有这样的做法,没有揭露根本的本质原因,发现自然规律,然后利用自然规律去做一件巧妙的事情得到了很好的结果,但还是要解释清楚这样的这样的规律是如何带来这样巧妙的结果的。
数学证明
[1] 朴素的串匹配算法
[2] KMP算法证明
证明思想:在 j 处失配,下一次直接让 next[j]=Pk 和 i 匹配,则需要证明 Si-j+2,…, Si-k这 j-k-1 个数不用再和P0比较(即一定会匹配失败),直接让P1和Si-k+1进行比较,即Pk与Si进行比较。
根据已知条件得到:
递推推导
求解next[j] 数组
求解的思想就是数学证明的思想,利用递推来求解:
(相等,不相等(相等,不相等(相等,不相等(…))))
已知next[1]=0,next[j]=k,如何由next[j]求得next[j+1]?(得到一个递推式)
1o若P[j] == P[next[j]],则next[j+1] = k+1(即next[j+1] = next[j]+1);
2o 若P[j]≠P[next[j]],则令P[j]和P[next[next[j]]]比较:
若相等,则next[j+1] = next[next[j]]+1;
若不等,则沿失败链继续查找,直到某个P[next[…next[j]…]] == P[j],或next[…next[j]…]==0,这时都置next[j+1] = next[…next[j]…]+1。
代码实现:
已知:next[1]=0;设设next[j]=k,则1<k<j,满足:‘p1……pk-1’=‘pj-k+1……pj-1’
1、若pk == pj,则next[j+1]= next[j]+1;
2、若pk≠ pj,此时看作模式串作为子串与自身在第k位失配,应从next[k]位开始与j位比较,若pnext[k] ≠ pj,即重复 2 直至相等或初始条件next[1]=0;若pnext[k] = pj ,则next[j+1]=next[k]+1。
int Get_Index(sstring T, int next[]) {
//求模式串T的next函数值并存入数组next
i=1; next[1]=0; j=0;
while(i<=Strlength(T)) {
if ( j==0 || T[i]==T[j]) {++i; ++j;next[i]=j;}
else j=next[j];
}
} // Get_Index
注:可能存在next[j+1]与next[j]相等,故在pk= pj时改为next[j+1]=next[j],则代码改进如下:
int Get_Index(sstring T, int next[]) {
//求模式串T的next函数值并存入数据next
i=1; next[1]=0; j=0;
while(i<=Strlength(S) && j<=Strlength(T)) {
if ( j==0 || T[i]==T[j]) {
++i; ++j;
if (T[i]!=T[j]) next[i]=j
else next[i]=next[j];
else j=next[j];
}
}
} // Get_Index
KMP代码实现
int Index(sstring S,sstring T,int pos) {
//返回子串T在主串S中从第pos个字符开始的位置
//要求T非空,1≤pos ≤Strlength(S)
i=pos; j=1;
while(i<=Strlength(S) && j<=Strlength(T)) {
if (S[i]==T[j]) {++i; ++j;}
else {j=next[j];}
}
if (j> Strlength(T)) return (i-Strlength(T));
return 0;
} // Index