子字符串查找

  字符串的一种基本操作就是子字符串查找:给定一段长度为N的文本text和一个长度为M的模式字符串pattern,在文本中找到一个和该模式相符的子字符串。解决该问题的大部分算法都可以很容易地扩展为找出文本中和该模式相符的子字符串,统计该模式在文本中的出现次数,或者找出上下文(和该模式相符的子字符串周围的文字)的算法。现如今主要算法有暴力子字符串查找,KMP,BM字符串查找和RK指纹字符串查找。
   今天主要整理和利用python实现暴力子字符串查找算法和KMP字符串查找算法。
   设长度为N的文本为txt,长度为M的模式字符串为pat。

暴力子字符串查找算法

   顾名思义,暴力简单,就是在文本中模式可能出现匹配的任何地方检查匹配是否存在。其使用i跟踪文本,使用j跟踪模式。首先遍历i,对每个i,将j重置为0并遍历模式字符串,直到出现不匹配字符或者模式结束。然后在模式字符串结束之前文本字符串就已经结束了,那么就没有找到匹配。其中,找到匹配返回匹配起始位置,否则返回N(len(txt))。暴力子字符串查找算法的时间复杂度为O(M*N)

代码实现

'''
Implementation of a violent substring lookup algorithm
Args:pat: pattern stringtxt: text
Return:match successfully, return i which is the location of pat in txtfailed, return the length of txt
'''def search1(pat, txt):M = len(pat)  # the length of patternN = len(txt)  # the length of textfor i in range(N-M+1):  # i is following txtm = 0  # the value of jfor j in range(M):  # j is following patif txt[i+j] != pat[j]:breakm += 1if m == M:return i  # we find the matchreturn N  # not founddef search2(pat, txt):M = len(pat)  # the length of patternN = len(txt)  # the length of texti = 0  # i is following txtj = 0  # j is following patwhile(i<N):  if txt[i] == pat[j]:j += 1i += 1else:i -= j-1j = 0if j == M:return i-M  # we find the matchreturn N  # not found
    前者使用了双层for循环,后者在此基础上对其做了一些改进,对指向模式的j的值和指向文本的i的值进行了回溯,如果字符不匹配,则将i指向本次匹配开始位置的下一个字符位置,将j指向模式的开头。

KMP子字符串查找算法之前沿——确定有限状态自动机

     KMP算法命名是根据发明人Knuth,Morris和Pratt的名字命名,它的基本思想就是当出现不匹配时,我们就能知晓一部分文本的内容,因为在匹配失败之前这部分文本内容已经和模式字符串的部分内容相匹配。这样,我们就可以不用每次都将指针i退回到所有我们知晓的文本内容之前。KMP算法是暴力子字符串查找算法的优化,它的时间复杂度是O(M+N)。
     在KMP算法中,我们不会回退文本指针i,而使用一个dfa[][]数组来记录匹配失败情况和匹配成功情况下模式指针j回退的具体位置。这个数组定义的是一个确定有限状态自动机。——转自工程师WWW的博客
      图中显示的是和模式字符串 A B A B A C 对应的确定有限自动机,它是由状态和转换的方向组成。每个字符对应着一个状态,每个状态能够转换为字母表中的任意字符(我在这里采用ascii码值表示所有的字符,一共有256个)。字符对应的每个状态,只有一条转换是匹配成功的转换,其他都是非匹配转换。状态与字符的比较相对应,每个状态都表示一个模式字符的索引值。
      我们举个例子,假设txt为“ABABCABABACA”,pat为“ABABAC”。如图是pat对应的确定有限状态自动机。下面是对有限自动机的转换过程的详细解释。
  1. i = 0, j = 0, txt[i] = pat[j] = 'A', dfa[ord('A')][j] = 1,则 next[j] = 1, i++;
  2. i = 1, j = 1, txt[i] = pat[j] = 'B', dfa[ord('B')][j] = 2, 则 next[j] = 2, i++;
  3. i = 2, j = 2, txt[i] = pat[j] = 'A', dfa[ord('A')][j] = 3, 则 next[j] = 3, i++;
  4. i = 3, j = 3, txt[i] = pat[j] = 'B', dfa[ord('B')][j] = 4, 则 next[j] = 4, i++;
  5. i = 4, j = 4, txt[i] = 'C', pat[j] = 'A', dfa[ord('C')][j] = 0, 则 next[j] = 0, i++;
  6. i = 5, j = 0, txt[i] = pat[j] = 'A', dfa[ord('A')][j] = 1,  则 next[j] = 1, i++;
  7. i = 6, j = 1, txt[i] = pat[j] = 'B', dfa[ord('B')][j] = 2,  则 next[j] = 2, i++;
  8.   i = 7, j = 2, txt[i] = pat[j] = 'A', dfa[ord('A')][j] = 3, 则 next[j] = 3, i++;
  9. i = 8, j = 3, txt[i] = pat[j] = 'B', dfa[ord('B')][j] = 4, 则 next[j] = 4, i++;
  10. i = 9, j = 4, txt[i] = pat[j] = 'A', dfa[ord('A')][j] = 5, 则 next[j] = 5, i++;
  11. i = 10, j = 5, txt[i] = pat[j] = 'C', dfa[ord('C')][j] = 6, 则 next[j] = 6, i++;
  12. i = 11, j = 6, j == len(pat), 匹配转换,到达停止状态, 则返回查找匹配成功的位置 i - j = 5(从0开始)。
    
     如果自动机到达了状态M,我们就称之为确定有限状态自动机识别了该模式,也就是在文本中找到了和该模式字符串相匹配的子字符串。
     在构造DFA上,我们一般遵循三个准则
  • 对于匹配失败的情况,我们将 dfa[][X] 复制 dfa[][j]。
  • 对于匹配成功的情况,我们将 dfa[ord(pat[j])][j] 设为 j+1,也就是模式字符的下一个位置。
  • 更新X的状态,X为匹配后的重启状态。

