字符串匹配
什么叫字符串匹配或者字符串模式匹配?
假设主串为:“abcdeabcef” 子串为"abce" ,找出子串在主串中第一次出现的位置,即红色部分第一个字符的索引,上述问题就是我们通常说的字符串匹配问题,或者一些要用到上面知识的问题。
朴素算法
这个算法就跟它的名字一样,很朴素,就是理解起来比较简单,大家都会想到的思路,就是主串,子串各自建立一个索引,从下标为0开始比较,如果S[i]=P[i] 则继续比较下一位,当出现不等的情况时,子串整体向右移动一位,注意,是整体右移,与S[1]及其后面的字符开始比较。此处似乎应该放些图,更好理解。
好,前面那些步骤应该是很正常的,也是可以理解的,此时我们已经发现有6个字符是匹配好的,然后朴素算法的下一步却是:如下图
是不是觉得很可惜,觉得子串为什么不往后多移动几个位置,那么,移动几个位置呢?为什么要移动这个数量的位置呢?移动这个数量有什么根据吗?好这就引出了接下来的KMP算法
以上图片来源:http://www.ruanyifeng.com/blog/2013/05/Knuth–Morris–Pratt_algorithm.html
链接中算法原理讲的非常详细,但是个人觉得有点冗长,还是很推荐看一下的。
朴素算法的代码如下:
# -*- coding: utf-8 -*-
def String_Match(string,substring):
len1,len2=len(string),len(substring)
i,j=0,0
while i<len1-len2+1:
if string[i+j]==substring[j]: #如果相等则一直往后匹配
if j==len2-1:#当匹配至子串最后一个字符时,返回索引i
return i
j+=1
else: #匹配失败,则子串整体右移一位
j=0
i+=1
return -1 #不存在该子串
s='abcdeabcef'
p1='abce'
p2='abef'
print(String_Match(s,p1))
print(String_Match(s,p2))
![](https://i-blog.csdnimg.cn/blog_migrate/c37b8d5d5ca66232d42ca87f092ac85b.png)
KMP算法
总的来说,字符串匹配还是经常遇到的,也看到过很多KMP算法思想的介绍,最终根据自己的体会,得到一张图,可能有点抽象,但是个人认为是这个算法的核心了,能看懂最好,看不懂也别怪我了:
图中写的较抽象,勿怪,更专业一点,子串当出现匹配失败时,子串右移位数是由一个叫做next表的东西来决定的,看了不少的博客,这个地方都是讲得似乎比较含糊,要不就是贴一段代码上来,为什么是这样求?怎么可以这样求?求next表才是是KMP算法的关键,因此我画了上面那图来帮助理解。
next表
首先,next表是对于子串而言的,它决定了子串的某一位匹配失败时,应该回退到哪,即这一位匹配失败回退值由这一位的next值决定。通过上面我画的图,相等区域,有个更专业的名称,叫最长公共前后缀,举个例子:
- abcddabc :最长公共前后缀为 abc
- efabcdef :最长公共前后缀为 ef
那么,当我们匹配失败时,(看上面的图的右下角部分),我们从最长公共前后缀的前缀后一位开始继续匹配,因为它前面的都已经是匹配成功的。
next表的建立
next表的由来上面已经解释清楚了,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的最大长度值 下面通过一个例子来看建立过程
例如子串为: a b c d e a b f
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
子串/模式串 | a | b | c | d | e | a | b | f |
next[i] | -1 | 0 | 0 | 0 | 0 | 0 | 1 | 2 |
-
next[0]=-1 这个记一下,-1表示不存在相同的最大前缀和最大后缀,模式串首字符的next值为-1
-
next[1],next[2],next[3],next[4],next[5] 值均为 0,因为 i 位前的串最长公共前后缀长度都为0
-
next[6]=1 ,因为substring[6] 前面为 abcdea 最长公共前后缀为 a 长度为 1
-
next[7]=2 ,同next[6]理可得
即next[k]: substring 的前k个元素组成字符串的公共前后缀最大长度
def Get_next(substring):
n=len(substring)
next_table=[0]*n
next_table[0]=-1
j=0
for i in range(1,n-1): #i+1最大取到n-1
while j>0 and substring[i]!=substring[j]:
j=next_table[j]
if substring[i]==substring[j]:
j+=1
next_table[i+1]=j #next_table[i+1]是前面0到i串的公共缀最大长度
return next_table
#打印next表
print(next('ABCABD'))
print(next('abcdeabcef'))
KMP算法代码实现
# -*- coding: utf-8 -*-
def Get_next(substring):
n=len(substring)
next_table=[0]*n
next_table[0],next_table[1]=-1,0
j=0
for i in range(1,n-1): #i+1最大取到n-1
while j>0 and substring[i]!=substring[j]:
j=next_table[j]
if substring[i]==substring[j]:
j+=1
next_table[i+1]=j #next_table[i+1]是前面0到i串的公共缀最大长度
return next_table
#打印next表
print(Get_next('ABCABD'))
print(Get_next('abcdeabcef'))
def Kmp_Match(s,p):
len1,len2=len(s),len(p)
next_table=Get_next(p)
i,j=0,0
while i<len1:
if s[i]!=p[j] and j>0:
j=next_table[j] #子串回退
else:
if j==len2-1: #全部匹配成功
return i-len2+1 #返回开头处索引
j+=1
i+=1
return -1 #不存在该子串
#测试案例
print(Kmp_Match('abcdeabcef','abce')) # 5
print(Kmp_Match('abcfe','abce')) # -1 表示不存在该子串
print(Kmp_Match('abcef','abce')) # 0
print(Kmp_Match('abc','abce')) #len2 >len1 不存在该子串
print(Kmp_Match('ABABABC','ABC')) # 4
print(Kmp_Match('aabbABccABCABD','ABCA')) # 8
print(Kmp_Match('1235551234','1234')) # 6
![](https://i-blog.csdnimg.cn/blog_migrate/b6da451405a3837e577a97fcf47addbe.png)
如有问题,请随时指出,万分感谢!