字符串匹配:KMP算法

字符串匹配问题:给定字符串 s t r str str s u b s t r substr substr ,若 s u b s t r substr substr s t r str str的子串,返回子串所在位置,若不是子串,返回-1。


  假设 s t r = ′ B B C A B C D A B A B C D A B C D A B D E ′ str='BBC ABCDAB ABCDABCDABDE' str=BBCABCDABABCDABCDABDE s u b s t r = ′ A B C D A B D ′ substr='ABCDABD' substr=ABCDABD,下面以此为例,分别说明暴力法、KMP算法解决字符串匹配问题的思想。

朴素字符换匹配算法(暴力)

  1. 首先,比较字符串 s t r str str和搜索词 s u b s t r substr substr的第一个字符, ′ B ′ 'B' B ′ A ′ 'A' A不匹配,字符串 s t r str str后移一位。
    在这里插入图片描述
    在这里插入图片描述

  2. 直到字符串 s t r str str中有一个字符,与搜索词 s u b s t r substr substr的第一个字符相同为止。
    在这里插入图片描述

  3. 字符串与搜索词同时后移一位,比较下一个字符是否相同。
    在这里插入图片描述

  4. 直到字符串有一个字符,与搜索词对应的字符不相同为止。
    在这里插入图片描述

  5. 将搜索词整个后移一位,再从搜索词第一个字符逐个比较。
    在这里插入图片描述

  6. 以此类推。

时间复杂度: O ( m ∗ n ) O(m*n) O(mn),其中 m = l e n ( s t r ) , n = l e n ( s u b s t r ) m=len(str),n=len(substr) m=len(str),n=len(substr)

KMP字符串匹配算法

  KMP算法的思想是,利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。为利用已经匹配的信息,KMP算法针对搜索词,计算出一张部分匹配表

1. 创建部分匹配表

  首先给出概念:
在这里插入图片描述

  • 前缀:指除了最后一个字符以外,一个字符串的全部头部组合。
  • 后缀:指除了第一个字符以外,一个字符串的全部尾部组合。
  • 部分匹配值:"前缀"和"后缀"的最长的共有元素的长度。

部分匹配表 p a r t i a l _ m a t c h _ t a b l e partial\_match\_table partial_match_table是与搜索词 s u b s t r substr substr长度相同的数组, p a r t i a l _ m a t c h _ t a b l e [ i ] partial\_match\_table[i] partial_match_table[i]表示搜索词的子串 s u b s t r [ : i + 1 ] substr[:i+1] substr[:i+1](不包含 i+1 位)的部分匹配值。

  搜索词 s u b s t r = ′ A B C D A B D ′ substr='ABCDABD' substr=ABCDABD的部分匹配表如下:
在这里插入图片描述

  • "A"的前缀和后缀都为空集,共有元素的长度为0;

  • "AB"的前缀为[A],后缀为[B],共有元素的长度为0;

  • "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

  • "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

  • “ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A”,长度为1;

  • “ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB”,长度为2;

  • "ABCDABD"的前缀为 [A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

2. 利用部分匹配表进行字符串匹配

  暴力法遇到不匹配的字符时,将整个字符串向后移动一位,从搜索词的第一个字符重新匹配。KMP算法中给出了部分匹配表,根据不匹配字符的部分匹配值,即可利用已匹配信息,移动搜索词:搜索词移动维数=已匹配字符数-最后一个匹配字符的部分匹配值

  • 空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此搜索词向后移动的位数为:6-2=4。
    在这里插入图片描述
  • 空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2(“AB”),对应的"部分匹配值"为0。所以将搜索词向后移位数 = 2 - 0。
    在这里插入图片描述
  • 空格与A不匹配,继续后移一位。
    在这里插入图片描述
  • 逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。
    在这里插入图片描述
  • 逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。
    在这里插入图片描述

Python代码

def KMP(s, sub):
    m = len(s);
    n = len(sub)
    cur = 0  # 起始指针cur
    table = partial_match_table(sub)
    while cur <= m - n:     # 只去匹配前m-n个
        for i in range(n):
            if s[i + cur] != sub[i]:
                cur += max(i - table[i - 1], 1)  # 有了部分匹配表,我们不只是单纯的1位1位往右移,可以一次移动多位
                break
        else: # for 循环中,如果没有从任何一个 break 中退出,则会执行和 for 对应的 else,只要从 break 中退出了,则 else 部分不执行。
            return True
    return False

# 部分匹配表
def partial_match_table(sub):
    '''''partial_table("ABCDABD") -> [0, 0, 0, 0, 1, 2, 0]'''
    prefix = set()
    postfix = set()
    ret = [0]
    for i in range(1, len(sub)):
        prefix.add(sub[:i])
        postfix = {sub[j:i + 1] for j in range(1, i + 1)}
        ret.append(len((prefix & postfix or {''}).pop()))
    return ret

print(partial_match_table("ABCDABD"))
print(KMP("ABCDAB ABCDABCDABDE", "ABCDABD"))

时间复杂度: O ( m + n ) O(m+n) O(m+n),其中 m = l e n ( s t r ) , n = l e n ( s u b s t r ) m=len(str),n=len(substr) m=len(str),n=len(substr)


for else语句(Python)

  • 当迭代对象完成所有迭代后且此时的迭代对象为空时,如果存在else子句则执行else子句,没有则继续执行后续代码;
  • 如果迭代对象因为某种原因(如带有break关键字)提前退出迭代,则else子句不会被执行,程序将会直接跳过else子句继续执行后续代码。
for i in range(10):
    if i == 5:
        print 'found it! i = %s' % i
else:
    print 'not found it ...'

输出:
found it! i = 5
not found it …

for i in range(10):
    if i == 5:
        print 'found it! i = %s' % i
        break
else:
    print 'not found it ...'

输出:
found it! i = 5


算法思想:字符串匹配的KMP算法
代码:字符串匹配的kmp算法 及 python实现
Python中for循环搭配else的陷阱

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值