您的位置:首页 > 游戏 > 游戏 > 青岛网络推广服务_咸阳网站建设工作室_电商关键词工具_seo引擎优化方案

青岛网络推广服务_咸阳网站建设工作室_电商关键词工具_seo引擎优化方案

2024/12/23 15:20:24 来源:https://blog.csdn.net/xuchaoxin1375/article/details/144361453  浏览:    关键词:青岛网络推广服务_咸阳网站建设工作室_电商关键词工具_seo引擎优化方案
青岛网络推广服务_咸阳网站建设工作室_电商关键词工具_seo引擎优化方案

文章目录

    • KMP 串模式匹配算法
    • 串的模式匹配算法——KMP 算法
      • 暴力算法为什么慢
    • 字符串子串的相关概念
      • 前缀和后缀
      • 部分匹配值(PM)
      • 部分匹配值序列(PM表)
      • 滑动位数计算公式
      • 小结
      • 应用示例
      • 完整步骤
    • 算法时间复杂度
    • 小结

KMP 串模式匹配算法

  • In computer science, the Knuth–Morris–Pratt string-searching algorithm (or KMP algorithm) searches for occurrences of a “word” W within a main “text string” S by employing the observation that when a mismatch occurs, the word itself embodies sufficient information to determine where the next match could begin, thus bypassing re-examination of previously matched characters.
  • 理解KMP算法有几个层次,第一个层次是认识朴素暴力的模式匹配算法的不足之处,针对该不足找到提高效率的突破口
  • 第二个层次是找到通过探索找到系统的方法来描述这个加速的操作及其依赖的要素
  • 第三个层次是优化和改进算法使其更适合编写为程序和细节的深刻认识

串的模式匹配算法——KMP 算法

暴力算法为什么慢

在暴力匹配中,每趟匹配失败都是模式串后移一位再从头开始比较。而某趟已匹配相等的字符序列是模式串的某个前缀这种频繁的重复比较相当于模式串不断地进行自我比较,这就是其低效率的根源

因此,可以从分析模式串本身的结构着手,若已匹配相等的前缀序列中有某个后缀正好是模式串的前缀,则可将模式串向后滑动到与这些相等字符对齐的位置,主串 i i i 指针无须回溯,并从该位置开始继续比较。

事实上,模式串向后滑动位数的计算仅与模式串本身的结构有关,而与主串无关

例如
s t s t + 1 ⋯ s x s x + 1 ↓ i ⋯ p 1 p 2 ⋯ p j p j + 1 ↑ j ⋯ \Large\begin{matrix} s_{t}&s_{t+1}&\cdots&s_{x}&\overset{\downarrow^{\Large i}}{\boxed{s_{x+1}}}\cdots\\ p_{1}&p_{2}&\cdots&p_{j}&\underset{{\uparrow}_{\Large j}}{\boxed {p_{j+1}}}\cdots\\ \end{matrix} stp1st+1p2sxpjsx+1ijpj+1
主串的 s t ⋯ s x s_{t}\cdots{s_{x}} stsx p 1 ⋯ p j p_{1}\cdots{p_{j}} p1pj是对应相等的即 s t ⋯ s x s_{t}\cdots{s_{x}} stsx= p 1 ⋯ p j p_{1}\cdots{p_{j}} p1pj,而在 s x + 1 ≠ p j + 1 s_{x+1}\neq{p_{j+1}} sx+1=pj+1;也就是说此次匹配检查中有 j j j个字符匹配上了

所以可以改写为
p 1 p 2 ⋯ p j s x + 1 ↓ i ⋯ p 1 p 2 ⋯ p j p j + 1 ↑ j ⋯ \begin{matrix} p_{1}&p_{2}&\cdots&p_{j}&\overset{\downarrow^{\Large i}}{\boxed{s_{x+1}}}\cdots\\ p_{1}&p_{2}&\cdots&p_{j}&\underset{{\uparrow}_{\Large j}}{\boxed {p_{j+1}}}\cdots\\ \end{matrix} p1p1p2p2pjpjsx+1ijpj+1
由此可以看到,模式串向后滑动位数的计算仅与模式串本身的结构有关,而与主串无关

现在问题是模式串需要滑动到下一个位置继续比较,滑动偏移量应该如何确定?

显然偏移量越大越好,这样可以减少比较次数,尽快扫描完主串结束算法

为了便于理解,这里举几个例子

某个主串序列 a b c a b m ⋯ abcabm\cdots abcabm,模式串序列 a b c a b c n ⋯ abcabcn\cdots abcabcn;可以看到 a b c a b abcab abcab是两个比较串共同的前缀,从 m ≠ n m\neq{n} m=n处开始失配( m , n m,n m,n可以是任意不想等的两个字符,比如分别取 a , b a,b a,b)

