KMP算法—最通俗易懂的讲解!

简介: 我们把寻找字符串A(模式串)在字符串B(主串)第一次完整地出现的位置,把这个过程叫做字符串匹配

引言

我们把寻找字符串A(模式串)在字符串B(主串)第一次完整地出现的位置,把这个过程叫做字符串匹配,例如下图:


在这种模式匹配中,最粗暴简单的方法:


开始之前记个k=0作为匹配失败的次数,i作为模式串比较字符的位置,j作为主串比较字符的位置;

匹配成功时,接续向下一个字符进行匹配,直到匹配模式串匹配完成;

当发现匹配失败时,模式串重新到首个字符,而主串则回溯到k+1的位置,k自加1;


![image](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWcuYWxpY2RuLmNvbS9pbWdleHRyYS9pMi8yNTk2NTMzOTcyL08xQ04wMU5ZN1ZWeDFmRERqWGkya1R1XyEhMjU5NjUzMzk3Mi5wbmc?x-oss-process=image/format,png)


这种方法是最简单的,但同时也是最低效的:因为在匹配的过程中,需要同时回溯i,j两个参数;但是这种回溯大部分都是多余的;为了解决这种低效,1977由Donald Knuth、Vaughan Pratt、James H. Morris三人于年联合了一个字符串模式匹配算法,Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP算法”。


代码实现:

int index(Str str,Str substr)
{
    int i=1,j=1,k=i;
    while(i <= str.length && j<= substr.length)
    {
        if (str.ch[i]==substr[j])
        {
            ++i;
            ++j;
        }
        else
        {
            j = 1;
            i =++k; #匹配失败; 
        }   
    }
    if(j > substr.length)
    {
        return k;
    }
    else
    {
        return 0;
    }
}

KMP算法的匹配流程

假设主串S匹配到j的位置,模式串P匹配到i的位置,还没匹配结束,继续匹配下去会出现下面两种情况:


1,当P[i] == S[j]时,则继续匹配 i++,j++;

2,当P[i] != S[j]时,则 i =next[i],j++,此时等价于模式串P向右移动了 i -next[i]个单位;

以上只是带大家初步认识了KMP算法的基本匹配思想,下面则需要进行深入地了解一下next数组,以及对匹配思想的理解;


第一步,字符串相同的前后缀元素

在理解next数组之前,需要直到next数组是怎么求出来的,首先我们看一下下面的这张图

网络异常,图片无法展示
|



图中我们了解到几个信息:


S为主串;P为模式串;

图中的S[i-k] — S[i]部分与P中的P[j-k]到P[j](模式串P的前面)部分相同,同时也与P[0]到P[k](模式串P的前面)相同;

模式串向后移动了j-next[j]个单位;

从以上几部分信息,可以这样理解:


在P未移动之前之前有部分连续字符串与S已经匹配成功了,同时P的前面一小部分也能匹配成功,所以直接可以把P的首个字符移动到与后面字符串重复地方的开始字符,也就是P[j-k];



因为只是部分连续,所以在移动的过程捕获出现字符串匹配成功的可能。

上面重复的部分就是模式串的相同前后缀的最大长度,也就等于next函数,而移动的距离刚好是 j(匹配失败的位置) - next[j](前面字符串前后缀重复的最大长度)


字符串重复前后缀的最大长度也就是字符串前面的连续字符(从第一个开始)有多少个与后面的相同,自身与自身不算同一个。如下图:

网络异常,图片无法展示
|

也就能求出next数组,只不过是关于后一字符的;


第二步,已知next[1]....next[j],怎样求得next[j+1]

在第一步中,已经分析过了,next数组的值与前后缀的最大长度相关,而关于最大长度得出的next值 是后一字符的next数组的值;


这里,有一种比较权威的求next[j+1]的方法(这里是以第一个字符未p[0]为依据的):


next[0] = -1,因为第一个字符比较特殊;

