KMP经典算法与变形的应用(字符串parttern匹配问题)

KMP经典算法与变形的应用(字符串parttern匹配问题)

 1. 问题描述

求主串字符串a是否含有模式串字符串parttern b,也就是匹配问题经典KMP算法是计算next[j]数组,然后每次移动若干位而不是暴力求解的每次移动到partern的首位来比较。

暴力求解的算法很明显时间复杂度是O(m*n),而KMP(三个发明者的首字母)算法 的时间复杂度是O(m+n),为什么时间复杂度是O(m+n),以及KMP算法的

关键点next[]数组的求解的精髓——相等的最长前缀后缀串长度数组的求解在下面进行介绍。

最后还会介绍一下KMP的next数组的变形(有些情形下将会提速)。

2.暴力求解BF

主串每次移动一个位置,每次都与模式串partern进行比较,当遇到不相同元素的时候进行break结束当前循环,然后主串移动到下一个位置.....不用多说

 1 def bf(s, partern):
 2     start = 0
 3     while start <= (len(s) - len(partern)):
 4         partern_index = 0
 5         for i in xrange(start, start+len(partern)):
 6             if partern[partern_index] != s[i]:
 7                 break
 8             elif partern_index == (len(partern)-1):
 9                 return start
10             partern_index += 1
11         start += 1
12     return -1

 

3. KMP经典算法

核心思想:

  在比较的过程中,当遇到a[i] != b[j]的时候并不是将索引 i 移动到当前比较开始处的下一位而是移动模式串!!!这就是主串不回溯!

  那么把模式串移动到哪里??这就是接下来分析的next[]

以下图片来源于july课程,不再标注

目的就是黄色元素和绿色元素不相等时候,将模式串移动若干位使得BD串=AC串,是不是节省了很多很多比较

3.1 next[]数组求解

    s      =   'abababcdgababcabcdcfabcabbbaabccc'
partern =   'abcabcdcfabcabbb'

 求解模式串parttern的每一个元素的最大相等的前缀串和后缀串

思想:如果p[k] == p[j],那么令next[j+1] = next[j]+1

     如果p[k] != p[j],那么令h = next[k];比较p[h] 与p[j]的相等关系

          重复此过程,看起来像是从后向前的递归实际上求解过程是从前向后的一个求解

还是举个例子比较通俗易懂:partern =   'abcabcdcfabcabbb'

next[0] = -1

指针移动到b的位置时,很显然a自己没有所谓的前缀后缀,所以next[1] =0

指针移动到c..............,显然ab没有相等的前缀后缀..................next[2] = 0

.................a.........................abc.................................................next[3]=0

.................b.........................abca的前缀有a ab abc abca,后缀有a c bca有一个相同的就是a,长度是1,所以next[4] =1

以此类推

最终得到:

next=[-1, 0, 0, 0, 1, 2, 3, 0, 0, 0, 1, 2, 3, 4, 5, 0]

 1 def get_next(partern):
 2     nex = [-1] * len(partern)
 3     index = 0
 4     k = -1
 5     while (index < len(partern) - 1):
 6         if k == -1 or partern[index] == partern[k]:
 7             index += 1
 8             k += 1
 9             nex[index] = k
10         else:
11             k = nex[k]
12     return nex

接下来就可以进行kmp的求解了。

3.2 KMP算法代码

 一句话:与暴力求解的思维一样,只不过主串不回溯,根据next[]移动模式串。

 1 def kmp(s, partern):
 2     nex = get_next(partern)
 3     index = 0
 4     start = 0
 5     while start < (len(partern)-1):
 6         if start == -1:
 7             index += 1
 8             start = 0
 9             continue
10         if s[index] == partern[start]:
11             index += 1
12             start += 1
13         else:
14             start = nex[start]
15     return index - start
s = 'adgababcabcdcfabcabbbaabccc'
parttern = 'abcabcdcfabcabbb'
结果是 5

3.3 KMP算法时间复杂度解释

 为什么KMP的算法时间复杂度是O(m+n)

首先在求解next[]数组的时候是遍历的求,因此式m,关于递归问题上面已经说过了,真正求解是从前向后,因此前面的next值已经求出来了,所以可以忽略

在执行KMP的时候实际上也是遍历,长度是n,过程中不回溯,因此比较的次数仍然是n(平均),因此是O(m+n)

4.KMP算法变形

 现在考虑这样一个问题,当s[i] == p[j]的时候i++ j++,当出现s[i] != p[j]的时候会根据next[]移动p的索引,假如说 j=10,next[j] = 2,同时p[10] == p[2]这种情况,是不是做了一次无用功,因为s[i] != p[j]已经确定了,不然不可能移动p,换句话说s[i] != p[2]所以本次移动是无用功,还得继续移动。看下图

因此在求解KMP的时候加上一个判断,如果p在j位置的元素值与要移动到k的位置的元素值是相同的,那么就移动到p[next[next[j]]]  而不是p[next[j]]

 1 def kmp2(s, partern):
 2     nex = get_next(partern)
 3     index = 0
 4     start = 0
 5     while start < (len(partern) - 1):
 6         if start == -1:
 7             index += 1
 8             start = 0
 9             continue
10         if s[index] == partern[start]:
11             index += 1
12             start += 1
13         else:
14             if parttern[start] == parttern[nex[start]]:
15                 start = nex[nex[start]]
16             start = nex[start]
17     return index - start

结果仍然是5

同时都调用kmp(s, parttern) 和kmp2(s, parttern)可以count一下循环的次数来比较两者的速度。

参考链接:

http://www.cnblogs.com/c-cloud/p/3224788.html

https://www.cnblogs.com/yjiyjige/p/3263858.html

有任何疑问请留言或者发送至邮箱397585361@qq.com

posted @ 2018-11-21 22:01  亮亮·Leo  阅读(788)  评论(0编辑  收藏  举报