给定一个文本文件,查找最长的重复子串。
如文本“Ask not what your country can do for you, but what you can do for your country”,最长的重复子串为“can do for you”和“your country”。单词banana的最长重复子串为“ana“。该问题可以看成是一个由颠倒字母组成的单词组合问题。假设该输入字符串保存在c[0…,n-1]数组中,一般比较常见的做法是用下面的伪代码来比较每个子串。
maxlen = -1
for i = [0, n)
for j = (i, n)
if (thislen = comlen(&c[i], &c[j])) > maxlen
maxlen = thislen
maxi = i
maxj = j
comlen()函数返回两个字符串中相同的最长的字符数目,从第一个字符开始计算。
int comlen(char *p, char *q)
i = 0
while *p && (*p++ == *q++)
i++
return i
由于上面的算法要比较所有可能的子串对,所以时间复杂度比较高,为o(n^2)。我们可以采用“后缀数组“来降低它的时间复杂度。
假设处理的文本的字符总数最多为MAXN,保存在数组c中。
#define MAXN 5000000
char c[MAXN], *a[MAXN];
其中数组a为后缀数组。
初始化代码如下:
while (ch = getchar()) != EOF
a[n] = &c[n]
c[n++] = ch
c[n] = 0
其中a[0]指向整个字符串,a[1]指向从第二个字符开始往后的所有字符,依次类推。如下:
a[0]: banana
a[1]: anana
a[2]: nana
a[3]: ana
a[4]: na
a[5]: a
如果一个长字符串在C中出现两次,它们的后缀不相同。然后按照字母顺序对数组a的字符串进行排序,然后比较相邻的字符串,找出最长的重复子串为ana。
a[0]: a
a[1]: ana
a[2]: anana
a[3]: banana
a[4]: na
a[5]: nana
这里采用qsort对“后缀数组“进行排序。
qsort(a, n, sizeof(char *), pstrcmp);
用下面的代码比较相邻字符串的最大长度:
for i = [0, n)
if comlen(a[i], a[i+1]) > maxlen
maxlen = comlen(a[i], a[i+1])
maxi = i
printf("%.*s\n", maxlen, a[maxi])
完整的代码如下:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int pstrcmp(char **p, char **q)
{ return strcmp(*p, *q); }
int comlen(char *p, char *q)
{ int i = 0;
while (*p && (*p++ == *q++))
i++;
return i;
}
#define M 1
#define MAXN 5000000
char c[MAXN], *a[MAXN];
int main()
{ int i, ch, n = 0, maxi, maxlen = -1;
while ((ch = getchar()) != EOF) {
a[n] = &c[n];
c[n++] = ch;
}
c[n] = 0;
qsort(a, n, sizeof(char *), pstrcmp);
for (i = 0; i < n-M; i++)
if (comlen(a[i], a[i+M]) > maxlen) {
maxlen = comlen(a[i], a[i+M]);
maxi = i;
}
printf("%.*s\n", maxlen, a[maxi]);
return 0;
}