zoukankan      html  css  js  c++  java
  • 【转】后缀数组求最长重复子串

    原文地址:http://blog.csdn.net/hackbuteer1/article/details/7968623

    问题描述
    给定一个字符串,求出其最长重复子串
    例如:abcdabcd
    最长重复子串是 abcd,最长重复子串可以重叠
    例如:abcdabcda,这时最长重复子串是 abcda,中间的 a 是被重叠的。

    直观的解法是,首先检测长度为 n - 1 的字符串情况,如果不存在重复则检测 n - 2, 一直递减下去,直到 1 。
    这种方法的时间复杂度是 O(N * N * N),其中包括三部分,长度纬度、根据长度检测的字符串数目、字符串检测。

    改进的方法是利用后缀数组
    后缀数组是一种数据结构,对一个字符串生成相应的后缀数组后,然后再排序,排完序依次检测相邻的两个字符串的开头公共部分。
    这样的时间复杂度为:生成后缀数组 O(N),排序 O(NlogN*N) 最后面的 N 是因为字符串比较也是 O(N)
    依次检测相邻的两个字符串 O(N * N),总的时间复杂度是 O(N^2*logN),优于第一种方法的 O(N^3)


          对于类似从给定的文本中,查找其中最长的重复子字符串的问题,可以采用“后缀数组”来高效地完成此任务。后缀数组使用文本本身和n个附加指针(与文本数组相应的指针数组)来表示输入文本中的n个字符的每个子字符串。
        首先,如果输入字符串存储在c[0..n-1]中,那么就可以使用类似于下面的代码比较每对子字符串:

     1 int main(void)
     2 {
     3     int i , j , thislen , maxlen = -1;
     4     ......
     5     ......
     6     ......
     7     for(i = 0 ; i < n ; ++i )
     8     {
     9         for(j = i+1 ; j < n ; ++j )
    10         {
    11             if((thislen = comlen(&c[i] , &c[j])) > maxlen)
    12             {
    13                 maxlen = thislen;
    14                 maxi = i;
    15                 maxj = j;
    16             }
    17         }
    18     }
    19     ......
    20     ......
    21     ......
    22     return 0;
    23 }

    当作为comlen函数参数的两个字符串长度相等时,该函数便返回这个长度值,从第一个字符开始:

    1 int comlen( char *p, char *q )
    2 {
    3     int i = 0;
    4     while( *p && (*p++ == *q++) )
    5         ++i;
    6     return i;
    7 }

        由于该算法查看所有的字符串对,所以它的时间和n的平方成正比。下面便是使用“后缀数组”的解决办法。
        如果程序至多可以处理MAXN个字符,这些字符被存储在数组c中:

    #define MAXCHAR 5000 //最长处理5000个字符
    
    char c[MAXCHAR], *a[MAXCHAR];

    在读取输入时,首先初始化a,这样,每个元素就都指向输入字符串中的相应字符:

    n = 0;
    while( (ch=getchar())!='
    ' )
    {
         a[n] = &c[n];
         c[n++] = ch;
    }
    c[n]='';     // 将数组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 ; i < n-1 ; ++i )
    {
            temp=comlen( a[i], a[i+1] );
            if( temp>maxlen )
        {
                  maxlen=temp;
                  maxi=i;
            }
    }
    printf("%.*s
    ",maxlen, a[maxi]);

    完整的实现代码如下:

    #include <iostream>
    using namespace std;
    
    #define MAXCHAR 5000 //最长处理5000个字符
    
    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(void)
    {
        char ch;
        int  n=0;
        int  i, temp;
        int  maxlen=0, maxi=0;
        printf("Please input your string:
    ");
    
        n = 0;
        while( (ch=getchar())!='
    ' )
        {
            a[n] = &c[n];
            c[n++] = ch;
        }
        c[n]='';     // 将数组c中的最后一个元素设为空字符,以终止所有字符串
    
        qsort( a, n, sizeof(char*), pstrcmp );
        for(i = 0 ; i < n-1 ; ++i )
        {
            temp=comlen( a[i], a[i+1] );
            if( temp>maxlen )
            {
                maxlen=temp;
                maxi=i;
            }
        }
        printf("%.*s
    ",maxlen, a[maxi]);
        
        return 0;
    }
  • 相关阅读:
    分层图最短路(DP思想) BZOJ2662 [BeiJing wc2012]冻结
    动态规划 BZOJ1925 地精部落
    线性DP SPOJ Mobile Service
    线性DP codevs2185 最长公共上升子序列
    数位DP POJ3208 Apocalypse Someday
    线性DP POJ3666 Making the Grade
    杨氏矩阵 线性DP? POJ2279 Mr.Young's Picture Permutations
    tarjan强连通分量 洛谷P1262 间谍网络
    树链剖分 BZOJ3589 动态树
    二分图 BZOJ4554 [Tjoi2016&Heoi2016]游戏
  • 原文地址:https://www.cnblogs.com/xmuliushuo/p/3327926.html
Copyright © 2011-2022 走看看