在做Usaco_1_3_Calf Flac一题时用到了后缀数组,自己写了一个,构造时间为O(N*lgN),访问时间为O(1),相关文件如下(包含代码、论文等):
注意,代码是j#,需用.net2005打开
SuffixArray
package SDJL.SuffixArray;
import java.io.*;
import java.util.*;
import SDJL.RMQ.*;
public class SuffixArray
{
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)
{
this.datas = datas;
this.length = datas.length;
computePosAndRank();
computeHeight();
}
//O(1)
//获得suffix(firstIndex)与suffix(secondIndex)的lcp
public int getLCP(int firstIndex, int secondIndex)
{
int lcp;
if (firstIndex == secondIndex)
{
lcp = this.length - firstIndex;
}
else
{
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)
{
return this.datas[index];
}
//根据位置position获得suffix(position)的名次
public int getRank(int position)
{
return this.rank[position];
}
//根据名次rank获得第rank名的位置
public int getPosition(int rank)
{
return this.position[rank];
}
// O(N*logN)
//计算位置数组与后缀数组
private void computePosAndRank()
{
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)
{
computePos_2kByRank_k();
computeRank_2kByPos_2kAndRank_k();
k += k;
}
}
//O(N)
//用k前缀名次数组计算2k前缀位置数组
private void computePos_2kByRank_k()
{
//计数排序中用于统计每个比较值出现的次数,与统计小于等于某个数的比较值出现的次数
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++)
{
this.position[i] = this.length - this.k + i;
}
System.arraycopy(this.rank, this.length - this.k, newRank, 0, this.k);
//注意,从第0个后缀数组开始名次分别为k、k+1、k+2……,因此如下初始化被排序值
for (int i = this.k; i < length; i++)
{
this.position[i] = i - this.k;
}
//置c[]为0,开始统计比较值出现的次数
Arrays.fill(c, 0);
for (int i = 0; i < this.length - this.k; i++)
{
c[this.rank[i+k]]++;
}
//统计小于等于某个值的比较值个数
for (int i = 1; i < this.length; i++)
{
c[i] = c[i] + c[i - 1];
}
//开始第一次计数排序,对前length - k 个后缀从最后一个开始放到指定位置
for (int i = this.length - 1 - this.k; i >= 0; i--)
{
//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++)
{
c[newRank[i]]++;
}
//统计小于等于某个数的比较值出现次数
for (int i = 1; i < this.length; i++)
{
c[i] = c[i] + c[i - 1];
}
//依次放置被排序值
for (int i = this.length - 1; i >= 0; i--)
{
b[c[newRank[i]] - 1] = this.position[i];
c[newRank[i]]--;
}
//更新位置数组
this.position = b;
}
//O(N*logN)
//计算1前缀名次数组
private void computeRank_1()
{
//构造1前缀后缀数组,然后排序
charWithSatelliteData[] suffixArray = new charWithSatelliteData[this.length];
for (int i = 0; i < this.length; i++)
{
suffixArray[i] = new charWithSatelliteData(this.datas[i], i);
}
Arrays.sort(suffixArray);
//计算名次,为了使得相同的字符拥有相同的名次,所以应用 _rank
int _rank = 0;//目前出现的名次
for (int i = 0; i < this.length; i++)
{
//遇到新字符时更新名次
if (i > 0 && suffixArray[i].c != suffixArray[i - 1].c)
{
_rank = i;
}
this.rank[suffixArray[i].position] = _rank;
}
}
//O(N)
//用位置数组与名次数组计算新的名次数组
private void computeRank_2kByPos_2kAndRank_k()
{
//为了让相同的后缀数组拥有相同的名次,所以使用_rank,使用方法类似computeRank_1()
int _rank = 0;//目前出现的名次
int[] newRank = new int[this.length];
for (int i = 0; i < this.length; i++)
{
//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])
)
)
{
_rank = i;
}
newRank[this.position[i]] = _rank;
}
this.rank = newRank;
}
//O(N)
//用1前缀名次数组计算1前缀位置数组
//这个方法也可以用k前缀名次数组计算k前缀位置数组
private void computePos_1ByRank_1()
{
//因为相同的名次要分配不同的位置,所以使用rankCount[]
int rankCount[] = new int[this.length];
Arrays.fill(rankCount, 0);
for (int i = 0; i < this.length; i++)
{
this.position[this.rank[i] + rankCount[this.rank[i]]] = i;
rankCount[this.rank[i]]++;
}
}
//call after computePosAndRank()
private void computeHeight()
{
int[] h = new int[this.length];//h[i]表示suffix(i)与比它小一个名次的后缀字符串的lcp
for (int i = 0; i < this.length; i++)
{
if (this.rank[i] == 0)
{
h[i] = 0;
}
else if ((i == 0) || (h[i-1] <= 1))
{
h[i] = getSameLength(i, this.position[this.rank[i] - 1]);
}
else
{
h[i] = h[i - 1] - 1 + getSameLength(i + h[i - 1] - 1, this.position[this.rank[i] - 1] + h[i - 1] - 1);
}
}
int[] tmpHeight = new int[this.length];
for (int i = 0; i < this.length; i++)
{
tmpHeight[i] = h[this.position[i]];
}
this.height = new RMQ(tmpHeight);
}
//通过逐渐比较获得suffix(firstIndex)与suffix(secondIndex)的lcp
private int getSameLength(int firstIndex, int secondIndex)
{
int sameLength = 0;
while ((firstIndex < this.length) && (secondIndex < this.length) && (this.datas[firstIndex] == this.datas[secondIndex]))
{
sameLength++;
firstIndex++;
secondIndex++;
}
return sameLength;
}
}
//此class用于计算1前缀名次数组时的排序,c为后缀,position为后缀的位置
class charWithSatelliteData implements Comparable
{
public char c;
public int position;
public charWithSatelliteData(char c, int position)
{
this.c = c;
this.position = position;
}
public int compareTo(Object arg0)
{
charWithSatelliteData arg = (charWithSatelliteData)arg0;
if (this.c > arg.c)
{
return 1;
}
else if (this.c == arg.c)
{
return 0;
}
else
{
return -1;
}
}
}
package SDJL.SuffixArray;
import java.io.*;
import java.util.*;
import SDJL.RMQ.*;
public class SuffixArray
{
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)
{
this.datas = datas;
this.length = datas.length;
computePosAndRank();
computeHeight();
}
//O(1)
//获得suffix(firstIndex)与suffix(secondIndex)的lcp
public int getLCP(int firstIndex, int secondIndex)
{
int lcp;
if (firstIndex == secondIndex)
{
lcp = this.length - firstIndex;
}
else
{
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)
{
return this.datas[index];
}
//根据位置position获得suffix(position)的名次
public int getRank(int position)
{
return this.rank[position];
}
//根据名次rank获得第rank名的位置
public int getPosition(int rank)
{
return this.position[rank];
}
// O(N*logN)
//计算位置数组与后缀数组
private void computePosAndRank()
{
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)
{
computePos_2kByRank_k();
computeRank_2kByPos_2kAndRank_k();
k += k;
}
}
//O(N)
//用k前缀名次数组计算2k前缀位置数组
private void computePos_2kByRank_k()
{
//计数排序中用于统计每个比较值出现的次数,与统计小于等于某个数的比较值出现的次数
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++)
{
this.position[i] = this.length - this.k + i;
}
System.arraycopy(this.rank, this.length - this.k, newRank, 0, this.k);
//注意,从第0个后缀数组开始名次分别为k、k+1、k+2……,因此如下初始化被排序值
for (int i = this.k; i < length; i++)
{
this.position[i] = i - this.k;
}
//置c[]为0,开始统计比较值出现的次数
Arrays.fill(c, 0);
for (int i = 0; i < this.length - this.k; i++)
{
c[this.rank[i+k]]++;
}
//统计小于等于某个值的比较值个数
for (int i = 1; i < this.length; i++)
{
c[i] = c[i] + c[i - 1];
}
//开始第一次计数排序,对前length - k 个后缀从最后一个开始放到指定位置
for (int i = this.length - 1 - this.k; i >= 0; i--)
{
//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++)
{
c[newRank[i]]++;
}
//统计小于等于某个数的比较值出现次数
for (int i = 1; i < this.length; i++)
{
c[i] = c[i] + c[i - 1];
}
//依次放置被排序值
for (int i = this.length - 1; i >= 0; i--)
{
b[c[newRank[i]] - 1] = this.position[i];
c[newRank[i]]--;
}
//更新位置数组
this.position = b;
}
//O(N*logN)
//计算1前缀名次数组
private void computeRank_1()
{
//构造1前缀后缀数组,然后排序
charWithSatelliteData[] suffixArray = new charWithSatelliteData[this.length];
for (int i = 0; i < this.length; i++)
{
suffixArray[i] = new charWithSatelliteData(this.datas[i], i);
}
Arrays.sort(suffixArray);
//计算名次,为了使得相同的字符拥有相同的名次,所以应用 _rank
int _rank = 0;//目前出现的名次
for (int i = 0; i < this.length; i++)
{
//遇到新字符时更新名次
if (i > 0 && suffixArray[i].c != suffixArray[i - 1].c)
{
_rank = i;
}
this.rank[suffixArray[i].position] = _rank;
}
}
//O(N)
//用位置数组与名次数组计算新的名次数组
private void computeRank_2kByPos_2kAndRank_k()
{
//为了让相同的后缀数组拥有相同的名次,所以使用_rank,使用方法类似computeRank_1()
int _rank = 0;//目前出现的名次
int[] newRank = new int[this.length];
for (int i = 0; i < this.length; i++)
{
//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])
)
)
{
_rank = i;
}
newRank[this.position[i]] = _rank;
}
this.rank = newRank;
}
//O(N)
//用1前缀名次数组计算1前缀位置数组
//这个方法也可以用k前缀名次数组计算k前缀位置数组
private void computePos_1ByRank_1()
{
//因为相同的名次要分配不同的位置,所以使用rankCount[]
int rankCount[] = new int[this.length];
Arrays.fill(rankCount, 0);
for (int i = 0; i < this.length; i++)
{
this.position[this.rank[i] + rankCount[this.rank[i]]] = i;
rankCount[this.rank[i]]++;
}
}
//call after computePosAndRank()
private void computeHeight()
{
int[] h = new int[this.length];//h[i]表示suffix(i)与比它小一个名次的后缀字符串的lcp
for (int i = 0; i < this.length; i++)
{
if (this.rank[i] == 0)
{
h[i] = 0;
}
else if ((i == 0) || (h[i-1] <= 1))
{
h[i] = getSameLength(i, this.position[this.rank[i] - 1]);
}
else
{
h[i] = h[i - 1] - 1 + getSameLength(i + h[i - 1] - 1, this.position[this.rank[i] - 1] + h[i - 1] - 1);
}
}
int[] tmpHeight = new int[this.length];
for (int i = 0; i < this.length; i++)
{
tmpHeight[i] = h[this.position[i]];
}
this.height = new RMQ(tmpHeight);
}
//通过逐渐比较获得suffix(firstIndex)与suffix(secondIndex)的lcp
private int getSameLength(int firstIndex, int secondIndex)
{
int sameLength = 0;
while ((firstIndex < this.length) && (secondIndex < this.length) && (this.datas[firstIndex] == this.datas[secondIndex]))
{
sameLength++;
firstIndex++;
secondIndex++;
}
return sameLength;
}
}
//此class用于计算1前缀名次数组时的排序,c为后缀,position为后缀的位置
class charWithSatelliteData implements Comparable
{
public char c;
public int position;
public charWithSatelliteData(char c, int position)
{
this.c = c;
this.position = position;
}
public int compareTo(Object arg0)
{
charWithSatelliteData arg = (charWithSatelliteData)arg0;
if (this.c > arg.c)
{
return 1;
}
else if (this.c == arg.c)
{
return 0;
}
else
{
return -1;
}
}
}