【C++研发面试笔记】22. 常用算法-字符串查找算法

22. 常用算法-字符串查找算法

22.1 KMP算法

KMP字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法。简单匹配算法的时间复杂度为O(m*n);而KMP算法,可以证明它的时间复杂度为O(m+n)。
在字符串O中寻找f,当匹配到位置i时两个字符串不相等,这时我们需要将字符串f向前移动。常规方法是每次向前移动一位,但是它没有考虑前i-1位已经比较过这个事实,所以效率不高。事实上,如果我们提前计算某些信息,就有可能一次前移多位。

22.1.1 相关概念

22.1.1.1公共前缀与后缀

kmp算法的核心即是计算字符串f每一个位置之前的字符串的前缀和后缀公共部分的最大长度
以”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。
    《【C++研发面试笔记】22. 常用算法-字符串查找算法》

22.1.1.2 Next数组

“部分匹配”的实质是,有时候字符串头部和尾部会有重复。比如,”ABCDAB”之中有两个”AB”,那么它的”部分匹配值”(最大公共长度)就是2。搜索词移动的时候,第一个”AB”向后移动4位(字符串长度-部分匹配值),就可以来到第二个”AB”的位置。
理解了kmp算法的基本原理,下一步就是要获得字符串f每一个位置的最大公共长度。这个最大公共长度在算法导论里面被记为next数组。
《【C++研发面试笔记】22. 常用算法-字符串查找算法》

22.1.1.3 计算Next数组的方法

假设我们现在已经求得原字符串的next[1]、next[2]、……next[i],分别表示长度为1到i的字符串的前缀和后缀最大公共长度,现在要求next[i+1]。

  1. 如果位置i和位置next[i]处的两个字符相同(下标从零开始),则next[i+1]等于next[i]加1。
  2. 如果两个位置的字符不相同,我们可以将长度为next[i]的字符串继续分割,获得其最大公共长度next[next[i]],然后再和位置i的字符比较。这是因为长度为next[i]前缀和后缀都可以分割成上部的构造,如果位置next[next[i]]和位置i的字符相同,则next[i+1]就等于next[next[i]]加1。如果不相等,就可以继续分割长度为next[next[i]]的字符串,直到字符串长度为0为止。

22.1.2 具体实现

《【C++研发面试笔记】22. 常用算法-字符串查找算法》
《【C++研发面试笔记】22. 常用算法-字符串查找算法》

22.2 BM算法

Boyer-Moore字符串搜索算法是一种非常高效的字符串搜索算法。它由Boyer和Moore设计于1977年。此算法仅对搜索目标字符串(关键字)进行预处理,而非被搜索的字符串。虽然Boyer-Moore算法的执行时间同样线性依赖于被搜索字符串的大小,但是通常仅为其它算法的一小部分:它不需要对被搜索的字符串中的字符进行逐一比较,而会跳过其中某些部分。通常搜索关键字越长,算法速度越快。它的效率来自于这样的事实:对于每一次失败的匹配尝试,算法都能够使用这些信息来排除尽可能多的无法匹配的位置。
KMP算法和BM算法在最坏情况下均具有线性的查找时间。但是在实用上,KMP算法并不比最简单的c库函数strstr()快多少,而BM算法则往往比KMP算法快上3-5倍。

22.2.1 相关概念

22.2.1.1 坏字符规则:即不匹配的字符

  1. 如果T中的这个不匹配的字符出现在对应P中当前位置的左侧,那么P移动位置将这两个在字符对齐。
  2. 如果T中这个不匹配字符不在P中当前位置的左侧,那么将当前位置左侧的所有字符均移到该不匹配字符后(坏字符)。

“坏字符规则”:

  1. 后移位数 = 坏字符的位置 – 搜索词中的上一次出现位置
  2. 如果”坏字符”不包含在搜索词之中,则上一次出现位置为-1。
    《【C++研发面试笔记】22. 常用算法-字符串查找算法》

22.2.1.2 好后缀规则:即所有尾部匹配的字符串

后移位数 = 好后缀的位置 – 搜索词中的上一次出现位置

  1. 好后缀的位置以最后一个字符为准。假定”ABCDEF”的”EF”是好后缀,则它的位置以”F”为准,即5(从0开始计算)。
  2. 如果”好后缀”在搜索词中只出现一次,则它的上一次出现位置为 -1。比如,”EF”在”ABCDEF”之中只出现一次,则它的上一次出现位置为-1(即未出现)。
  3. 如果”好后缀”有多个,则除了最长的那个”好后缀”,其他”好后缀”的上一次出现位置必须在头部。
    比如:在所有的”好后缀”(MPLE、PLE、LE、E)之中,只有”E”在”EXAMPLE”还出现在头部,所以后移 6 – 0 = 6位。
    《【C++研发面试笔记】22. 常用算法-字符串查找算法》

22.2.1.3 实际移动:通过这两条规则计算出的最大移动个数

这两个规则的移动位数,只与搜索词有关,与原字符串无关。因此,可以预先计算生成《坏字符规则表》和《好后缀规则表》。使用时,只要查表比较一下就可以了。所以要查询的字符串长度越长,BM算法的效率也越好!

22.2.2 具体实现

《【C++研发面试笔记】22. 常用算法-字符串查找算法》
《【C++研发面试笔记】22. 常用算法-字符串查找算法》

22.3 有限自动机算法

首先我们看看《算法导论》里的一个例子:
《【C++研发面试笔记】22. 常用算法-字符串查找算法》
图a描述的是匹配模式串ababaca的状态转换图,图b描述的是匹配模式串ababaca的状态转换表,图c描述的是搜索文本串abababacaba中匹配模式串过程的状态变换过程。
图b中状态转换表的含义:比如状态为0,输入为a的项为1,表示转换函数δ(0,a)=1,即当状态为0时,输入为a将转移到状态1。

22.3.2 状态转换表的自动构造过程:

《【C++研发面试笔记】22. 常用算法-字符串查找算法》

22.4 RK算法(Rabin-Karp算法)

RK算法的原理:两个数如果其关于某数的取模相等,则其这两个数可能相等。我们通过将字符串视为一串数字(某种进制),然后比较这些数字的取模值,取模值相同的数作为伪命中点,之后在伪命中点集再去匹配字符串。
这么做的原因是:相邻字符串的取模值有非常快速计算迭代公式:
T[s+1]=((T[s]-h*w mod q)+d )mod q
这里的h表示高位数字,T[s]表示字符串数字的模,T[s+1]表示下一个字符串数字的模,h表示高位数字,d表示低位数字,w表示进制数。下面是一个具体的例子:
《【C++研发面试笔记】22. 常用算法-字符串查找算法》

这篇博文是个人的学习笔记,内容许多来源于网络(包括CSDN、博客园及百度百科等),博主主要做了微不足道的整理工作。由于在做笔记的时候没有注明来源,所以如果有作者看到上述文字中有自己的原创内容,请私信本人修改或注明来源,非常感谢>_<

    原文作者:查找算法
    原文地址: https://blog.csdn.net/tostq/article/details/52733649
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