关于KMP算法的一种个人理解

1.KMP算法的引入:Brute-Force模式匹配算法
   对于串的模式匹配,也可以理解成串的相等的比较,一种直观的想法就是Brute-Force模式匹配算法。虽然这个算法是以人名命名的(以人名命名的都很重要),但是这个算法很容易被我们想到:
    假设有目标串S(长度为m)和模式串T(长度为n),T先和S的第一个字符对齐,然后依次比较S[1]和T[1]是否相等,如果相等就再比较S[2]和T[2].如果不相等了,那么就将T和S的第二个字符对齐(我们称之为回溯),再次重复上述的过程。那么这个算法的时间复杂度最坏的情况是:对于每一个S的子串(一共可以有m个)和T的比较,都是在比较到T的最后一个字符的时候才发现不相等,也就是比较了n次,所以时间复杂度为O(m*n)。当然这个是极端的情况,正常的情况下时间复杂度约为O(m+n)。可以这么理解,绝大多数的匹配都是在第一个字符匹配完之后就知道不相等了,只有成功的那一次将T的所有字符都匹配完了,所以大致是O(m+n)。

2.在之前的基础上延伸出KMP算法
    记得之前在不知道什么地方看过这样的一句话:(对于算法而言)做某件事情最好不要超过一次。借着这句话,我们回过头去看之前的Brute-Force算法,就会发现一个问题,(假设T和S的第一个字符对齐比较的情况下),一直比较到S[i]和T[i]都是相等的,但是S[i+1]和T[i+1]不相等,按上面的分析是要回溯到S的第二个字符重新开始比较的。但是我们发现S[1]一直到S[i]我们都是已经知道的,就是T[1]到T[i],所以能不能借助这一点来改进我们的算法,减少比较的次数呢?
    下面我们举一个例子:
    假设S = “ababcabcaabc”, T = “ababcaba”,i指针指向S当前比较的位置,j指针指向T当前比较的位置。那么第一次比较就是(一直比较到了如下的i和j的位置才出现的不相等):
                  ↓(i指针)
    ababcabcaabc
    ababcaba
                  ↑(j指针)

    我们如果按照Brute-Force算法,那么下一次比较的时候就是:
      ↓(i指针)
    ababcabcaabc
      ababcaba
      ↑(j指针)
    但是我们可以提前下结论:这样的比较是不需要的!为什么呢?因为T串中从第1位开始的子串ababca不等于从第1位开始的子串babcab(T串自己和自己做比较),而T串中从第2位开始的子串babcab恰好就是S串中我们要比较的部分,所以T和S不可能相等。所以这样的比较是多余的。

    再下一次的比较是:
        ↓(i指针)
    ababcabcaabc
        ababcaba
        ↑(j指针)

    但是我们又可以提前下结论:这样的比较是不需要的!为什么呢?因为T串中从第1位开始的子串ababc不等于从第3位开始的子串babca(T串自己和自己做比较),而T串中从第3位开始的子串babca恰好就是S串中我们要比较的部分,所以T和S不可能相等。所以这样的比较也是多余的。
    重复上述的判断过程,一直可以到如下的比较之前都可以不用比(大家可以自己在纸上面操作一下):
                  ↓(i指针)
    ababcabcaabc
              ababcaba
                  ↑(j指针)

    为什么到这里就需要比较了呢?因为S串之后的部分我们不知道,所以需要进一步的比较,但我们把此次比较中i和j指针的位置和第一次比较时候的对比来看,就会发现i指针是没有变的,但是j指针移动了,所以我们得出结论,对于KMP算法来说,i指针是不需要回溯的,仅仅需要移动j指针,把j指针移动到之前的某一个位置k上。那么这个位置k和S串有没有关系呢?答案是没有关系,因为仅仅取决于T串自己的构造。而这个位置,是下一次比较的时候j指针的位置,所以我们可以称之为“next”(代表下一次的意思)。
    下面我们抛开S串不谈,仅仅研究T串“ababcaba”。
                                                                              ↑(目前的j指针)
    给出如下的一个比较:
    ababcaba(1)
      ababcaba(2)
        ababcaba(3)
          ababcaba(4)
            ababcaba(5)
              ababcaba(6)
                ababcaba(7)
    我们抛开最好一位就是j指针指向的位置不看,根据我们上面所说的比较方法,我们从(1)直接跳到了(6)的位置上,而中间的都不需要去再做比较了。(6)需要比较的原因是因为(6)的前缀ab和(1)中j指针之前的后缀是相等的(加粗部分)。
    在做完上述的比较之后,我们应该明白了寻找下一个(也就是next)j指针的位置的大致思路。现在稍微整理一下:就模式串T而言,对于目前j指针之前的子串,也就是T[1]到T[j-1]的部分,如果存在k,使得T[1]到T[k-1]这部分的子串,和T[j-k+1]到T[j-1]这部分的子串相等,并且如果存在多个k的话我们取最大的那个k(因为k最大的话,j的回溯是最少的)。
    所以我们下面引入next函数如下:
在这里插入图片描述
    (这张图是我在网上找的,所以在字母表示上面略有不同,本质上是一样的)

    在求得了next[j]值之后,KMP算法的思想是:
    设目标串(主串)为s,模式串为t ,并设i指针和j指针分别指示目标串和模式串中正待比较的字符,设i和j的初值均为1。若有si=tj,则i和j分别加1。否则,i不变,j退回到j=next[j]的位置,再比较si和tj,若相等,则i和j分别加1。否则,i不变,j再次退回到j=next[j]的位置,依此类推。直到下列两种可能:
    1.j退回到某个下一个[j]值时字符比较相等,则指针各自加1继续进行匹配。
    2.退回到j=0,将i和j分别加1,即从主串的下一个字符si+1模式串的t1重新开始匹配。

    最后可以总结一下KMP算法的精髓之处:目标串i指针的反复回溯被引入的next[j]函数转化了,而next数组只需要计算一次即可,所以大致的时间复杂度为O(m+n)

    下面附上我自己写的python程序:

# 首先是next数组的求解
# 参数是传入模式串,然后传出next数组,以list形式进行返回,里面存储的是int类型的索引
def get_next(pattern_string):
    next = []
    for i in range(len(pattern_string)):
        j = i + 1
        if j == 1:
            next.append(0)  # 对应j=1的情况
            continue
        str_tmp = pattern_string[:i]
        list_tmp = []
        for k in range(1, i):
            a = str_tmp[:k]
            b = str_tmp[-k:]
            if str_tmp[:k] == str_tmp[-k:]:
                list_tmp.append(k + 1)
        if len(list_tmp) == 0:
            next.append(1)  # 对应其他情况
        else:
            next.append(max(list_tmp))  # 对应第二种情况
    return next


# 下面是KMP算法的实现,返回第一个匹配到的子串的下标,如果没有匹配上,则返回-1
def KMP(target_string, pattern_string):
    next = get_next(pattern_string)  # 这是我们得到的next数组
    print(next)
    i, j = 0, 0
    while i < len(target_string) and j < len(pattern_string):
        # 在进入了这一层循环之后,i指针是不会回溯的,j指针会按照next数组进行移动
        while j >= 0 and target_string[i] != pattern_string[j]:
            a = target_string[i]
            b = pattern_string[j]
            d = next[j]
            j = next[j] - 1  # 这里减去1是和数组从0开始的下标表示法相对应
        j, i = j + 1, i + 1
        if j == len(pattern_string):
            return i - j
    return -1  # 对应匹配失败的情况


if __name__ == '__main__':
	# 下面是模式串和目标串,可以自行赋值检验
    pattern_string = "ababc"
    target_string = "babababcbabababacbsbcbabs"
    print(KMP(target_string, pattern_string))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值