若p[k] == p[j],则next[j+1] = next[j]+1 =k+1;

若p[k ] ≠ p[j],如果此时p[next[k]] == p[j ],则next[j+1] =next[k] + 1,否则继续递归前缀索引k = next[k],而后重复此过程。

注意到没,k字符在这里起到的是最大长度值的作用,例如下面这张图,


当j = 2时,k =0, p[k]= A 则因为p[3]!=p[k],所以 k =next[k]一直递归到 k =-1,则next[3] =k+1 =0;

依次类推我们可以了解到当 p[j] =D 时,next[j] =k +1 =2 ;

当p[j+1] = E时,next[j+1] = next[next[k]] +1 = 0;


总结

整个分析到这里基本上也就结束了,整个KMP算法的关键核心是求next函数,next函数需要我们联系到模式串的前后缀最大长度值。KMP算法虽然在字符串的匹配过程中著以高效,但依然存在着自己的一些弊端,一些大佬在此基础之上又进一步地对算法进行了优化。


KMP算法代码实现:

void getnext(Str substr,int next[])
{
    int i = 1,j = 0;
    next[1] = 0;
    while(i < substr.length)
    {
        if(j==0||substr.ch[i] == str.ch[j])
        {
            i++;
            j++;
            next[i] = j;
        }
        else
        {
            j =next[j]
        }
    }
}
int KMP(Str str, Str substr, int next[])
{
    int i=1,j=1;
    while(i<= str.length && j <= substr.length)
    {
        if(j == 0 || str.ch[i] == substr.ch[j])
        {
            ++i;
            ++j;
        }
        else
        {
            j =next[j];
        }
    }
    if(j > substr.length)
    {
        return i -substr.length;
    }
    else
    {
        return 0;
    }
}
相关文章
|
2月前
|
算法
数据结构中的KMP算法及其改进算法
KMP算法通过引入部分匹配表,有效避免了重复计算,从而将字符串匹配的时间复杂度降低到O(m+n)。通过进一步优化next数组,KMP算法的效率得到了进一步提升。对于大规模字符串匹配问题,KMP算法及其改进算法提供了高效的解决方案,是计算机科学领域的经典算法之一。
40 3
|
3月前
|
机器学习/深度学习 算法
【软件设计师】通俗易懂的去了解算法的时间复杂度
【软件设计师】通俗易懂的去了解算法的时间复杂度
|
1月前
|
数据采集 算法 JavaScript
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
JavaScript字符串搜索涵盖`indexOf`、`includes`及KMP算法。`indexOf`返回子字符串位置,`includes`检查是否包含子字符串。KMP是高效的搜索算法,尤其适合长模式匹配。示例展示了如何在数据采集(如网页爬虫)中使用这些方法,结合代理IP进行安全搜索。代码示例中,搜索百度新闻结果并检测是否含有特定字符串。学习这些技术能提升编程效率和性能。
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
|
2月前
|
人工智能 算法 BI
一篇文章讲明白KMP算法(俗称看毛片算法)
一篇文章讲明白KMP算法(俗称看毛片算法)
30 0
|
2月前
|
算法 Java
Java数据结构与算法:字符串匹配算法之KMP算法
Java数据结构与算法:字符串匹配算法之KMP算法
|
3月前
|
算法 搜索推荐 程序员
第六十三练 字符串匹配 - KMP算法
第六十三练 字符串匹配 - KMP算法
30 2
|
3月前
|
算法
|
3月前
|
存储 自然语言处理 算法
【算法】----BF算法&KMP算法
【算法】----BF算法&KMP算法
28 0
|
3月前
|
存储 算法 程序员
【软件设计师】通俗易懂的去了解算法的特性和要求
【软件设计师】通俗易懂的去了解算法的特性和要求
|
3月前
|
算法 C语言
KMP算法(C语言实现)
KMP算法(C语言实现)
33 0

热门文章

最新文章