后缀数组 height 数组的构建

http://churuimin425.blog.163.com/blog/static/34129877201141005542104/

先转一个后缀数组的简单总结:

 后缀数组就是将字符串所有后缀排序后的数组,设字符串为S,令后缀Suffix(i)表示S[i..len(S)]。用两个数组记录所有后缀的排序结果:

·         Rank[i]记录Suffix(i)排序后的序号,即Suffix[i]在所有后缀中是第Rank[i]小的后缀

·         SA[i]记录第i位后缀的首字母位置,即Suffix[SA[i]]在所有后缀中是第i小的后缀

然后就是怎么快速求所有后缀的顺序了,其中的关键是如何减少两个后缀比较的复杂度
方法是倍增法,定义一个字符串的k-前缀为该字符串的前k个字符组成的串,关于在k-后缀上的定义Suffix(k,i)SA[k,i]Rank[k,i]类似于前,则有

·         Rank[k,i]Rank[k,j]Rank[k,i+k]Rank[k,j+k],则Suffix[2k,i]Suffix[2k,j]

·         Rank[k,i]Rank[k,j]Rank[k,i+k]<Rank[k,j+k],则Suffix[2k,i]<Suffix[2k,j]

·         Rank[k,i]<Rank[k,j],则Suffix[2k,i]<Suffix[2k,j]

这样就能在常数时间内比较Suffix(2^k, i)之间的大小,从而对Suffix(2^k,i)时行排序,最后2^k>n时,Suffix(2^k, i)之间的大小即为所有后缀之间的大小

