当前位置:   article > 正文

KMP算法--字符串匹配问题

KMP算法--字符串匹配问题

        参考文章:python之KMP算法(原理详解)_kmp算法代码python-CSDN博客

图解KMP算法,带你彻底吃透KMP-CSDN博客

        B站视频:【最浅显易懂的 KMP 算法讲解】https://www.bilibili.com/video/BV1AY4y157yL?vd_source=c8da04bb60a93f6134c95538c2559fb1

做题困扰 

        首先第一次接触到关于字符串匹配的问题,我第一时间想的就是python中的字符串查找index函数,比如说给你一个被查找字符串 s = 'ababcabcacbab' ,查找字符串 t = 'abcac',就可以实现如下操作:

  1. s = 'ababcabcac'
  2. t = 'abcac'
  3. print(f't出现的位置: {s.index(t)})
  4. ***
  5. t出现的位置: 5
  6. ***

        如果题目上要求一些其他条件就可以在这个基础上进行改进,就可以在处理长字符串检索时加一些条件进行逐一匹配。

        同样的,还有一种朴素的匹配算法:

  1. def search(s, t, m, n):
  2. i, j = 0, 0
  3. while i < m and j < n:
  4. if s[i] == t[j]:
  5. i += 1
  6. j += 1
  7. else:
  8. i = i - j + 1
  9. j = 0
  10. if j == n:
  11. return i - j
  12. return 0
  13. s = 'ababcabcac'
  14. t = 'abcac'
  15. print(f't出现的位置: {search(s, t, len(s), len(t))}')
  16. ***
  17. t出现的位置: 5
  18. ***

        这两种方法原理其实都一样,都是通过逐一字符串匹配,进行目标检索,其时间复杂度非常大。因此,学习了一种减少时间复杂度的算法--KMP算法

KMP算法简介

        KMP算法(Knuth-Morris-Pratt算法)是一种用于在字符串中查找子串的高效算法。它的主要思想是利用已经部分匹配的信息来避免无效的回溯,从而实现更快的字符串匹配

        KMP算法的核心是构建一个部分匹配表(也称为Next数组),用于记录在匹配过程中已经匹配的部分,从而在匹配失败时可以快速地确定应该将模式串向右移动的位置。

具体来说,KMP算法的实现步骤如下:

  1. 构建部分匹配表(Next数组):遍历模式串,对于每个位置记录其前缀子串中的最长相等前缀后缀的长度。
  2. 匹配过程:在文本串中从左到右逐个字符地与模式串进行匹配。如果当前字符匹配成功,则继续比较下一个字符;如果匹配失败,则根据部分匹配表确定模式串向右移动的位置。
  3. 匹配成功:当模式串完全匹配文本串中的子串时,匹配成功,返回匹配的起始位置。
  4. 匹配失败:如果模式串未能完全匹配文本串,则根据部分匹配表确定模式串的移动距离,继续匹配。

KMP算法的时间复杂度为O(m+n),其中m为模式串的长度,n为文本串的长度。相较于朴素的字符串匹配算法,KMP算法避免了大量的重复比较操作,因此具有更高的效率。        

        正所谓,基础不牢地动山摇,next就是整个算法的基础。其中next数组的求法也涉及到了前后缀的求法。

next数组求法

        next的本质就是一个前缀表,前缀表是一张表(或者直接理解为一个数组),表中记录的是字符串中的每个子串的最大相等前后缀的长度。举个例子:

假如有一个目标字符串 T :‘abcab’,其前缀表用next来表示:

1. 前一个是 a 无前后缀,长度为 0

2.前两个是 ab ,前缀为a ,后缀为b。前后缀不相等,长度也为 0。

3.前三个是 abc, 前缀为 a,ab;后缀为c,bc。前后缀不相等,长度为 0。

4.前四个是 abca, 前缀为a,ab,abc;后缀为a,ca,bca。前后缀最长相等长度为 1。

5.前五个是 abcab,前缀为a,ab,abc,abca;后缀为b,ab,cab,bcab。前后缀最长相等长度为 2。

所以可以得到next数组:

Tabcab
next00012

        既然我们得到了next数组,那么KMP算法已经掌握一大半了。next数组就决定着字符串匹配过程中指针回溯的位置。

那么接下来用代码实现如何求得next数组:

  1. def build_next(patt):
  2. next = [0] # next数组 初始值为0
  3. prefix_len = 0 # 当前共同前后缀的长度
  4. i = 1
  5. while i < len(patt):
  6. if patt[prefix_len] == patt[i]:
  7. prefix_len += 1
  8. next.append(prefix_len)
  9. i += 1
  10. else:
  11. if prefix_len == 0:
  12. next.append(0)
  13. i += 1
  14. else:
  15. prefix_len = next[prefix_len - 1]
  16. return next
  17. t = 'abcab'
  18. print(f'next: {build_next(t)}')
  19. ***
  20. next: [0,0,0,1,2]
  21. ***
  • 首先建一个next数组,初始值为0,就是前一个a的前后缀长度。
  •  predix_len = 0,即为当前前一个a 的前后缀长度
  • i = 1 ,初始指针为字符串第二个字符
  •  接下来进行while 循环,直到指针指向最后一个为止
  • 如果当前指针指向的字符patt[i] 和 patt[predix_len]相等,那么就说明 前 i+1 个字符的前后缀最长相等长度为 predix_len+1(比方说前俩字符为‘aa’,此时 i 指向第二个a,predix_len指向的是第一个,他俩相等,就说明aa的前后缀相等,分别为a,a。)所以next.append(predix_len+1),(next就变成了[0,1]),i + 1继续进行判断下一个字符的前后缀最长相等长度(这一步分比较抽象,大家学习的时候可以在本上画图演示一遍)
  • 如果不相等的话,进入else语句里面,再进行①判断prefix_len是否为0,(比方说字符串为‘ab’,此时 i 指向b,predix_len指向a,他俩不相等,同时predix_len为 0 ,那么就是说这个字符串前后缀长度为0),如果为0,i + 1进行下一个判断;②如果不为0,predix_len变为前一位字符串的最大相等长度。
  • 最后经过循环,返回next数组。

        整个过程可能会有些抽象,接下来我用图表的格式来进行一个简单演示。

 

KMP代码实现 

  1. def build_next(patt):
  2. next = [0] # next数组 初始值为0
  3. prefix_len = 0 # 当前共同前后缀的长度
  4. i = 1
  5. while i < len(patt):
  6. if patt[prefix_len] == patt[i]:
  7. prefix_len += 1
  8. next.append(prefix_len)
  9. i += 1
  10. else:
  11. if prefix_len == 0:
  12. next.append(0)
  13. i += 1
  14. else:
  15. prefix_len = next[prefix_len - 1]
  16. return next
  17. def kmp(target, patt):
  18. next = build_next(patt)
  19. i = 0
  20. j = 0
  21. while i < len(target):
  22. if target[i] == patt[j]:
  23. i += 1
  24. j += 1
  25. elif j > 0:
  26. j = next[j - 1]
  27. else:
  28. i += 1
  29. if j == len(patt):
  30. return i - j
  31. t = 'abcabc'
  32. s = 'asabdababcabc'
  33. print(f't 在 s 的 位置 {kmp(s,t)}')
  34. ***
  35. t 在 s 的 位置 7
  36. ***

        整个KMP算法中,通过 匹配过程中省去重头开始匹配的时间,只需每次匹配失败时求出next中next[j-1]的值,使之与i对应再次进行匹配,直至匹配成功。

        接下来,用图来演示整个流程:

总结:

        经过查了可多资料,以及看了好多视频,开始看了好多理论知识,完全搞不懂,特别是求next的那一部分,但是看过那个图解kmp以后,感觉kmp算法也不是那么难。因此我也是,做了很多图解,能够更加清晰地了解kmp。

        我觉得最难的还得是next的求法,如果这些图解也搞不懂,可以举个例子,在演草本上把每个流程写下来,写完一遍就会把这个算法吃透了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/900889
推荐阅读
相关标签
  

闽ICP备14008679号