KMP算法求next中 ,k = next[k]或者j = next[j]回溯的意义与目的 其不能替换为K--,j--的原因

KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。

写法一

经典五行代码(其将next[1]设为0 。ps(next[j]就是第j个元素前j-1个元素首尾重合部分个数加一next[j]就是第j个元素前j-1个元素首尾重合部分个数加一))


int GetNext(char ch[],int cLen,int next[]){//cLen为串ch的长度
    next[1] = 0;
    int i = 1,j = 0;
    while(i<=cLen){
        if(j==0||ch[i]==ch[j]) next[++i] = ++j;
        else j = next[j];
    }
}


写法二

单K值求法(其将next[1]设为-1 。ps(1不为下标,1在这里表示第一位数.因此next[1]表示第一位的最长相同前后缀的长度 next[i]储存的是string中前i位字符串前缀和后缀的最长长度再减一。如abadefg,next[3]存的是aba这个字符串前缀和后缀的最长长度减一。))

void cal_next(char *str, int *next, int len)
{
    next[0] = -1;//next[0]初始化为-1,-1表示不存在相同的最大前缀和最大后缀
    int k = -1;//k初始化为-1
    for (int q = 1; q <= len-1; q++)
    {
        while (k > -1 && str[k + 1] != str[q])//如果下一个不同,那么k就变成next[k],注意next[k]是小于k的,无论k取任何值。
        {
            k = next[k];//往前回溯
        }
        if (str[k + 1] == str[q])//如果相同,k++
        {
            k = k + 1;
        }
        next[q] = k;//这个是把算的k的值(就是相同的最大前缀和最大后缀长)赋给next[q]
    }
}

意义与目的

这里的虽然分为两种不同的写法,但是其代码目的是相同的,这里的K或者J指的就是最长相同前后缀的长度也可以理解为next[α](α为任意未知数) 的值
其中

 if(j==0||ch[i]==ch[j])
 while (k > -1 && str[k + 1] != str[q])

这两句的意思都是判断求下一关next[α](α为任意未知数)是否可以续上,是否可以续上的意思就是,例如abcdabcXY这里我们以next[α]表示α前一位的最长相同前后缀的长度加一为例假如我们已经知道了 next[9] (9表示X),next[9]=4 表示最长相同前后缀的长度为3(4-1)即abc 当我们想求next[10]时 就需要在已知的next[9]的基础上求,
我们先让X等于d即原式变为abcdabcdY,接下来我们就要判断abcdabcdY中的d是否可以接上,接上的意思就是,能否使最长相同前后缀的长度从3变成4即从abc变成abcd在这里我们可以看到是可行的,那么就是接上了,我们的K或者J就变成了5(4+1)。

然后实验另外一种结果,我们让X等于a即原式变成abcdabcaY,接下来我们就要判断abcdabcaY中的a是否可以接上,显然在上一段讲述中,只有d能接上,所以a不能接上。

所以来看我们的主题

k = next[k]或者j = next[j]回溯的意义与目的
当X=a而导致不能接上的时候,我们就需要用到k = next[k]或者j = next[j]来进行回溯,那么为什么要回溯呢?还记得我们KMP算法的目的吗,就是为了避免朴素算法(也叫暴力算法)的繁琐。我们现在不能接上了难道就要放弃吗?不,我们要退而求其次,看看还有没有短一点的最长相同前后缀的长度可以利用,如果就连长度为1的也好不到,那么下一步我们只好老老实实的用朴素移动一格。
那么问题来了?我们如何找到短一点的最长相同前后缀的长度呢?递减一个一个找吗?例如j=j--; k=k--;吗?答案是否定的。
还是刚才那个例子abcdabcaY在求next[10]时它等于4即abc当我们求next[Y]时如果用j=j--; k=k--;那么我们能得到next[10]=3 但是通过观察ab!=ca所以显然是错误的于是再进行j=j--; k=k--;。得到next[10]=2 观察a=a所以结果正确了,但是这只是凑巧了,当你带入abababaaY 这个例子的时候,当你求next[9]时你会发现用这个方法得到的结果为4但是正确的结果为2.
那我们怎么办呢?于是我们可以利用k = next[k]或者j = next[j],这条代码什么意思呢,就相当于abcdabcaY我们判断k=next[10]时找到以 最长相同前后缀(1) 为数组的 最长相同前后缀(2) 我们可以把 最长相同前后缀(1)的前缀 移动到 最长相同前后缀(2)的后缀处,再判断最长相同前后缀(2)的长度加一位置处的值是否与所求next的前一位置的数相等 即ch[i] ==ch[j]
讲的有点抽象,其中难点就在于next的具体表达模式,有好几种模式
请参考三种不同的设置方法
next【1】为-1 且下标从1开始,next的值为最长相同前后缀的长度再-1
next【1】为0 且下标从1开始,next的值为最长相同前后缀的长度再+1
next【0】为-1 且下标从0开始,next的值为最长相同前后缀的长度再-1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值