后缀数组(SuffixArray)

在做Usaco_1_3_Calf Flac一题时用到了后缀数组,自己写了一个,构造时间为O(N*lgN),访问时间为O(1),相关文件如下(包含代码、论文等):

 SuffixArray

注意,代码是j#,需用.net2005打开

 

 

ContractedBlock.gif ExpandedBlockStart.gif SuffixArray
package SDJL.SuffixArray;

import java.io.*;
import java.util.*;
import SDJL.RMQ.*;

public class SuffixArray
ExpandedBlockStart.gifContractedBlock.gif
{
    
private char[] datas;//存放字符串数据
    private int[] position;//position[3]=5表示第3名的后缀数组开头位置为5
    private int[] rank;//position的反函数,rank[5]=3表示开头位置为5的后缀数组名次等于3
    private RMQ height;//为什么用height? 我也不知道,看论文,height[i]表示第i名与第i-1名后缀串的lcp
    private int length;//数组的长度
    private int k;//用来计算k前缀名次数组与后缀数组
    
    
public SuffixArray(char[] datas)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
this.datas = datas;
        
this.length = datas.length;

        computePosAndRank();
        computeHeight();
    }


    
//O(1)
    
//获得suffix(firstIndex)与suffix(secondIndex)的lcp
    public int getLCP(int firstIndex, int secondIndex)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
int lcp;
        
if (firstIndex == secondIndex)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            lcp 
= this.length - firstIndex;
        }

        
else
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
int minRank = Math.min(this.rank[firstIndex], this.rank[secondIndex]);
            
int maxRank = Math.max(this.rank[firstIndex], this.rank[secondIndex]);
            lcp 
= this.height.getMinimum(minRank + 1, maxRank);
        }

        
return lcp;
    }


    
public char getData(int index)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
return this.datas[index];
    }

    
    
//根据位置position获得suffix(position)的名次
    public int getRank(int position)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
return this.rank[position];
    }


    
//根据名次rank获得第rank名的位置
    public int getPosition(int rank)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
return this.position[rank];
    }


    
// O(N*logN)
    
//计算位置数组与后缀数组
    private void computePosAndRank()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
this.position = new int[this.length];
        
this.rank = new int[this.length];
        
        
//计算1前缀名次数组
        computeRank_1();
        
//用1前缀名次数组计算1前缀位置数组
        computePos_1ByRank_1();

        
//依次计算2、4、8……名次数组与后缀数组
        k = 1;
        
while (k < this.length)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            computePos_2kByRank_k();
            computeRank_2kByPos_2kAndRank_k();
            k 
+= k;
        }

    }


    
//O(N)
    
//用k前缀名次数组计算2k前缀位置数组
    private void computePos_2kByRank_k()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
//计数排序中用于统计每个比较值出现的次数,与统计小于等于某个数的比较值出现的次数
        int[] c = new int[this.length];
        
//计数排序中保存已排序的值
        int[] b = new int[this.length];
        
//由于基数排序中需要用到两次计数排序,而第一次计数排序时由于被排序值的位置改变,
        
//比较值的位置也随着改变,因此需要使用newRank[]来保存新的比较值,用于第二次计数排序
        int[] newRank = new int[this.length];

        
//===============开始第一次计数排序==============

        
//在计算2k前缀名次数组时,最后k个后缀的名次必然是唯一的,因此第一次计数排序时可以不用对这k个后缀排序,
        
//但是我们需要把这k个后缀放在“名次”的前面(考虑排序“acc”,k=2),且需要同时移动比较数据
        for (int i = 0; i < this.k; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
this.position[i] = this.length - this.k + i;
        }

        System.arraycopy(
this.rank, this.length - this.k, newRank, 0this.k);

        
//注意,从第0个后缀数组开始名次分别为k、k+1、k+2……,因此如下初始化被排序值
        for (int i = this.k; i < length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
this.position[i] = i - this.k;
        }


        
//置c[]为0,开始统计比较值出现的次数
        Arrays.fill(c, 0);        
        
for (int i = 0; i < this.length - this.k; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            c[
this.rank[i+k]]++;
        }


        
//统计小于等于某个值的比较值个数
        for (int i = 1; i < this.length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            c[i] 
= c[i] + c[i - 1];
        }


        
//开始第一次计数排序,对前length - k 个后缀从最后一个开始放到指定位置
        for (int i = this.length - 1 - this.k; i >= 0; i--)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
//this.rank[i + k]表示第i个被排序值对应的比较值
            
//c[this.rank[i + k]]表示小于等于这个比较值的个数
            
//c[this.rank[i + k]] - 1 + k,因为我们把前k个位置留给了最后k个后缀,因此需要加上偏移量k,因为从0开始计数,所以保存的位置减1
            
//b[c[this.rank[i + k]] - 1 + k]就是因该被放置的地方
            
//this.position[i + k]为第i个被排序值
            b[c[this.rank[i + this.k]] - 1 + this.k] = this.position[i + this.k];
            
//保存新的比较值,this.rank[i]为下一次计数排序时第i个被排序值对应的比较值
            newRank[c[this.rank[i + this.k]] - 1 + this.k] = this.rank[i];
            
//出现次数减1,以便下一次出现相同被比较值时放在前面一个位置
            c[this.rank[i + this.k]]--;
        }


        
