您的位置:首页 > 其它

KMP算法详解

2016-02-06 11:38 211 查看
说道KMP算法,一定要先指出问题所在之处了,问题如下:


已知一个字符串a,b

想找到在字符串a中b首次完整出现的位置。



听到这个问题,首先想到的就是朴素算法,挨个来进行匹配,然后找到对应的答案。

朴素算法

朴素算法很简单,就是挨着遍历,但是会有很多的重复计算,算法复杂度为O(mn)

代码如下:

# 朴素算法
def algorithm1(src, target):
for i in range(len(src)):
flag = True
for j in range(len(target)):
if i + j >= len(src):
return -1

if src[i + j] != target[j]:
flag = False
break

if flag:
return i

return -1


考虑如下字符串,重复的计算就很多了,因为直到最后才最终匹配。


src:abbacabbacabbacd

target:abbacd



如果匹配如上两个字符串,就会出现大量的重复计算,所以才有了之后的优化算法

KMP算法

KMP算法跟朴素算法是对朴素算法的一个优化,朴素算法很简单,但是会有大量的重复匹配,而KMP算法就是要把重复的匹配去掉。

KMP算法有个核心的概念,就是next数组,该数组记录最长共有元素的长度。

以abcab为例,其next = [0,0,0,1,2]

说起来比较难以理解,就是部分匹配的值。

算法思想

部分匹配的实质就是其实也是KMP算法的核心思想,KMP算法的核心思想在我看来就是减少匹配的次数,将重复的匹配次数去掉,那这个信息该如何去掉呢?

我们使用两个标志位,一个为主字符串的标志位,一个是模式串的标志位。

代码如下:

# KMP 算法
def algorithm2(src, target):
l = make_next(target)
target_length = len(target)
src_length = len(src)
src_index = 0
target_index = 0
while src_index < src_length:
while target_index < target_length:
if src_index + target_index >= src_length:
return -1
if src[src_index + target_index] == target[target_index]:
target_index += 1
elif target_index >= 1:
src_index += target_index - l[target_index - 1]
target_index = l[target_index - 1]
else:
src_index += 1
target_index = 0

if target_index == target_length:
return src_index

return -1


先不看next数组的计算方法,直观的来看KMP算法的话,我们就能理解其思想了,KMP算法虽然是2层循环叠加,但是本质上,src_index 和 target_index 都是只进行了单次遍历的,所以算法复杂度为O(m + n)

比朴素算法的O(mn)快了很多。

了解了算法思想就该了解next数组的计算原理了

next数组

看如下代码

# next 数组的计算
def make_next(target):
l = [0] * len(target)
flag = 0
for i in range(1, len(target)):
if target[i] == target[flag]:
flag += 1
else:
flag = 0
l[i] = flag

return l


看代码可能不慎明了,其实这是计算相同的前后缀来的。

以字符串abcab为例,它的next数组为[0,0,0,1,2],

其前三个字符没有公共的前后缀,所以都是0,到了第四位,模式串有了一个公共的前后缀,就是a然后其字符长度1,所以next[3] = 1

到了第五位,其公共的前后缀变成了ab,所以next[4] = 2

详细列如下:

next值模式串前缀后缀公共部分
2abcababca,abc,ab,abcab,cab,ab,aab
1abcaabc,ab,abca,ca,aa
0abcab,abc,c
后面的就不用说了,所有的公共前后缀均为无

而公共的前后缀可以用来计算下次的src_index的偏移距离,如果模式串在匹配到src_index位置时不匹配了,可以根据之前src_index 的下次偏移的位置应该就是src_index += target_index - l[target_index - 1]

增量为模式串已经匹配的长度与next数组的在该处值的差

其实仔细想想就可以理解该处的意义,因为l[target_index - 1]其实代表的是之前最后匹配处的公共匹配值,如果没有匹配,那么偏移的距离则正好应该为target_index

说的比较复杂,实际上就是有2个问题需要弄清楚

为何src_index的偏移需要增加l[target_index - 1]

为何target_index的偏移需要回到l[target_index - 1]

target_index的偏移

先说这个,为何要回到l[target_index - 1]的位置,其实就是因为前面所说道的前后缀的概念。

当前的位置不匹配了,但是前面的内容其实都是匹配的,而匹配的内容,刚好跟前面有重复的前缀,所以,target_index 需要回到之前前缀的后面的位置,也就是l[target_index - 1]的位置。

src_index的偏移

因为l[target_index - 1]的偏移位置已经变化了,所以src_index也必须进行变化,才能够让我们匹配的位置对应起来。


举例来说:

假设匹配串为abcaabcaabcab,模式串为abcab,第一次匹配,在src_index = 0,target_index = 4时出现了第一次不匹配,但是abcab的l[target_index - 1] = 1,所以src_index挪移的距离就应该为3,target_index也回到了l[target_index - 1]的位置

这样就保证了匹配总能够保证最少,可以不进行重复匹配

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: