关于KMP的笔记

文章目录


前言

今天数据结构课上讲了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算法都要优于它。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值