【KMP算法】学习记录

KMP算法

最近学习记录下,方便后面自己查看

一、什么是KMP

Knuth,Morris和Pratt,取了三位学者名字的首字母。 我何时有这么牛逼!!!哈哈!~~~

二、作用

字符串匹配

主要思想:

​ 当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去匹配了

本算法难点:

​ 利用前缀表记录已经匹配的文本内容(这个前缀表真的不好理解!!我花了老半天的时间。。。。)

看下面的内容前:思考下下面的两个问题

1.next数组里的数字表示的是什么

2.为什么这么表示?

三、前缀表是什么东东?

在这里先卖个关子:next数组

我们在看书或者写算法题时,都会看到答案的解释中都有next数组或者prefix table

解释

就是我们说的前缀表((⊙o⊙)…)

记录下标i之前的(包括i)的字符串中,有多大长度的相同前缀后缀

仔细揣摩相同二字含义!!!

作用

前缀表用来回退的,记录模式串文本串(主串)不匹配的时候,模式串应该从哪开始重新匹配

实例演示:

需求: 要在主串【aabaabaafa】中查找是否出现一个模式串【aabaaf】

在看下面过程,思考下主串模式串的作用是什么

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWD0F0TI-1654617955803)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B21.gif)]

上图中的:

关键点前缀表的aa最长前缀匹配(就这一点我理解了半天!!)----后面我会说明

如果采用暴力解法:当主串的第六个字符b和模式串的第六个字符f不匹配时,就要重头来了

但是:

采用前缀表:

​ 1.这玩意儿就不会重头开始,

​ 2.而是从上次已经匹配的内容开始匹配,

​ 3.找到模式串中的中第三个字符b继续开始匹配(是不是懵逼了!!哈哈哈!!硬着头皮往后看)

> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tyqKlMue-1654617955804)(Untitled.assets/297CB85B.png)]

**心中的疑问?**凭啥在模式串的第三个字符开始匹配

这就是我们所说的前缀表如何记录的呢?

前缀表任务:

  1. 当前的位置匹配失败,

  2. 找到之前已经匹配上的位置

  3. 重新匹配,

    就是某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置(怎么跳,再往下读)

我们说了:

再刚刚的匹配的过程,在下标5 的大方遇到不匹配,模式串指向f:如下图所示

KMP精讲1

然后就莫名其妙的指向下标2(第三字符)

KMP精讲2

解释:划重点了哈!!

注意!(先看下)

**字符串的前缀**是指不包含最后一个字符的所有以第一个字符开头的连续子串

后缀:不包含第一个字符的所有以最后一个字符结尾连续子串

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-50Qzt6BB-1654617955804)(Untitled.assets/29950F6F.gif)]

下标5之前的这部分的字符串(就是aabaa)的最长相等前缀后缀字符串是子字符串aa

因为:找到了最长相等前缀后缀,匹配失败的位置是后缀子串后面,

所以:我们找到与其相同的前缀的后面重新开始新匹配就可以了.(这句话好好咀嚼!!!精华)

还是懵逼??哈哈~

  1. 你仔细向下模式串的前五个aabbaa与主串中前五个aabaa是一样的对吧?
  2. 这是我在模式串找最长前缀后缀相等的数,是不是就是变相的在主串的子串aabaa中找?(目的找到首次尾部能和头部相同的位置)
  3. 这时aa在主串中位置是不是就可确定了???

注意!(再瞅瞅!!)

**字符串的前缀**是指不包含最后一个字符的所有以第一个字符开头的连续子串

后缀:不包含第一个字符的所有以最后一个字符结尾连续子串

四、前缀表的计算

如图:

长度为1的子字符串a,最长相同前后缀的长度为0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R9wHcA8O-1654617955805)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B25.png)]

按照上面的方法寻找:(子字符串)

  1. aa:最长相同的前后缀的长度为1----------------------------------长度为2
  2. aab:最长相同的前后缀的长度为0---------------------------------长度为3
  3. aaba:最长相同的前后缀的长度为1--------------------------------长度为4
  4. aabaa:最长相同的前后缀的长度为2------------------------------长度为5
  5. aabaaf:最长相同的前后缀的长度为0----------------------------长度为6

对应的最长相同前后缀长度对应的前缀表的元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5rHxPr8f-1654617955805)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B28.png)]

