【编程珠玑】第十五章--字符串:用后缀数组查找最长不重叠的重复子串

问题:给定一个文本文件作为输入,查找其中最长的重复子字符串。例如:“Ask not what your country can do for you, but what you can do for your country”中最长的重复字符串是“can do for you”,第二长的是“your country”。

我们使用一个叫做“后缀数组”的简单数据结构。它是一个字符指针数组,记为a。读取输入时,我们对a进行初始化,使得每个元素指向输入字符串中的相应字符:

const int MAXN = 5000000;
char c[MAXN],*a[MAXN];

gets(c);
while(c[n]!='\0')
{
	a[n] = &c[n];
	n++;
}

元素a[0]指向整个字符串,下一个元素指向从第二个字符串开始的数组后缀,等等。对于输入字符串“banana”,该数组能够表示下面这些后缀:

a[0]:banana
a[1]:anana
a[2]:nana
a[3]:ana
a[4]:na
a[5]:a
数组a中指针所指的对象包含了字符串的每一个后缀,因此称a为“后缀数组”。

如果某个长字符串在数组c中出现两次,那么它将出现在不同的后缀中,因此我们对数组排序以寻找相同的后缀。“banana”数组排序为:

a[0]:a
a[1]:ana
a[2]:anana
a[3]:banana
a[4]:na
a[5]:nana
然后我们就可以扫描数组,通过比较相邻元素来找出最长的重复字符串,本例为“ana”

可以用qsort函数对后缀数组进行排序:

int pstrcmp(const void * a , const void *b)
{
	const char *a_ = *(const char **)a;
	const char *b_ = *(const char **)b;
	return strcmp( a_ , b_ ) ;
}
qsort(a,n,sizeof(char *),pstrcmp);

使用comlen函数统计两个相邻单词共有的字母数:

int maxlen = 0 , maxi = 0;
int templen = 0;
for(int i=0;i<n-1;i++)
{
	templen = comlen(a[i],a[i+1]);
	if( templen>maxlen )
	{
		maxlen = templen ;
		maxi = i;
	}
}


完整的程序为:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<algorithm>
#include<iomanip>

using namespace std;

const int MAXN = 5000000;
char c[MAXN],*a[MAXN];  //其中a为后缀数组 

int pstrcmp(const void * a , const void *b)
{
	const char *a_ = *(const char **)a;
	const char *b_ = *(const char **)b;
	return strcmp( a_ , b_ ) ;
}

int comlen(char* p ,char* q)
{
	int i = 0;
	while( *p && (*p++ == *q++) )
	{
		i++;
	}
	return i;
}

int main()
{
	int n = 0;
	
	/*
     * 获取字符串c,并且给后缀数组a赋值 
     */ 
	gets(c);
	while(c[n]!='\0')
	{
		a[n] = &c[n];
		n++;
	}
	
	c[n] = 0;

    //对后缀数组进行排序,实际上就是对指针数组a进行排序 
	qsort(a,n,sizeof(char *),pstrcmp);

	for(int i=0;i<n;i++)
	{
		cout<<a[i]<<endl;
	}

    //扫描数组,使用comlen函数统计两个相邻单词共有的字母数 
	int maxlen = 0 , maxi = 0;
	int templen = 0;
	for(int i=0;i<n-1;i++)
	{
		templen = comlen(a[i],a[i+1]);
		if( templen>maxlen )
		{
			maxlen = templen ;
			maxi = i;
		}
	}
	
	printf("\n%.*s\n",maxlen,a[maxi]); //printf语句使用"*" 精度输入字符串中的maxlen个字符 
	cout.write(a[maxi],maxlen)<<endl;  //和上一句起到相同的效果 
    system("pause");
	return 0;

}

尝试用这种方法去解决POJ的2774题( http://poj.org/problem?id=2774):

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<algorithm>
#include<iomanip>
#include<stdio.h>

using namespace std;

const int MAXN = 100010;
char source[MAXN*2],append[MAXN];
char *a[MAXN*2];

inline int pstrcmp(const void * a , const void *b)
{
	const char *a_ = *(const char **)a;
	const char *b_ = *(const char **)b;
	return strcmp( a_ , b_ ) ;
}

inline int comlen(char* p ,char* q,int maxlen)
{
    if(strncmp(p,q,maxlen) != 0)
    {
        return 0;
    }
	int i = maxlen;
	p += maxlen;
	q += maxlen; 
	while( *p && *q && (*p++ == *q++) )
	{
		i++;
	}
	return i;
}

int main()
{
	int i;
	scanf("%s",source);
	scanf("%s",append);

	source[strlen(source)] = '$';
	strcat(source,append);
//	cout<<source<<endl;

	for(i=0;i<strlen(source);i++)
	{
		a[i] = &source[i];
	}

//	for(i=0;i<strlen(source);i++)
//	{
//		cout<<a[i]<<endl;
//	}

	qsort(a,strlen(source),sizeof(char *),pstrcmp);

//	for(int i=0;i<strlen(source);i++)
//	{
//		cout<<a[i]<<endl;
//	}

	int maxlen = 0 , maxi = 0;
	int templen = 0;
	int append_len = strlen(append);

	for(int i=0;i<strlen(source)-1;i++)
	{
		if( ( strlen(a[i])<=append_len&&strlen(a[i+1])>append_len+1) || ( strlen(a[i])>append_len+1&&strlen(a[i+1])<=append_len) )
		{
            if( strlen(a[i])>maxlen || strlen(a[i+1])>maxlen ) 
            {
				templen = comlen(a[i],a[i+1],maxlen);
				if( templen>maxlen )
				{
					maxlen = templen ;
					maxi = i;
				}
            }
            else
            {
                continue;    
            }
		}
	}
		
	//printf("\n%.*s\n",maxlen,a[maxi]);
	//cout.write(a[maxi],maxlen)<<endl;

	cout<<maxlen<<endl;
	system("pause");
	return 0;

}


12302529 niuliguo 2774 Time Limit Exceeded     G++ 2094B 2013-11-15 16:33:03


结果是一直超时,可以看出,这个算法在处理超长字符串时的效率并非是那么高了。因此一定还有更加快速的算法。

未完待续。。。



注明出处:http://blog.csdn.net/lavorange/article/details/16857419








你可能感兴趣的:(C++,poj,后缀数组,重复最长子字符串)