java 實現後綴數組及最長迴文子串問題

摘要: 後綴數組的java實現。 利用後綴數組來求解最長迴文子串問題。

關鍵詞: 後綴數組, 倍增算法, 基數排序,height[]數組,最長迴文子串

參考文獻:《後綴數組_處理字符串的有效工具》。


part I .  後綴數組中一些相關定義

Suffix(i)表示以i開始的後綴, 對於字符串"aabaaaab"來說,Suffix(2)=baaaab

Rank[i] 表示以Suffix(i) 在所有的後綴 的rank.

Sa[i] 表示排名第i的後綴的下標。

參照圖1,很容易理解Rank[] 和Sa[]。

Rank[] 與 Sa[] 有簡單的置換關係:

Rank[S[i]] = i; 

S[Rank[i]] = i; 

求出一個來可以很容易的獲得另外一個。

接下來的問題,怎麼樣求出後綴數組?羅大牛在論文中提到兩種算法倍增算法和DC3算法。

倍增算法的思路如下:

第 i  次 倍增時,對以 j 開始的長度爲2^i的子串進行排序。利用第 i -1 次 倍增後的結果。

長度爲2^i的子串可以表示成2個長度爲2^(i-1)的子串的rank,即有兩個關鍵字。然後進行基數排序。

如下圖所示,可以很簡單的理解。有時候圖片比文字的表達能力強大很多。

羅大牛的c語言模板寫的相當簡潔,不過對於一個java程序員來說,搞清楚那四個數組的含義,還有其中用到的運算技巧真是很痛苦。等哪天心情好了,再看。

一副圖片其實就把後綴數組的含義解釋的很清楚了。

算法複雜度: 總共需要O(logn)次倍增,每一次倍增進行基數排序需要兩次分配和收集,複雜度O(2*n),  總複雜度O(nlogn).


part II .   height[]數組

height[]數組的含義:height[i]表示排序爲 i 的後綴與排序爲 i-1 的後綴的最長公共前綴的長度。

還是看圖:

假設rank[i] < rank[j],  任意兩個後綴 i 和 j 的最長公共前綴的長度,  是height[rank[i]+1], height[rank[i]+2]...height[rank[j]]的最小值。

如圖4所示,suffix(4)=aaab  與 suffix(1)=abaaaab的最長公共前綴的長度爲1。

怎麼計算height[]數組。

最簡單的想法是,計算排序相鄰的兩個後綴的公共前綴。

for(int i = 1;i<n;i++){

      int j = sa[i];

      int k = sa[i-1];

      height[i] = 0;

     while(str[j+height[i]]==str[k+height[i]])  height[i]++;

}

上面幾行代碼看做僞代碼吧,沒有考慮太多細節。 最壞的情況:str="aaaaaaaa", suffix(sa[0]) =" a",  suffix(sa[1]) ="aa", suffix(sa[2])="aaa",suffix(sa[3])="aaaa" .....

這樣最多需要1+2+3+...+n-1次比較,O(n^2)的算法。

怎麼樣去優化? 可以利用某些性質,使得height[i] 不是每次都從0開始。

對於圖4中的例子,suffix(4) ="aaab" , suffix(5)="aab", sa[1] = 4, rank[4] = 1 ,  sa[2] =5, rank[5] =2 , suffix(4)在suffix(5)的前面,height[rank[5]]  =height[2] = 2, 那麼suffix(4)和suffix(5)各自向後移動一個,變成aab=suffix(5)和ab=suffix(6),那麼aab也在ab的前面, 因此,對於ab來說,其height[rank[6]]的長度至少爲height[rank[5]]-1=1. 仔細觀察圖4,suffix(sa[2])=aab,suffix(sa[4])=ab,此外,

suffix(sa[3]) = aabaaaab.  因此在計算ab的height時,在與suffix(sa(rank[6]-1))進行比較時,不需要再從0開始進行匹配。


令h[i] 表示height[rank[i]],則h[i-1]=height[rank[i-1]]

h[]數組的重要性質:h[i]>=h[i-1]-1




part III .   最長迴文子串

計算最長迴文子串,怎麼利用後綴數組解決?