代码实现

'''
Implementation of a Knuth-Morris-Pratt substring lookup algorithmord() : Converts ASCII characters to corresponding values
'''
class KMP:def __init__(self, pat, txt, R = 256):self.__pat = patself.__txt = txtself.R = R  # ASCII numself.dfa = [[0 for i in range(len(self.__pat))] for j in range(self.R)]  # DFA'''Args:pat: patternReturn:dfa'''def __initdfa(self):self.dfa[ord(self.__pat[0])][0] = 1X = 0  # restarting indexfor j in range(1,len(self.__pat)):  # count dfa[][j]for c in range(self.R):self.dfa[c][j] = self.dfa[c][X]self.dfa[ord(self.__pat[j])][j] = j+1X = self.dfa[ord(self.__pat[j])][X]  # update restarting index'''Args:txt: textReturn:match successfully, return i-M which is the location of pat in txtfailed, return the length of txt N'''def search(self):self.__initdfa()M = len(self.__pat)  # the length of patternN = len(self.__txt)  # the length of texti = 0  # i is following txtj = 0  # j is following patwhile(i<N):  j = self.dfa[ord(self.__txt[i])][j]i += 1if j == M:return i-M  # we find the matchreturn N  # not found

KMP子字符串查找算法

    在接下来的学习之后突然发现上面的基于确定有限状态自动机的KMP算法还不算是KMP算法,只是利用有限自动机进行字符串匹配。真正的KMP算法不用提前预先计算变迁函数(用以表示自动机),只需要用到辅助函数s[1,m], 这个前缀函数包含有模式与其自身的位移进行匹配的信息。这些信息可用于避免在朴素的字符串匹配算法中,对无用位移进行测试,也可以避免在字符串匹配自动机中对变迁函数的预先计算过程(也就是dfa[][]数组)。
       数组s有m个元素,而数组dfa有m*256个元素,通过预先计算s而不是dfa,会使得子字符串查找中减少了一定的时间(时间会减少一个字母表的个数因子,这里是256)。
       而这个数组s我们就叫做关于模式的前缀函数,它包含有模式与其自身的位移进行匹配的信息。我们还是按照之前的例子来进行解释。 txt 为“ABABCABABACA”, pat 为“ABABA”。N = 12 , M = 5。i 是 txt 的索引, j 是 pat 的索引,d 是 当前模式字符串处于文本中的位置, dd 是下次匹配不成功时模式字符串应当在文本中的有效位置。
  1. i = 0, j = 0, d = 0, txt[i] = pat[j] = 'A', 则 next[j] = 1, i++, dd = 0 + (1-s[0]) = 1;
  2. i = 1, j = 1, d = 0, txt[i] = pat[j] = 'B', 则 next[j] = 2, i++, dd = 0 + (2-s[1]) = 2;
  3. i = 2, j = 2, d = 0, txt[i] = pat[j] = 'A', 则 next[j] = 3, i++, dd = 0 + (3-s[2]) = 2;
  4. i = 3, j = 3, d = 0, txt[i] = pat[j] = 'B', 则 next[j] = 4, i++, dd = 0 + (4-s[3]) = 2;
  5. i = 4, j = 4, d = 0, txt[i] = 'C', pat[j] = 'A', txt[i] != pat[j] ,则 next->d = 2, next[j] = 2;
  6. i = 4, j = 2, d = 2, txt[i] = 'C', pat[j] = 'A, txt[i] != pat[j], 则 next[j] = 0, i++,  next->d = 5-0 = 5;
  7. i = 5, j = 0, d = 5, txt[i] = pat[j] = 'A', 则 next[j] = 1, i++, dd = 6;
  8. i = 6, j = 1, d = 5, txt[i] = pat[j] = 'B', 则 next[j] = 2, i++, dd = 7;
  9. i = 7, j = 2, d = 5, txt[i] = pat[j] = 'A', 则 next[j] = 3, i++, dd = 7;
  10. i = 8, j = 3, d = 5, txt[i] = pat[j] = 'B', 则 next[j] = 4, i++, dd = 7;
  11. i = 9, j = 4, d = 5, txt[i] = pat[j] = 'A', 则 next[j] = M = 5, 达到终止状态, 匹配成功。返回d=5(从0开始)。
      我们来解释一下 i = 3 的时候的dd值大小,其他的类似(解释第三个无他,幸运数字是3)。 此时我们设已匹配到的模式字符串为“ABAB”, 长度为4,我们对其求真后缀的最长前缀为“AB”,长度为2。那么 s[4] = 2(这里的s[]数组索引从1开始容易对其定义)。 于是我们可以得出一个结论:在模式字符串开始的位移d(=1)处有q(=4)个字符成功匹配,则下一个模式字符如果匹配不成功时模式字符串的有效位置为 dd = d + (q - s[q]) = 1+(4-2) = 3 。
      前缀是除了最后一个字符之外此字符串的全部头部组合,比如“ABCD”,其前缀有A, AB, ABC。
      后缀是除了第一个字符之外此字符串的全部尾部结合,比如“ABCD”,其后缀有BCD, CD, D。
      真后缀的最长前缀就是字符串的前后缀的最长的公共的元素组合。
      这样我们就得到了关于模式的前缀函数 s 的值,我在下面表格里有写出来。
