后缀数组1模板(强推罗XX的论文,贼棒)

先%罗DADA建议按照论文手推,更易明白
再%kuangbin大神

1、什么是后缀数组

后缀数组是后缀树的替代品,十分精巧,简洁
SA[]:后缀数组,按照后缀子串字典序进行排名
Rank[]:按照后缀子串长度由大到小进行排名
Height[]:两后缀之间的重复前缀长度
以下代码出自kuangbin与罗DADA的论文,略有改动

DA算法(倍增,O(nlogn),空间复杂度6n)

/**kuangbin大神的栗子
*n = 8
*num[]    = { 1, 1, 2, 1, 1, 1, 1, 2, $ }; 注意num最后一位为0,其他大于0
*rank[]   = { 4, 6, 8, 1, 2, 3, 5, 7, 0 }; rank[0~n-1]为有效值,rank[n]必定为0无效值
*sa[]     = { 8, 3, 4, 5, 0, 6, 1, 7, 2 }; sa[1~n]为有效值,sa[0]必定为n是无效值
*height[] = { 0, 0, 3, 2, 3, 1, 2, 0, 1 };
height[2~n]为有效值 
**/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=20010;
int t1[M], t2[M, c[M]; //求SA数组的排序过程,所需要的的中间变量

//待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m,
//除s[n-1]外的所有s[i]都大于0,r[n-1]=0
//函数结束以后结果放在sa数组中

bool cmp(int *r,int a,int b,int l) 
{
    return r[a] == r[b] && r[a+l] == r[b+l]; 
}

void da(int str[], int sa[], int Rank[], int height[], int n,int m)
{
    n++;
    int i, j, p;
    int *x = t1;  //用于第一关键字排序,相当于Rank[]数组
    int *y = t2; //用于第二关键字排序,保存排序结果
    //第一轮基数排序,如果s的最大值很大,可改为快速排序(第一关键字排序)
    for(i = 0; i < m; i++)     c[i] = 0;//清空c[]
    for(i = 0; i < n; i++)     c[x[i] = str[i]]++; 
    for(i = 1; i < m; i++)     c[i] += c[i-1]; 
    for(i = n-1; i >= 0; i--)  sa[ --c[ x[i] ] ] = i;

    for(j = 1; j <= n; j <<= 1)
    {
        p = 0;         //直接利用sa数组排序第二关键字
        for(i = n-j; i < n; i++) y[p++] = i;//后面的j个数第二关键字为空的最小
        for(i = 0; i < n; i++)
            if(sa[i] >= j) y[p++] = sa[i] - j;

        //基数排序第一关键字
        for(i = 0; i < m; i++)     c[i] = 0;
        for(i = 0; i < n; i++)     c[ x[ y[i]]  ]++;
        for(i = 1; i < m; i++)     c[i] += c[i-1];
        for(i = n-1; i >= 0; i--)  sa[ --c[ x[ y[i] ] ] ] = y[i];        //根据sa和x数组计算新的x数组

        /**
        求出 sa 后,下一步是计算 rank 值。
        可能有多个字符串的 rank 值是相同的,所以必须比较两个字符串是否完全相同,
        并且 y 数组的值已经没有必要保存,为了节省空间,这里用 y 数组保存 rank值。
        **/
        swap(x, y);  p=1;  x[sa[0]]=0;
        for(i = 1; i < n; i++)
            x[sa[i]] = cmp(y,sa[i-1],sa[i],j) ? p-1 : p++;
        if(p >= n) break;
        m = p;//下次基数排序的最大值
    }
}

void Get_Height(int str[], int sa[], int Rank[], int Height[], int n)//计算Height值
{
    int k = 0;
    for(i = 0; i <= n; i++) Rank[sa[i]] = i;
    for(i = 0; i < n; i++)
    {
        if(k) k--;
        j = sa[Rank[i]-1];
        while(str[i+k] == str[j+k]) k++;
        height[Rank[i]] = k;
    }
}

int Rank[MAXN], height[MAXN];
int RMQ[MAXN];
int Log[MAXN];
int best[20][MAXN];

void initRMQ(int n)
{
    Log[0] = -1;
    for(int i = 1; i <= n; i++)
        Log[i]=((i&(i-1))==0) ? Log[i-1]+1 : Log[i-1];

    for(int i = 1; i <=n; i++)
        best[0][i] = i;

    for(int i = 1; i <= Log[n]; i++)
        for(int j=1; j+(1<<i)-1<=n; j++)
        {
            int a = best[i-1][j];
            int b = best[i-1][j+(1<<(i-1))];
            if(RMQ[a] < RMQ[b]) best[i][j] = a;
            else best[i][j] = b;
        }
}

int askRMQ(int a,int b)
{
    int t;
    t = Log[b-a+1];
    b -= (1<<t)-1;
    a = best[t][a];
    b = best[t][b];
    return RMQ[a] < RMQ[b] ? a : b;
}

int LCP(int a,int b)//最长公共子序列
{
    a = Rank[a];
    b = Rank[b];
    if(a > b) swap(a,b);
    return height[askRMQ(a+1, b)];
}

char str[MAXN];
int r[MAXN];
int sa[MAXN];

int main()
{
    while(scanf("%s",str) == 1)
    {
        int len = strlen(str);
        int n = 2*len + 1;
        for(int i = 0; i < len; i++)  r[i] = str[i]; //r[]负值
        for(int i = 0; i < len; i++)  r[len+1+i] = str[len-1-i];
        r[len] = 1;
        r[n] = 0;

        da(r,sa,Rank,height,n,128);
        Get_Height(r, sa, Rank, height, n);
        for(int i=1; i<=n; i++) RMQ[i] = height[i];

        /**以下是计算重复子序列**/
        initRMQ(n);
        int ans=0, st;
        int tmp;
        for(int i=0; i<len; i++)
        {
            tmp = LCP(i, n-i);//偶对称
            if(2*tmp > ans)
            {
                ans = 2*tmp;
                st = i-tmp;
            }
            tmp =LCP(i, n-i-1);//奇数对称
            if(2*tmp-1 > ans)
            {
                ans=2*tmp-1;
                st=i-tmp+1;
            }
        }
        str[st+ans]=0;
        printf("%s\n",str+st);
    }
    return 0;
}

DC3(分治,O(n),空间复杂度10n)
将后缀分成两部分,第一部分是后缀 k%3!=0, 第二部分是后缀 k%3==0。(下标从零开始)
例如n=4

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 2010;
#define F(x) ((x)/3+((x)%3==1?0:tb))        //计算原字符串 suffix(x)在新的字符串中的起始位置
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)  //计算新字符串 suffix(x)在原字符串中的位置
//F(x)与G(x)为互逆运算
int wa[MAXN*3], wb[MAXN*3],  wv[MAXN*3],  wss[MAXN*3];//计算SA的过程数组