//更新position为已排序值
        System.arraycopy(b, this.k, this.position, this.k, this.length - this.k);


        
//==========开始第二次计数排序=============
        
        
//初始化比较值出现的次数
        Arrays.fill(c, 0);
        
//统计比较值出现的次数
        for (int i = 0; i < this.length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            c[newRank[i]]
++;
        }

        
//统计小于等于某个数的比较值出现次数
        for (int i = 1; i < this.length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            c[i] 
= c[i] + c[i - 1];
        }

        
//依次放置被排序值
        for (int i = this.length - 1; i >= 0; i--)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            b[c[newRank[i]] 
- 1= this.position[i];
            c[newRank[i]]
--;
        }

        
//更新位置数组
        this.position = b;
    }


    
//O(N*logN)
    
//计算1前缀名次数组
    private void computeRank_1()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
//构造1前缀后缀数组,然后排序
        charWithSatelliteData[] suffixArray = new charWithSatelliteData[this.length];
        
for (int i = 0; i < this.length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            suffixArray[i] 
= new charWithSatelliteData(this.datas[i], i);
        }

        Arrays.sort(suffixArray);
        
        
//计算名次,为了使得相同的字符拥有相同的名次,所以应用 _rank
        int _rank = 0;//目前出现的名次
        for (int i = 0; i < this.length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
//遇到新字符时更新名次
            if (i > 0 && suffixArray[i].c != suffixArray[i - 1].c)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                _rank 
= i;
            }

            
this.rank[suffixArray[i].position] = _rank;
        }

    }

    
    
//O(N)
    
//用位置数组与名次数组计算新的名次数组
    private void computeRank_2kByPos_2kAndRank_k()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
//为了让相同的后缀数组拥有相同的名次,所以使用_rank,使用方法类似computeRank_1()
        int _rank = 0;//目前出现的名次
        int[] newRank = new int[this.length];

        
for (int i = 0; i < this.length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
//if suffix(position[i]) != suffix(position[i-1]) and suffix(position[i]+k) != suffix(position[i-1]+k)
            
//equal to rank[position[i]] != rank[position[i-1]] and rank[position[i]+k] != rank[position[i-1]+k]
            
//where position[i-1] + k > length , suffix(position[i-1]) is distinct
            if ( 
                (i 
> 0
                
&& (
                    (
this.rank[this.position[i]] != this.rank[this.position[i - 1]])
                    
|| (this.position[i - 1+ this.k >= this.length) 
                    
|| (this.rank[this.position[i] + this.k] != this.rank[this.position[i - 1+ this.k])
                    )
                )
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                _rank 
= i;
            }

            newRank[
this.position[i]] = _rank;
        }

        
this.rank = newRank;
    }


    
//O(N)
    
//用1前缀名次数组计算1前缀位置数组
    
//这个方法也可以用k前缀名次数组计算k前缀位置数组
    private void computePos_1ByRank_1()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
//因为相同的名次要分配不同的位置,所以使用rankCount[]
        int rankCount[] = new int[this.length];
        Arrays.fill(rankCount, 
0);
        
for (int i = 0; i < this.length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
this.position[this.rank[i] + rankCount[this.rank[i]]] = i;
            rankCount[
this.rank[i]]++;
        }

    }


    
//call after computePosAndRank()
    private void computeHeight()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
int[] h = new int[this.length];//h[i]表示suffix(i)与比它小一个名次的后缀字符串的lcp
        for (int i = 0; i < this.length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
if (this.rank[i] == 0)
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                h[i] 
= 0;
            }

            
else if ((i == 0|| (h[i-1<= 1))
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                h[i] 
= getSameLength(i, this.position[this.rank[i] - 1]);
            }

            
else
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                h[i] 
= h[i - 1- 1 + getSameLength(i + h[i - 1- 1this.position[this.rank[i] - 1+ h[i - 1- 1);
            }

        }


        
int[] tmpHeight = new int[this.length];
        
for (int i = 0; i < this.length; i++)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            tmpHeight[i] 
= h[this.position[i]];
        }


        
this.height = new RMQ(tmpHeight);
    }


    
//通过逐渐比较获得suffix(firstIndex)与suffix(secondIndex)的lcp
    private int getSameLength(int firstIndex, int secondIndex)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
int sameLength = 0;
        
while ((firstIndex < this.length) && (secondIndex < this.length) && (this.datas[firstIndex] == this.datas[secondIndex]))
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            sameLength
++;
            firstIndex
++;
            secondIndex
++;
        }

        
return sameLength;
    }


    
}




//此class用于计算1前缀名次数组时的排序,c为后缀,position为后缀的位置
class charWithSatelliteData implements Comparable
ExpandedBlockStart.gifContractedBlock.gif
{
    
public char c;
    
public int position;

    
public charWithSatelliteData(char c, int position)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        
this.c = c;
        
this.position = position;
    }


    
public int compareTo(Object arg0)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
{
        charWithSatelliteData arg 
= (charWithSatelliteData)arg0;
        
if (this.c > arg.c)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
return 1;
        }

        
else if (this.c == arg.c)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
return 0;
        }

        
else
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
return -1;
        }
        
    }

}

 

 

 

转载于:https://www.cnblogs.com/SDJL/archive/2008/10/30/1323175.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值