zoukankan      html  css  js  c++  java
  • [后缀数组]【学习笔记】

    飒飒飒飒飒飒飒飒飒飒


    研究了好长时间....(诶好像莫比乌斯反演时也说过这句话)

    参考资料:

    1.http://wenku.baidu.com/link?url=Beh6Asxvtm7M2QY5kiPyKKaP87xvBrNBKW9LXOeGKm-WM4GoUM3opnHZ8z-DahF7TRaLZZ4cpUe6jfFF064XUEmAiIDF7t90CpgNfSC3_Pq

    2.http://www.cnblogs.com/staginner/archive/2012/02/02/2335600.html

    3.http://www.xymlife.com/2016/01/16/后缀数组小结/#more

    【概念】:(论文里很清楚了,这里说自己的理解

    1. 后缀:后缀是指从某个位置i开始到结束的子串。Suffix(i)=r[i..len(r)]

    后缀i是从i开始的子串

    2. 后缀数组: 后缀数组 SA 是一个一维数组,它保存1..n 的某个排列 SA[1],SA[2], ……,SA[n],并且保证Suffix(SA[i]) < Suffix(SA[i+1]),1 ≤ i < n。也就是将 S 的 n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA 中。

    后缀排序之后的结果

    sa[i]是排名为i的后缀的起点下标(开始位置)

    3. 名次数组:名次数组 Rank[i] 保存的是 Suffix(i) 在所有后缀中从小到大排列的 “ 名次 ” 。
    rank[i]是后缀i的名次(从i开始的子串 位置i的子串)


    【倍增算法】:

    见论文描述

    一张图,每个位置开始长度为j的排序,然后用两个j组成2*j排序.........


    【实现】:

    排序使用计数排序,

    n是字符串长度,m是字符大小上限

    a是要排序数组 sa后缀数组

    c是计数排序用的 

    r为“第一关键字key1”的名次数组(类似于给每个位置开始的子串分配一个名次/权值 一开始一个字符r[i]=a[i])

    k为“第二关键字key2”的排序结果 

    k[i]表示的是按key2排序后第i名整个子串(key1+key2)开始的位置,不是key2开始的位置

    可以发现 key1开始位置+j=key2开始位置

     

    先给单个字符计数排序

    然后从j=1开始(每次循环是把两个j合成j<<1)

    p最后是当前不同的子串(不一定是后缀)的个数,p==n就没必要继续排序了(已经可以区分每个位置开始的字符串的大小关系了)

    1.给key2排序:先处理没有key2的(n-j+1...n),再通过上一次排序后的sa得到k(上次sa就是长度j的结果,这次的一个关键字长度也是j,只要sa[i]-j变成key1开始的位置就行了)

    2.这时r[k[i]]就是key2排第i名的key1的权值,k[i]就是i的开始位置,计数排序,结果用sa保存

    3.更新r为长度j<<1时的名次,把r复制给k,这个名次会有相同的,应该算一个,用离散化去重的写法....如何判断相同:先判断是不是都有key2,有一个没有key2的话两个一定不相同(长度就不可能相同了啊),再分别判断两个关键字是否相同(这时的k是长度j时的名次,判断就行了)

    「这里和论文里不太一样,他的做法是加了一个0....」


    【求LCP】

    height数组:定义 height[i]=suffix(sa[i-1])和 suffix(sa[i])的最长公共前缀

    排名i和排名i-1后缀的LCP

    对于第i名和第j名,他们的LCP就是RMQ{height[i+1],height[i+2],...,height[j]}

    定义h[i]=height[rank[i]]

    第i个后缀和在它的前一名的后缀的LCP。 

    性质:h[i]>=h[i-1]-1

    设 suffix(k)是排在 suffix(i-1)前一名的后缀,则它们的最长公共前缀是 h[i-1]。那么 suffix(k+1)将排在 suffix(i)的前面(这里要求 h[i-1]>1,如果 h[i-1]≤1,原式显然成立 )并且 suffix(k+1)和 suffix(i)的最长公共前缀是 h[i-1]-1,所以 suffix(i)和在它前一名的后缀的最长公共前缀至少是 h[i-1]- 1。按照h[1],h[2],......,h[n]的顺序计算,并利用 h 数组的性质,时间复杂度可 以降为 O(n)。 
    证明

    也就是说第i个和他的前一名的LCP至少是i-1和它的前一名的LCP-1,直观上理解,i-1往后一位(长度-1)就是i啊

    实现上,并没有必要弄一个h[],只需要按照h的顺序(也就是原来1..n的顺序)算就行了


    注意:m的大小最后会为n,所以c数组至少为n 

        void getHeight(){
            int k=0;
            for(int i=1;i<=n;i++) rnk[sa[i]]=i;
            for(int i=1;i<=n;i++){
                if(k) k--;
                if(rnk[i]==1) continue;
                int j=sa[rnk[i]-1];
                while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
                height[rnk[i]]=k;
            }
        }
        inline bool cmp(int *r,int a,int b,int j){
            return a+j<=n&&b+j<=n&&r[a]==r[b]&&r[a+j]==r[b+j];
        }
        void getSA(){ m=300
            int *r=t1,*k=t2;
            for(int i=0;i<=m;i++) c[i]=0;
            for(int i=1;i<=n;i++) c[r[i]=s[i]]++;
            for(int i=1;i<=m;i++) c[i]+=c[i-1];
            for(int i=n;i>=1;i--) sa[c[r[i]]--]=i;
            
            for(int j=1;j<=n;j<<=1){
                int p=0;
                for(int i=n-j+1;i<=n;i++) k[++p]=i;
                for(int i=1;i<=n;i++) if(sa[i]>j) k[++p]=sa[i]-j;
                
                for(int i=0;i<=m;i++) c[i]=0;
                for(int i=1;i<=n;i++) c[r[k[i]]]++;
                for(int i=1;i<=m;i++) c[i]+=c[i-1];
                for(int i=n;i>=1;i--) sa[c[r[k[i]]]--]=k[i];
                
                swap(r,k);p=0;r[sa[1]]=++p;
                for(int i=2;i<=n;i++) r[sa[i]]=cmp(k,sa[i],sa[i-1],j)?p:++p;
                if(p>=n) break;m=p;
            }
        }
        
        int lcp(int x,int y){
            x=rnk[x];y=rnk[y];
            if(x>y) swap(x,y);x++;
            int t=Log[y-x+1];
            return min(mn[x][t],mn[y-Pow[t]+1][t]);
        }
  • 相关阅读:
    弹性盒模型的实际应用
    大图滚动--这是精髓实例
    三级联动
    sql
    jsp2
    marquee
    人机五子棋(AI算法有瑕疵)
    Jsp1
    倒计时
    时间
  • 原文地址:https://www.cnblogs.com/candy99/p/6224400.html
Copyright © 2011-2022 走看看