收藏 分销(赏)

算法合集之《寻找最大重复子串》.doc

上传人:精*** 文档编号:2336036 上传时间:2024-05-28 格式:DOC 页数:15 大小:1.75MB
下载 相关 举报
算法合集之《寻找最大重复子串》.doc_第1页
第1页 / 共15页
算法合集之《寻找最大重复子串》.doc_第2页
第2页 / 共15页
算法合集之《寻找最大重复子串》.doc_第3页
第3页 / 共15页
算法合集之《寻找最大重复子串》.doc_第4页
第4页 / 共15页
算法合集之《寻找最大重复子串》.doc_第5页
第5页 / 共15页
点击查看更多>>
资源描述

1、(完整版)算法合集之寻找最大重复子串寻找最大重复子串江苏金陵中学 林希德【关键字】后缀树 KMP推广算法 最优子串 【摘要】关于字符串处理,无论是国内的个大竞赛还是中文的经典宝书,都相对较少涉及.曾见各位高手在信息学论坛上就某前辈提出的此类问题讨论得热火朝天,于是我决心翻阅多方资料仔细研究,仅望对大家发表高见起抛砖引玉的小作用。本文第一章给出了问题的详细描述和我对该问题的拙见,第二、三章简述了字符串处理的两个常用算法-后缀树和KMP,第四章阐明了寻找最大重复子串的主算法。目录一 问题提出和初步认识1.1问题描述1.2初步分析二 后缀树2.1后缀树的定义2.2后缀树的构建2。3后缀链接2。4性能

2、分析三 模式匹配3.1朴素模式匹配3。2 KMP模式匹配3。3前缀函数3。4性能分析3。5 KMP推广算法四 主算法4。1问题转化4。2字符串分解4。3范围限定4.4找到循环节4。5辅助函数4.6性能分析五 论文小结正文一 、问题提出和初步分析1。1章节 定义一:对于字符串S,如果存在正整数p使得1 x S| p:S x = S x + p.那么称p是S的循环周期(Period),e = |S / p是S的指数(Power),任何长度为p的S的子串都是S的一个循环节(Repetend),特别的,当e 2时,S是个大小为p的重复字符串(Repetition or P-power word).问题

3、描述:给出一个由大写字母组成的字符串W,W长度为1 n 105,请你在W的所有子串中找出循环周期最长的那个重复字符串(Finding Maximal Repetition),即最大重复子串.1.2章节定义二:为方便表达,我们用符号W(u,v)表示开始于位置u结束于位置v的W的子串。初步分析:一个O(n2)的算法是乍一看最直观的收获。我们可以首先从n到1枚举循环周期p,然后针对p从位置1到n搜索重复子串,一旦找到立即输出并退出循环,如下:For p = n 1 do For i = 1 n doIf i p并且 Wi = Wi-p thenGi = Max(Gi1 + 1,1)Else Gi =

4、 0 End if If Gi = p then 输出W(i2p+1,i);退出循环End forEnd for这个O(n2)的算法简单易编,但速度较慢。那么,怎样让复杂度从O(n2)降到O(n)呢?为说清楚这个线性算法,我们不得不先介绍一种关于字符串处理的数据结构后缀树,还有就是模式匹配的KMP。二、 后缀树2.1章节后缀树定义:令W = W + $,这样W的最后一个字符从未在前面的任何一个位置上出现过。添置$的目的,将在下一小节中予以说明。后缀树其实就是一棵检索树(Trie),和普通检索树一样我们不断往树中插入字符串和添置顶点,只不过,后缀树还有一些特殊的性质: )我们从大到小依次往树中插

5、入W的后缀,这其实是后缀树的名称由来,也是它的构建过程。)树上每条边E(u,v)有两个参数和,记为E(u,v) = (,),代表子串W(,)。)树上每个节点均有26个儿子,代表26个大写字母.如果父亲节点u的第1个儿子是节点a,那么E(u,a)上的子串一定以字母A开头;如果第2个儿子是节点b,那么E(u,b)上的子串一定以字母B开头以此类推.定义三:如果从根Root到节点x途径若干条边,将这些边上的子串顺次连接得到字符串Sx,那么称Sx是x的对应子串,x是Sx的对应节点。易知,“节点对应子串” 和 “字符串对应节点” 都是一一映射的关系。)后缀树有n个叶子节点Leaf1、Leaf2 Leafn