最長迴文子串可以轉換成求兩個後綴的最長公共前綴。

令S="aabaaaab", 構造SS'="aabaaab$baaabaa",其長度爲len ,那麼求以S中以i爲中心的最長迴文子串,根據迴文串的性質,就變成在SS'中求suffix(i)和suffix(len-i)的最長公共前綴的問題。


另外需要做一些長度爲奇數偶數的處理。主要思路是關鍵。

源碼分析如下:

public interface Buckable{
       /**第i次基數排序時映射到哪個桶裏*/
    public int map(int pass);
}
 
import java.util.Arrays;
                                          
public class Bucket {
        /**桶,只用來進行計數*/
    private int[] buckets;
        /**桶的個數,最多需要多少個*/
    private int bucketNum;
        /**總共需要多少次排序,也就是基數排序時,多關鍵字的位數*/
    private int passes;
    public Bucket(int m,int n){
        this.bucketNum = m;
        this.buckets = new int[m];
        this.passes = n;
    }
    public int[] sort(Buckable[]data){
               /**order表示data的進入桶的順序*/
        int[] order = new int[data.length];
        for(int i = 0;i<order.length;i++){
            order[i] = i;
        }
        return sort(data,order);
    }
       /**
        *order表示data的進入桶的順序, 排序完需要重新設置order,
        *設置data在下一輪進入桶時的順序。
       */
    public int[] sort(Buckable[]data,int[] order){
        for(int i = 0;i<this.passes;i++){
             sort(data,order,i);
        }
                /** data的rank值,從1開始,data相同的話,其rank也相同*/
        int[] rank = new int[data.length];
        rank[order[0]] = 1;
        for(int i = 1;i<order.length;i++){
            if(data[order[i]].equals(data[order[i-1]])){
                rank[order[i]] = rank[order[i-1]];
            }else{
                rank[order[i]] = rank[order[i-1]]+1;
            }
        }
        return rank;
    }
                                              
    public void sort(Buckable[] data,int[] order,int pass){
                                                  
        if(data.length!=order.length || pass<0){
            throw new IllegalArgumentException();
        }
                                                  
           //map the data to the bucket no.
        int[] bucketNo = new int[data.length];
        for(int i = 0;i<data.length;i++){
            bucketNo[i] = data[i].map(pass);
        }
        Arrays.fill(this.buckets, 0);
        //put into bucket
        for(int i = 0;i<order.length;i++){
            int or = order[i];
            int bu = bucketNo[or];
            this.buckets[bu]++;
        }
                                                  
        for(int i = 1;i<this.bucketNum;i++){
            this.buckets[i] += this.buckets[i-1];
        }
                                                  
               /**新的order的計算要從後面開始計算,因爲一個桶中,進入桶中比較晚的元素,其order也比較大*/
        int[] newOrder = new int[order.length];
        for(int i = order.length-1;i>=0;i--){
            newOrder[this.buckets[bucketNo[order[i]]]-1] = order[i];
            this.buckets[bucketNo[order[i]]]--;
        }
        for(int i = 0;i<order.length;i++){
            order[i] = newOrder[i];
        }
    }
}
 
/**倍增算法中每個元素的鍵值,由兩個數組成*/
public class DAPair implements Buckable{
                                         
    private int rank[]= new int[2];
                                             
    public DAPair(int r0,int r1){
        this.rank[0] = r0;
        this.rank[1] = r1;
    }
                                             
    @Override
    public int map(int pass) {
        if(pass>=2 || pass <0){
            throw new IllegalArgumentException();
        }
        if(pass==0){
            return this.rank[1];
        }
        return this.rank[0];
    }
                                         
    @Override
    public boolean equals(Object o){
        if(o instanceof DAPair){
            return this.rank[0] == ((DAPair)o).rank[0] &&
                    this.rank[1] == ((DAPair)o).rank[1];
        }
        return false;
    }
}
 
public class SuffixArray {
                                        
    private String string;
    //記錄下表爲i的後綴的排序
    private int rank[];
    //記錄排序爲i的後綴的下標
    private int sa[];
                                        
    private int height[];
                                        