于是求出了所有后缀的排序,有什么用呢?主要是用于求它们之间的最长公共前缀(Longest Common PrefixLCP

LCP(i,j)为第i小的后缀和第j小的后缀(也就是Suffix(SA[i])Suffix(SA[j]))的最长公共前缀的长度,则有如下两个性质:

1.   对任意i<=k<=j,有LCP(i,j) = min(LCP(i,k),LCP(k,j))

2.   LCP(i,j)=min(i<k<=j)(LCP(k-1,k))

第一个性质是显然的,它的意义在于可以用来证明第二个性质。第二个性质的意义在于提供了一个将LCP问题转换为RMQ问题的方法:
height[i]LCP(i-1,i),即height[i]代表第i小的后缀与第i-1小的后缀的LCP,则求
LCP(i,j)就等于求height[i+1]~height[j]之间的RMQ,套用RMQ算法就可以了,复杂度是预处理O(nlogn),查询O(1)

然后height的求法要用到另一个数组:令h[i]=height[Rank[i]],即h[i]表示Suffix(i)height值(同时height[i]就表示Suffix(SA[i])height值),则有height[i]=h[SA[i]]
然后h[i]有个性质:

·         h[i] >= h[i-1]-1

用这个性质我们在计算h[i]的时候进行后缀比较时只需从第h[i-1]位起比较,从而总的比较的复杂度是O(n),也就是说h数组在O(n)的时间内解决了。求出了h数组,根据关系式height[i]=h[SA[i]]可以在O(n)时间内求出height数组,于是可以在O(n)时间内求出height数组,从而整个LCP问题就解决了^_^

然后后缀数组的应用就是利用它的LCP在需要字符串比较时降低复杂度。同时由于后缀数组的有序性可以很方便地使用二分

于是总结一下要点:

·         利用倍增算法在O(nlogn)的时间内对后缀数组进行排序

·         利用h数组的性质在O(n)的时间内求出储存排序后相邻后缀间的LCP数的组height

·         利用LCP的性质将平凡LCP问题转化为height数组上的RMQ问题

 

本题是求两字符串的最长公告子串(注意是子串不是子序列),构造出SA,RAHEIGHT后,答案就是最大的HEIGHT,但这个最大的HEIGHT可能是同一个串中的,

所以这个最大的HEIGHT同时要满足sa[i-1]sa[i]不在同一个串中。http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif代码

 

Constructing Suffix Array with Doubling Algorithm, O(n log n).
/////////////////////////////////////////////////////////////////
#include <algorithm>//sort
#include <cstring>//memset

using namespace std;

const int MAX_SFX = 210000;

struct Sfx {
    int i;  int key[2];
    bool operator < (const Sfx& s) const
    {   return key[0] < s.key[0]
               || key[0] == s.key[0] && key[1] < s.key[1]; }
};

int g_buf[MAX_SFX + 1];
Sfx g_tempSfx[2][MAX_SFX], *g_sa = g_tempSfx[0];

void cSort(Sfx* in, int n, int key, Sfx* out) {
    int* cnt = g_buf;  memset( cnt, 0, sizeof(int) * (n + 1) );
    for (int i = 0; i < n; i++) { cnt[ in[i].key[key] ]++; }
    for (int i = 1; i <= n; i++) { cnt[i] += cnt[i - 1]; }
    for (int i = n - 1; i >= 0; i--)
        { out[ --cnt[ in[i].key[key] ] ] = in[i]; }
}

//Build a suffix array from string 'text' whose length is 'len'.
//write the result into global array 'g_sa'.
void buildSA(char* text, int len) {
    Sfx *temp = g_tempSfx[1];
    int* rank = g_buf;
    for (int i = 0; i < len; i++)
        { g_sa[i].i = g_sa[i].key[1] = i;  g_sa[i].key[0] = text[i]; }
    sort(g_sa, g_sa + len);
    for (int i = 0; i < len; i++) { g_sa[i].key[1] = 0; }
    int wid = 1;
    while (wid < len) {
        rank[ g_sa[0].i ] = 1;
        for (int i = 1; i < len; i++)
        {   rank[ g_sa[i].i ] = rank[ g_sa[i - 1].i ];
            if ( g_sa[i-1] < g_sa[i] ) { rank[ g_sa[i].i ]++; } }
        for (int i = 0; i < len; i++)
            { g_sa[i].i = i;  g_sa[i].key[0] = rank[i];
              g_sa[i].key[1] = i + wid < len? rank[i + wid]: 0; }
        cSort(g_sa, len, 1, temp);  cSort(temp, len, 0, g_sa);
        wid *= 2;
    }
}

int getLCP(char* a, char* b)
{ int l=0;  while(*a && *b && *a==*b) { l++; a++; b++; }  return l; }

void getLCP(char* text, Sfx* sfx, int len, int* lcp) {
    int* rank = g_buf;
    for (int i=0, r=0; i < len; i++, r++) { rank[ sfx[i].i ] = r; }
    lcp[0] = 0;
    if (rank[0])
        { lcp[ rank[0] ] = getLCP( text, text + sfx[ rank[0]-1 ].i ); }
    for (int i = 1; i < len; i++) {
        if ( !rank[i] ) { continue; }
        if (lcp[ rank[i - 1] ] <= 1)
        { lcp[ rank[i] ] = getLCP( text+i, text+sfx[ rank[i]-1 ].i ); }
        else
        { int L = lcp[ rank[i - 1] ] - 1;
          lcp[rank[i]] = L+getLCP(text+i+L, text+sfx[rank[i]-1].i+L); }
    }
}

//Test suite and usage example
#include <iostream>
using namespace std;
int main() {
    char str[] = "aabbaa{post.content}ababab";
    int from[] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1};
    int lcp[13];
    buildSA(str, 13);  getLCP(str, g_sa, 13, lcp);
    for (int i=1; i<13; i++)//The first suffix is useless (empty).
    { cout<<from[g_sa[i].i]<<' '<<str+g_sa[i].i<<' '<<lcp[i]<<endl; }
    return 0;//output: 0 a 0
             //        0 aa 1
             //        0 aabbaa 2
             //        1 ab 1
             //        1 abab 2
             //        1 ababab 4
             //        0 abbaa 2
             //        1 b 0
             //        0 baa 1
             //        1 bab 2
             //        1 babab 3
             //        0 bbaa 1
}

posted @ 2012-10-15 11:42  董雨  阅读(398)  评论(0编辑  收藏  举报