6、,Leafi的对应子串实际就是W的后缀W(i,n)。这一点通过性质)就可以得到,这里无非明确一下.2。2章节定义四:Headi是W(i,n)和W(j,n)的最长公共前缀,其中j是小于i的任意正整数,Taili使得Headi + Taili = W(i,n)。定义五:只要子串S = W(u,v)满足u x,我们就说S在范围x内出现过.Headi其实就是在范围i-1内出现过的W(i,n)的最长前缀。初始化后缀树为只有根结点的树For i = 1 n do找到Headi的对应节点hi;增加叶节点Leafi,使得E(hi,Leafi) = (Headi+1,n) ;End for后缀树的构建:举例一:

7、下面让我们来看看字符串W = “AGATAGAG$的后缀树被逐步构建的具体例子,其中黑色圆圈是叶节点,圆括号内的是边参数(,):增加Leafi只是O(1)的工作,关键问题是如何找到Headi的对应节点hi。最直观也是最野蛮的方法是从根节点开始对W(i,n)实行逐个字符地扫描:hi = Find(Root,W(i,n) ;Function Find(实参:开始节点p;字符串S):指针类型;While true doe = p的第ord(S1) 64个儿子节点;If e不存在 then 返回指针p,Break;End if逐个字符比较找出S与E(p,e)的最长公共前缀UL = |U|IF L|S|

8、 thenp = eS = S(L+1,S)Else增加新节点q,令V = E(p,e)令q成为p的儿子,E(p,q) = V(1,L)令e成为q的儿子,E(q,e) = V(L+1,|V|)返回指针q,Break;End ifEnd whileEnd function请看 处,或许你会担心V(L+1,|V)可能是个无意义的空串,其实,L = V|的情况一定不会出现。反证法一:如果L = |V|,那么就说明W(i,n)是某个W(j,n)(1ji)的前缀,也就是说W的末尾字符$至少出现过两次。这与刚才 处的规定违背。 故,任何增添进后缀树中的边都不是空串。这种野蛮的扫描算法使得时间复杂度高达O(

9、n2),还外加一个不小的常数因子。其实,这个Find函数并不是完全的没有用处,只是,为加快寻找hi的速度我们需要使用辅助结构后缀链接。2.3章节后缀链接的定义(McCreight Arithmetic):令Headi1 = az,其中a是字符串W的第i1位字符。由于z在范围i内出现过至少两次,所以一定有|Headi| |z|,z是Headi的前缀.所谓hi-1的后缀链接(Suffix Link)实际是由hi1指向z对应节点d的指针Link hi-1。当然,z有可能是空串,此时Link hi-1由hi-1指向根节点Root.创建方法:通过后缀链接,我们在O(1)时间内找到节点d,然后再使用函数F

10、ind(d,W(i+|z,n)找到hi,速度自然要快很多倍。现在的问题是,怎样在每次建立节点hi后创建Link hi呢?规定:1) 根节点Root的后缀链接指向它自身2) 任何一个非叶节点的后缀链接都在该节点出现以后被立即创建根据这两条规定,hi的父亲节点c的后缀链接一定存在。我们从Link c出发,向下搜索,即可找到z的对应节点d。举例二:下面让我们来看看字符串W = “AAAA$”是如何利用后缀链接加快寻找hi的速度的。图中,虚线箭头表示相应节点的后缀链接,黑色圆圈是叶子节点,红色圆圈是当前后缀链接指向的节点。创建后缀链接的伪代码如下:p = hiWhile Link p不存在 doc =

