【详解】KMP算法

前言

KMP算法是学习数据结构 中的一大难点,不是说它有多难,而是它这个东西真的很难理解(反正我是这么感觉的,这两天我一直在研究KMP算法,总算感觉比较理解了这套算法,

在这里我将自己的思路分享给大家,也是检验一下自己有没有真正掌握这个算法,错误的地方也请大家指正。嘤嘤嘤~~~

注:可供参考的资料有很多,视频的话个人推荐B站的UP主正月点灯笼,博客的话有很多,不过不要贪多,不然容易混乱,不同的人对这个算法也是有不同的理解的!

背景

了解一个算法你要明白它的出处,KMP算法是由三位大牛同时研究出来的,他们分别是D.E.Knuth、J.H.Morris和V.R.Pratt,好吧其实明白他的出处也没太大用。。。(逃

那么KMP算法主要用来解决哪一方面的问题呢? 主要是用来解决字符串中的模式串(通俗点说是关键字)在主串中的定位问题,比较通俗点说就是找你这个模式串在你这个主串的什么位置,并把它表示出来。

暴力匹配思路

看到这你就会感觉,这不是很简单吗,然后我们就会萌生下面这种思路:

有两个字符串:一个是“BDABCDBCDABCDAC”,另一个是“BCDAC”

直接从第一个字符开始比较,发现第一个字符相同,再往后找,发现第二个不相同,就把BCDAC往后移一位,从主串的第二个开始往后匹配,这不就完了??简单粗暴!

但是其实这样做浪费了很多的时间,简单来说:

如果你的两个字符串是这样的:一个是“AAAAAAAAAAAAAAAAB”,另一个是“AB”

那么你可以很清楚的发现一个问题,这种暴力匹配算法,真的太刚了,简直就是铁头娃,那么我们应该怎么解决这个问题呢?这就要讲到我们的重点:KMP算法

NEXT数组

求next数组是KMP算法里面最为重要的一部分,求出这部分也就几乎成功了一半,那么我们求的这个数组到底是用来做什么的呢?其实是为了找到模式串失配后的下一个匹配位置

从而省去一些不必要的操作。求解next数组就不得不说到最长前后缀问题:

next数组的各元素是用来存放模式串的最长前后缀的长度,比如ABCDABD这个模式串,我们可以把它分成:

"A”、“AB”、“ABC”、“ABCD”、“ABCDA”、“ABCDAB”、“ABCDABD”七部分,分别求得他们的最长前后缀(前后缀不包含自身)是:

“A”:0 ,“AB”:0 ,“ABC”:0 ,“ABCD”:0 ,”ABCDA“:1,”ABCDAB“:2,”ABCDABD“:0

所以我们得到的next数组为{0,0,0,0,1,2,0} 怎么样?很简单易懂吧,那么我们应该怎么用代码来实现呢?

一般来说我们都会把next数组的第一位设为0,因为第一个字符的最大前后缀始终为0,至于有的设为-1,虽然之前我都是按照-1做的,但那只是版本不同,我们这种的思路是比那种要清晰的。

代码实现如下:

void get_next(const char P[],int next[])
{
    int i,len;//i:模式串下标;len:最大前后缀的长度
    int m = strlen(P);//模式串长度
    next[0] = 0;//模式串的第一个字符的最大前后缀长度为0
    for (i = 1,len = 0; i < m; ++i)//从第二个字符开始,依次计算每一个字符对应的next值
    {
        while(len > 0 && P[i] != P[len])//递归的求出P[0]···P[i]的最大的相同的前后缀长度len
            len = next[len-1];
        if (P[i] == P[len])//如果相等,那么最大相同前后缀长度加1
        {
            len++;
        }
        next[i] = len;  }
}

看完上面这段代码以后,我们发现最难懂的地方就是上面的while循环了,至于为什么要这样写呢? 你可以理解为:如果模式串ABCDABD中进行到A,我们要填next[1]时,

发现A后的这个B和前面的A不相同,那么我们的len是不会变的,所以我们要确保它等于上一个字符的next值。

KMP算法

有了next数组,我们就可以很好地实现KMP算法了,下面给出代码:

void kmp(const char T[],const char P[],int next[])
{
    int n,m;
    int i,q;
    n = strlen(T);
    m = strlen(P);
    get_next(P,next);
    for (i = 0,q = 0; i < n; ++i)
    {
        while(q > 0 && P[q] != T[i])//如果模式串和主串不匹配,看不懂下面会讲
            q = next[q-1];
        if (P[q] == T[i])//如果二者匹配,q加一
        {
            q++;
        }
        if (q == m)//如果全部匹配成功了,输出位置
        {
            printf("%d\n",i-m+2);
        }
    }
}

那么为什么要写while那一句呢?其实原因很简单,我们的next数组是表示的每一段的最长前后缀的长度,如果失配了,我们就会返回与模式串失配位置前相同的后面那一部分,

比如说主串为”ABCABDCABCDABD“,模式串为”ABCDABD“,

当我们进行到ABC后我们发现q=4时失配了,这时我们应该返回的应该是next[q-1],即它前一位的next数组,即next[3],即标红色的那一部分,从那再开始进行,

也就是应该进行   "ABCDABD",这样以此类推,仔细想想是不是这样,这一段和next数组都是比较难理解的,但也是最关键的。

总结

相信各位巨巨在看完以上讲解以后也基本理解了KMP算法,把它从头到尾想一遍,发现其实它也不是很难,无非就是找一个next数组和进行一次KMP查找而已,接下来我们分析一下KMP算法的时间复杂度:

假设现在主串T匹配到 i 位置,模式串P匹配到 q 位置

  1.  如果q>0并且P[q] ! = T[i],即匹配失败那么q=next[q-1],模式串也就相当于主串向右移动了q-next [q-1] 位。
  2.  如果P[q]==T[i],表示匹配成功,那么q++,往后移。

我们发现如果某个字符匹配成功,模式串q++;如果匹配失配,i 不变(即 i 不回溯),模式串会跳过匹配过的next [q-1]个字符。

当然我们做最坏的打算,当模式串首字符位于i-(q-1)的位置时才匹配成功,算法结束。
所以,如果主串的长度为n,模式串的长度为m,那么匹配过程的时间复杂度为O(n),加上计算next的O(m)时间,KMP的整体时间复杂度为O(m + n)。

  

代码

#include<stdio.h>
#include<string.h>
void get_next(const char P[],int next[])
{
    int i,len;//i:模式串下标;len:最大前后缀的长度
    int m = strlen(P);//模式串长度
    next[0] = 0;//模式串的第一个字符的最大前后缀长度为0
    for (i = 1,len = 0; i < m; ++i)//从第二个字符开始,依次计算每一个字符对应的next值
    {
        while(len > 0 && P[i] != P[len])//递归的求出P[0]···P[i]的最大的相同的前后缀长度len
            len = next[len-1];
        if (P[i] == P[len])//如果相等,那么最大相同前后缀长度加1
        {
            len++;
        }
        next[i]=len;
    }
}

void kmp(const char T[],const char P[],int next[])
{
    int n,m;
    int i,q;
    n = strlen(T);
    m = strlen(P);
    get_next(P,next);
    for (i = 0,q = 0; i < n; ++i)
    {
        while(q > 0 && P[q] != T[i])//如果模式串和主串不匹配,看不懂下面会讲
            q = next[q-1];
        if (P[q] == T[i])//如果二者匹配,q加一
        {
            q++;
        }
        if (q == m)//如果全部匹配成功了,输出位置
        {
            printf("%d\n",i-m+1);
        }
    }
}

int main()
{
    int i;
    int next[20]={0};
    char T[] = "ABCABDCABCDABD";
    char P[] = "ABCDABD";
    printf("主串:%s\n",T);
    printf("模式串:%s\n\n",P );
    // get_next(P,next);
    printf("位置:");
    kmp(T,P,next);
    printf("\n");
    printf("next数组:\n");
    for (i = 0; i < strlen(P); ++i)
    {
        printf("%d ",next[i]);
    }
    printf("\n");
    return 0;
}

后记

花了两个多小时,终于是打完了,KMP的讲解就到这里了,关于KMP的各项优化这里也就不再多说,感兴趣的话可以去baidu搜索BM算法和Sunday算法,

如果发现上文有什么错误之处,还请随时指正,谢谢!

                                                        ------------BY 孑、然---------------

                                                        --------2018.8.18   11:01-----------

                                                        --------------------------------------

原文地址:https://www.cnblogs.com/jkxsz2333/p/9496534.html

时间: 2024-10-15 17:35:22

【详解】KMP算法的相关文章

字符串模式匹配算法--详解KMP算法

在软考的复习中,看到过几次  字符串的模式匹配算法.看起来挺难的.所以花了点时间查了查关于字符串匹配的算法.下面详细介绍一下KMP模式匹配算法 什么是字符串的匹配? 在文章中进行查找.需要找到要查找的内容所在的位置.就是字符串的匹配. 朴素的模式匹配算法 朴素的模式匹配算法,就是把要查找的内容,一步步的与要查找的文章进行进行比较.如果匹配失败,则主串和字串回溯.字串位置加1.重新匹配. 模式匹配算法的流程如下: 在匹配失败的情况下,模式串仅右移一个 之后.在从头开始匹配. 两个for循环 For

详解KMP算法

KMP算法应该是每一本<数据结构>书都会讲的,算是知名度最高的算法之一了,但很可惜,我大二那年压根就没看懂过~~~ 之后也在很多地方也都经常看到讲解KMP算法的文章,看久了好像也知道是怎么一回事,但总感觉有些地方自己还是没有完全懂明白.这两天花了点时间总结一下,有点小体会,我希望可以通过我自己的语言来把这个算法的一些细节梳理清楚,也算是考验一下自己有真正理解这个算法. 什么是KMP算法: KMP是三位大牛:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的.其中第一位就是&

Java虚拟机详解----GC算法和种类

本文主要内容: GC的概念 GC算法  引用计数法(无法解决循环引用的问题,不被java采纳) 根搜索算法 现代虚拟机中的垃圾搜集算法: 标记-清除 复制算法(新生代) 标记-压缩(老年代) 分代收集 可触及性 Stop-The-World 一.GC的概念: GC:Garbage Collection 垃圾收集 1960年 Lisp使用了GC Java中,GC的对象是Java堆和方法区(即永久区) 我们接下来对上面的三句话进行一一的解释: (1)GC:Garbage Collection 垃圾收

详讲KMP算法

两个字符串: 模式串:ababcaba 文本串:ababcabcbababcabacaba KMP算法作用:快速在文本串中匹配到模式串 如果是穷举法的方式: 大家有发现,这样比效率很低的. 所以就需要使用一种高效率模式的算法:KMP算法. 大家有看到上面的穷举法,是一位一位的挪.那可以一次挪多位不就行了.像下面: 那么为什么可以这样挪呢?  模式串向右移动的距离 = 已匹配字符数 - 失配字符的上一位字符所对应的最大长度值 那么我们要怎么找出每位上的最大长度值呢呢?   我们来找一下. 所以,使

聚类之详解FCM算法原理及应用

(一)原理部分 模糊C均值(Fuzzy C-means)算法简称FCM算法,是一种基于目标函数的模糊聚类算法,主要用于数据的聚类分析.理论成熟,应用广泛,是一种优秀的聚类算法.本文关于FCM算法的一些原理推导部分介绍等参考下面视频,加上自己的理解以文字的形式呈现出来,视频参考如下,比较长,看不懂的可以再去看看: FCM原理介绍 FCM分析1 FCM分析2 FCM分析3 首先介绍一下模糊这个概念,所谓模糊就是不确定,确定性的东西是什么那就是什么,而不确定性的东西就说很像什么.比如说把20岁作为年轻

枚举所有子集的三种算法详解-《算法入门经典》

方法一:增量构造法 理解递归必须得理解函数到底是做什么的. #include<cstdio> void print_subset(int n,int *a,int cur) //print_subset的功能是打印集合{0,1,...,n-1}的cur个元素的子集,并且目前a数组中已经存储了要打印的集合 { printf("{ "); for (int i=0;i<cur;i++) printf("%d ",a[i]); printf("

[转July]KMP算法(mark)

从头到尾彻底理解KMP 作者:July时间:最初写于2011年12月,2014年7月21日晚10点 全部删除重写成此文,随后的半个多月不断反复改进. 1. 引言 本KMP原文最初写于2年多前的2011年12月,因当时初次接触KMP,思路混乱导致写也写得混乱.所以一直想找机会重新写下KMP,但苦于一直以来对KMP的理解始终不够,故才迟迟没有修改本文. 然近期因在北京开了个算法班,专门讲解数据结构.面试.算法,才再次仔细回顾了这个KMP,在综合了一些网友的理解.以及跟我一起讲算法的两位讲师朋友曹博.

“浅析kmp算法”

"浅析kmp算法" By 钟桓 9月 16 2014 更新日期:9月 16 2014 文章目录 1. 暴力匹配: 2. 真前缀和真后缀,部分匹配值 3. 如何使用部分匹配值呢? 4. 寻找部分匹配值 5. 拓展 5.1. 最小覆盖字串 6. 参考资料 首先,KMP是一个字符串匹配算法,什么是字符串匹配呢?简单地说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道这个字符串里面是否有"ABCDABD":我想,你的脑海中马上就

(转)KMP字符串模式匹配详解

(转)KMP字符串模式匹配详解 个人觉得这篇文章是网上的介绍有关KMP算法更让人容易理解的文章了,确实说得很“详细”,耐心地把它看完肯定会有所收获的--,另外有关模式函数值next[i]确实有很多版本啊,在另外一些面向对象的算法描述书中也有失效函数 f(j)的说法,其实是一个意思,即next[j]=f(j-1)+1,不过还是next[j]这种表示法好理解啊: KMP字符串模式匹配详解 KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法.简单匹配算法的时间复杂度为O(m*n)

关联规则算法(The Apriori algorithm)详解

一.前言 在学习The Apriori algorithm算法时,参考了多篇博客和一篇论文,尽管这些都是很优秀的文章,但是并没有一篇文章详解了算法的整个流程,故整理多篇文章,并加入自己的一些注解,有了下面的文章.大部分应该是copy各篇博客和翻译了论文的重要知识. 关联规则的目的在于在一个数据集中找出项之间的关系,也称之为购物蓝分析 (market basket analysis).例如,购买鞋的顾客,有10%的可能也会买袜子,60%的买面包的顾客,也会买牛奶.这其中最有名的例子就是"尿布和啤酒