• 详解KMP算法


    算法介绍

    字符串匹配是计算机的基本任务之一,而Knuth-Morris-Pratt算法(简称KMP)算法就是一种常见的处理字符串匹配问题的算法。

    最基本最简单的字符串匹配(也就是一位一位将搜索词向后移,再从搜索词的第一位向后逐个比较)方法的时间复杂度(设字符串长度为n,搜索词长度为m)最差是O(n*m),而KMP算法的时间复杂度最差只有O(n)。

    举个例子:有一个字符串”BDCC AABCDAB ABCDABCDABD”,我想知道,里面是否包含另一个字符串”ABCDABD”?

    算法流程

    1、

    首先,将字符串”BDCC AABCDAB ABCDABCDABD”与搜索词“ABCDABD”的第一位进行比较,因为“B”与“A”不匹配所以将搜索词向后移一位

    2、

    继续按照上述步骤进行匹配

    3、

    就这样,一直找到一组匹配的字符

    4、

    继续匹配搜索词的第二位,发现无法匹配

    先别急着将搜索词后移一位再从头逐个比较,这样有可能使效率变差,因为你要把搜索过的字符再搜索一遍

    那么如何解决这个问题呢?

    这时我需要针对搜索词算出一张《部分匹配表》(Partial Match Table)

    至于这张《部分匹配表》(这张表也是KMP的关键部分)是怎样算出来的我们后面再说

    已知最后一个匹配的字符是A,而A对应的部分匹配值是0,所以我们套用公式:

    移动位数 = 已匹配的字符数 – 搜索词中最后一个匹配字符对应的部分匹配值

    得出:  移动位数=1-0=1

    也就是向后移一位

     5、

     我们将搜索词向后移动一位并继续匹配

    发现第一位匹配成功,我们就逐个匹配搜索词的后几位

     6、

    直到匹配到一位不同为止

    继续套用之前的公式:移动位数 = 已匹配的字符数 – 搜索词中最后一个匹配字符对应的部分匹配值

    得出: 移动位数=6-2=4

    于是我们将搜索词向后移4位继续匹配(这样就省去了很多无用的时间)

    7、

    我们继续匹配,直到再一次匹配到一样的

    8、

    我们依旧逐个匹配搜索词的后几位,一直到不同为止

    紧接着我们算出移动位数=2-0=2

    我们将搜索词向后移2位

    9、

    继续仿制上述的方法进行求解

    补充

    我们现在来介绍一下《部分匹配表》(Partial Match Table)

    首先我们明确两个概念:

    • 前缀:指的是字符串的子串中从原串最前面开始的子串,如abcdabd的前缀有:a,ab,abc,abcd,abcda,abcdab
    • 后缀:指的是字符串的子串中在原串结尾处结尾的子串,如abcdabd的后缀有:d,bd,abd,dabd,cdabd,bcdabd

    那有人会问了,这个“前缀”和“后缀”又与这个《部分匹配表》有什么关系呢?

    其实这个匹配表中的每一个“部分匹配值”就是”前缀”和”后缀”的最长的共有元素的长度

    比如:表中的第一个0表示“A”的”前缀”和”后缀”的最长的共有元素的长度

               表中的第二个0表示“AB”的”前缀”和”后缀”的最长的共有元素的长度

               表中的1表示“ABCDA”的”前缀”和”后缀”的最长的共有元素的长度

               表中的2表示“ABCDAB”的”前缀”和”后缀”的最长的共有元素的长度

    接下来我们以“ABCDABD”为例,算出一张《部分匹配表》:

    - ”A”的前缀和后缀都为空集,最长的共有元素的长度为0;

    - ”AB”的前缀为[A],后缀为[B],最长的共有元素的长度为0;

    - ”ABC”的前缀为[A, AB],后缀为[BC, C],最长的共有元素的长度0;

    - ”ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],最长的共有元素的长度为0;

    - ”ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],最长的共有元素为”A”,长度为1;

    - ”ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],最长的共有元素为”AB”,长度为2;

    - ”ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],最长的共有元素的长度为0。

    代码

      

    for(int i=2;i<=m;i++)
    {                                  //b数组是搜索串
        while(j>0&&b[i]!=b[j+1])j=p[j];//好好体会这个while循环,失配的精髓。
        if(b[i]==b[j+1])j++;
        p[i]=j;
    }
    

      

    int kmp(char s,char p)
    {
        int i=0;
        int j=0;
        int sLen=strlen(s);
        int pLen=strlen(p);
        while(i<sLen && j<pLen)
        {
            //①如果j=-1,或者当前字符匹配成功(即S[i]==P[j]),都令i++,j++
            if(j == -1 || s[i] == p[j])
            {
                i++;
                j++;
            }
            else
            {
                //②如果j!=-1,且当前字符匹配失败(即S[i]!=P[j]),则令 i 不变,j=next[j]
                //next[j]即为j所对应的next值
                j=next[j];
            }
        }
        if(j == pLen) return i-j;
        else return -1;
    }
    

      

      

  • 相关阅读:
    python-装饰器
    Django-session相关操作+redis
    Could not connect to Redis at 127.0.0.1:6379: 由于目标计算机积极拒绝,无法连接。redis启动失败
    mybatis高级特性
    Elasticsearch从入门到熟练使用
    sharding-jdbc从入门到熟练使用
    mysql主从复制搭建(普通安装和docker方式)
    领域驱动设计入门及简单落地
    docker的一些基本命令
    docker发布jar包项目
  • 原文地址:https://www.cnblogs.com/chenjiaxuan/p/10989751.html
Copyright © 2020-2023  润新知