11、 p的父亲节点V = E(c,p)If c = Root thenLink p = Down(Link c,V(2,V)ElseLink p = Down(Link c,V)End ifp = Link pEnd while函数Down的伪代码如下:Function Down(实参:开始节点p;字符串S):指针类型;While true doe = p的第ord(S1) 64个儿子节点;If e不存在 or S是空串 then 返回指针p,Break;End ifL = Min(|S,E(p,e))IF L |S thenp = eS = S(L+1,|S)Else增加新节点q,令V = E(

12、p,e)令q成为p的儿子,E(p,q) = V(1,L)令e成为q的儿子,E(q,e) = V(L+1,V|)返回指针q,Break;End ifEnd whileEnd function观察一下就会发现,函数Down和Find几乎一模一样,只不过 处,L不是通过逐个的字符比较而是直接的长度比较得出。为什么呢?反正法二:令L = |S和E(p,e)的最长公共前缀|。如果L Min(|S|,|E(p,e)|),那么就证明z在范围i内从未出现过。这于 处的结论矛盾.故,逐个字符比较的结果一定是L = Min(S|,|E(p,e)|)。2.4章节算法主框架回顾:回顾和整理一下算法主框架,看看我们究竟

13、做了些什么?For i = 1 n do步骤1、函数Find从Link hi1开始向下搜索找到节点hi步骤2、增添叶子节点Leafi步骤3、函数Down创建hi的后缀链接Link hi End for后缀树性能分析:接着刚才文本框内的伪代码来谈论.对于给定的i,步骤2的复杂度为O(1),但由于无法确定Link hi1到hi之间的节点个数,所以不能保证步骤1总是线性的.局部估算失败,不妨从整体入手。有一点是肯定的,那就是i + |Headi|总随着i的递增而递增。因此,W中的每个字符只会被Find函数遍历1次,总体复杂度是O(n)的.三、模式匹配的KMP算法如何判断字符串T(称T为模式)是否是字

14、符串S(称S为主串)的子串呢?至今为止,解决这个问题最优秀的算法是1xxx年由D.E。Knuth、J。H.Morris和V.R。Pratt共同提出的模式匹配KMP算法。大多数选手早已熟练掌握KMP,故,我在此仅为论文的完整性对它进行简要叙述。3。1章节为方便说明,我们令n = |S|,m = |T|。朴素的模式匹配算法朴素模式匹配的基本思想是:将主串S的第一个字符和模式T的第一个字符进行比较,若相等则进一步比较二者的后续字符;否则,从S的第二个字符开始重新和T的第一个字符进行比较以此类推,直至模式T和主串S中的某一个子串相等,则称匹配成功,否则称匹配失败。该算法复杂度是O(nm),显然令我们很

15、不满意.3.2章节KMP模式匹配如果说索引指针指向了当前有待比较的S和T中的两字符,那么朴素模式匹配算法效率不高的主要原因就是没有充分利用匹配过程中已经得到的部分匹配信息,而任由索引指针回溯。KMP正是针对这点缺陷对朴素算法做了实质性的改进,它主要的思想是:当出现字符比较不相等时,让模式在主串上尽可能的向右滑动,然后接着进行比较。举例三:例如下图,其中,S = “a b a b c a b c a c b a b”,T = “a b c a c”,表示索引指针。3。3章节前缀函数一切问题集中在如何让模式T尽可能向右滑动上。假设当前索引指针正向右移动,在它指向字符 Tj 和 Si 时发现Tj S

16、i,那么对于一切正整数k(i j + 1 k n m + 1), 满足S(k,k + m) = T的必要条件(记为号)是k i 或者 T(1,i k)是T(1,j - 1)的后缀。为防止重复或者遗漏,模式T向右滑动的距离应当等于Minx:i j + 1 + x满足必要条件。因为空串T(1, i - i)一定是T(1,j - 1)的后缀,所以必要条件中的k i完全可以省略.KMP算法的核心就是:令j-1 = Maxx:T(1,x)是T(1,j - 1)的后缀,然后将模式T向右滑动,使得索引指针指向Si和T的第j-1 + 1个字符。i的大小与主串S并无关联,所以我们应该事先通过预处理求出并保存所有

17、i(1 i m)值,即所谓的前缀函数。过程如下:0 = -1For i = 1 mK = i1While (K 0) 并且(t的第K+1个字符 t的第i个字符)K = K End whilei = K + 1;End for有了前缀函数,KMP主算法就如下方框所述般简单明了:Q = 0For i = 1 mWhile (Q 0) and (t的第Q+1个字符 s的第i个字符)Q = Q End whileIf t的第Q+1个字符 = s的第i个字符 thenQ = Q + 1If Q = m then找到了模式匹配,退出循环。End ifEnd ifEnd for3。4章节KMP复杂度计算由于

18、不清楚 处到底循环了多少次,所以前缀函数的复杂度似乎不好分析。不过,因为K总是0的,所以“操作K = K造成的K值减少量”一定不会比“操作K = i1和i = K + 1造成的K值增加量多,因而 处循环总次数 m。主算法复杂度的证明同上,亦为线性,所以KMP算法的时间复杂度同空间复杂度一样,均为O(n+m)。3.5章节定义六:函数Bi = |T与S(i,n)的最长公共前缀|,这里1in。KMP推广算法普通KMP算法只是在判断是否存在整数i满足Bi = m,但如果希望求出所有B1Bn的函数值并保持复杂度不变,又该怎么办呢?很自然的,我们继承经典KMP的核心思想但尝试对其进行改造以得到推广算法。定

19、义七:函数Ai = |T与T(i,m)的最长公共前缀|,这里2im。 推广算法将借助函数A求出函数B。函数A的求解函数A的求解类似于数学归纳法.1、 首先通过逐个的比较字符求出A2,同时令k = 2。2、 假设已经求出函数A2、A3Ai-1,并且知道k满足1ki,k + Ak最大。现在我们希望借助A2Ai-1求出函数Ai.3、 令Len = k + Ak 1,L = Aik+1,然后分类讨论:a) 如果Leni并且L Len i + 1,那么Ai = L。理由:因为子串T(i,k + Ak 1) = 子串T(i k + 1,Ak),所以字符Ti+L = Tik+1+L TL,Ai延伸到长度L时

