java 实现后缀数组及最长回文子串问题

摘要: 后缀数组的java实现。 利用后缀数组来求解最长回文子串问题。

关键词: 后缀数组, 倍增算法, 基数排序,height[]数组,最长回文子串

参考文献:《后缀数组_处理字符串的有效工具》。


part I .  后缀数组中一些相关定义

Suffix(i)表示以i开始的后缀, 对于字符串"aabaaaab"来说,Suffix(2)=baaaab

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

Sa[i] 表示排名第i的后缀的下标。

参照图1,很容易理解Rank[] 和Sa[]。

java 实现后缀数组及最长回文子串问题_第1张图片

Rank[] 与 Sa[] 有简单的置换关系:

Rank[S[i]] = i; 

S[Rank[i]] = i; 

求出一个来可以很容易的获得另外一个。

接下来的问题,怎么样求出后缀数组?罗大牛在论文中提到两种算法倍增算法和DC3算法。

倍增算法的思路如下:

第 i  次 倍增时,对以 j 开始的长度为2^i的子串进行排序。利用第 i -1 次 倍增后的结果。

长度为2^i的子串可以表示成2个长度为2^(i-1)的子串的rank,即有两个关键字。然后进行基数排序。

如下图所示,可以很简单的理解。有时候图片比文字的表达能力强大很多。

java 实现后缀数组及最长回文子串问题_第2张图片

罗大牛的c语言模板写的相当简洁,不过对于一个java程序员来说,搞清楚那四个数组的含义,还有其中用到的运算技巧真是很痛苦。等哪天心情好了,再看。

一副图片其实就把后缀数组的含义解释的很清楚了。

算法复杂度: 总共需要O(logn)次倍增,每一次倍增进行基数排序需要两次分配和收集,复杂度O(2*n),  总复杂度O(nlogn).


part II .   height[]数组

height[]数组的含义:height[i]表示排序为 i 的后缀与排序为 i-1 的后缀的最长公共前缀的长度。

还是看图:

java 实现后缀数组及最长回文子串问题_第3张图片

假设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

      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=0;i--){
            newOrder[this.buckets[bucketNo[order[i]]]-1] = order[i];
            this.buckets[bucketNo[order[i]]]--;
        }
        for(int i = 0;i=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+inc0) k--;
                if(rank[i]==0) continue;
                int j = this.sa[rank[i]-1];
                for(;i+kr2?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=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


你可能感兴趣的:(算法,源代码,Java编程)