利用后缀数组求字符串的最长重复子串

来源:互联网 发布:看刀路软件 编辑:程序博客网 时间:2024/05/01 08:26
把一个字符串中的所有后缀以其开始位置编号,按字典顺序排列这些后缀后,把相应编号存放在一个数组中,这个数组就是这个字符串的后缀数组。

  以字符串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找到。

//suffix_array.h

void CreateSuffixArray(const char str[], int length, int suff[], int rank[])
{
        const int bucket_size = 256;
        int *bucket = new int[bucket_size];

        int idx;
        for(idx = 0; idx < bucket_size; idx++)
                bucket[idx] = 0;
        for(idx = 0; idx < length; idx++)
                bucket[str[idx]]++;
        for(idx = 1; idx < bucket_size; idx++)
                bucket[idx] += bucket[idx - 1];
        for(idx = 0; idx < length; idx++)
                suff[--bucket[str[idx]]] = idx;

        for(rank[suff[0]] = 0, idx = 1; idx < length; idx++)
        {
                if(str[suff[idx]] == str[suff[idx-1]])
                        rank[suff[idx]] = rank[suff[idx - 1]];
                else
                        rank[suff[idx]] = rank[suff[idx - 1]] + 1;
        }

        int *suff2 = new int[length];
        int *rank2 = new int[length];
        for(int h = 1; h < length && rank[suff[length - 1]] < length - 1; h *= 2)
        {
                for(idx = length - 1; idx >= 0; idx--)
                {
                        bucket[rank[suff[idx]]] = idx;
                }

                for(idx = length - 1; idx >= length - h; idx--)
                        suff2[bucket[rank[idx]]++] = idx;

                for(idx = 0; idx < length; idx++)
                        if(suff[idx] >= h)
                                suff2[bucket[rank[suff[idx] - h]]++] = suff[idx] - h;

                for(idx = 0; idx < length; idx++)
                        suff[idx] = suff2[idx];

                for(rank2[suff[0]] = 0, idx = 1; idx < length; idx++)
                {
                        if(rank[suff[idx]] == rank2[suff[idx - 1]])
                        {
                                if(suff[idx] + h < length && suff[idx - 1] + h < length
                                                && rank[suff[idx] + h] == rank[suff[idx - 1] + h])
                                        rank2[suff[idx]] == rank2[suff[idx - 1]];
                                else
                                        rank2[suff[idx]] = rank2[suff[idx - 1]] + 1;
                        }
                        else
                                rank2[suff[idx]] = rank2[suff[idx - 1]] + 1;
                }

                for(idx = 0; idx < length; idx++)
                        rank[idx] = rank2[idx];
        }

        delete []bucket;
        delete []suff2;
        delete []rank2;
}

void GeneHeight(const char str[], int length, int suff[], int rank[], int hei
ght[])
{
        for(int delta = 0, idx = 0; idx < length; idx++)
        {
                if(rank[idx] == 0)
                        height[rank[idx]] = 0;
                else
                {
                        int q = suff[rank[idx] - 1];
                        while(str[idx + delta] == str[q + delta])
                                delta++;
                        height[rank[idx]] = delta;

                        if(delta > 0)
                                delta--;
                }
        }
}


//suffix_array.cpp

#include "suffix_array.h"
#include <iostream>
#include <cstring>
using namespace std;

int main(int argc, char *argv[])
{
        if(argc != 2)
        {
                cout<<"USAGE: "<<"./suffix_array <string>"<<endl;
                return -1;
        }

        int size = strlen(argv[1]);
        int *Suffix = new int[size];
        int *Rank = new int[size];
        int *Height = new int[size];
        int idx;

        CreateSuffixArray(argv[1], size, Suffix, Rank);
        GeneHeight(argv[1], size, Suffix, Rank, Height);

        int max = Height[0];
        int max_idx = 0;
        for(idx = 1; idx < size; idx++)
        {
                if(Height[idx] > max)
                {
                        max = Height[idx];
                        max_idx = idx;
                }
        }

        if(max > 0)
        {
                cout<<"The longest repeated substring is ";
                for(idx = Suffix[max_idx]; idx < Suffix[max_idx] + max; idx++
)
                        cout<<argv[1][idx];
                cout<<"."<<endl;
        }
        else
                cout<<"There is no repeated substring."<<endl;

        delete []Suffix;
        delete []Rank;
        delete []Height;
}


原创粉丝点击