20、恰好不能匹配。b) 如果Leni并且LLen i + 1,那么AiL.理由同上。此时,我们先令Ai = L,然后通过逐个的比较字符将Ai延伸到它应有的函数值大小,最后赋值k = i.c) 如果Leni,那么我们无法通过A2Ai-1得到任何关于Ai的信息。此时,我们先令Ai = 0,然后通过逐个的比较字符将Ai延伸到它应有的函数值大小,最后赋值k = i。复杂度分析无论哪种情况,k + Ak的值都只增不减-这就是KMP算法的核心思想-所以逐个比较字符的总次数是O(m)的,求函数A的复杂度为O(m),过程如下:j = 0While 字符T1+j = 字符T2+j do j = j + 1 End

21、WhileA2 = j,k = 2For i = 3 m do Len = k + Ak 1,L = Ai-k+1If L Len i + 1 then Ai = LElsej = Max(0,Len i +1)While 字符Ti+j = 字符T1+j do j = j + 1 End WhileAi = j,k = iEnd ifEnd for函数B的求解函数B的求解和函数A的求解几乎一模一样,因而我就不再赘言而只给出求解过程的伪代码以供参考:j = 0While 字符T1+j = 字符S1+j do j = j + 1 End WhileB1 = j,k = 1For i = 2 n d

22、oLen = k + Bk 1,L = Aik+1If L Len i + 1 then Bi = LElse j = Max(0,Len i +1)While 字符T1+j = 字符Si+j do j = j + 1 End WhileBi = j,k = iEnd ifEnd for函数B有着和函数A一样的复杂度分析,所以求解函数B的复杂度是O(n)的.综上,我们彻底阐述完了KMP推广算法的整个求解过程,推广算法的复杂度和普通KMP一样均是O(n+m)的。四、主算法4。1章节最优子串的定义:对于子串S = W(u,v)和正整数L,如果满足:1) S是循环周期为L的重复子串2) S不能向左扩

