Posted on 2012-12-05 17:58
hoshelly 阅读(1127)
评论(0) 编辑 收藏 引用 所属分类:
Programming
描述
对于一个字符串S1,其中S2是他的一个子串(长度严格小于S1长度),如果S2在S1中出现次数超过1次,那么S2就是一个重复子串,现在的要求是给定S1,请求出他的最长重复子串;
如果有多个长度一样的最长子串,请输入字典序最小那个串;
比如bbbaaaccc
那么最长子串就是aa
输入
第一行包含一个整数T,表示有T组数据
对于每组数据包含一行,该行有一个字符串,长度小于10,000
输出
对于每组数据请输出他的最长重复子串,保证每组数据都有;
样例输入
2
abacabac
abacabbac
样例输出
abac
bac
代码测试通过(普通版):
#include<stdio.h>
#include<string.h>
#define N 10000
int main()
{
char a[N];
int i,j,n,t,p,max,t1;
scanf("%d",&t1);
while(t1--)
{
max = 0;
scanf("%s",a);
n=strlen(a);
for(i=0;i<n;i++)
{
for(j=i+1;j<n;j++)
{
t=0;
while(a[i+t]==a[j+t]&&(j+t)<n)
t++;
if(t>max)
{
max=t;
p=i;
}
else if(t == max) //如果有长度一样的最长重复子串,那么比较它们的字典序
{
if(a[i]<a[p])
{
max = t;
p = i;
}
}
}
}
for(i=p;i<p+max;i++)
printf("%c",a[i]);
printf("\n");
}
return 0;
}
普通算法效率较低,为O(n²)。
第二种方法是用后缀数组实现。转自:http://hi.baidu.com/qwertlooker/item/44f3fe52ad772cdbd58bacfd
如果程序至多可以处理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 //将数组c中的最后一个元素设为空字符,以终止所有字符串
这样,元素a[0]指向整个字符串,下一个元素指向以第二个字符开始的数组的后缀,等等。如若输入字符串为"banana",该数组将表示这些后缀:
a[0]:banana
a[1]:anana
a[2]:nana
a[3]:ana
a[4]:na
a[5]:a
由于数组a中的指针分别指向字符串中的每个后缀,所以将数组a命名为"后缀数组"
第二,对后缀数组进行快速排序,以将后缀相近的(变位词)子串集中在一起
qsort(a, n, sizeof(char*), pstrcmp)后
a[0]:a
a[1]:ana
a[2]:anana
a[3]:banana
a[4]:na
a[5]:nana
第三,使用以下comlen函数对数组进行扫描比较邻接元素,以找出最长重复的字符串:
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])
由于少了内层循环,只是多了一次排序,因此该算法的运行时间为O(n logn). (nlogn比n大,取nlogn)
实现代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXCHAR 10000 //最长处理10000个字符
char c[MAXCHAR], *a[MAXCHAR];
int comlen( char *p, char *q ){ //计算最长重复子串的长度
int i = 0;
while( *p && (*p++ == *q++) )
++i;
return i;
}
int pstrcmp( const void *p1, const void *p2 ){
return strcmp( *(char* const *)p1, *(char* const*)p2 );
}
int main( ){
int t;
char ch;
int i, temp;
scanf("%d\n",&t);
while(t--)
{
int n=0;
int maxlen=0, maxi=0;
while( (ch=getchar())!='\n' ){
a[n]=&c[n];
c[n++]=ch;
}
c[n]='\0';
qsort( a, n, sizeof(char*), pstrcmp ); //快速排序对后缀数组进行排序,以使后缀相同的子串集中在一起,
//以便接下来comlen函数对这些子串进行计算其最长重复子串
for(i=0; i<n-1; ++i ){
temp=comlen( a[i], a[i+1] );
if( temp>maxlen )
{
maxlen=temp;
maxi=i;
}
}
printf("%.*s\n",maxlen, a[maxi]); //输出最长重复子串
}
return 0;
}
第三种方法似乎可以用后缀树实现,效率可以提高到O(n),具体的后缀树讲解可以参照这篇文章:
http://blog.csdn.net/v_july_v/article/details/6897097(PS:智商有限,后面部分讲解理解不了)