根据暴力匹配算法(回溯方法),可以得知模式串在最初失配的时候,模式串的第 3 3 3次向右滑动(每次滑动一个位置)前都会失配,第三次滑动时能匹配上模式串的前缀 a b c abc abc,但是后续如何取决于 m m m之后的序列
a b c a b m ⋯ a b c a b c n ⋯ \begin{matrix} \boxed a&b&c&\boxed a&b&m&\cdots\\ &&&a&b&c&a&b&c&n&\cdots \end{matrix} abcaabbmcabcn
可以看到即使仅根据已经匹配的部分模式串的子串 a b c a b abcab abcab,就可以把模式串向后滑动若干位置,本例中是滑动了3个位置,但是这里还是用了回溯的方法来确定滑动位置数,后面有更好的方法,这里只是说明模式串和字串已经匹配上的头部字串可以利用起来

将这个结论记录起来,之后如果还遇到主串和模式串的头部匹配上的序列为 a b c a b abcab abcab的情况,我们就可以直接滑动3个位置,依次类推,如果将可将所有模式串和主串匹配上的情况都列举出来,那么每次可以滑动的距离就可以直接查出来,做到不回溯

为了将问题具象化,我们把本例的模式串定为 T = a b c a b c T=abcabc T=abcabc,它的长度为6,那么所有可能产生的部分匹配或全部匹配共有6种情况

这6种情况枚举出来就是头部子串集合 { a , a b , a b c , a b c a , a b c a b , a b c a b c } \set{a,ab,abc,abca,abcab,abcabc} {a,ab,abc,abca,abcab,abcabc}

对于 a a a,滑动的距离只能是1

对于 a b ab ab滑动的距离是 2 2 2

又比如另一对主串和模式串 a b c d e abcde abcde做匹配,某次匹配到的部分为 a b c d abcd abcd,那么在 m m m处失配后,能够继续滑动4个位置得到以下结果
a b c d m ⋯ a b c d e \begin{matrix} a&b&c&d&m&\cdots\\ &&&&a&b&c&d&e \end{matrix} abcdmabcde
同理,主串的后续部分如果又遇到了部分匹配 a b c d abcd abcd,那么可以不回溯将模式串向后移动 4 4 4个位置

更一般的主串某个部分和模式串匹配上的部分为 p 1 ⋯ p j p_{1}\cdots{p_{j}} p1pj
p 1 ⋯ p j s j + 1 ⋯ p 1 ⋯ p j p j + 1 ⋯ \begin{matrix} p_{1}& \cdots&p_{j}&s_{j+1}&\cdots\\ p_{1}& \cdots&p_{j}&p_{j+1}&\cdots \end{matrix} p1p1pjpjsj+1pj+1
那么今根据匹配上的这部分内容,挖掘 p 1 ⋯ p j p_{1}\cdots{p_{j}} p1pj序列上的信息,

p 1 ⋯ p k 1 p_{1}\cdots{p_{k_{1}}} p1pk1= p k 2 ⋯ p j p_{k_{2}}\cdots{p_{j}} pk2pj; ( k 1 , k 2 ∈ { 1 , ⋯ , j } ) (k_{1},k_{2}\in\set{1,\cdots,j}) (k1,k2{1,,j}),设等式两边的串的长度为 L k L_{k} Lk, k 1 , k 2 k_{1},k_{2} k1,k2可能有多个取值组合,但是能过作为偏移参考的只有使得 L k L_{k} Lk取最大值的那一组是我们关心的,后面我们默认 L k L_{k} Lk是最大的那一个,除非特别强调

这时,主串失配后的偏移量就是 L k L_{k} Lk的相关表达式: j − L k j-L_{k} jLk,此时我们希望 L k L_{k} Lk越小越好

为了方便继续讨论,需要了解以下关于字串的概念, L k L_{k} Lk,称为串 p 1 ⋯ p j p_{1}\cdots{p_{j}} p1pj部分匹配值,后面我们会将其记为 P M ( p 1 ⋯ p j ) = L k PM(p_{1}\cdots{p_{j}})=L_{k} PM(p1pj)=Lk

字符串子串的相关概念

要了解子串的结构,首先要弄清楚几个概念:

前缀、后缀和部分匹配值。前两个在这里理解为串的集合

前缀和后缀