j
0
1
2
3
4
pat[j]
A
B
A
B
A
s[j]
0
0
1
2
3

代码实现

'''
Implementation of a Knuth-Morris-Pratt substring lookup algorithm'''
class KMP:def __init__(self, pat, txt):self.__pat = patself.__txt = txtself.s = [0 for j in range(len(self.__pat))]  # s'''Find the maximum same prefix length for a stringArgs:pat: patternReturn:s'''def __inits(self):self.s[0] = 0  # Maximum prefix length of the first character of a template string is 0X = 0  # restarting indexfor j in range(1,len(self.__pat)):  # count s[j]while (X > 0 and self.__pat[j] != self.__pat[X]):X = self.s[X-1]  # update restarting indexif self.__pat[j] == self.__pat[X]:X += 1self.s[j] = X'''Args:txt: textpat: patternReturn:match successfully, return d which is the location of pat in txtfailed, return the length of txt Nj      0 1 2 3 4pat[j] A B A B As[j]   0 0 1 2 3'''        def search(self):#s = [0,0,1,2,3]self.__inits()M = len(self.__pat)  # the length of patternN = len(self.__txt)  # the length of texti = 0  # i is following txtj = 0  # j is following patd = 0  # current distancedd = 0  # willing distancewhile(i < N):  if self.__txt[i] == self.__pat[j]:dd = d + (j + 1 - self.s[j])print i,j,d,ddi += 1j += 1else:d = ddj = i - dif j < 0:i += 1j = 0dd = d + (j + 1 - self.s[j])print i,j,d,ddif j == M:return d  # we find the matchreturn N  # not found
      