int c0(int *r,int a,int b) //比较是否完全相同
{
    return r[a]==r[b] && r[a+1]==r[b+1] && r[a+2]==r[b+2];
}
int c12(int k,int *r,int a,int b) //比较后缀大小的函数
{
    if(k == 2)   return r[a]<r[b] || (r[a]==r[b] && c12(1,r,a+1,b+1));
    else return r[a]<r[b] || ( r[a] == r[b] && wv[a+1]<wv[b+1] );
}
void sort(int *r,int *a,int *b,int n,int m) //基数排序
{
    int i;
    for(i = 0; i < n; i++) wv[i] = r[a[i]];
    for(i = 0; i < m; i++) wss[i] = 0;
    for(i = 0; i < n; i++) wss[wv[i]]++;
    for(i = 1; i < m; i++) wss[i] += wss[i-1];
    for(i = n-1; i >= 0; i--) b[--wss[wv[i]]] = a[i];
}
/*
ta: 下标i,满足 i%3==0 的个数
tb: 下标i,满足 i%3==1 的个数
tbc:下标i,满足 i%3!=0 的个数
*/
void dc3(int *r,int *sa,int n,int m) 
{
    int i, j, *rn = r+n;
    int *san = sa+n, ta = 0, tb=(n+1)/3, tbc=0, p;

    //起始位置模 3不等于 0 的后缀进行排序。
    //用基数排序把 3 个字符“合并”成一个新的字符
    r[n] = r[n+1] = 0;
    for(i = 0; i < n; i++) if(i%3 != 0) wa[tbc++] = i;
    sort(r+2, wa, wb, tbc, m);
    sort(r+1, wb, wa, tbc, m);
    sort(r, wa, wb, tbc, m); //基数排序结束后,新的字符的排名保存在 wb 数组中。
    
    //求新的字符串时要判断两个字符组是否完全相同。
    p=1, rn[F(wb(0))]=0;
    for(i = 1; i < tbc; i++)
        rn[F(wb[i])] = c0(r, wb[i-1], wb[i]) ? p-1 : p++;
    //求san
    if(p < tbc) dc3(rn, san, tbc, p);
    else for(i = 0; i < tbc; i++) san[rn[i]] = i;

    //起始位置模 3 等于 0 的后缀进行排序。
    for(i = 0; i < tbc; i++)  if(san[i] < tb) wb[ta++] = san[i]*3;
    if(n%3 == 1) wb[ta++] = n - 1; //因为在 san 数组里并没有suffix(n),要特殊处理
    sort(r, wb, wa, ta, m);

    //合并所有排序结果
    for(i = 0; i < tbc; i++) wv[wb[i] = G(san[i])] = i;
    i = 0, j = 0;
    for(p = 0; i < ta && j < tbc; p++)
        sa[p] = c12(wb[j]%3, r, wa[i], wb[j]) ? wa[i++] : wb[j++];
    for(; i < ta; p++)sa[p] = wa[i++];
    for(; j < tbc; p++)sa[p] = wb[j++];
} //str和sa也要三倍

void Get_Height(int str[],int sa[],int Rank[],int height[],int n)
{
    int i,j,k = 0;
    for(i = 0; i <= n; i++) Rank[sa[i]] = i;
    for(i = 0; i < n; i++)
    {
        if(k) k--;
        j = sa[Rank[i]-1];
        while(str[i+k] == str[j+k]) k++;
        height[Rank[i]] = k;
    }
}

void solve(int str[],int sa[],int Rank[],int height[],int n,int m)
{
    for(int i = n; i < n*3; i++) str[i] = 0;
    dc3(str, sa, n+1, m);
    Get_Height(str, sa, Rank, height, n);
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容