天之道

享受编程的乐趣。
posts - 118, comments - 7, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

最长重复子串

Posted on 2012-12-05 17:58 hoshelly 阅读(1113) 评论(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( *(charconst *)p1, *(charconst*)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:智商有限,后面部分讲解理解不了)

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理