9.KMP算法

KMP算法

1.KMP算法解决的问题

字符串str1和str2,str1是否包含str2,如果包含返回str2在str1中开始的位置,如果不包含返回-1。如果做到时间复杂度O(N)完成?

测试用例:str1 = “ABC1234de”, str2 = “1234”.返回3。

1.1最大相等前后缀

如一个序列abbabbk,求字符k的最大相等前后缀。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-34XjHgui-1649841326326)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1649840893670.png)]

因此,序列abbabbk中k的最大相等前后缀长度为3。注意:

  1. 前后缀是k之前的序列的前后缀不包括k。
  2. 最大相等前后缀长度不能是前后缀的整体,这样没有意义。

1.2next数组

对str2的每一个字符都求它位置之前的字符子串的最大相等前后缀长度,形成了一个next数组。

如:str2 = "aabaabs",求str2的next数组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWfMSnQt-1649841326327)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1649841305073.png)]

  • **下标0位置之前没有子串,**没有前后缀,因此认为规定next[0] = -1。
  • **下标1位置有子串a,**前后缀只有a,但最大相等前后缀长度不包括整个前后缀,因此认为规定next[1] = 0.
  • 下标2位置有子串aa,前后缀有a,因此2位置的最大相等前后缀为1。
  • 以此类推。

1.3KMP匹配过程

如:str1 = “abbstkscabbstks”, str2 = “abbstkscabbstkz”

在这里插入图片描述

尝试str1从0位置,str2从0位置,str1是否能匹配str2,当来到下标14位置时才第一次出现str1[14] != str2[14]。

如果是经典过程(暴力双循环)则会来到str1从下标1位置,str2继续从0位置来判断str1能否匹配str2。而KMP加速过程如下:

str2在下标14位置的最大相等前后缀长度为6,则str2会跳到下标6位置开始与str1的14位置字符继续比较。

在这里插入图片描述

这个匹配过程有两个实质:

  1. 其实就相当于str1来到8的j位置(14 - 6)与str2来到0位置开始比较str1是否能匹配出str2,只不过str2中z的最长相等前后缀长度为6,0~5位置的字符不用比较就能知道相等。

  2. 从i到j中间任何一个位置都不能匹配出str2。证明如下:

    (1)假设i和j任意一个位置k能匹配出整个str2。

    (2)str1(k….14 - 1)可以与str2(0…14 - k - 1)匹配。

    (3)但是str1(k…14 - 1)与str2(k….14 - 1)相同。

    (4)也就是说str2(0…14 - k - 1)与str2(k…14 - 1)相同,也就是说str2的最大相等前后缀长度为k。然而k < j,我们已经求出str2的最大相等前后缀长度为14 - j ,假设这种情况的最大相等前后缀为14 - k > 14 - j,也就是说如果k位置能够匹配出str2,那么str2最大相等前后最长度为14 - k > 14 - j,超出str2本来的最大相等前后缀长度,前后矛盾,因此假设不成立。

在这里插入图片描述

1.4next数组求法
  1. next[0]人为规定为-1,next[1]认为规定为0,next[2]看str2[0]和str2[1]是否相等,相等则next[2] = 1,否则next[2] = 0。
  2. next[3]可以根据next[0]、next[1]、next[2]求出;next[4]可以根据next[0]、next[1]、next[2]、next[3]求出…依次类推
  3. 假设来到i位置,可以知道next[i - 1]的位置k,如果str2[k] == str2[i],则next[i] = next[i - 1] + 1。否则跳到next[k]位置,如果str2[next[k]] == str2[i],则next[i] = next[i] = next[k] + 1。依次类推。有点抽象,看如下跳转的例子。

【例子】str2 = abbstabbecabbstabb?。?为可变字符。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x5Sf47Wx-1649841326327)(C:\Users\ThinkStation K\AppData\Roaming\Typora\typora-user-images\1649839594414.png)]

1.5KMP代码

vector<int> getNext(const string &s) {
	if (s.size() == 1) {
        return {-1}; 
    }	    
    vector<int> next(s.size());
    next[0] = -1;
    next[1] = 0;
    int i = 2;   //next数组位置
    int cn = 0;  //用哪个位置的字符和i - 1位置的字符比,也代表当前使用的最大相等前后缀长度是多少
    while (i < next.size()) {
        if (s[i - 1] == s[cn]) {
            next[i++] = ++cn;
        } else if (cn > 0) { //当前跳到cn位置的字符,和i - 1位置字符配不上
            cn = next[cn];
        } else {
            next[i++] = 0;
        }
    }
    return next;
}

int strStr(string str1, string str2) {
    if (str1.size() < str2.size() || str2.size() == 0) {
        return -1;
    }
    int i1 = 0, i2 = 0;
    //得到str2的next数组
    vector<int> next = getNext(str2);
    while (i1 < str1.size() && i2 < str2.size()) {
        if (str1[i1] == str2[i2]) {
            ++i1;
            ++i2;
        } else if (next[i2] == -1) { //i2 == 0,无法向前跳
            ++i1;
        } else {
            i2 = next[i2];
        }
    }
    //i1越界或者i2越界
    return i2 == str2.size() ? i1 - i2 : -1; 
}	
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值