设串 S = s 1 s 2 s 3 ⋯ s n S=s_1s_2s_3\cdots s_{n} S=s1s2s3sn; 我们一这个一般性的形式化的串作为讨论对象

  • 前缀指除最后一个字符以外,字符串的所有头部子串
    • S S S的所有头部子串的通式可以描述为 s 1 s 2 ⋯ s x s_{1}s_{2}\cdots{s_{x}} s1s2sx, ( x = 1 , 2 , ⋯ , n − 1 ) (x=1,2,\cdots,n-1) (x=1,2,,n1)
    • 则串 S S S的前缀(头部子串集合)为 F ( S ) F(S) F(S)= { s 1 ⋯ s x ∣ x = 1 , 2 , ⋯ , n − 1 } \set{s_{1}\cdots{s_{x}}|x=1,2,\cdots,n-1} {s1sxx=1,2,,n1},共有 n − 1 n-1 n1个元素,长度各不相同, s 1 ⋯ s x s_{1}\cdots{s_{x}} s1sx的长度为 x x x
  • 后缀指除第一个字符外,字符串的所有尾部子串
    • S S S的所有尾部子串的通式可以描述为 s x s x + 1 ⋯ s n s_{x}s_{x+1}\cdots{s_{n}} sxsx+1sn, ( x = 2 , ⋯ , n ) (x=2,\cdots,n) (x=2,,n)
    • 则串 S S S的后缀(尾部子串集合)为 R ( S ) R(S) R(S)= { s x ⋯ s n ∣ x = 2 , ⋯ , n } \set{s_{x}\cdots{s_{n}}|x=2,\cdots,n} {sxsnx=2,,n},共有 n − 1 n-1 n1个元素,长度各不相同, s x ⋯ s n s_{x}\cdots{s_{n}} sxsn的长度为 n − ( x − 1 ) n-(x-1) n(x1)= n − x + 1 n-x+1 nx+1

为什么这里要约定前缀和后缀不包含串本身?这和前缀和后缀的应用有关,也就是用来计算串的部分匹配值,如果前后缀包含串本身,部分匹配值将失去意义,无法推进算法

部分匹配值(PM)

  • 字符串的部分匹配值(Partial Match, PM)则为字符串的前缀和后缀的最长相等前后缀长度
    • U ( S ) U(S) U(S)= m a x ( P ( S ) ∩ R ( S ) ) \rm{max(P(S)\cap{R(S)})} max(P(S)R(S)),这里的 m a x ( S ) \rm{max(S)} max(S)表示求字符串集合中最长串元素的长度

部分匹配值序列(PM表)

对于给定字符串 S = s 1 s 2 ⋯ s n S=s_{1}s_{2}\cdots{s_{n}} S=s1s2sn,其不同的头部字串,以及其本身共有 n n n个子串

分别计算 s 1 ⋯ s x s_{1}\cdots{s_{x}} s1sx, ( x = 1 , 2 , ⋯ , n ) (x=1,2,\cdots,n) (x=1,2,,n)部分匹配值(PM),最后得出 n n n个值,构成字符串 S S S部分匹配值序列(PM表)

PM表对于KMP算法中是核心内容,在模式串失配的时候应该往后滑动模式串多少位置就需要通过模式串的PM表来确定,例如 a b a b a ababa ababa的PM值为3(最长公共前后缀为 a b c abc abc,其长度为3)

滑动位数计算公式

若模式匹配问题中,主串 S S S和模式串 T T T匹配上的部分为子串 C C C,而在下一个字母处失配,那么就可以通过公式 D ( C ) = L ( C ) − P M ( C ) D(C)=\rm{L(C)-PM(C)} D(C)=L(C)PM(C)求出需要移动的位数(其中 L ( C ) , P M ( C ) L(C),PM(C) L(C),PM(C)分别表示 C C C串长和 C C C的PM值)

可以看到,滑动位数计算公式仅用到了模式串 T T T和主串 S S S匹配上的这部分公共子串 C C C, C ⊂ T C\sub{T} CT,分别计算 C C C的长度和部分匹配值做差就得到滑动位数,如果根据T的所有子串枚举出所有部分匹配值,就可以实现不回溯的快速模式匹配

下面以 ‘ababa’ 为例进行说明,它长度为5,可以写出5个头部子串(包括其本身):

  1. a a a’ 的前缀和后缀都为空集,最长相等前后缀长度为 0。
  2. a b ab ab’ 的前缀为 { a } \{a\} {a},后缀为 { b } \{b\} {b} { a } ∩ { b } = ∅ \{a\} \cap \{b\} = \emptyset {a}{b}=,最长相等前后缀长度为 0。
  3. a b a aba aba’ 的前缀为 { a , a b } \{a, ab\} {a,ab},后缀为 { a , b a } \{a, ba\} {a,ba} { a , a b } ∩ { a , b a } = { a } \{a, ab\} \cap \{a, ba\} = \{a\} {a,ab}{a,ba}={a},最长相等前后缀长度为 1。
  4. a b a b abab abab’ 的前缀和后缀的交: { a , a b , a b a } ∩ \{a, ab, aba\} \cap {a,ab,aba} { b , a b , b a b } = { a b } \{b, ab, bab\} = \{ab\} {b,ab,bab}={ab},最长相等前后缀长度为 2。
  5. a b a b a ababa ababa’ 的前缀和后缀的交: { a , a b , a b a , a b a b } \{a, ab, aba, abab\} {a,ab,aba,abab} ∩ \cap { a , b a , a b a , b a b a } = { a , a b a } \{a, ba, aba, baba\} = \{a, aba\} {a,ba,aba,baba}={a,aba},公共元素有两个,最长相等前后缀长度为 3。

