KMP算法

作者: 大橘猪猪侠 | 来源:发表于2020-04-23 16:11 被阅读0次

简介:

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。

字符串匹配

有一个主串S = {a,b,c,a,c,a,b,d,c},模式串T = {a,b,d},找出模式串在主串中第一次出现的位置。

之前我们说过BF算法和Rk算法来进行字符串匹配的问题,BF算法是通过最简单的方法,一个一个去遍历比较,RK算法是通过计算散列值来匹配字符串,那KMP又是如何去匹配字符串的呢?

KMP模式匹配算法原理探索

假设主串S= “abcababca”,模式串T= “abcaabx”

用暴风算法去进行匹配时,他会根据主串S[0]和T[0]进行匹配,直到出现不相同的情况,此时会丢弃前面的匹配信息,让S[1]与T[0]相比,直到主串结束或者匹配成功,这样的方式极大的降低了匹配效率。也会增加多余的判断。

15876223054841.png 15876223915482.png

因此,为了减少多余的判断,我们需要用一个数组next,记录模式串中匹配失败时应该回退的位置,而不是匹配失败,又从第一个数开始匹配,这样会产生多余的判断。

KMP模式串匹配算法next数组的推导

一、T = “abcd”

当j = 1时,next[1] = 0
当j = 2时,j由1到j-1范围内只有字符"a",属于其他情况 next[2] = 1
当j = 3时,j由1到j-1范围内只有字符"ab",显然a不等于b,属于其他情况 next[3] = 1
当j = 4时,j由1到j-1范围内只有字符"abc",显然abc不存在相等的情况,属于其他情况 next[4] = 1
因此,next = [0,1,1,1]

二、T = “abcabd”

当j = 1时,next[1] = 0
当j = 2时,j由1到j-1范围内只有字符"a",属于其他情况 next[2] = 1
当j = 3时,j由1到j-1范围内只有字符"ab",显然a不等于b,属于其他情况 next[3] = 1
当j = 4时,j由1到j-1范围内只有字符"abc",显然abc不存在相等的情况,属于其他情况 next[4] = 1
当j = 5时,j由1到j-1范围内只有字符"abca",显然abca存在相等的字符"a",因此,可以得到next[5] = 2,表示当第五个位置匹配失败时,直接从模式串的第二个位置与匹配失败的那个字符进行相比。
当j = 6时,j由1到j-1范围内只有字符"abcab",显然abcab存在相等的字符"ab",因此,可以得到next[6] = 3,表示当第五个位置匹配失败时,直接从模式串的第三个位置与匹配失败的那个字符进行相比。
因此,next = [0,1,1,1,2,3]

三、T = “aaaabc”
当j = 1时,next[1] = 0
当j = 2时,j由1到j-1范围内只有字符"a",属于其他情况 next[2] = 1
当j = 3时,j由1到j-1范围内只有字符"aa",显然存在a等a next[3] = 2
当j = 4时,j由1到j-1范围内只有字符"aaa",显然前缀aa等于后缀aa,next[4] = 3
当j = 5时,j由1到j-1范围内只有字符"aaaa",前缀aaa与后缀aaa相等,因此,可以得到next[5] = 4
当j = 6时,j由1到j-1范围内只有字符"aaaab",前缀与后缀不想等,因此,next[6]= 1

因此,next = [0,1,2,3,4,1]

理解回退

模式串 T = "ababaaaba"

判断前


截屏2020-04-23 下午3.18.17.png

判断后

截屏2020-04-23 下午3.18.25.png

比较T[i]和T[j],但是i=0,则表示[0,1]这个范围[a]智能从1的位置开始;
j++,i++,所以i=1,j=2
并且更新next[j]=i;则next[2] = 1;

比较T[i]!=T[j],所以将i的位置回退到a之后,那么i = next[i]=0,

此时,i = 0,i++,j++,next[3] = 1;
比较T[i]==T[j],i++,j++,next[4] = 2;

此时i = 2,j = 4,T[i] = T[j],i++,j++,next[5] = 3;

此时i = 3, j = 5,T[i] = T[j],i++,j++ ,next[6] = 4;

此时i = 4, j = 6,T[i] != T[j],i = next[4] = 2;

……
以此类推

注意:我们这里找的next数组跟主串没有任何关系,next数组中的值都是在模式串中获取得到的,不要将next与主串进行联系

下面通过代码来写一下如何实现KMP模式串匹配算法

//kmp模式串匹配算法
//1、通过计算返回串T的next数组
//注意T[0]中存储的是字符串的长度,真正的字符内容从T[1]开始;
void get_next(String T,int *next){
    int i,j;
    //i为回退的数组T下标,j为next的下标对应的回退值
    i = 0;
    j = 1;
    //数组第1个值的回退值为0
    next[1] = 0;
    //遍历字符串T
    while (j<T[0]) {
        //计算模式串对应每个字母的回退到那个位置
        if(i == 0 || T[i] == T[j]){
            i++;
            j++;
            next[j] = i;
        }else{
            //如果字符串值不想等,则i值回退;
            i = next[i];
        }
    }
}