下图利用前缀表找到当字符不匹配时应该指针移动到什么位置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M9bBVbvQ-1654617955806)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B22.gif)]

过程分析:

  1. 找到不匹配位置,我们要看他的前一个字符前缀表数值是多少(这里是2)
  2. 找前缀表的前一个字符的原因:找到要找前面字符串最长相同前缀和后缀

五、前缀表与next数组

其实就是一样的,只是具体的实现方式不一样罢了

next数组:就是前缀表,只是很多实现将前缀表进行统一的**减一(右移一位操作)**作为后面的next数组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6H88xp9F-1654617955807)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B24.gif)]

六、时间复杂度----O(n+m)

O(n+m)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8z5NnoQ1-1654617955807)(Untitled.assets/29C539D2.png)]

七、构造next数组

定义函数:getNext()

函数参数为指向next的数组的指针和一个字符串

void getNext(int* next,const string& s)

构造``next数组就是计算模式串`,过程步骤分析

1.初始化

//定义两个指针
//j指向前缀末尾位置,i指向后缀末尾位置,并对next数组初始化赋值
int j=-1;
next[0]=j;

说明:

j初始化-1的原因–前缀表的统一减一操作,

next[i]表示i(包括i)之前的最长相等的前后缀长度

2.处理前后缀不相同的情况

//因为i=-1,右移一位操作,所以i1开始

for(int i=1;i<s.size();i++)

如果s[i]s[j]不相同,进行回退操作

next[j]就是记录这``j`之间的子串相同前后缀的长度

s[i]s[j+1]不相同,找j+1前一个元素在next数组里的值(其实就是next[j])

//前后缀不相同
while(j>=0&&s[i]!=s[j+1])
{
    //向前回退
    j=next[j];
}

3.处理前后缀相同的情况

s[i]yus[j+1]相同,那马旧同事向后移动ij说明找相同的前后缀,

将``j`(前缀的长度)赋给next[i],记录相同的前后缀长度

//找到相同的前后缀
if(s[i]==s[j+1])
{
    j++;
}
next[i]=j;

整合代码,万朝归元

void getNext(int* next,const string& s)
{
    int j=-1;
    next[0]=j;
    //注意i从一开始
    for(int i=1;i<s.size();i++){
        //前后缀不同
        while(j>=0&&s[i]!=s[j+1]){
            //回退
            j=next[j];
        }
        //z找到相同的前后缀
        if(s[i]==s[j+1]){
            j++;
        }
        //将j(前缀的长度)赋给next[i]
        next[i]=j;
    }
}

代构造的next数组动画演示图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nGoByR9Z-1654617955807)(Untitled.assets/KMP%E7%B2%BE%E8%AE%B23.gif)]

主要实现代码(采用统一减一的操作!)

注意:看里面的注释!!

//前缀表统一减一操作
    public  void getNext(int[] next,String s){
        //两个下标,j指向模式串needle的起始位置,i指向文本串haystack的起始位置
        //j的初始值为-1,因为next数组中里面记录的起始位置为-1
        int j=-1;
        next[0]=j;
        for(int i=1;i<s.length();i++){//注意当中=的i从1开始
            while(j>=0&&s.charAt(i)!=s.charAt(j+1)){
                j=next[j];//向前回退(是不是在想j=-1呢?看while里面的j>=0的条件)
            }
            if(s.charAt(i)==s.charAt(j+1)){
                //找到相同的前后缀
                j++;
            }
            //将j前缀的长度赋给next[j]
            next[i]=j;

        }
    }
    public   int strStr_Sub1(String haystack,String needle){
        if(needle.length()==0){
            return 0;
        }
        int[] next=new int[needle.length()];
        getNext(next,needle);
        int j=-1;
        //因为next数组里面记录的起始位置为-1
        for(int i=0;i<haystack.length();i++){//注意这里i从0开始!!!(i指向的是文本串首位!!)
            while(j>=0&&haystack.charAt(i)!=needle.charAt(j+1)){
                //不匹配,寻找之前匹配的位置
                j=next[j];
            }
            if(haystack.charAt(i)==needle.charAt(j+1)){//匹配,jhei同时向后移动
                j++;//i的增加在for循环中
            }
            if(j==(needle.length()-1))//j到达模式串的末尾位置,说明文本串haystack出现了模式串needle
            {
                return (i-needle.length()+1);
            }
        }
        return -1;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值