KMP算法

转自队友wwg博客:KMP算法

部分参考于大佬的博客:https://blog.csdn.net/starstar1992/article/details/54913261/

从头到尾彻底理解KMP:https://blog.csdn.net/v_july_v/article/details/7041827

一:单模字符串匹配问题与BF算法O(n*m):

(一)单模字符串匹配问题

    所谓单模就是模式串只有一个,多模字符串匹配问题属于ac自动机部分,这里不做分析 

(二)BF算法O(n*m)

    两个指针,一个指针遍历文本串,另一个遍历模式串,每次遍历模板串后,文本串指针向前移动一个单位,时间复杂度为O(n*m)

 
#include<iostream>
#include<string>
 
using namespace std;
 
int BF(string ts, string ps){
    int result = -1;
    int ts_len = ts.size(),ps_len = ps.size();
    int i,j;
    for(i = 0, j = 0; i< ts_len - ps_len && j < ps_len;)
        if(ts[i] == ps[j])  ++i,++j;
        else    i = i - j + 1,j = 0;
    if(j == ps_len) result = i-j;
    return result;
}
 
int main(){
    string s = "ABCDABABGFABAC";
    string p = "ABAB";
    cout<<BF(s,p)<<endl;
    return 0;
}

二:KMP算法所解决的问题:

  kmp算法是解决单模字符串匹配问题的高效算法,利用next数组(失配函数)存储最长公共前缀后缀长度,优化BF算法,时间复杂度为      O(n+m)

三:算法描述与详解:

(一)前缀,真前缀,后缀,真后缀,最长公共前缀后缀长度

    (1)前缀:字符串从0 ~ i ( i <= len )的所有子串,例如:”ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB,ABCDABD]

    (2)真前缀:前缀除去本身,例如:”ABCDABD”的真前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB]

    (3)后缀:字符串i ~ n( i >= 0 )的所有子串,例如:”ABCDABD”的后缀为[ABCDABD,BCDABD, CDABD, DABD, ABD, BD, D]

    (4)真后缀:后缀除去本身,例如:”ABCDABD”的后缀为[BCDABD, CDABD, DABD, ABD, BD, D]

    (5)最长公共前缀后缀长度(默认为真前缀后缀):前缀与后缀相同的子串的最大长度:

         例如:”ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为 [BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,最大公共                      前缀后缀长度为2

(二)kmp算法描述:

       在BF算法基础上,首先回顾BF算法,BF算法中每次一轮匹配结束,文本串的指针回溯到下一位,而实际情况下,会导致很多重复的 check,其实只需要回溯至最长公共前缀后缀处即可,如图所示,看图会更加清晰:

        next数组返回当前的最长公共前后缀长度,假设为len。因为数组是由0开始的,所以next数组让第len位与主串匹配就是拿最长前缀之后  的第1位与失配位重新匹配

(三)算法可行性的描述:

四:算法实现:

(一)next数组的求解---kmp核心所在

 
void ca_next(string str, int len){
    int k = -1;next[0] = -1;//-1表示不存在最大公共前缀后缀,方便后面的访问
    for (int i = 1; i < len; i++){
        while (k > -1 && str[k + 1] != str[i])  k = next[k];
        if (str[k + 1] == str[i])   k++;
        next[i] = k;
    }
}

(二)KMP算法:

 

#include<bits/stdc++.h>
 
using namespace std;
 
const int maxn = 206;
 
int next[maxn];
 
void ca_next(string str, int len){
    int k = -1;next[0] = -1;//-1表示不存在最大公共前缀后缀,方便后面的访问
    for (int i = 1; i < len; i++){
        while (k > -1 && str[k + 1] != str[i])  k = next[k];
        if (str[k + 1] == str[i])   k++;
        next[i] = k;
    }
}
 
int KMP(string ts, string ps){
    int ts_len = ts.size(),ps_len = ps.size();
    ca_next(ps, ps_len);
    int k = -1;
    for (int i = 0; i < ts_len; i++){
        while (k >-1 && ps[k + 1] != ts[i])  k = next[k];
        if (ps[k + 1] == ts[i])   k++;
        if (k == ps_len - 1)  return i - ps_len + 1;
    }
    return -1;
}
 
int main(){
    string s = "ABCDABABGFABAC";
    string p = "ABAB";
    cout<<KMP(s,p)<<endl;
    return 0;
}

五:难点解析:k = next [ k ]

1.相同的最长前缀和最长后缀的长度 就是k。

2.假设循环进行到 第i次,即已经计算了next[i],我们是怎么计算next[i+1]呢?

比如我们已经知道ababab,i=4时,next[4]=2(k=2,表示该字符串的前5个字母组成的子串ababa存在相同的最长前缀和最长后缀的长度是3,所以k=2,next[4]=2.那么对于字符串ababab,我们计算next[5]的时候,此时i=5, k=2(上一步循环结束后的结果)。那么我们需要比较的是str[k+1]和str[i]是否相等,其实就是str[1]和str[5]是否相等
(1)如果相等,那么跳出while(),进入if(),k++,接着next[i]=k。即对于ababab,我们会得出next[5]=3
(2)如果不等,我们可以用”ababac“描述这种情况。 不等,进入while()里面,进行k=next[k],这句话是说,在str[k + 1] != str[i]的情况下,我们往前找一个k,使str[k + 1]==str[i],程序的意思是说,一旦str[k + 1] != str[i],即在后缀里面找不到时,我是可以直接跳过中间一段,跑到前缀里面找,next[k]就是相同的最长前缀和最长后缀的长度。所以,k=next[k]就变成,k=next[2],即k=0。此时再比较str[0+1]和str[5]是否相等,不等,则k=next[0]=-1。跳出循环。

3. k--不可行:

k = next[k] 这一句其实包含了条件,str[k] = str[i - 1]且长度为k的前缀应该和str[i - 1]结尾的后缀相同,k--则有可能提前跳出循环,导致长度为k的前缀应该和str[i - 1]结尾的后缀相同不满足。比如串“acceaccc”,但q为最后一个c时,next[7]应该等于-1。但如果用k--来做的话,循环到i等于7时,next[6] = 2,k = 2,满足while循环条件,进入循环,当k=1时,跳出循环,str[k + 1] = str[7],得到next[7] = 2,正确的应该next[7] = -1。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值