字符串之子字符串查找相关推荐

  1. Python判断字符串包含子字符串(个数、索引、全部位置)

    从左向右查找子串,存在则输出子串首字符的索引值,不存在则输出-1 # find()a = 'love you' b = 'you' c = 'no' print(a.find(b)) #5 print ...

  2. 如何在Python中对字符串进行子字符串化

    Python offers many ways to substring a string. It is often called 'slicing'. Python提供了许多对字符串进行子字符串化的 ...

  3. C语言检查一个字符串是否为另一个字符串的子字符串的算法(附完整源码)

    C语言检查一个字符串是否为另一个字符串的子字符串的算法 C语言检查一个字符串是否为另一个字符串的子字符串的算法完整源码(定义,实现,main函数测试) C语言检查一个字符串是否为另一个字符串的子字符串 ...

  4. js字符串slice_JavaScript子字符串示例-JS中的Slice,Substr和Substring方法

    js字符串slice In daily programming, we often need to work with strings. Fortunately, there are many bui ...

  5. Python是否具有字符串“包含”子字符串方法?

    我正在寻找Python中的string.contains或string.indexof方法. 我想要做: if not somestring.contains("blah"):co ...

  6. 如何在Python中获取字符串的子字符串?

    有没有一种方法可以在Python中为字符串加上字符串,以从第三个字符到字符串的末尾获取新的字符串? 也许像myString[2:end] ? 如果离开第二部分意味着"直到最后",而 ...

  7. Shell 如何判断字符串包含子字符串

    包含子字符串 #!/bin/bash # string='hello world' sub='hello'if [[ $string =~ $sub ]] # if [[ $string = *$su ...

  8. Java源码-判断两个字符串的子字符串是否匹配(Comparing Portions of Strings)

    字符串在现实中几乎无所不在,所有文本都可以看做是字符串,因为实用,所以"挺好玩!". 当然,编程的目的肯定不止为了好玩. 代码如下: import java.util.Scanne ...

  9. LeetCode简单题之最长的美好子字符串

    题目 当一个字符串 s 包含的每一种字母的大写和小写形式 同时 出现在 s 中,就称这个字符串 s 是 美好 字符串.比方说,"abABB" 是美好字符串,因为 'A' 和 'a' ...

最新文章

  1. 像我这种垃圾学校出来的人...【原话,不是我编的】
  2. 这个椅子哪里卖?我也想买啊!
  3. 压缩视频 html5播放,将HTML5视频呈现为Canvas正在压缩图像
  4. 模式识别与智能系统和计算机视觉,天津大学模式识别与智能系统
  5. 《剑指offer》——04. 二维数组中的查找——暴力法、线性查找——java实现
  6. mysql删除数据不会减少存储占用_Mysql单文件存储删除数据文件容量不会减少的bug与解决方法...
  7. 西北大学计算机转专业,西北大学可以转专业吗,西北大学新生转专业政策
  8. C# 数组增加元素_C#的集合类型及使用技巧
  9. Vue应用框架整合与实战--Vue技术生态圈篇
  10. 大学mysql期末试题_四川大学数据库系统期末试题2014-2015.doc
  11. LightGBM-GBDT-LR使用树集合进行特征转换
  12. 【静脉检测】基于matlab手指静脉图像检测【含Matlab源码 1654期】
  13. java识别验证码图片_Java识别图像、验证码
  14. java 图片压缩 base64,图片的尺寸 大小压缩 和转化为base64
  15. CISC和RISC的区别
  16. 【技能教学】如何通过FFMPEG编码推RTSP视频直播流到EasyDarwin开源平台时叠加时间水印?
  17. Swift中键盘的弹出隐藏,页面抬高,Return键等的配置
  18. Inner Join与Left Join
  19. hashmap和数组哪个速度快
  20. CIU软考联盟:软件设计师上午试题解析-操作系统篇

热门文章

  1. Outlook 2019 for mac(outlook邮箱)
  2. 【软件测试 #1】策略练习题
  3. 【数据分发服务DDS】软件定义汽车【二】- 面向服务的架构设计
  4. 分布式事务解决方案之RabbitMQ
  5. 金盘系统无法连接服务器,Re:从零开始的服务器
  6. AUTOCAD学习笔记4:常用电器元件的绘制
  7. 运算放大器的工作原理
  8. 安川伺服调试软件,软件可修改伺服参数,jog运行
  9. 将nginx作为视频点播服务器
  10. 苹果x要不要升ios14.4系统?