KMP匹配算法
- 应用环境:有一个文本串S,和一个模式串P,现在要判断S中是否有和P匹配的子串,并查找P在S中的位置,怎么解决呢?
暴力算法思路
假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置,则有:
如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;如果匹配失败(即S[i]! = P[j]),令i = i - j + 1,j = 0,即每次匹配失败时,i 回溯到上次开始匹配的下一个位置,j 被置为0。
s[N],p[M];
for(int i=1;i<=n;i++){
bool flag=true;
for(int j=1;j<m;j++){
if(s[i]!=p[j]){
flag=false;
break;
}
}
}
思路
- next[i]=j:以i为终点后缀和从1开始的前缀相等,且后缀长度最长;
- 最大前缀数和后缀数要小于字符串本身长度
P=“ababf” 的最长公共前后缀:
- P[0] 前面没有字符串,所以最长公共前后缀长度为 0。
- P[1] 前面的字符串为:a,a没有前后缀(前后缀长度要小于字符串长度)。最长公共前后缀长度为 0。
- P[2] 前面的字符串为:ab,它的前缀为:a,后缀为b。前缀不等于后缀,所以没有公共前后缀,最长公共前后缀长度为 0。
- P[3] 前面的字符串为:aba,aba 的前缀有:a,ab, 后缀有:a,ba。因为 ab 不等于 ba,所以最长公共前后缀为 a,最长公共前后缀长度为 1。
- P[4] 前面的字符串为:abab,abab 的前缀有:a,ab,aba,后缀有:a,ab, bab。最长公共前后缀为 ab,长度为 2。
//p是模式串(子串)
p[1,j]=p[i-j+1,i]//长度相等
KMP主要分两步:求next数组、匹配字符串。
求next数组
- 就是求P数组自身的最长前缀
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
for (int i = 2, j = 0; i <= n; i ++ )
{
//j大于0才有意义;当p[i] != p[j + 1]说明不匹配,需要重新
while (j && p[i] != p[j + 1]) j = ne[j];//将上一个位置的值赋给当前位置
if (p[i] == p[j + 1]) j ++ ;//当前元素匹配,j++
ne[i] = j;//记录当前数组下标为i的前缀数
}
动画
匹配字符串
- 当匹配过程到上图所示时,
- s[ a , b ] = p[ 1, j ] && s[ i ] != p[ j + 1 ] 此时要移动p串(不是移动1格,而是直接移动到下次能匹配的位置)
- 其中1串为[ 1, next[ j ] ],3串为[ j - next[ j ] + 1 , j ]。由匹配可知 1串等于3串,3串等于2串。所以直接移动p串使1到3的位置即可。这个操作可由**j = next[ j ]**直接完成。 如此往复下去,当 j == m时匹配成功。
- 不满足时相等时,相当于移动最长公共前后缀的长度,保证了此时最长前缀是一样的。
动画
for (int i = 1, j = 0; i <= m; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];//相当向后移动字符串
if (s[i] == p[j + 1]) j ++ ;//当最后一个满足是j++就可以与n相等
if (j == n)
{
printf("%d ", i - n);
j = ne[j];
}
}