小锅炒的数据结构与算法学习系列之算法,本节主要简单了解字符串匹配算法。
1.字符串匹配
对于字符串对象,最重要的操作之一是字符串匹配。这个操作不仅本身很重要,还是许多其他字符串操作的基础。字符串匹配也被称为字串匹配(string matching)或者字符串查找(string searching)。有些书里称为模式匹配(pattern matching)而模式匹配是一个内涵更广的概念。假设有两个字符串:
字符串匹配就是在字符串t中查找与字符串p相同的子串的操作。字符串t称为目标串,字符串p为模式串。通常有m<,也就是说,模式串的长度远小于目标串的长度。如果从目标串的某个位置j开始,模式串里的每个字符都与目标串里的对应字符相同,就是找到一个配对。如果在比较中遇到了一对不同的字符,那就是不匹配,说明模式串不能与目标串中从位置j开始的子串匹配。
2. 字符串朴素匹配算法
朴素的字符串匹配算法(Naive String Matching Algorithm),就是穷举法,枚举法,也叫暴力匹配。其基本流程如下:
1. 从目标的第j个字符开始,同模式串的第1个字符比较。
2. 如果相同,目标和模式串均指向下一个字符继续比较。
3. 如果不相同,目标串指针指向初始字符的下一个字符,
模式串指针返回第一个字符位置。
2.1 朴素算法实现
def navie_matching(t, p):
m, n = len(p), len(t)
i, j = 0, 0
while i and
j
if p[i] == t[j]:
i += 1
j += 1 else:
i, j = 0, j - i + 1 if i == m: return j - i return -1
3.KMP 算法
朴素字符串算法,寻找匹配的字符子串时,当中间某个字符不匹配时,目标串相对原始位置只移动一个字符,前面匹配正确的字符信息完全没有使用到。KMP算法考虑到这一点,对模式串进行编码,利用了模式自身的特性提高了搜索匹配的效率。KMP算法大致思想:当目标字符和模式字符不匹配时,目标串指针不改变,模式串指针返回到next[i]的位置,i为模式串的第i+1个字符的下标。next数组详见3.2。
3.1 KMP算法实现
def matching_KMP(t, p, pnext):
j, i = 0, 0
n, m = len(t), len(p)
while i and j if i == -1 or t[j] == p[i]:
j += 1
i += 1
else:
i = pnext[i]
if i == m:
return j - i
return -1
pnext 即为next数组,详细见3.2
def gen_pnext(p):
i, k, m = 0, -1, len(p)
pnext = [-1] * m
while i -1: if k == -1 or p[i] == p[k]: i, k = i+1, k+1 pnext[i] = k else: k = pnext[k] return pnext
3.2 next数组
- 1.什么是next数组字符串前缀:字符串除最后一个字符外的全部头部集合。字符串后缀:除字符串除第一个字符外的全部尾部集合。next数组: 除当前字符外的最长相同前缀后缀的长度。例如,字符串p = abcac的前缀为{a,ab,abc,abca},后缀{c,ac,cac,bcac}
模式串 | a | b | c | a | c |
---|---|---|---|---|---|
max(len) | 0 | 0 | 0 | 1 | 0 |
next | -1 | 0 | 0 | 0 | 1 |
- 2.next数组的作用如图所示,p[i] != t[j]时,目标串指针保持不动,模式串指针i=next[i]。所以next数组的作用很明显是记录模式串指针的回溯位置,换一句话说就是next数组隐含了模式指针应该移动的距离:i-next[i]-1。
- 3.next数组的生成采用动态规划的思想
初始化:next[0] = -1, next[1] = 0
已 知:next[0], ...,next[i-1]
且next[i-1] = k, 求:next[i]
if p[i] = p[k],则 next[i] = k+1,
if p[i] != p[k], 指针 k = next[k], 继续比较。
为何递归前缀指针 k = next[k]?根据next数组的含义,next存储的值为最长相同前后缀的长度。
如果p[k] != p[i],将指针移动到next[k],即具有最长相同前缀和后缀的长度。如果p[i] = p[next[k]],则 next[i] = next[k] + 1, 如果p[i] != p[next[k]],指针继续移动到上一个最长前后缀相等的位置,即k = next[next[k]]。next数组的生成函数如下:
def gen_pnext(p):
i, k, m = 0, -1, len(p)
pnext = [-1] * m
while i -1:
if k == -1 or p[i] == p[k]:
i, k = i+1, k+1
pnext[i] = k
else:
k = pnext[k]
return pnext
- 4.为什么KMP高效朴素匹配算法时,当遇到错误匹配时目标串指针要返回到j-i+1的位置,而KMP算法目标串指针不需要回溯,仍然在当前位置。
说明:
KMP算法的精髓就是开发了一套分析和记录模式串信息的机制(和算法)(next数组!!!),而后借助得到的信息加速匹配。KMP最大的难点在于next数组的理解。
上述next数组表述还存在不少瑕疵。
参考:
1.数据结构与算法:Python语言描述 (裘宗燕)
2.https://www.cnblogs.com/zhangtianq/p/5839909.html
3.https://baijiahao.baidu.com/s?id=1659735837100760934&wfr=spider&for=pc
4.https://blog.csdn.net/dark_cy/article/details/88698736
- END -