问题:比如 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]里。
- #include <stdio.h>
- #include <stdlib.h> //qsort
- #include <string.h>
- #define MAXCHAR 1000 //最长处理1000个字符
- 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 );
- }
- /**
- * find Longest Repeat String by suffix array
- * "weksabcdjklabcdgg" 最长重复字串为"abcd"
- */
- char * LRS_sufarr()
- {
- char ch;
- int n=0;
- int i, temp;
- int maxlen=0, maxi=0;
- printf("Please input your string:\n");
- while( (ch=getchar())!='\n' )
- {
- // a是字符指针数组 *a[i],用来保存后缀数组
- a[n]=&c[n];
- c[n++]=ch;
- }
- c[n]='\0';
- /* 打印后缀数组,不是必须的。*/
- for(i = 0; i < n; ++i)
- printf("%d: %s\n", i+1, a[i]);
-
- qsort( a, n, sizeof(char*), pstrcmp );
- //qsort后,n个后缀数组[i..n-1]就是排好序的了.
- printf("------ after sort------\n");
- for(i = 0; i < n; ++i)
- printf("%d: %s\n", i, a[i]);
-
- //在后缀数组中,找出最长重复前缀的,即是最长重复子串
- for(i=0; i < n-1; ++i ){
- if( *(a[i]) == *(a[i+1]) )
- {
- temp = comlen( a[i], a[i+1] );
- if( temp > maxlen ){
- maxlen = temp;
- maxi = i;
- }
- }
- }
- //printf("\nSelect first %d from:%s\n", maxlen, a[maxi]);
- *(a[maxi] + maxlen) = '\0';
- printf("max repeat str: %s\nwith length of %d.\n", a[maxi], maxlen);
- return a[maxi];
- }
后缀数组的应用:
上面的代码添加一个height数组,
temp = comlen( a[i], a[i+1] ); 写成
height[i] = comlen( a[i], a[i+1] ); //0<=i
例 1 :最长公共前缀
给定一个字符串,询问某两个后缀的最长公共前缀。
直接套用,ret = min(height[i]) k这个在后缀树里有重要意义。
例 2 :可重叠最长重复子串
给定一个字符串,求最长重复子串,这两个子串可以重叠。
// ret = max(height[i]) 0<=i
例 3 :最长回文子串
将整个字符串反过来写在原字符串后面,中间用一个特殊的字符隔开。这样就把问题变为了
求这个新的字符串的某两个后缀的最长公共前缀。
eg:abcbaebf ----> abcbaebf#fbeabcba
阅读(5514) | 评论(0) | 转发(0) |