cpp 最长重复子串

来源:互联网 发布:ssh的客户端软件 编辑:程序博客网 时间:2024/06/03 19:05

把一个字符串中的所有后缀以其开始位置编号,按字典顺序排列这些后缀后,把相应编号存放在一个数组中,这个数组就是这个字符串的后缀数组。

以字符串 aboreabo为例,把它的所有后缀按字典顺序排列后为:

abo

aboreabo

bo

boreabo

eabo

o

oreabo

reabo

这个字符串的后缀数组为{5,0,6,1,4,7,2,3}。

 

Manber和Myers在《Suffix arrays: A new method for on-line string searches》中提出后缀数组,

并给出一种算法复杂度为O(nlogn)的算法,04年国家集训队有篇关于后缀数组的论文,就是用的这个算法。

2006年,Karkkainen等人在《Linear Work Suffix Array Construction》中提出了DC3(difference cover)算法,可以在用O(n)的时间复杂度构建出后缀数组,并且在文章末尾给出了使用C++的实现代码。

 

后缀数组是个很好用的数据结构,在求单个字符串的重复子串和多个字符串的公共子串时,应该优先考虑是否可以使用后缀数组。

 

下面的程序就是利用后缀数组求一个字符串的最长公共子串的算法。这个算法主要利用各个后缀之间的关系,因为对于同一个字符串的任意两个后缀,较短后缀也是较长后缀的后缀。

每轮排序都以相应后缀的定长前缀为关键字来排序,前缀长度的选取为2的倍数,分别为1,2,4,…  每轮排序后所得后缀数组依次记做S1,S2,S3,…

 

首先,以每个后缀的首字符为关键字,进行桶式排序,复杂度为O(n);

假如在当前阶段,关键字的长度为h,其中h为2的倍数,所有前h个关键字相同的后缀都存放在同一个h桶中;那么下个阶段排序使用的关键字长度为2h,排序时就可以使用前一阶段的结果,排序后前2h个关键字相同的后缀将存放在同一个2h桶中。

         B1           B2            B3                  B4           B5

   ———————    ——      ———————     ———————    ———

atta...
    

athl…
    

envy…
    

erro…
    

er…
    

hl…
    

hl…
    

ta…

   ↑Ai     ↑Aj                                   ↑Aj+h             ↑Ai+h

 

上图所示是以前2个字符为关键字的第二轮排序完成后所得的后缀数组S2,图中后缀共存放在5个桶中,分别为B1,…,B5。

在第三轮排序时,使用前4个字符为关键字,上轮排序后位于不同的桶中的后缀相对顺序已经确定,所以只需把上轮位于相同的桶中的后缀按前4个关键字划分为不同的桶即可。在本论中比较后缀Ai和Aj,由于Ai和Aj前2个关键字相同,Ai+h和Aj+h分别是Ai和Aj去掉前2个字符后的后缀,所以只需比较Ai+h和Aj+h即可,而在第二轮已经得到 Ai+h和Aj+h的相对顺序,因Ai+h > Aj+h,所以得Ai > Aj。

    B        B2        B3              B4                 B5             B6

  ———   ———    ———      ———————      ———————     ———

athl...
    

atta…
    

envy…
    

erro…
    

er…
    

hl…
    

hl…
    

ta…

   ↑Ai<---->↑Aj                                  ↑Aj+h             ↑Ai+h

从头到尾遍历上一轮的后缀数组,对于每个Ai, 把Ai-h前移到所在桶的下一个填充位置,则得到本轮的后缀数组,然后可划分新的桶。

当所有的元素都位于不同的桶时,排序完成,得到所需的后缀数组。

最多需排序O(logn)轮,每轮所需时间为O(n),所以算法总的复杂度为O(nlogn)。

 

下面是通过构造后缀数组来求给定字符串最长重复子串的程序。其中的两个子函数主要是JGShining实现的,但是我调试的时候发现存在的一些问题,对这两个函数加以改进和完善,主要修改了以下部分:

比如Rank数组越界,以及当字符串由同一字符重复组成时不能得出正确的height数组。

一、原方法在求S(2h)时采用逆向遍历后缀数组,由高端到低端的顺序填充桶;我使用正向遍历的方法,由低端到高端的顺序填充桶;

二、修改了Rank(2h)的计算方法,原方法在一些情况下不能得到正确的Rank数组;

三、修改了Height数组的计算方法,原方法在字符串由同一字符重复组成的情况下不能得到正确的height数组;

四、简化了两个子函数的接口。

JGShining的代码可以在http://jgshining.cn/blog/post/suffix_array_implemention.php找到。