字符串匹配的kmp算法 及 python实现
一:背景
给定一个主串(以 S 代替)和模式串(以 P 代替),要求找出 P 在 S 中出现的位置,此即串的模式匹配问题。
Knuth-Morris-Pratt 算法(简称 KMP)是解决这一问题的常用算法之一,这个算法是由高德纳(Donald Ervin Knuth)和沃恩 · 普拉特在 1974 年构思,同年詹姆斯 ·H· 莫里斯也独立地设计出该算法,最终三人于 1977 年联合发表。
在继续下面的内容之前,有必要在这里介绍下两个概念:真前缀 和 真后缀。

由上图所得, "真前缀" 指除了自身以外,一个字符串的全部头部组合;"真后缀" 指除了自身以外,一个字符串的全部尾部组合。
二:朴素字符串匹配算法
初遇串的模式匹配问题,我们脑海中的第一反应,就是朴素字符串匹配(即所谓的暴力匹配)
暴力匹配的时间复杂度为 O(nm),其中 n 为 S 的长度,m 为 P 的长度。很明显,这样的时间复杂度很难满足我们的需求。
接下来进入正题:时间复杂度为 Θ(n+m) 的 KMP 算法。
三:KMP 字符串匹配算法
3.1 算法流程
以下摘自阮一峰的字符串匹配的 KMP 算法,并作稍微修改。
(1)

首先,主串 "BBC ABCDAB ABCDABCDABDE" 的第一个字符与模式串 "ABCDABD" 的第一个字符,进行比较。因为 B 与 A 不匹配,所以模式串后移一位。
(2)

因为 B 与 A 又不匹配,模式串再往后移。
(3)

就这样,直到主串有一个字符,与模式串的第一个字符相同为止。
(4)

接着比较主串和模式串的下一个字符,还是相同。
(5)

直到主串有一个字符,与模式串对应的字符不相同为止。
(6)

这时,最自然的反应是,将模式串整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把 "搜索位置" 移到已经比较过的位置,重比一遍。
(7)

一个基本事实是,当空格与 D 不匹配时,你其实是已经知道前面六个字符是 "ABCDAB"。KMP 算法的想法是,设法利用这个已知信息,不要把 "搜索位置" 移回已经比较过的位置,而是继续把它向后移,这样就提高了效率。
(8)
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| 模式串 | A | B | C | D | A | B | D | '\0' |
| next[i] | -1 | 0 | 0 | 0 | 0 | 1 | 2 | 0 |
怎么做到这一点呢?可以针对模式串,设置一个跳转数组int next[],这个数组是怎么计算出来的,后面再介绍,这里只要会用就可以了。
(9)

已知空格与 D 不匹配时,前面六个字符 "ABCDAB" 是匹配的。根据跳转数组可知,不匹配处 D 的 next 值为 2,因此接下来从模式串下标为 2 的位置开始匹配。
(10)

因为空格与C不匹配,C 处的 next 值为 0,因此接下来模式串从下标为 0 处开始匹配。
(11)

因为空格与 A 不匹配,此处 next 值为 - 1,表示模式串的第一个字符就不匹配,那么直接往后移一位。
(12)

逐位比较,直到发现 C 与 D 不匹配。于是,下一步从下标为 2 的地方开始匹配。
(13)