通过执行一遍代码,可以更清楚的了解到next数组的实现以及原理,这是一个比较绕的过程。

//KMP匹配算法
//返回子串T在主串S中的第pos个字符后的位置,不存在返回0
int Index_KMP_1(String S,String T,int pos){
    //i是主串当前位置的下标,j是模式串当前位置的下标
    int i = pos;
    int j = 1;
    //定义一个空的next数组,用来存放模式串的每个位置的回退值
    int next[MAXSIZE];
    //对next数组赋值
    get_next(T, next);
    //注意: T[0] 和 S[0] 存储的是字符串T与字符串S的长度;
    //若i小于S长度并且j小于T的长度是循环继续;
    while (i<=S[0]&&j<=T[0]) {
        //如果两字母相等,继续
        if(j == 0||S[i] == T[j]){
            i++;
            j++;
        }else{
            //如果不匹配时,j回退到合适的位置,i值不变
            j = next[j];
        }
    }
    //当j大于模式串的长度时,就证明已经找到了匹配的字符串,那么i的值也在与模式串对应的最后一个字母上,因此需要将i减去模式串的长度,来得到开始符合匹配的起始位置
    if(j>T[0]){
        return i-T[0];
    }else{
        return -1;
    }
}

第20行代码就使用到了next数组中的值,当发现不匹配时,模式串会在第next[j]的位置开始与主串中第i个位置进行匹配,从而减少多余的判断,以此来对算法进行优化。

打印结果:

15876275371042.png

KMP模式匹配算法next数组优化

当模式串中有较多相同的子串时,上面的方法还是会进行多次无所谓的比较;
例如:主串:aabaaacd 与模式串aaacd进行匹配,当知道模式串中第三位与主串中第三位不相同,那么模式串会前移一位与主串的第三位相比,但是我们已经知道a和b不想等,没有必要往后前移一位,为什么不直接向前移动三位

因此,在进行i++和j++之后,我们需要对T[i]和T[j]进行比较,比较两个数是否相等,相等时,将next[j] = next[i],不想的时,将next[j] = i

代码:


void get_nextVal(String T,int *nextVal){
    int i,j;
    //i为回退的数组T下标,j为next的下标对应的回退值
    i = 0;
    j = 1;
    //数组第1个值的回退值为0
    nextVal[1] = 0;
    //遍历字符串T
    while (j<T[0]) {
        //计算模式串对应每个字母的回退到那个位置
        if(i == 0 || T[i] == T[j]){
            i++;
            j++;
            //如果当前字符与前缀不同,则当前的j为nextVal 在i的位置的值
            if(T[i]!=T[j])
                nextVal[j] = i;
            else
                //如果当前字符与前缀相同,则将前缀的nextVal 值赋值给nextVal 在i的位置
                nextVal[j] = nextVal[i];
        }else{
            //如果字符串值不想等,则i值回退;
            i = nextVal[i];
        }
    }
}

最后将所有的结果打印:


15876290771362.png

KMP算法比较绕,最好通过手写画图和将代码的过程走几遍,去细细的品。

相关文章

  • KMP 专题整理

    KMP 学习记录 kuangbin专题十六——KMP KMP 学习总结 朴素 KMP 算法 拓展 KMP 算法(E...

  • 对KMP算法的一些理解

    最近学到KMP算法,下面讲讲对KMP算法的一些个人理解,希望对大家有帮助! 对于KMP算法的理解: 整个KMP算法...

  • KMP算法文章合集

    字符串的查找:朴素查找算法和KMP算法 暴力匹配算法与KMP算法(串的匹配) 字符串查找算法BF和KMP 字符串匹...

  • 串的模式匹配算法

    KMP算法 算法匹配

  • 问答|KMP算法学习笔记

    问题 目录KMP是什么,做什么用的KMP算法的高效体现在哪如何KMP算法的next数组KMP的代码KMP的时间复杂...

  • KMP算法——寻找子串位置

    KMP算法——寻找子串位置 1、KMP算法简介: KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J....

  • 字符串匹配 - KMP算法

    前面我们介绍非常高效的 BM 算法,今天我们介绍另一个非常出名且高效的 KMP 算法。 KMP 算法思想 KMP ...

  • KMP算法及优化

    转载请注明出处: KMP算法及优化 今天看到同学在复习数据结构书上的KMP算法,忽然发觉自己又把KMP算法忘掉了,...

  • KMP算法(字符串匹配问题)

    一、是什么? 注意,是KMP算法,不是MMP哈,我没有骂人。KMP算法是用来做字符串匹配的,除了KMP算法分,还有...

  • KMP算法

    KMP算法

网友评论

      本文标题:KMP算法

      本文链接:https://www.haomeiwen.com/subject/gquaihtx.html