KMP算法

本文详细介绍了KMP算法,包括其原理、BF算法的对比、next数组的求解过程,以及如何用Python实现LeetCode中的字符串查找问题,重点在于优化时间复杂度和理解next数组在匹配过程中的作用。
摘要由CSDN通过智能技术生成

KMP算法

本文用python实现具体内容
用LeetCode28.找出字符串中第一个匹配项的下标举例.

难点:

  1. 理解KMP算法的匹配原理
  2. 理解next数组的求解方式

BF算法(朴素算法)

首先从BF算法(暴力匹配)开始理解字符串匹配。

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        n = len(haystack)
        m = len(needle)
        # 因为主串中每次要对比的只有i到i+m这一段,所以为不越界,将i的遍历上限变为n-m
        for i in range(0, n - m + 1):
        	# 当匹配上直接返回下标i
            if haystack[i: i + m] == needle:
                return i
        # 反之return -1
        return -1

时间复杂度为最差为O(n*m),最好为O(n+m),当情况差且字符串过长时就得寄。
所以KMP算法应运而生,将匹配字符串的时间复杂度降到O(n+m),用空间换时间,多开了个next数组。

最长公共前后缀

要想理解KMP算法,首先要理解最长公共前后缀的概念。

前缀与后缀

给定一个字符串s = “abcba”,它的前缀为a,ab,abc,abcb,后缀为a,ba,cba,bcba.
即前后缀为不包含最后或最前的字符的所有连续子字符串。

所以,最长公共前后缀为前缀后缀中相同且最长的子串,在上述例子中为a。

KMP算法的匹配原理

首先我们知道公共前后缀是相同的,所以我们能将前后缀相替换。当匹配不上当前字符时,找模式串在当前字符的前一个字符所代表的最长公共前后缀长度,通过该前后缀长度,能直接跳过前缀,从前缀的后一个字符开始重新匹配。相对于BF算法,减少了时间花费。

这里的匹配原理其实用动图或者视频更好理解,可以上b站上找相关视频。推荐一个

给出相关匹配代码(无next数组的求解)

class Solution:
	def getNext(self, needle: str): -> list:
		....
		return ne

    def strStr(self, haystack: str, needle: str) -> int:
        n = len(haystack)
        m = len(needle)
		
		ne = self.getNext(needle)
		
		# 匹配代码
        j = 0
        for i in range(n):
        	# 当j大于0的目的是防止连续回退导致越界
        	# 当匹配不上时,就将j回退到下标j-1对应的前缀的下一个字符开始重新匹配
        	# while循环的意义在下面求解nxt数组中有讲解
            while j > 0 and haystack[i] != needle[j]:
                j = ne[j-1]
            # 匹配上时,将j移动一位
            if haystack[i] == needle[j]:
                j += 1
            # 当needle完全匹配完,返回haystack中第一个匹配项的下标
            if j == m:
                return i - j + 1
        return -1

理解了匹配原理后,下面就是对定义跳几步的next数组进行求解。

求解next数组

由上面原理可以知道,next数组就是求模式串中每个字符对应的最长公共前后缀,下面给出具体代码。

def getNext(self, needle: str): -> list:
	j = 0
	n = len(needle)
	ne = [0] * 1010
	for i in range(1, n):
		# j和i分别代表前缀的末端和后缀的末端
		# 当匹配不上时,则找前一个字符对应的公共前缀的下一个字符,继续匹配,如果还是匹配不上,则继续回退,循环就是这么来的,直到j为0,说明退到起点了。
		# 对于这一处的循环,B站上有更详细的图解视频,链接(https://www.bilibili.com/video/BV16X4y137qw),该视频进度条14:13后。
		while j > 0 and ne[i] != ne[j]:
			j = ne[j - 1]
		# 当匹配上时,说明已经出现公共前后缀,继续往后遍历,找出最长
		# j其实也代表最长公共前后缀的长度
		if ne[i] == ne[j]:
			j += 1
            # 因为初始化了ne数组中的值都为0,所以可以将ne[i] = j放在if needle[j] == needle[i]中
            # 如果没没有初始化ne数组中的值都为0,则不能将ne[i] = j放在if needle[j] == needle[i]中,
            #       因为此时除了相等时要赋值给ne[i],当j==0时也要赋值给ne[i]	
		ne[j] = i
	return ne

其实next数组的求解也是模式串和自己匹配的过程,用双指针求出对应的最长公共前后缀。

总结

给出全部代码先

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        n = len(haystack)
        m = len(needle)
        ne = [0] * 1010
        
        # getNext()
        j = 0
        for i in range(1, m):
            while j > 0 and needle[j] != needle[i]:
                j = ne[j - 1]
            if needle[j] == needle[i]:
                j += 1
            ne[i] = j
            
        # 匹配
        j = 0
        for i in range(n):
            while j > 0 and haystack[i] != needle[j]:
                j = ne[j-1]
            if haystack[i] == needle[j]:
                j += 1
            if j == m:
                return i - j + 1
        return -1

理解匹配原理其实不难,本人觉得最难的就是next数组中对于j的回退,其他多看几遍也就会了。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值