最长重复子序列问题除了使用穷举法,还可以使用后缀数组和后缀树来求解。这里给出使用后缀数组解决的最长重复子序列的过程,并以“banana”为例进行演示。首先写下一个比较两个字符串从头开始共同部分的长度的函数:
int comlen(char *p, char *q) {
int i = 0;
while(*p&&(*p++ == *q++))
i++;
return i;
}
设定该程序最多处理MAXN个字符,并存放在数组c中,a是对应的后缀数组:
#define MAXN 5000000
char c[MAXN],*a[MAXN];
读取输入时,对a进行初始化:
//n是已读入的字符数目
int input(int n) {
int ch;
while((ch = getchar())!=EOF) {
a[n] = &c[n];
c[n++] = ch;
}
c[n] = 0;
return n;
}
这样,对于“banana”,对应的后缀数组为:
a[0]:banana
a[1]:anana
a[2]:nana
a[3]:ana
a[4]:na
a[5]:a
它们是"banana"的所有后缀,这也是“后缀数组”命名原因。
如果某个长字符串在数组c中出现了两次,那么它必然出现在两个不同的后缀中,更准确的说,是两个不同后缀的同一前缀。通过排序可以寻找相同的前缀,排序后的后缀数组为:
a[0]:a
a[1]:ana
a[2]:anana
a[3]:banana
a[4]:na
a[5]:nana
扫描排序后的数组的相邻元素就能得到最长的重复字串,本例为“ana”。
这里做一个扩展:(习题16.8)如何寻找至少出现过n次的最长重复子序列?
解法是比较a[i...i+n]中第一个和最后一个的最长公共前缀长度,上文是对n=1的特例。因此写出一般化的扫描函数:
int scan(int n,int k) {
int maxi=0,i;
int temp,maxlen = 0;
for(i=0;i<n-k;i++) {
temp = comlen(a[i],a[i+k]);
if(temp>maxlen) {
maxlen = temp;
maxi = i;
}
}
printf("%d times the longest:%.*s\n",k,maxlen,a[maxi]);
return 0;
}
int pstrcmp(char **p, char **q)
{ return strcmp(*p, *q); }
int main(void) {
int i,n;
n = input(0);
qsort(a,n,sizeof(char *),pstrcmp);
printf("\n");
for(i=0;i<n;i++)
printf("%s\n",a[i]);
scan(n,1);
scan(n,2);
return 0;
}