参照了http://blog.csdn.net/v_july_v/article/details/7041827大神的总结
一、KMP的作用:匹配原始串和模式串,在原始串中找到最早出现模式串的坐标并返回之,如果没有找到返回-1
二、KMP的思想:对模式串搜索重复的子串,列出next[]数组,根据next[]数组在原始串和模式串匹配过程中移动模式串而非回溯原始串
三、KMP的时间复制度O(n+m),n为原始串的长度,m为模式串的长度
四、KMP的两个模板(两种写法的next[]数组是不同滴):
1、正规写法
/*其实在KMP匹配过程中,当发生两个串中字符比较不等时指针 i 不需要回溯, 而只要将 T 串向右滑动到一定位置继续进行字符间的比较。例如按此算法进行 模式串T = "abcac" 和主串S ="ababcabcabcacabca",S串中除了第3、7和10个 字符和T串中的字符比较了两次之外,其它字符和T串中的字符均只进行了一次 比较。你知道是因为什么原因吗? 以S6!=t4而失配的情况为例,因为已经得到 的匹配信息是:S2=t0,S3=t1,S4=t2,S5=t3,S6!=t4,而从T串本身得到的信 息是:t1!=t0,t2!=t0,t3=t0,这就是说,不可能存在一个从 S2或S3或S4开始 的子串和T串相等,但可能存在从S5开始的子串和T串相等,并且由于已经得到的 " S5=t3,t3=t0"信息就不需要再进行S5和t0的比较,而直接进行S6和t1的比较 即可。这里假定lena>lenb。 */ #include<cstdio> #include<iostream> #include<cstring> using namespace std; int lena,lenb; /*求next函数的过程是一个递推的过程: 1.首先由定义得next[0]=-1,next[1]=0; 2.假设已知next[j]=k,又T[j] = T[k],则显然有next[j+1]=k+1; 3.如果T[j]!= T[k],则令k=next[k],直至T[j]等于T[k]为止。*/ void getnext(string b,int next[]) { int j=0,k=-1; next[0]=-1; while(j<lenb) { if(k==-1||b[j]==b[k]) //此举旨在找寻B串中重复的东东 { k++,j++; //如果下一个b[j]等于b[k],那下一次next[j]要回到0的位置,这是高速回位的方法 if(b[k]!=b[j]) next[j]=k; else next[j]=next[k]; } else k=next[k]; } } int kmp(string a,string b) { int next[50],i=0,j=0; lena=a.length(); lenb=b.length(); getnext(b,next); while(i<lena&&j<lenb) { if(j==-1||a[i]==b[j]) { i++,j++; } else j=next[j]; } if(j==lenb) return i-lenb; return -1; } int main() { string a,b; while(cin>>a>>b) { printf("%d\n",kmp(a,b)); } }
倒写KMP:返回的是从右到左第一个匹配到的下标
#include<cstdio> #include<iostream> #include<cstring> using namespace std; void getnext(string b,int next[]) //寻找所需匹配字符串的next数组 { int len=b.size(); len--; next[len]=len+1; int k=len+1,j=len; while(j>=0) { if(k==len+1||b[j]==b[k]) { k--,j--; if(b[k]!=b[j]) next[j]=k; else next[j]=next[k]; } else k=next[k]; } } int kmp(string a,string b) { int i=a.size()-1,j=b.size()-1; int lenb=b.size(),lena=a.size(); int next[50]; getnext(b,next); while(i>=0&&j>=0) { if(j==lenb||a[i]==b[j]) { i--,j--; } else j=next[j]; } if(lena-i>=b.size()) return i+1; //返回下标 return -1; } int main() { string a,b; while(cin>>a>>b) { printf("%d\n",kmp(a,b)); } }
2、覆盖函数写法(转自http://blog.csdn.net/v_july_v/article/details/7041827,再次膜拜下大神):
覆盖函数所表征的是pattern本身的性质,可以让为其表征的是pattern从左开始的所有连续子串的自我覆盖程度。比如如下的字串,abaabcaba
可能上面的图令读者理解起来还是不那么清晰易懂,其实很简单,针对字符串abaabcaba
a(-1) b(-1)a(0) a(0) b(1) c(-1) a(0) b(1)a(2)
解释:
由于计数是从0始的,因此覆盖函数的值为0说明有1个匹配,对于从0还是从来开始计数是偏好问题,具体请自行调整,其中-1表示没有覆盖,那么何为覆盖呢,下面比较数学的来看一下定义,比如对于序列a0a1...aj-1 aj
要找到一个k,使它满足
a0a1...ak-1ak=aj-kaj-k+1...aj-1aj
而没有更大的k满足这个条件,就是说要找到尽可能大k,使pattern前k字符与后k字符相匹配,k要尽可能的大,原因是如果有比较大的k存在。
但若我们选择较小的满足条件的k,那么当失配时,我们就会使pattern向右移动的位置变大,而较少的移动位置是存在匹配的,这样我们就会把可能匹配的结果丢失。比如下面的序列,
在红色部分失配,正确的结果是k=1的情况,把pattern右移4位,如果选择k=0,右移5位则会产生错误。计算这个overlay函数的方法可以采用递推,可以想象如果对于pattern的前j个字符,如果覆盖函数值为k
a0a1...ak-1ak=aj-kaj-k+1...aj-1aj
则对于pattern的前j+1序列字符,则有如下可能
⑴ pattern[k+1]==pattern[j+1] 此时overlay(j+1)=k+1=overlay(j)+1
⑵ pattern[k+1]≠pattern[j+1] 此时只能在pattern前k+1个子符组所的子串中找到相应的overlay函数,h=overlay(k),如果此时pattern[h+1]==pattern[j+1],则overlay(j+1)=h+1否则重复(2)过程.
#include "stdafx.h" #include<iostream> #include<string> #include <vector> using namespace std; int kmp_find(const string& target,const string& pattern) { const int target_length=target.size(); const int pattern_length=pattern.size(); int* overlay_value=new int[pattern_length]; overlay_value[0]=-1; //remember:next array's first number was -1. int index=0; //next array for (int i=1;i<pattern_length;++i) //注,此处的i是从1开始的 { index=overlay_value[i-1]; while (index>=0 && pattern[index+1]!=pattern[i]) { index=overlay_value[index]; } if(pattern[index+1] == pattern[i]) { overlay_value[i]=index+1; } else { overlay_value[i]=-1; } } //mach algorithm start int pattern_index=0; int target_index=0; while (pattern_index<pattern_length && target_index<target_length) { if (target[target_index] == pattern[pattern_index]) { ++target_index; ++pattern_index; } else if(pattern_index==0) { ++target_index; } else { pattern_index=overlay_value[pattern_index-1]+1; } } if (pattern_index==pattern_length) { return target_index-pattern_index; } else { return -1; } delete [] overlay_value; } int main() { string sourc="ababc"; string pattern="abc"; cout<<kmp_find(sourc,pattern)<<endl; system("pause"); return 0; }