引言
我们把寻找字符串A(模式串)在字符串B(主串)第一次完整地出现的位置,把这个过程叫做字符串匹配,例如下图:
在这种模式匹配中,最粗暴简单的方法:
开始之前记个k=0作为匹配失败的次数,i作为模式串比较字符的位置,j作为主串比较字符的位置;
匹配成功时,接续向下一个字符进行匹配,直到匹配模式串匹配完成;
当发现匹配失败时,模式串重新到首个字符,而主串则回溯到k+1的位置,k自加1;
![image](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWcuYWxpY2RuLmNvbS9pbWdleHRyYS9pMi8yNTk2NTMzOTcyL08xQ04wMU5ZN1ZWeDFmRERqWGkya1R1XyEhMjU5NjUzMzk3Mi5wbmc?x-oss-process=image/format,png)
这种方法是最简单的,但同时也是最低效的:因为在匹配的过程中,需要同时回溯i,j两个参数;但是这种回溯大部分都是多余的;为了解决这种低效,1977由Donald Knuth、Vaughan Pratt、James H. Morris三人于年联合了一个字符串模式匹配算法,Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP算法”。
代码实现:
int index(Str str,Str substr) { int i=1,j=1,k=i; while(i <= str.length && j<= substr.length) { if (str.ch[i]==substr[j]) { ++i; ++j; } else { j = 1; i =++k; #匹配失败; } } if(j > substr.length) { return k; } else { return 0; } }
KMP算法的匹配流程
假设主串S匹配到j的位置,模式串P匹配到i的位置,还没匹配结束,继续匹配下去会出现下面两种情况:
1,当P[i] == S[j]时,则继续匹配 i++,j++;
2,当P[i] != S[j]时,则 i =next[i],j++,此时等价于模式串P向右移动了 i -next[i]个单位;
以上只是带大家初步认识了KMP算法的基本匹配思想,下面则需要进行深入地了解一下next数组,以及对匹配思想的理解;
第一步,字符串相同的前后缀元素
在理解next数组之前,需要直到next数组是怎么求出来的,首先我们看一下下面的这张图
图中我们了解到几个信息:
S为主串;P为模式串;
图中的S[i-k] — S[i]部分与P中的P[j-k]到P[j](模式串P的前面)部分相同,同时也与P[0]到P[k](模式串P的前面)相同;
模式串向后移动了j-next[j]个单位;
从以上几部分信息,可以这样理解:
在P未移动之前之前有部分连续字符串与S已经匹配成功了,同时P的前面一小部分也能匹配成功,所以直接可以把P的首个字符移动到与后面字符串重复地方的开始字符,也就是P[j-k];
因为只是部分连续,所以在移动的过程捕获出现字符串匹配成功的可能。
上面重复的部分就是模式串的相同前后缀的最大长度,也就等于next函数,而移动的距离刚好是 j(匹配失败的位置) - next[j](前面字符串前后缀重复的最大长度)
字符串重复前后缀的最大长度也就是字符串前面的连续字符(从第一个开始)有多少个与后面的相同,自身与自身不算同一个。如下图:
也就能求出next数组,只不过是关于后一字符的;
第二步,已知next[1]....next[j],怎样求得next[j+1]
在第一步中,已经分析过了,next数组的值与前后缀的最大长度相关,而关于最大长度得出的next值 是后一字符的next数组的值;
这里,有一种比较权威的求next[j+1]的方法(这里是以第一个字符未p[0]为依据的):
next[0] = -1,因为第一个字符比较特殊;
若p[k] == p[j],则next[j+1] = next[j]+1 =k+1;
若p[k ] ≠ p[j],如果此时p[next[k]] == p[j ],则next[j+1] =next[k] + 1,否则继续递归前缀索引k = next[k],而后重复此过程。
注意到没,k字符在这里起到的是最大长度值的作用,例如下面这张图,
当j = 2时,k =0, p[k]= A 则因为p[3]!=p[k],所以 k =next[k]一直递归到 k =-1,则next[3] =k+1 =0;
依次类推我们可以了解到当 p[j] =D 时,next[j] =k +1 =2 ;
当p[j+1] = E时,next[j+1] = next[next[k]] +1 = 0;
总结
整个分析到这里基本上也就结束了,整个KMP算法的关键核心是求next函数,next函数需要我们联系到模式串的前后缀最大长度值。KMP算法虽然在字符串的匹配过程中著以高效,但依然存在着自己的一些弊端,一些大佬在此基础之上又进一步地对算法进行了优化。
KMP算法代码实现:
void getnext(Str substr,int next[]) { int i = 1,j = 0; next[1] = 0; while(i < substr.length) { if(j==0||substr.ch[i] == str.ch[j]) { i++; j++; next[i] = j; } else { j =next[j] } } } int KMP(Str str, Str substr, int next[]) { int i=1,j=1; while(i<= str.length && j <= substr.length) { if(j == 0 || str.ch[i] == substr.ch[j]) { ++i; ++j; } else { j =next[j]; } } if(j > substr.length) { return i -substr.length; } else { return 0; } }