对于KMP很早之前就学过,但是一直没写博客,当再次用到时,发现忘的差不多了,所以题主花了两个小时重新拾取了一下,现在打算留下笔记,写下自己对KMP算法的理解。(在看本文章之前需要对KMP大致的理解,本文章只针对Next数组的求法+代码解析)
第一个问题:
字符串1: abacabababacababc
字符串2: abacababc
在字符串1中查找字符串2出现的次数,常规思路,暴力,时间复杂度:O(n2),只能解决1000长度以内的字符串。
暴力算法因为存在指针回溯,导致复杂度偏高。暴力基础上解决指针回溯就是KMP算法,时间复杂度:O(n)
学习KMP算法前,需要先学习下什么是最长公共前后缀。
假设字符串长度为len, 即str【0】 ~ str【len-1】 , 设定 K在区间 【0,len-1】内的任意整数
字符串前缀:包含首字符在内的子串。 即 子串: str【0】 ~ str【k】
字符串后缀:包含尾字符在内的子串。 即 子串: str【k】 ~ str【len-1】
公共前后缀:即字符串前缀完全等于字符串后缀。
最长公共前后缀: 公共前后缀加个最长条件即可。
那么来求字符串abacababc 的next。
第一步分离出全部的字符串前缀。
第二步:将得到最长公共前后缀长度表向下移一位,末尾舍去,首位填-1,得到的数组就是next数组。(如下图)
那么得到了next的数组有什么用呢?我们模拟一遍匹配就好了
还是上面的例子:
字符串1: abacabababacababc
字符串2: abacababc
我们得到的字符串2的next数组为 {-1,0,0,1,0,1,2,3,2};
首先暴力匹配, 设定变量i是指向字符串1待匹配的元素, 变量k指向字符串2待匹配元素
回退如下
不难看出:
当不成功匹配的时,则k回退,k值等于对应的next的值。
即:next数组的作用: 当发现不匹配时,k回退到对应的next值,即next值之前的元素都已经记忆匹配,如上图绿色下划线部分。
代码解析
next:
1 void GetNext(){ //next数组求法 2 int i=0,k=-1; 3 Next[0]=-1; 4 while (i<m){ 5 if (k==-1||st[i]==st[k]){ 6 i++; 7 k++; 8 Next[i]=k; 9 } 10 else 11 k=Next[k]; 12 } 13 }
唯一不好理解的应该是 第11行的 k=Next【k】,为什么要这样回退?
如下例子。
KMP:
1 int KMP(char str[],char st[]){ //不存在输出-1,存在则输出首地址 2 GetNext(); 3 int i=0,j=0,n=strlen(str),m=strlen(st); 4 while (i<n&&j<m){ 5 if (j==-1||str[i]==st[j]){ 6 i++; 7 j++; 8 9 } 10 else 11 j=Next[j]; 12 } 13 if (j>=m) return i-j+1; 14 return -1; 15 }
附上签到题:
1 #include<iostream> 2 using namespace std; 3 4 5 int n,m,str[1000010],st[1010],Next[10010]; 6 7 void GetNext(){ 8 int i=0,k=-1; 9 Next[0]=-1; 10 while (i<m){ 11 if (k==-1||st[i]==st[k]){ 12 i++; 13 k++; 14 Next[i]=k; 15 } 16 else 17 k=Next[k]; 18 } 19 } 20 21 int KMP(){ 22 GetNext(); 23 int i=0,j=0; 24 while (i<n&&j<m){ 25 if (j==-1||str[i]==st[j]){ 26 i++; 27 j++; 28 29 } 30 else 31 j=Next[j]; 32 } 33 if (j>=m) return i-j+1; 34 return -1; 35 } 36 int main(){ 37 int T; 38 scanf("%d",&T); 39 while (T--){ 40 scanf("%d%d",&n,&m); 41 for (int i=0;i<n;i++) 42 scanf("%d",&str[i]); 43 for (int i=0;i<m;i++) 44 scanf("%d",&st[i]); 45 cout<<KMP()<<endl; 46 } 47 return 0; 48 }
1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 5 char str[1010],st[1010]; 6 int n,m,ans,Next[1010]; 7 void GetNext(){ 8 int i=0,k=-1; 9 Next[0]=-1; 10 while (i<m){ 11 if (k==-1||st[i]==st[k]){ 12 i++; 13 k++; 14 Next[i]=k; 15 } 16 else k=Next[k]; 17 } 18 } 19 20 void KMP(){ 21 GetNext(); 22 ans=0; 23 int i=0,j=0; 24 while (i<n){ 25 if (j==-1||str[i]==st[j]){ 26 i++; 27 j++; 28 } 29 else 30 j=Next[j]; 31 if (j==m){ 32 ans++; 33 j=0; 34 } 35 } 36 return ; 37 } 38 int main(){ 39 while (true){ 40 scanf("%s",str); 41 if (str[0]=='#') break; 42 scanf("%s",st); 43 n=strlen(str); 44 m=strlen(st); 45 KMP(); 46 printf("%d ",ans); 47 } 48 49 return 0; 50 }