后缀数组的应用:最长重复(or回文)子串

来源:互联网 发布:苹果微信加附近人软件 编辑:程序博客网 时间:2024/05/26 09:55

Link:http://blog.chinaunix.net/uid-26448049-id-3054641.html


问题:比如 weksabcdjklabcdll 最长重复字串为"abcd"

解析:
//blog.csdn.net/zdl1016/article/details/4731875
后缀数组举例
    如下目标字符串: bananas 其长度为7,则后缀数组的长度为7,分别是以b开头的字串(长度为7),以a开头的字串(长度为6),以n开头的字串(长度为5)。。。最后一个是以s开头的字串(长度为1)。
    后缀[0] bananas
    后缀[1] ananas
    后缀[2] nanas
    后缀[3] anas
    后缀[4] nas
    后缀[5] as
    后缀[6] s
回到正题,查找一段文字中最长的重复字串。(注意:这不同于算法设计课中常讲的两个字符串的最长公共子序列问题(LCS),LCS问题的最长公共字串可以不是连续的)
    最朴素的算法是,让后缀数组之间两两比较,找出最长的公共字串(注意,这里的最长的公共字串必须是以首字符参与匹配的,如果首字母都不匹配,那么长度为0,eg后缀[0]和后缀[1]之间的首字母不匹配,则两者的最长公共字串长度为0.。),但是时间复杂度为O(n^2).

该思想基于以下两个信息:
  1)如果存在一个最长的重复字串,那么两个字串均是来自文本串不同的后缀,但这两个后缀有相同的前缀!(这个前缀也就是重复字串了)
  2)既然最终结果的后缀肯定拥有相同的前缀,那么我就没有必要让全部后缀之间两两比较,而仅仅比较具有相同的前缀(首字母)的后缀即可!这可以大大的减少比较的次数,提高效率。
所以,算法的流程是,先将后缀数组字母排序(其实就是对字符数组排序),然后顺次比较(避免了两两比较)即可。
    后缀[0] ananas
    后缀[1] anas
    后缀[2] as
    后缀[3] bananas
    后缀[4] nanas
    后缀[5] nas
    后缀[6] s
后缀[2]和后缀[3]不用比较(a!=b)
……
后缀[5]和后缀[6]不用比较(n!=s)
So,最终的比较结果是 后缀[1]和后缀[2] 之间存在最长公共字串"ana"在后缀[0]里。
  1. #include <stdio.h>
  2. #include <stdlib.h> //qsort
  3. #include <string.h>

  4. #define MAXCHAR 1000 //最长处理1000个字符

  5. char c[MAXCHAR], *a[MAXCHAR];

  6. int comlen( char *p, char *){
  7.     int i = 0;
  8.     while( *&& (*p++ == *q++) )
  9.         ++i;
  10.     return i;
  11. }

  12. int pstrcmp( const void *p1, const void *p2 ){
  13.     return strcmp( *(char* const *)p1, *(char* const*)p2 );
  14. }

  15. /** 
  16.  * find Longest Repeat String by suffix array
  17.  * "weksabcdjklabcdgg" 最长重复字串为"abcd"
  18.  */
  19. char * LRS_sufarr()
  20. {
  21.     char ch;
  22.     int n=0;
  23.     int i, temp;
  24.     int maxlen=0, maxi=0;
  25.     printf("Please input your string:\n");
  26.     while( (ch=getchar())!='\n' )
  27.     {
  28.         // a是字符指针数组 *a[i],用来保存后缀数组
  29.         a[n]=&c[n]; 
  30.         c[n++]=ch;
  31.     }
  32.     c[n]='\0';
  33.     /* 打印后缀数组,不是必须的。*/
  34.     for(= 0; i < n; ++i)
  35.         printf("%d: %s\n", i+1, a[i]);    
  36.     
  37.     qsort( a, n, sizeof(char*), pstrcmp );
  38.     //qsort后,n个后缀数组[i..n-1]就是排好序的了.

  39.     printf("------ after sort------\n");
  40.     for(= 0; i < n; ++i)
  41.         printf("%d: %s\n", i, a[i]);
  42.     
  43.     //在后缀数组中,找出最长重复前缀的,即是最长重复子串
  44.     for(i=0; i < n-1; ++){
  45.         if( *(a[i]) == *(a[i+1]) )
  46.         {
  47.             temp = comlen( a[i], a[i+1] );
  48.             if( temp > maxlen ){
  49.                 maxlen = temp;
  50.                 maxi = i;
  51.             }
  52.         }
  53.     }
  54.     //printf("\nSelect first %d from:%s\n", maxlen, a[maxi]);
  55.     *(a[maxi] + maxlen) = '\0';
  56.     printf("max repeat str: %s\nwith length of %d.\n", a[maxi], maxlen);

  57.     return a[maxi];
  58. }
后缀数组的应用:
 
上面的代码添加一个height数组,
    temp = comlen( a[i], a[i+1] ); 写成
    height[i] = comlen( a[i], a[i+1] ); //0<=i<len-1;
    
例 1 :最长公共前缀
给定一个字符串,询问某两个后缀的最长公共前缀。   
    直接套用,ret = min(height[i])    k<i<=j
这个在后缀树里有重要意义。
 
例 2 :可重叠最长重复子串
给定一个字符串,求最长重复子串,这两个子串可以重叠。   
// ret = max(height[i])  0<=i<len< font="" style="word-wrap: break-word;">
 
例 3 :最长回文子串
将整个字符串反过来写在原字符串后面,中间用一个特殊的字符隔开。这样就把问题变为了
求这个新的字符串的某两个后缀的最长公共前缀。
eg:abcbaebf  ---->  abcbaebf#fbeabcba

0 0
原创粉丝点击