//概念: 前缀 后缀 //文本T a b c a b a a b c a b // | | | | //模式P a b a a
//KMP算法考虑T的每一个前缀的每一个后缀和P的前缀匹配(这句话太绕了,先不管)
对于模式P,1.写出要搜索的字符串它的prefix table
//例如,a b a b c //5个前缀 //a //a b //a b a //a b a b //a b a b c
2.对前缀表的每个字符串,当成独立的一个字符串,找出它的最长公共前后缀
//例如,对于上面的前缀a b a b //对这个字符串来说, //最长前缀是 a b a //最长后缀是 b a b
//但是a b a 和b a b不相等
//这个前后缀长度是3,所以减少到2 //前缀就是a b //后缀也是a b //相等,所以最长公共前后缀就是ab,长度是2
//对上面这个前缀表中的每一个字符串找出最长公共前后缀的长度
//长度, 字符串 //0 a //0 a b //1 a b a //2 a b a b //0 a b a b c
所以这个长度就是要写出的prefix table
//如果数组下标从0开始,一般最后prefixtable那个数不要,而是在table最前面加上-1 //如下: // a b a b c //-1 0 0 1 2
3.把文本,模式下标,前缀表写上
T a b a a c a b a b c a c P a b a b c -1 0 0 1 2 下标 0 1 2 3 4
4.开始匹配
//从P的下标0开始和T进行匹配,直到发现P的第三个下标位置的b和T相应位置不相等 //此时就是前缀表的作用了 //不匹配的这个b的前缀表的数字是1 //那么就把P的下标为1的字符移到这个位置来 // | //a b a a c a b a b c a c // a b a b c // -1 0 0 1 2 // |
结果P[1]和T对应位置还是不匹配,那么就看P[1]的前缀表对应是多少了,可以看出,对应的是0
那么,就把P[0]移动到这个不匹配的位置:
// a b a a c a b a b c a c // a b a b c // -1 0 0 1 2
继续匹配:
//P[0]这个位置不匹配,所以移动: // a b a a c a b a b c a c // a b a b c // -1 0 0 1 2
继续匹配:
//P[0]这个位置不匹配,移动,但是要注意,现在不匹配的这个位置的前缀表是-1,那么就是把P的最前面的?这个位置移动来和文本T对齐,-1就是0之前位置 // a b a a c a b a b c a c // ? a b a b c // -1 0 0 1 2
//P[0]这个位置不匹配,移动,但是要注意,现在不匹配的这个位置的前缀表是-1,那么就是把P的最前面的?这个位置移动来和文本T对齐,-1就是0之前位置 // a b a a c a b a b c a c // ? a b a b c // -1 0 0 1 2
由于?这个位置实际就是空的,所以是从P[0]开始匹配
// // a b a a c a b a b c a c // ? a b a b c // -1 0 0 1 2
匹配成功。
但是,如果T里面含有多个P,还要继续寻找的话
//P的最后个前缀表的前缀是2,那么就把P[2]移到c这个位置: // a b a a c a b a b c a c // ? a b a b c // -1 0 0 1 2接下来不用再移动了,因为已经到了边界
每次匹配都是从匹配不等的地方继续比较,最重要的是前缀表的计算方法。
接下来是计算前缀表的过程:
// 0 1 2 3 4 5 6 7 8 下标 // A B A B C A B A A 字符串 // 0 0 1 2 0 1 ...... prefix
//为了找出计算前缀表的规律,假设从下标为5的地方分隔字符串,如下:
// 0 1 2 3 4 5 |6 7 8 下标 // A B A B C A |B A A 字符串 // 0 0 1 2 0 1 |...... prefix //在这里先让prefix[0]=0,而不是上面讲的-1,这个-1之后再写
接下来就写出求前缀表的部分:
测试一下前缀表是否正确:
#include<iostream> using namespace std; void prefix_table(char pattern[], int prefix[], int n) { prefix[0] = 0; int len = 0; int i=1; while(i<n) { if(pattern[i] == pattern[len]) { len++; prefix[i] = len; i++; }else{ if(len>0) len = prefix[len-1]; else{ prefix[i] = len; i++; } } } } int main() { char pattern[] = "ABABCABAA"; int prefix[9]; int n =9; prefix_table(pattern,prefix,n); for(int i =0;i < n;++i) cout<<prefix[i]<<endl; return 0; }
结果如图:
结果正确,接下来对前缀表进行移位,因为为了KMP算法好写,所以前缀表依次后移一格,首位再变成-1
void move_prefix_table(int prefix[], int n) { int i; for(int i = n - 1;i>0;i--) prefix[i] = prefix[i - 1]; prefix[0] = -1; }
开始写KMP算法:
//设T长度为m, P长度为n //i指示T ,j指示P //i和j都从左开始向右移
void kmp(char text[],char pattern[]) { int n = strlen(pattern);//模式的长度 int m = strlen(text);//文本长度 int* prefix = new int[n]; //int* prefix = malloc(sizeof(int) * n); //准备前缀表 prefix_table(pattern, prefix, n); move_prefix_table(prefix,n); int i =0;//文本的下标 int j =0;//模式P的下标 while(i < m) { if(j == n-1 && text[i] == pattern[j])//说明找到了一个,然后继续,看后面的文本还有没有模式P { cout<<"位置:"<< (i-j)<<endl; j = prefix[j]; } if(text[i] == pattern[j])//如果当前位置的字符匹配的话,都后移一位 { i++; j++; }else{ j = prefix[j]; if(j == -1)//这就是特殊情况,就是反复说的前缀表的首位是-1,这时把pattern的第0位和text下一位对齐,在这里体现出来就是,i++;j++ { i++; j++; } } } }
最后做一个测试:
#include<iostream> using namespace std; void prefix_table(char pattern[], int prefix[], int n)//生成前缀表 { prefix[0] = 0; int len = 0; int i=1; while(i<n) { if(pattern[i] == pattern[len]) { len++; prefix[i] = len; i++; }else{//这部分要注意 if(len>0) len = prefix[len-1]; else{ prefix[i] = len; i++; } } } } void move_prefix_table(int prefix[], int n) { int i; for(int i = n - 1;i>0;i--) prefix[i] = prefix[i - 1]; prefix[0] = -1; } void kmp(char text[],char pattern[]) { int n = strlen(pattern);//模式的长度 int m = strlen(text);//文本长度 int* prefix = new int[n]; //int* prefix = malloc(sizeof(int) * n); //准备前缀表 prefix_table(pattern, prefix, n); move_prefix_table(prefix,n); int i =0;//文本的下标 int j =0;//模式P的下标 while(i < m) { if(j == n-1 && text[i] == pattern[j])//说明找到了一个,然后继续,看后面的文本还有没有模式P { cout<<"位置:"<< (i-j)<<endl; j = prefix[j]; } if(text[i] == pattern[j])//如果当前位置的字符匹配的话,都后移一位 { i++; j++; }else{ j = prefix[j]; if(j == -1)//这就是特殊情况,就是反复说的前缀表的首位是-1,这时把pattern的第0位和text下一位对齐,在这里体现出来就是,i++;j++ { i++; j++; } } } } int main() { char pattern[] = "ABABCABAA"; char text[] = "ABABCABABCABAAB"; kmp(text,pattern); /*int prefix[9]; int n =9; prefix_table(pattern,prefix,n); move_prefix_table(prefix,n); for(int i =0;i < n;++i) cout<<prefix[i]<<endl;*/ return 0; }
结果:
文章来源: KMP算法