在c ++中实现后缀数组

时间:2013-07-02 14:17:45

标签: c++ time-complexity suffix-array

#include<iostream>
#include<string.h>
#include<utility>
#include<algorithm>

using namespace std;

struct xx
{
    string x;
    short int d;
    int lcp;
};

bool compare(const xx a,const xx b)
{
    return a.x<b.x;
}

int findlcp(string a,string b)
{
    int i=0,j=0,k=0;
    while(i<a.length() && j<b.length())
    {
        if(a[i]==b[j])
        {
            k++;
            i++;
            j++;
        }
        else
        {
            break;
        }
    }
    return k;
}

int main()
{   
    string a="banana";
    xx b[100];
    a=a+'$';
    int len=a.length();
    for(int i=0;i<len;i++)
    {
        b[i].x=a.substr(i);
        b[i].d=i+1;
    }
    sort(b,b+len,compare);
    for(int i=0;i<len;i++)
        cout<<b[i].x<<" "<<b[i].d<<endl;
    b[0].lcp=0;
    b[1].lcp=0;
    for(int i=2;i<len;i++)
    {
        b[i].lcp=findlcp(b[i].x,b[i-1].x);
    }
    for(int i=0;i<len;i++)
        cout<<b[i].d<<" "<<b[i].lcp<<endl;      
}

这是一个实现 Suffix Array。维基百科文章构造中我的问题是在最坏的情况下给出o(n)

所以在我的建设中:

  1. 我正在使用stl sort对字符串的所有后缀进行排序。在最坏的情况下,这可能至少是O(nlogn)。所以这里我违反了O(n)构造。
  2. 第二个是构建 longest common prefix array构造给出O(n)。但我认为我的实现在O(n ^ 2)
  3. 所以对于第一个,即排序

    • 如果我使用计数排序,我可能会减少到O(n)。如果我使用Count排序是否正确?我的理解是否正确?如果我的理解是错误的,请告诉我

    • 有没有办法在O(n)时间内找到LCP?

2 个答案:

答案 0 :(得分:4)

首先,关于你的两个陈述:

  

1)我正在使用stl sort对字符串的所有后缀进行排序。在最坏的情况下,这可能至少是O(nlogn)。所以我在这里违反O(n)结构。

std::sort的复杂性比O(n log n)差。原因是O(n log n)假设存在O(n log n)个体比较,并且每个比较在O(1)时间中执行。后一个假设是错误的,因为你要排序字符串,而不是原子项(如字符或整数)。

由于作为主字符串的子串的字符串项的长度是O(n),因此可以肯定地说排序算法的最坏情况复杂度是O(n 2 log n)。

  

2)第二个是构建一个最长的公共前缀数组,给出O(n)。但我认为我的实现在O(n ^ 2)

是的,你的LCP数组构造是O(n 2 ),因为你正在运行lcp函数n == len次,而你的{{1函数需要一对字符串x,y的O(min(len(x),len(y)))时间。

接下来,关于您的问题:

  

如果我使用计数排序,我可以减少到O(n)。如果我使用Count排序是否正确?我的理解是否正确?如果我的理解是错误的,请告诉我。

不幸的是,您的理解不正确。如果您可以在O(1)时间内访问要排序的每个项目的原子键,则计数排序只是线性的。同样,这些项目的长度为字符串O(n)个字符,因此不起作用。

  

有没有办法在O(n)时间内找到LCP?

是。最近的后缀数组计算算法,包括DC算法(又名Skew算法),提供了计算LCP数组和后缀数组的方法,并在O(n)时间内完成。

DC算法的参考文献是JuhaKärkkäinen,Peter Sanders:简单的线性后缀阵列构造,自动机,语言和编程 计算机科学讲义2729,2003,pp 943-955(DOI 10.1007 / 3-540-45061-0_73)。 (但这不是唯一允许您在线性时间内执行此操作的算法。)

您可能还想看看此SO帖子中提到的开源实现:What's the current state-of-the-art suffix array construction algorithm?。除了后缀数组结构之外,其中使用的许多算法都能实现线性时间LCP数组构造(但并非所有实现都可能包含其实现;我不确定)。

如果您对Java中的示例没有问题,您可能还需要查看jSuffixArrays的代码。除其他算法外,它还包括DC算法的实现以及线性时间内的LCP阵列构造。

答案 1 :(得分:0)

jogojapan已全面回答了您的问题。仅提及优化的cpp实现,您可能要看一下here

在GitHub出现故障的情况下在此处发布代码。

const int N = 1000 * 100 + 5; //max string length

namespace Suffix{
    int sa[N], rank[N], lcp[N], gap, S;
    bool cmp(int x, int y) {
        if(rank[x] != rank[y])
            return rank[x] < rank[y];
        x += gap, y += gap;
        return (x < S && y < S)? rank[x] < rank[y]: x > y;
    }
    void Sa_build(const string &s) {
        S = s.size();
        int tmp[N] = {0};
        for(int i = 0;i < S;++i)
            rank[i] = s[i],
            sa[i] = i;
        for(gap = 1;;gap <<= 1) {
            sort(sa, sa + S, cmp);
            for(int i = 1;i < S;++i)
                tmp[i] = tmp[i - 1] + cmp(sa[i - 1], sa[i]);
            for(int i = 0;i < S;++i)
                rank[sa[i]] = tmp[i];
            if(tmp[S - 1] == S - 1)
                break;
        }
    }
    void Lcp_build() {
        for(int i = 0, k = 0;i < S;++i, --k)
            if(rank[i] != S - 1) {
                k = max(k, 0);
                while(s[i + k] == s[sa[rank[i] + 1] + k])
                    ++k;
                lcp[rank[i]] = k;
            }
            else
                k = 0;
    }
};