因此,字符串 ‘ababa’ 的部分匹配值序列为 00123。

小结

按照上述定义法求给定字符串的部分匹配值序列不是那么方便,首先要列出前缀集合和后缀集合,然后一 一对比两个集合中相同长度的串是否相等,得到交集,再要从交集中算出最长元素的长度

后面有更好的方法求给定字符串的部分匹配值序列,这里先考虑这个部分匹配值有什么作用

前面的章节已经给出铺垫,现在我们有合适的称呼来描述相关过程

应用示例

考虑主串为 ababcabcacbab,子串为 abcac的模式匹配问题

利用上述方法容易写出子串 ‘abcac’ 的部分匹配值为 00010,将部分匹配值写成数组形式,就得到了部分匹配值表(PM表)

编号(公共序列长度L)12345
Sabcac
PM00010
D12335

这里 D ( C ) = L ( C ) − P M ( C ) D(C)=L(C)-PM(C) D(C)=L(C)PM(C), C C C表示模式串和主串匹配相等的序列串(有的地方少一样,这里为了方便加上最后一样,表示滑动位数)

这个表要如何理解和使用?

比如 L = 3 L=3 L=3 ( c , 0 ) (c,0) (c,0),(编号不重要,重要的是头部字串)它表示串 a b c a c abcac abcac的长度为3的头部子串 a b c abc abc的PM值为 0 0 0说明 ( P M ( a b c ) = 0 ) (PM(abc)=0) (PM(abc)=0),若某个匹配过程中发现模式串和主串匹配 a b c abc abc时都配上了,但是下一个字符失配,这时候需要移动的位置为 D ( a b c ) = L ( a b c ) − P M ( a b c ) D(abc)=L(abc)-PM(abc) D(abc)=L(abc)PM(abc)= 3 − 0 3-0 30= 3 3 3
a b c m ⋯ a b c d e \begin{matrix} a&b&c&m&\cdots\\ \boxed{}&&& \boxed a&b&c&d&e \end{matrix} abcmabcde
上图所示,模式串从 → a \boxed{}\to{\boxed{a}} a移动了3个位置(位数)

完整步骤

主串ababcabcacbab
子串abc

发现 ca 不匹配,前面的 2 个字符 ab 是匹配的。查表可知,最后一个匹配字符 b 对应的部分匹配值为 0。

因此按照下面的公式算出子串需要向后移动的位数:

移动位数=已匹配的字符数-对应的部分匹配值

因为 2 − 0 = 2 2 - 0 = 2 20=2,所以将子串向后移动 2 位,如下进行第二趟匹配:

主串ababcabcacbab
子串abcac

发现 cb 不匹配,前面 4 个字符 abca 是匹配的。最后一个匹配字符 a 对应的部分匹配值为 1。

因此按照下面的公式算出子串需要向后移动的位数:
$ \text{移动位数} = \text{已匹配的字符数} - \text{对应的部分匹配值} $

因为 4 − 1 = 3 4 - 1 = 3 41=3,所以将子串向后移动 3 位,如下进行第三趟匹配:

主串ababcabcacbab
子串abcac

算法时间复杂度

子串全部比较完成,匹配成功。整个匹配过程中,主串始终没有回退,所以KMP算法可以在 O ( n + m ) O(n+m) O(n+m)的时间数量级上完成串的模式匹配操作,大大提高了匹配效率。

某趟发生失配时,若对应的部分匹配值为0,则表示已匹配相等序列中没有相等的前后缀,此时移动的位数最大,直接将子串首字符后移到主串当前位置进行下一趟比较

若已匹配相等序列中存在最大相等前后缀(部分匹配值非0)(可理解为首尾重合部分字符数量非0),则将子串向右滑动到和该相等前后缀对齐(这部分字符下一趟显然不需要比较),然后从主串当前位置进行下一趙比较。

小结

上述讨论已经把KMP算法的最核心内容:部分匹配值表(PM表)及其使用做了介绍

更近一步的问题是如何方便的求出PM序列

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com