23、展,即 u = 1 或者 W(u-1,v)不满足条件1)3) S不能向右扩展,即 v = n 或者 W(u,v+1)不满足条件1)那么称S是一个周期为L的最优子串。举例三:例如当W = “ABAABABAABAABA”时,“ABABA”是周期为2的最优子串,而“ABAB或者“BABA则不是.“ABAABABAABA”是周期为5的最优子串,而“ABAABABAAB”或者“BAABABAABA”则不是。需要说明的是:即便周期相同,甚至部分重叠,两个不同子串也可能同时是最优子串。例如前缀“ABAABA”和后缀“ABAABAABA”就都是周期为3的最优子串,尽管它们有重叠部分W(4,3)是.问题的转化

24、:如果我们能够求出所有最优子串连同它们的周期,那么,从中找出周期最大的那个最优子串,最大重复子串的问题便迎刃而解。怎样才能找出最优子串S呢?算法的主要思想是:1、 找到一个长度为L的循环节D2、分别将D向左和向右扩展到不能扩展为止3、判断扩展以后的D是否长度2L。 如果是,便找到了一个周期为L的最优子串S.以下,4.2、4.3章节将说明如何寻找循环节D,4.4章节将说明如何将D快速地向两边扩展,4.5章节将进行总结.4.2章节字符串分解:将W分解成 W = U1 + U2 + U3 + + Um 的形式,其中Ui定义如下: P = U1 + U2 + + Ui-1 Q是W除去P的剩余部分,即W

25、 = P + Q如果字母Q1从未在范围|P|中出现过,那么 Ui = Q1单个字母组成的字符串否则 Ui = 范围i1中出现过的Q的最长前缀举例四:字符串W = “ABAABABAABAAB”应该被怎样划分呢? 第一步:显然U1 = “A;第二步:P =“A”,Q =“BAABABAABAAB”,W2未在范围|P中出现过,所以U2 =“B”;第三步:P =“AB”,Q =“AABABAABAAB,W3在范围P中出现过,所以U3 = PQ的最长公共前缀“A”;第四步:P =“ABA,Q =“ABABAABAAB”,W4在范围|P|中出现过,所以U4 = PQ的最长公共前缀“AB”;第五步:P =

26、“ABAAB”,Q =“ABAABAAB”,W6在范围P中出现过,所以U5 = PQ的最长公共前缀“ABAAB;第六步:P =“ABAABABAAB,Q =“AAB,W11在范围P中出现过,所以U6 = PQ的最长公共前缀“AAB;最终,W被划分成为|AB|A|AB|ABAAB|AAB|。你大概已经知道,字符串分解是利用后缀树算法实现的,因为Ui要么是单个的字符,要么就等于HeadP+1。至此,我们终于可以诠释后缀树的作用。但是问题又来了,你不禁想问字符串分解的意义何在?4。3章节任何一个最优子串S的结束位置End(S)一定在某个片段Ui之内.如果我们 暂且假设i是个已知常量,那么|S|和周期

27、L是否都有一定的范围限制?为回答这些问题,我们需要对S的存在形式进行前后4层的分类讨论。第1层:End(S)在Ui内,Ini(S)又在何处?情况1)Ini(S) Ini(Ui)情况2)Ini(S) Ini(Ui) 由于Ui是范围P内出现过的Q的最长前缀,所以被Ui包含的子串S一定在范围P内出现过。情况2)实际可以被情况1)包含.为避免重复劳动,情况2)将被忽略。定义七:我们把S(|S|-L+1,|S|)称为S的最末循环节,用符号R表示。第2层:既然Ini(S) Ini(Ui),那么Ini(R)又应当在何处呢?情况甲)Init(R) Ini(Ui-1)情况乙) Init(R) Ini(Ui-1)

