【算法】KMP算法

一. 暴力匹配

字符串匹配的最直接的方法就是暴力匹配,而KMP算法也是基于暴力算法进行改进。暴力匹配的思想如下:

  1. 对于文本串T和模式串P,从模式串P的第 0 号位置、文本串第 i 0 i_0 i0 号位置开始逐一比对;
  2. 比对到中间某个时刻,若 T [ i ] = = P [ j ] T[i ] == P[j] T[i]==P[j],则比对继续进行, i + + , j + + i++, j++ i++,j++
  3. 如果比对失败,则从模式串第0位和文本串的第 i 0 + 1 i_0 + 1 i0+1位继续进行
    在这里插入图片描述

但是 T [ i 0 , i ) T[i_0 , i) T[i0,i) P [ 0 , j ) P[0, j) P[0,j)比对成功意味着 T [ i 0 , i ) T[i_0 , i) T[i0,i) P [ 0 , j ) P[0, j) P[0,j)完全相同的,掌握了 P [ 0 , j ) P[0, j) P[0,j)那么也就意味着掌握了 T [ i 0 , i ) T[i_0 , i) T[i0,i),下一轮的比对方案完全可以提前预知。

例1
在这里插入图片描述
在图中的比对过程中,主串中的 x 和模式串中的 y 失配,根据模式串中 y 以前的内容可以获知主串对应部分的内容。如果在下一步的比对过程中直接将主串中的 x 和模式串中的 e 进行比对,可以省去 6 次比对

二.KMP的基本思想

在对暴力破解的算法的分析中发现,对比在某个位置失败意味着在这之前的比对完全成功,主串中失配字符前的一段内容已经完全获知。利用这一点,对暴力算法可以进行两个方面的优化:

  1. 避免主串的回溯。暴力匹配当比对失败后,文本串的第 i 0 + 1 i_0 + 1 i0+1位、 i 0 + 2 i_0 + 2 i0+2位、……、 i − 1 i-1 i1位的对比结果完全可以推导出来,没有必要再进行比对尝试;
  2. 模式串快速移动。基于和上面相同的原因,模式串的新的比对位置不需要从 0 开始。如例一中的模式串,字符y和字符e的前面都包含了"abc"这一部分,因此y前面的部分能和主串匹配成功,那么e前面的也一定能匹配成功。

因此对于模式串中的每个位置 j ,都能提前找到一个替代位置。

例2
在这里插入图片描述

模式串"abababca",对字符c而言,2号位的 a 和4号位的 a 都是能够在字符 c 发生失配时的一个可选择的位置

在诸多可选的继任位置中,位置下标越大,意味着已经成功匹配的长度越长,剩下需要比对的位置也就越少,因此 j 的继任位置 n e x t [ j ] next[j] next[j]定义为:

n e x t [ j ] = max ⁡ ( k ∣ p 0 p 1 . . . p k − 1 = p j − k p j − k + 1 . . . p j − 1 ) next[j] = \max(k | p_0 p_ 1...p_{k - 1} = p_{j - k}p_{j - k + 1}...p_{j - 1}) next[j]=max(kp0p1...pk1=pjkpjk+1...pj1)

通常定义 n e x t [ 0 ] = − 1 next[0] = -1 next[0]=1或者 n e x t [ 1 ] = 0 next[1] = 0 next[1]=0(当字符串的下标从1开始时),这种规定是假想在模式串的起始位置的前一个有一个通配哨兵。

三.next[]的求法

1. 暴力求解

根据 n e x t [ j ] next[j] next[j]的定义,从逐一枚举字符P[j]的真前缀和真后缀,找出相等的真前缀和真后缀的长度,取长度的最大值即为 n e x t [ j ] next[j] next[j]

2. 递推求解

假设已经求得 n e x t [ 0 , . . . , j ] next[0, ... , j] next[0,...,j],递推求解 n e x t [ j + 1 ] next[j + 1] next[j+1]

n e x t [ j ] next[j] next[j]已知意味着 P [ 0 , 1 , . . . , n e x t [ j ] − 1 ] P[0, 1, ..., next[j] - 1] P[0,1,...,next[j]1] P [ j − n e x t [ j ] , . . . , j − 1 ] P[j - next[j], ... , j - 1] P[jnext[j],...,j1]是相等的,并且这个相等的部分是最大的,求取 n e x t [ j + 1 ] next[j + 1] next[j+1]时,只需要考察 P [ n e x t [ j ] ] = = P [ j ] P[next[j]] == P[j] P[next[j]]==P[j]是否成立。如果成立, n e x t [ j + 1 ] = n e x t [ j ] + 1 next[j + 1] = next[j] +1 next[j+1]=next[j]+1 ,如果不成立,再考察 P [ n e x t [ n e x t [ j ] ] ] = = P [ j ] P[next[next[j]]] == P[j] P[next[next[j]]]==P[j]是否成立,依次类推,最终会收敛于 n e x t [ 0 ] + 1 = 0 next[0] + 1 = 0 next[0]+1=0
在这里插入图片描述
插图来自视频

void buildNext(string str, int nt[]){
    int len = str.size();
    nt[0] = -1;
    int t = nt[0], j = 0;
    while(j < len - 1){
        if(t < 0 || str[j] == str[t]){
            nt[++j] = ++t;
        }else{
            t = nt[t];
        }
    }
}

四.KMP算法

在求解了next数组之后,kmp算法变得非常简单了。

int kmp(string str1, string str2){
    int nt[str2.size()];
    buildNext(str2, nt);//构建next表
    int i = 0, j = 0;
    while(i < str1.size() && j < str2.size()){//逐步比对
        if(j < 0 || str1[i] == str2[j]){//比对成功时,前进一位,j < 0表示和通配符比对成功
            i++; j++;
        }else{//对比失败,找到新的位置比对
            j = nt[j];
        }
    }
    return i - j;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值