逐位比较,直到模式串的最后一位,发现完全匹配,于是搜索完成。
3.2 next 数组是如何求出的展开目录
next 数组的求解基于 “真前缀” 和 “真后缀”,即next[i]等于P[0]...P[i - 1]最长的相同真前后缀的长度(请暂时忽视 i 等于 0 时的情况,下面会有解释)。我们依旧以上述的表格为例,为了方便阅读,我复制在下方了。
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| 模式串 | A | B | C | D | A | B | D | '\0' |
| next[i] | -1 | 0 | 0 | 0 | 0 | 1 | 2 | 0 |
- i = 0,对于模式串的首字符,我们统一为
next[0] = -1; - i = 1,前面的字符串为
A,其最长相同真前后缀长度为 0,即next[1] = 0; - i = 2,前面的字符串为
AB,其最长相同真前后缀长度为 0,即next[2] = 0; - i = 3,前面的字符串为
ABC,其最长相同真前后缀长度为 0,即next[3] = 0; - i = 4,前面的字符串为
ABCD,其最长相同真前后缀长度为 0,即next[4] = 0; - i = 5,前面的字符串为
ABCDA,其最长相同真前后缀为A,即next[5] = 1; - i = 6,前面的字符串为
ABCDAB,其最长相同真前后缀为AB,即next[6] = 2; - i = 7,前面的字符串为
ABCDABD,其最长相同真前后缀长度为 0,即next[7] = 0。
那么,为什么根据最长相同真前后缀的长度就可以实现在不匹配情况下的跳转呢?举个代表性的例子:假如i = 6时不匹配,此时我们是知道其位置前的字符串为ABCDAB,仔细观察这个字符串,首尾都有一个AB,既然在i = 6处的 D 不匹配,我们为何不直接把i = 2处的 C 拿过来继续比较呢,因为都有一个AB啊,而这个AB就是ABCDAB的最长相同真前后缀,其长度 2 正好是跳转的下标位置。
python实现,如下:
def partial_table(p):
'''''partial_table("ABCDABD") -> [0, 0, 0, 0, 1, 2, 0]'''
prefix = set()
postfix = set()
ret = [0]
for i in range(1, len(p)):
prefix.add(p[:i])
postfix = {p[j:i + 1] for j in range(1, i + 1)}
ret.append(len((prefix & postfix or {''}).pop()))
return ret
print partial_table("ABCDABD")
#[0, 0, 0, 0, 1, 2, 0]
全部代码:
#coding=utf-8
def kmp_match(s, p):
m = len(s);
n = len(p)
cur = 0 # 起始指针cur
table = partial_table(p)
while cur <= m - n: #只去匹配前m-n个
for i in range(n):
if s[i + cur] != p[i]:
cur += max(i - table[i - 1], 1) # 有了部分匹配表,我们不只是单纯的1位1位往右移,可以一次移动多位
break
else: #for 循环中,如果没有从任何一个 break 中退出,则会执行和 for 对应的 else
#只要从 break 中退出了,则 else 部分不执行。
return True
return False # 部分匹配表
def partial_table(p):
'''''partial_table("ABCDABD") -> [0, 0, 0, 0, 1, 2, 0]'''
prefix = set()
postfix = set()
ret = [0]
for i in range(1, len(p)):
prefix.add(p[:i])
postfix = {p[j:i + 1] for j in range(1, i + 1)}
ret.append(len((prefix & postfix or {''}).pop()))
return ret print partial_table1("ABCDABD") print kmp_match("BBC ABCDAB ABCDABCDABDE", "ABCDABD")
参考 如何理解 KMP
字符串匹配的kmp算法 及 python实现的更多相关文章
- Luogu 3375 【模板】KMP字符串匹配(KMP算法)
Luogu 3375 [模板]KMP字符串匹配(KMP算法) Description 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来 ...
- 字符串匹配的 KMP算法
一般字符串匹配过程 KMP算法是字符串匹配算法的一种改进版,一般的字符串匹配算法是:从主串(目标字符串)和模式串(待匹配字符串)的第一个字符开始比较,如果相等则继续匹配下一个字符, 如果不相等则从主串 ...
- HDU 1711 Number Sequence (字符串匹配,KMP算法)
HDU 1711 Number Sequence (字符串匹配,KMP算法) Description Given two sequences of numbers : a1, a2, ...... , ...
- 字符串匹配(KMP 算法 含代码)
主要是针对字符串的匹配算法进行解说 有关字符串的基本知识 传统的串匹配法 模式匹配的一种改进算法KMP算法 网上一比較易懂的解说 小样例 1计算next 2计算nextval 代码 有关字符串的基本知 ...
- 实现字符串匹配的KMP算法
KMP算法是Knuth-Morris-Pratt算法的简称,它主要用于解决在一个长字符串S中匹配一个较短字符串s. 首先我们从整体来把我这个算法的思想. 字符串匹配的朴素算法: 我们容易想到朴素算法, ...
- 字符串匹配的KMP算法
~~~摘录 来源:阮一峰~~~ 字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串”BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含另一个字符串”ABCDABD”? 许 ...
- 字符串匹配的KMP算法详解及C#实现
字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD" ...
- 字符串匹配与KMP算法实现
>>字符串匹配问题 字符串匹配问题即在匹配串中寻找模式串是否出现, 首先想到的是使用暴力破解,也就是Brute Force(BF或蛮力搜索) 算法,将匹配串和模式串左对齐,然后从左向右一个 ...
- 字符串匹配的KMP算法(转)
转载:http://kb.cnblogs.com/page/176818/ 字符串匹配是计算机的基本任务之一. 举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE&quo ...
随机推荐
- hdu 1151 Air Raid 最小路径覆盖
题意:一个城镇有n个路口,m条路.每条路单向,且路无环.现在派遣伞兵去巡逻所有路口,伞兵只能沿着路走,且每个伞兵经过的路口不重合.求最少派遣的伞兵数量. 建图之后的就转化成邮箱无环图的最小路径覆盖问题 ...
- 【题解】 [ZJOI2009]假期的宿舍 (二分图匹配)
懒得复制题面,戳我 Solution: 处理出床位.要留校的人(注意来访问的人一定住校),和人与人的关系(连边) 再接着就是二分图. 注意的就是连向的人必须是有床位的 还要注意的就是只用判断住校的同学 ...
- ueditor的上传文件漏洞(c#)
项目中使用了ueditor,安全测试发现一个漏洞,涉及漏洞的文件名字为UploadHandler.cs,其中有一个方法: private bool CheckFileType(string filen ...
- Java: 扩大字节缓存区的大小,提升AIO的处理性能(并发性能)
前些日了,对AIO与NIO的并发性能进行了比较,在低并发的情况下,NIO性能表现比AIO好一些,主要原因是,NIO中可以使用FileChannel.transferTo(long position, ...
- 7:servlet request getHeader("x-forwarded-for") 获取真实IP
在JSP里,获取客户端的IP地址的方法是:request.getRemoteAddr(),这种方法在大部分情况下都是有效的.但是在通过了 Apache,Nagix等反向代理软件就不能获取到客户端的真实 ...
- java开发爬虫Deno
java开发爬虫Deno 身为一个程序员不会两三手爬虫怎么能在行业里立足啊,这是开发中自己写的一个java爬虫的Demo,供大家参考. java爬虫的开发依赖于jsoup.jar 直接上代码 publ ...
- 20181111 Quartz(慕课网)
Quartz体系结构 三个核心概念 调度器 任务 触发器 重要组成 Job JobBuilder JobDetail JobStore Trigger TriggerBuilder ThreadPoo ...
- WebService和Http的POST和GET请求区别和示例
web service(SOAP) Webservice的一个最基本的目的就是提供在各个不同平台的不同应用系统的协同工作能力.Web service 就是一个应用程序,它向外界暴露出一个能够通过Web ...
- bzoj千题计划207:bzoj1879: [Sdoi2009]Bill的挑战
http://www.lydsy.com/JudgeOnline/problem.php?id=1879 f[i][j] 表示匹配了i个字符,匹配字符串的状态为j的方案数 枚举下一个字符是什么 计算加 ...
- java Concurrent 中的数据结构
一:阻塞数据结构(线程安全) ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列. LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列. PriorityBl ...