【数据结构-查找】2.字符串(逐步演绎过程,超级详解KMP算法)

串的定义

串(string)是有0~n个字符组成的有限序列,一般记为
S = ′ a 1 a 2 … a n ′ ( n ≥ 0 ) S = 'a_1a_2…a_n'(n≥0) S=a1a2an(n0)
S 是字符串的名称, a i a_i ai 可以是字母数字或其他字符。

其中,串中任意连续的字符组成的子序列称为该串的 子串

字符在串中的位置 通常是该字符在序列中的序号

子串在主串中的位置 指的是子串第一个字符在主串中的位置

串的模式匹配

子串的定位操作通常称之为串的模式匹配,它的目的是要获取子串在主串的位置。

一般的,我们想到的是一种暴力破解的方法

也就是说,从主串 S 的第 pos 个字符起,与模式串的一个字符比较,若相等,则继续逐个比较主串和模式的后续字符;否则,从主串的下一个字符起,重新和模式的字符比较,以此类推,直至成功或失败。

int IndexSubstring(string s, string sub, int pos) {
    int i = pos;
    int j = 0;
    while(i < s.size() && j < sub.size()) {
		if(s[i]==sub[j]) {
            i++;
            j++;
        } else {
            i = i - j + 1;
            j = 0;
        }
    }
    if(j >= sub.size()) return i - sub.size() + 1;	// 查找成功
    else return 0; // 查找失败
}

但这种暴力破解的算法的最坏时间复杂度为O(mn),m和n分别是主串和模式串的长度。


KMP算法——改进的模式匹配算法

在回顾上面暴力破解的算法,我们可以得到,暴力算法每一次回溯都是回到模式串最初的起点,事实上,这一步是可以改进的。

【KMP算法】利用比较过的信息,i 指针不需要回溯,仅将子串向后滑动一个合适的位置,并从这个位置开始和主串比较,这个合适的位置仅与 子串本身结构有关,与主串无关

【KMP算法】实际上是一种【备忘录设计模式】,下面通过几个步骤或许可以让你了解这个算法的使用。

我们以主串 s='ababcabcacbab',模式串 sub=‘abcac’

1.获取模式串的 next 数组(备忘录)

子串前缀后缀前后缀相同的最长
a--0
abab0
abca,abc,bc0
abcaa,ab,abca,ca,bca1
abcaca,ab,abc,abcac,ac,cac,bcac0

所以 模式串subnext数组

sabcac
next00010

2.匹配过程

要满足一个公式:移动位数 = 已经匹配的位数 - 对应的部分匹配值

第一趟:发现 ac 不配

ababcabcacbab
abc

但是前面两个字符 'ab' 是匹配的,查表可知,最后一个匹配字符 b 对应匹配部分值为 0,按公式,计算 $ 2-0=2 $,所以,模式串向后移动 2 位。

第2趟:模式串向后移动 2 位后,发现 bc 不配

ababcabcacbab
abcac

但是前面两个字符 'abca' 是匹配的,查表可知,最后一个匹配字符 a 对应匹配部分值为 1 ,按公式,计算 4 − 1 = 3 4-1=3 41=3,所以,模式串向后移动 3 位。

第3趟:模式串向后移动 3 位后,匹配成功

ababcabcacbab
abcac

使用【KMP算法】后,时间复杂度变为了O(m+n)


KMP原理

已知公式:移动位数 = 已经匹配的位数 - 对应的部分匹配值

改为代码:
m o v e = ( j − 1 ) − n e x t [ j − 1 ] move = (j -1) - next[j-1] move=(j1)next[j1]
优化公式,如果我们将 next数组 向后挪一位,就不需要 next[j-1] ,只需要直接使用 next[j] 即可

sabcac
next00010
右移-10001

于是上述公式(2)既可以改为
m o v e = ( j − 1 ) − n e x t [ j ] move = (j -1) - next[j] move=(j1)next[j]
此时,得到串移动的公式
j = j − m o v e = j − ( ( j − 1 ) − n e x t [ j ] ) = n e x t [ j ] + 1 j = j - move =j -((j -1) - next[j])=next[j]+1 j=jmove=j((j1)next[j])=next[j]+1
进一步优化,我们可以将右移后是 next数组 总体+1,那么,最后的数组就变成了
j = n e x t [ j ] j = next[j] j=next[j]

sabcac
next00010
右移-10001
右移+101112

最后一行的意思是,把模式串的第 next[j] 个值,移动 j 这个位置。或者说,从模式串的第 next[j] 个数组开始查找,主串继续移动即可。

3.使用了改进后的next数组再匹配过程

第一趟:发现 ac 不配

ababcabcacbab
abc

c(pos=2) 这个位置匹配失败,查表得,c 的对应值是 1 ,也就是将模式串的第一个字符 (a) 移到 c(pos=2) 这个位置

第2趟:模式串向后移动 2 位后,发现 bc 不配

ababcabcacbab
abcac

c(pos=6) 这个位置匹配失败,查表得,c 的对应值是 2 ,也就是将模式串的第二个字符 (b) 移到 c(pos=6) 这个位置

第3趟:模式串向后移动 3 位后,匹配成功

ababcabcacbab
abcac

参考代码

获取next数组

void getNext(string sub, int next) {
    int i = 0;
    int j = 0;
    next[0] = 0;
    while(i<sub.size()) {
        if(j == 0|| s[i]==s[j]){
            i++;
            j++;
            next[i] = j;
        } else {
            j = next[j];
        }
    }
}

【KMP算法】

int Index(string s, string sub, int next[], inr pos) {
    int i = pos;
    int j = 0;
    while(i<s.size()&&j<sub.size()) {
        if(j==0||s[i]==sub[j]){
        	i++;
        	j++;    
        } 
        else {
            j = next[j];	// 模式串右移
        }
        
        if(j >= sub.size()) {
            return i - sub.size() + 1;
        } 
        eles {
            return 0;
        }
    }
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_之桐_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值