前言
今天数据结构课上讲了KMP算法,课上不是很理解,现在在纸上推算了一遍写下来以供将来复习用。
提示:我们是按照next[0]=-1来写的,网上有的可能不是这样。
完整代码如下(引用自csdn文章:点击查看
void GetNext(char s2[], int next[]) {
int len = strlen(s2);
int i = 0, j = -1;
next[0] = -1;
while (i < len) {
if (j == -1 || s2[i] == s2[j]) {
i++; j++;
next[i] = j;
}
else {
j = next[j];
}
}
}
int kmp(char s1[], char s2[], int next[]) {
int lens1 = strlen(s1);
int lens2 = strlen(s2);
int i = 0, j = 0;
while (i < lens1 && j < lens2)
{
if (j == -1 || s1[i] == s2[j]) {
i++; j++;
}
else {
j = next[j];
}
}
if (j == lens2) {
return i - j + 1;
}
else {
return -1;
}
}
int main() {
int next[15] = { 0 };
char s1[100], s2[100];
cin >> s1 >> s2;
GetNext(s2, next);
cout << kmp(s1, s2, next);
return 0;
}
首先声明,next数组表示我将目标串s与模拟串t一位位对比时读到第i个s与t不同,就向前将第next[i]个拽过来取代t中的i与s对齐。注意因为next数组不用管目标串,只有模拟串本身就可以写出他对应的next数组,所以我没法考虑第i个和第next[i]个是不是相同以及是不是与s中的相同,所以写出来代表的只是next[i]向前到头与i向前相同位数对应全等,不包括他们自己。例如:
综上,next数组给出了在第i个位置不相同时,向前回溯拉至此时i的位置的索引,特征是这两个元素之前拥有相同的一段序列且前面的这段序列(下面我们把它叫做最长相同序列)一直到头。
下面先说有了next数组后怎样程序化实现,即kmp函数。
ps一点:因为next表示在j之前拥有公共相同序列的位置,所以递推一次后的j一定是减小的。
int kmp(char s1[], char s2[], int next[]) {
//s1是目标串,s2是模拟串,next不说了
int lens1 = strlen(s1);
int lens2 = strlen(s2);
int i = 0, j = 0;
while (i < lens1 && j < lens2)
{
if (j == -1 || s1[i] == s2[j]) {
i++; j++;
}
//i,j是目前读到的位置,对应元素相同则再去比下一位置,所以
//s1[i] == s2[j]时,i,j++;而当j=-1时,说明从原本读到的j向前,
//每一个有最长相同序列的位置本身的元素与s1中的不相等,或者根本没有
//最长相同序列。这样一直会到0.而next[0]一定为-1,此时应该把s2整体
//拉至i的后面,故此时的i对应j为-1,j为0时i已经++了。
else {
j = next[j];
}
//此时的j1与s1中的不相等,那么看j2是否相等,以此类推。
}
if (j == lens2) {
return i - j + 1;
}//因为j=next[j]此递推是递减的,所以想要j走到头一定是通过
//s1[i]=s2[j]那部分走下来,所以j到头说明此时全等。
else {
return -1;
}
}
得到next数组的函数:
void GetNext(char s2[], int next[]) {
int len = strlen(s2);
int i = 0, j = -1;
next[0] = -1;
while (i < len) {
if (j == -1 || s2[i] == s2[j]) {
i++; j++;
next[i] = j;
}
else {
j = next[j];
}
}
}
假如此时前i已经得到了next值,next[i]=j1(假定此时j=j1),那么从j1往前一直到头(p2~p1)等于i前面相同长度的片段。(此时的工作都是为了求?即i+1处的next值)。此时循环:i<len ,j!=-1,如果s2[i]=s2[j1],那么j1往前包括j1都与i往前的片段相同,也就是?前的片段与j1+1前的片段相同,next[i+1]=j1,否则说明j1!=i,就看j2=i?因为next[j1]=j2,那么p2=p1中靠后的一段,而p1~p2整段等于i前面的一段,所以p2即等于i前面靠前的一段,也等于i前面靠后的一段。此时执行了j(j2)=next[j(j1)],若j2=i则取j2,否则一直递推下去,一直到找到最长的那段公共相同序列或者推到头(-1)。此时++i的next值为++j即0.
总结
掌握kmp一个是理解利用已走过的相同序列的原理,前i个next已知后第i+1个可以利用前i个的已知信息,利用了DP的思想。另外,KMP目前并不是最快的算法,后来的BM算法以及Sunday算法都要优于它。