KMP算法

匿名 (未验证) 提交于 2019-12-03 00:22:01
//概念: 前缀           后缀 //文本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算法
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!