28、请看下图,图中一段弧线表示一个循环节:显然,红色和绿色弧线标示了相同的子串,所以根据字符串分解的定义Ui1应当等于红色弧线或者长度更长的子串。但事实上,|Ui-1 红色弧线的水平长度。矛盾!情况甲不会出现。第3层:虽然已经证明Ini(S) Ini(Ui),不过我们还想进一步知道有关Ini(S)的信息。情况A)Ini(S) Ini(Ui1) |Ui1 + Ui情况B)Ini(S) - Ini(Ui1) |Ui-1 + Ui再请看下图,图中所示为情况B):因为周期L Ui1 + Ui,所以Ini(S)Ini(Ui1) L。同样道理,红色和绿色弧线标示了相同的子串,根据字符串分解的定义Ui-1应当等

29、于红色弧线或者长度更长的子串。矛盾!情况B)不存在。讨论完毕,综上所述,我们得出一个重要结论:如果令V = 长度为Ui+2Ui1|的P后缀,U = Ui那么,最优子串S的开始位置在V内,结束位置在U内。至此,我们终于明白:字符串分解的重大意义在于严格限制了最优子串的存在形式.4.4章节讨论了这么多,我们还是没有把S的循环节给找出来.其实,只要再进一小步分类,循环节就出来了。第4层:因为Ini(S) Ini(Ui) End(S)并且S|2L,所以以下两种情况S必然至少居其一:情况i)S在V中的长度L情况ii)S在U中的长度L情况i)和情况ii)类似,为简便表达,我们仅以情况ii)为例进行说明。易

30、知,U(1,L)就是S的一个循环节。上述4层分类都基于 处的假设,现在把假设去掉,并整理一下得:Answer = 0For i = 2 m doP = U1 + U2 + + Ui-1V = 长度为|Ui| + |Ui-1|*2的P的后缀U = UiFor L = 1 |U do确定U(1,L)为循环节R将R向左向右扩展到不能扩展为止判断扩展以后的R是否 |R 2L,如果是,那么Answer = Max(Answer,R(1,2L)End forEnd for输出Answer枚举L的复杂度是O(|U|),而U的总长度又是O(n)的,所以我们必须在O(1)时间内完成处的工作,才能保证算法总复杂度

31、是线性的.4。5章节定义辅助函数:LpL = U与U(L+1,U)的最长公共前缀 LsL = V与V + U(1,L)的最长公共后缀所谓将循环节向两边扩展,无非就是求得函数Lp和Ls。如果单独求解某个LpL的值是不能保证复杂度是O(1)的,但局部策略失败,不妨从整体入手,我们通过一次KMP推广算法求出所有Lp1 Lp|U的函数值,从而使得平摊复杂度为O(1)。4.6章节主算法性能分析其实主算法的复杂度已经在刚才几小节中陆陆续续的提到并证明了,现在我们将做的工作只是把这些证明汇总重申一遍.1、首先是字符串划分。这步工作需要借助辅助结构后缀树,由于后缀树时间空间复杂度均是线性的,所以该步工作也是线

32、性的。2、然后是辅助函数。Lp和Ls的求解借助KMP推广算法,对于特定的V和U,该步复杂度为O(V+U),因而总体复杂度为O(L)。3、最后是找出最优重复子串S。S分为完整周期在v和完整周期在u两种情况,对于每种情况,S可以根据周期j唯一地确定,由于周期j的范围是O(V+U|)的,所以S的求解也是线性O(V+U)的.小结后缀树和KMP尽管巧妙但卑之无甚高论,寻找最大重复子串算法本身也并没有多少可以广泛借鉴的价值.但是,如何将所学算法融会贯通、综合使用,却值得所有人认真思考。参考文献1算法与数据结构(第二版) 傅清祥 王晓东 编著2The Art of Computer Programming Donald E.Knuth著3Handbook of Computer Science and Engineering4Finding Maximal Repetition Roman Kolpakov著5Discrete Applied Mathematics 19896jsoi内部资料15

展开阅读全文
相似文档                                   自信AI助手自信AI助手
猜你喜欢                                   自信AI导航自信AI导航
搜索标签

当前位置:首页 > 考试专区 > 中考

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        获赠5币

©2010-2024 宁波自信网络信息技术有限公司  版权所有

客服电话:4008-655-100  投诉/维权电话:4009-655-100

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :gzh.png    weibo.png    LOFTER.png 

客服