    public SuffixArray(String string){
        if(string==null) throw new NullPointerException();
        this.string = string;
        this.rank = new int[string.length()];
        this.sa = new int[string.length()];
        this.height = new int[string.length()];
        build();
    }
                                        
    private DAPair[] fromArray(int[] a,int inc){
        DAPair[] pairs = new DAPair[a.length];
        for(int i = 0;i+inc<a.length;i++){
            pairs[i] = new DAPair(a[i],a[i+inc]);
        }
        for(int i = a.length-inc;i<a.length;i++){
            pairs[i] = new DAPair(a[i],0);
        }
        return pairs;
    }
                                        
    private void build(){
        if(this.string.isEmpty()==false){
            //build suffix array
            int len = string.length();
            class LowcaseCharacter implements Buckable{
                private char ch;
                public LowcaseCharacter(char c){
                    this.ch = c;
                }
                @Override
                public int map(int pass) {
                    if(pass!=0) throw new IllegalArgumentException();
                    return ch;
                }
                @Override
                public boolean equals(Object o){
                    if(o instanceof LowcaseCharacter){
                        return ((LowcaseCharacter) o).ch==this.ch;
                    }
                    return false;
                }
                                                    
            }
            Buckable[] buckables = new Buckable[len];
            for(int i=0;i<len;i++){
                buckables[i] = new LowcaseCharacter(string.charAt(i));
            }
            Bucket bucket = new Bucket(130,1);
            this.rank = bucket.sort(buckables);
            bucket = new Bucket(this.string.length()+2,2);
            //倍增算法
            for(int i = 1;i<len;i=i<<1){
                DAPair[] dapairs = fromArray(this.rank,i);
                for(int j = 0;j<this.sa.length;j++){
                    this.sa[j] = j;
                }
                this.rank = bucket.sort(dapairs,this.sa);
            }
            for(int i = 0;i<this.rank.length;i++){
                this.rank[i]--;
            }
            //build height array
            int k = 0;
            for(int i=0;i<this.sa.length;i++){
                if(k>0) k--;
                if(rank[i]==0) continue;
                int j = this.sa[rank[i]-1];
                for(;i+k<this.string.length() &&
                        j+k<this.string.length() &&
                        this.string.charAt(i+k)==this.string.charAt(j+k);
                        k++);
                      height[rank[i]] = k;
                                    
            }
        }
    }
    //Suffix(id1)和Suffix(id2)的最長公共前綴的長度
    public int getLCP(int id1,int id2){
        if(id1==id2) return this.string.length()-id1+1;
        int r1 = this.rank[id1];
        int r2 = this.rank[id2];
        int minr = r1<r2?r1:r2;
        int maxr = r1>r2?r1:r2;
        int lcp = this.string.length();
        for(int i = minr+1;i<=maxr;i++){
            if(lcp>this.height[i]){
                lcp = this.height[i];
            }
        }
        return lcp;
    }
    public int[] getRank(){
        return this.rank;
    }
    public int[] getSuffixArray(){
        return this.sa;
    }
    //最長迴文子串
    public String getLP(){
        StringBuffer buffer = new StringBuffer(this.string.length()*2+2);
        for(int i = 0;i<this.string.length();i++){
            buffer.append("#"+this.string.charAt(i));
        }
        buffer.append("#$");
        for(int i = this.string.length()-1;i>=0;i--){
            buffer.append("#"+this.string.charAt(i));
        }
        buffer.append("#");
        String newStr = buffer.toString();
        SuffixArray sa = new SuffixArray(newStr);
                                            
        int maxLcp = -1;
        int id = -1;
        for(int i = 1;i<newStr.length()/2;i++){
            int lcp = sa.getLCP(i, newStr.length()-i-1);
            if(maxLcp<lcp) {
                maxLcp = lcp;
                id = i;
            }
        }
                                            
        String rs = newStr.substring(id-maxLcp+1,id+maxLcp);
        rs = rs.replaceAll("#", "");
        return rs;
    }
    public static void main(String[]args){
        String s = "ababababa";
        SuffixArray sa = new SuffixArray(s);
        System.out.println(sa.getLP());
                                            
    }
}


發佈了24 篇原創文章 · 獲贊 17 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章