详解KMP及其Python实现

详解KMP及其Python实现


一、 KMP是什么

 KMP算法是一种改进的字符串匹配算法。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)

求解问题举例:

给定一下两个字符串str(n位)与ptr(m位),求ptr在srt中出现的次数或者其出现的位置

str = "ababacababadababadadda";
ptr = "ababad";

可以很容易得得出ptr在str中出现了2次,分别在str的6位和12位开始

普通算法暴力破解需要从每个str的字符开始对ptr进行匹配,如果该字符匹配失败,继续下以一个字符为始匹配ptr,时间复杂度为O(m*n),然而kmp可以使其时间复杂度降低为O(m+n)

二、算法介绍

1、计算next数组

next数组即为ptr中相同前后缀的最长长度,用-1代表没有,0代表1个字符,1代表2个字符,以此类推

eg: ptr 'ababaca',next数组有ptr长度位,即7位

对第一位'a'而言,没有相同前后缀,next[0] = -1

对前两位'ab'而言,next[1] = -1

对前三位'aba'而言,next[2] = 0

以此类推,next为[-1, -1, 0, 1, 2, -1, 0]。 注意‘ababa’中前后缀为aba和aba,前缀不包含最后一位,后缀不包含第0位,意味着前后缀不能等于总长。

这样做有什么好处呢?如下图所示

最上方为str,下方为ptr,3 4 为ptr在最后一位a之前的最长相同前后缀(3和4相同)。当ptr和str匹配到ptr的最后一位,发现ptr中最后一位为a,而str的对应为置为c。此时,1和2对应ptr的最长后缀3和4,所以1和2 相同。如果暴力匹配,应当取str[1]与ptr从[0]开始匹配,但是有了next,没有必要用暴力匹配的方式,因为中间一定匹配不到,直接在2的开始于3进行匹配即可,也就是说,比较2和3后面的是否相同

由此可见,不是简单的循序遍历,而是根据next表进行有选择的遍历

def cal_next(ptr):
	next = [-1]
    # ptr第一位的没有最长前后缀,直接赋值为-1
	k = -1
    # k代表最长前后缀长度,赋值为-1
	for p in range(1, len(ptr)):
        # 从第二位开始遍历ptr
		while k>-1 and ptr[p]!=ptr[k+1]:
            # 假设已有最长前缀为A,最长后缀为B,B的下一位ptr[p] != A的下一位ptr[k+1]
            # 说明最长前后缀不能持续下去
			k = next[k]
            # 往前回溯,尝试部分前缀,而非从第一位开始重新寻找最长前缀
		if ptr[p] == ptr[k+1]:
            # 如果A B的下一位相同
			k = k + 1
            # 最长前后缀长度 + 1
		next.append(k)
            # 第p位的最长前后缀赋值为k
	print('next: ', next)
	return next

2、KMP

这段和求解next非常相似

def kmp(str, ptr):
	next = cal_next(ptr)    # 求解next
	k = -1    # 此处k相当于ptr中已匹配的长度,类似一个指针指向ptr中已匹配的最后一位
	num = 0    # str中ptr的数量
	for p in range(len(str)):
        # 遍历str
		while k>-1 and str[p] != ptr[k+1]:
            # 假设str中的A片段和ptr中的前A位已匹配,但A的下一位和ptr中A+1位不匹配
			k = next[k]
            # 放弃A片段中的前若干位,因为它们不可能再匹配了
            # 用A片段的后若干位去匹配ptr中某一个最大前缀,像上面矩形图所示
		if str[p] == ptr[k+1]:
            # 如果A的下一位和ptr中A+1位相匹配
			k = k+1
		if k == len(ptr)-1:    # 如果ptr走到尽头
			num = num + 1    # 匹配到了一个
			k = next[k]
	return num

这里需要注意的是ptr匹配完成后下一次匹配该怎么走,也就是倒数第二行。例如有:

str = 'abcdabcd'
ptr = 'abcd'

当第一次匹配后,str走到了第一个d位,ptr走到了尽头,那么应该接着str的第二个a与ptr的第一位匹配。但是有以下问题:

str = 'abcabcabde'
ptr = 'abcab'

下一次匹配str虽然是从第三个a开始,ptr却不能从第一个a开始,因为很明显, 这里应该有两个匹配,如果ptr从第一个a开始,就会导致第二个匹配被错过。所以应该与此时ptr最长前后缀的下一位进行匹配,也就是c进行匹配

//错误
abcabcabde
abcab
     abcab
-------------------------------------------------------
//正确
abcabcabde
abcab
   abcab

所以有

if k == len(ptr)-1:
    num = num + 1
    k = next[k]

3、测试

    str =  'abcabcabdeabcab'
    ptn = 'abcab'
    output: 3

三、时间复杂度

ptr长度为m,str长度为n

易得求next的时间复杂度为O(m)(遍历一次)

KMP的时间复杂度基本为遍历一次str,时间是线性的,所以为O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值