高度数组与最长公共前缀

摘要

本文是关于后缀数组的一个拓展,问题模型来自于最长公共前缀(Longest Common Prefix Array)问题,我们为解决该模型,在后缀数组的基础上设计了一个精巧的数组——高度数组,同时由于该数组在处理各类字符串相关问题时有着较好的用途,因此值得掌握。

本文将直接从高度数组讲起,假设读者具有后缀数组的基础知识。首先本文会介绍高度数组的定义以及用途,接着讲解如何在O(N)时间内求出高度数组,最后将介绍高度数组的应用。

高度数组(LCP Array ,Longest Common Prefix Array)

所谓的后缀数组,指的是由后缀数组中相邻两个后缀的最长公共前缀(LCP)的长度组成的数组。记后缀数组为sa,高度数组为lcp,则有后缀S[ sa[i]… ] 与 S[S[i+1] … ] 的最长公共前缀的长度为lcp[i] 。我们可以在O(n)时间内高效地求得高度数组,有了高度数组,后缀数组将称为一个更加有力的工具。高度数据虽然计算简单,但非常巧妙,使用了类似尺取法的技巧。记rank[i] 为位置 i 开始的后缀在后缀数组中的顺序,即有rank[ s[i] ] = i。

构造方法

我们从位置0的后缀开始,从前往后依次计算后缀S[i…]与后缀S[sa[rank[i]-1]…](即后缀数组中的前一个后缀)的最长公共前缀的长度。此时,假设我们已经求得了位置 i 对应的高度 h i h_i hi,那么我们可以证明位置 i+1 对应的高度应该不小于 h i − 1 h_i-1 hi1

为什么呢?记k = sa[rank[i] - 1] ,已知后缀S[i…]和S[k…]的头部 h i h_i hi个字符是相等的,那么后缀S[i+1…]和S[k+1…]分别是二者去除首字符的结果,所以它们头部 h i − 1 h_i-1 hi1个字符是相等的。虽然在后缀数组中,S[i+1…]前面一个元素未必就是S[k+1…],但即便如此,公共前缀的长度也是只增不减的。因此,只要从 h i − 1 h_i-1 hi1开始检查,计算最长公共前缀的长度就好了。

代码模板

因为高度最多增加n次,所以总的时间复杂度为O(n)。如果把这个问题当作位置i对应的区间是[i,i+h)的尺取法来看,就很容易理解。区间的左右端点始终不会向左移,并且是不超过n的整数。

#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e6+10;
int n,k;
char str[N];
int sa[N],rank[N],tmp[N],lcp[N];
//比较(rank[i],rank[i+k])和(rank[j],rank[j+k]) 
bool compare_sa(int i,int j){
	if(rank[i] != rank[j]) return rank[i] < rank[j];
	int ri = i+k <= n ? rank[i+k] : -1;
	int rj = j+k <= n ? rank[j+k] : -1;
	return ri < rj;
}
//计算字符串S的后缀数组 
void construct_sa(char* S,int sa[]){
	n = strlen(str);
	//初始长度为1,rank直接取字符的编码
	for(int i = 0;i <= n;i++){
		sa[i] = i;
		rank[i] = i < n ? S[i] : -1;
	} 
	//利用对长度为k的排序结果对长度为2k的排序
	for(k = 1;k <= n;k *= 2){
		sort(sa,sa+n+1,compare_sa);
		tmp[sa[0]] = 0;
		for(int i = 1;i <= n;i++)
			tmp[sa[i]] = tmp[sa[i-1]] + (compare_sa(sa[i-1],sa[i])?1:0);
		for(int i = 0;i <= n;i++) rank[i] = tmp[i];
	} 
}
//最长公共前缀模板,其中sa为后缀数组,求得结果存放在lcp 
void construct_lcp(char *S,int sa[],int lcp[]){
	int n = strlen(S);
	for(int i = 0;i <= n;i++) rank[sa[i]] = i;
	int h = 0;lcp[0] = 0;
	for(int i = 0;i < n;i++){
		int j = sa[rank[i]-1];
		if(h > 0) h--;
		for(;j + h < n && i+h < n;h++){
			if(S[j+h] != S[i+h]) break;
		}
		lcp[rank[i]-1] = h;
	}
}
int main(){
	while(scanf("%s",str) != EOF){
		construct_sa(str,sa);
		construct_lcp(str,sa,lcp);
		for(int i = 1;i <= n;i++)
			printf("%d ",lcp[i]);
		puts("");
	}
	return 0;
}

参考资料

  • 秋叶拓哉,挑战程序设计竞赛第2版,北京:人民邮电出版社,2013.6,378-381
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:白松林 返回首页