kmp
经过几天断断续续的思考,KMP总算是差不多搞懂了。
主串s和模式串p进行匹配,p在s中出现的位置。
代码如下:
#include <cstdio> #include <iostream> using namespace std; const int N = 10001, M = 100001; char p[N], s[M]; int ne[N]; int main() { int n,m; cin>>n>>p+1>>m>>s+1; for(int i = 2, j = 0;i<=n;i++) { while(j && p[i] != p[j+1]) j = ne[j]; if(p[i] == p[j+1]) j++; ne[i] = j; } 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++; if(j == n) { cout<<i-n<<" "; j = ne[j]; } } }
首先ne[i]代表模式串中以i为结尾前缀和后缀相等的长度。
p字符串:a a c a a b
ne[i]值: 0 1 0 1 2 0
一位字母前后缀相等的长度为0,因为不能包括自身,所以从2开始。
i = 2, j = 0; p[2] = p[1+1], j = 1; 所以p[2] = 1;
j = 1, p[3] != p[1+1], j = ne[1] = 0; p[3] = 0;
p[4] = p[0+1], j = 1; ne[4] = 1;
p[5] = p[1+1], j = 2; ne[5] = 2;
p[6] != p[3], j = ne[2] = 1, p[6] != p[2], j = ne[1] = 0. i = i + 1 = 7;
模式串p以自己来不断的比较前缀后缀相等,采用双指针的方式,相等那么后移,不相等,那么j不停的前移。
最后一次当j = 0, 虽然没有再进入那个while循环判断,但是在下面的if(p[i] == p[j+1])这里还是会比较了第一个字母,相同那么j = 1, 不相同j还是为0,j = 0;
同样的,当匹配的时候,主串和模式串也是这样的不停的匹配的。
1 2 3 4 5 6 7 8
s: b a a c a a c a
p:a a c a a b
当i = 1, j = 0, a[i] != p[0+1]的时候,i自增下一次循环,j = 0;
当s[2] = p[0+1], j ++;
一直到s[7] != p[6], 这时候i = 7, j = 5,这时候就 j = ne[j] = ne[5] = 2;因为找到了前缀和后缀相等的长度,所以这时候p模式串就可以直接调过来,s[5] = p[1], s[6] = p[2],这是根据ne数组直接自动可以匹配的,这时候就只需比较:s[7] 与p[2+1],这里相等,所以j ++, i ++,继续往后比较,如果s[7] = d与p[3]不相等的话,那么j = ne[2] = 1, s[7] != p[2], j = ne[1] = 0,这时候跳出while循环,不要以为这里p[1] 和s[7]就不在进行比较了,实际上在下面的if(s[i] == p[j+1]) j++;这里实际上是再进行了一次比较的,如果相等, 那么下一次循环,j = 1, i++,比较的就是s[8] 和p[2]了。如果不相等的话,那么下一次比较就是s[8]和p[1],即从模式串p的第一个字母进行比较。
综上,如果模式串p的前后缀相等的长度越长即ne[]越大,跟s已经匹配的越多的话,那么即使当下一次s[i++] != p[j+1]的话,那么这时也不需要像双重枚举暴力那样,j 从头开始,i回到第一个匹配位置的下一个,如果上面越长,越大,实际上浪费了很多已有的资源空间。i没必要退回,j退回,而且还是通过ne数组来跳跃性的退回,比一位一位笨拙的退回效率高很多。j = n,说明已经匹配完了,因为p[n-1+1] = s[i], j++,这里是先比较匹配,再自增,从1开始的话,那么就是i - n。