1. 程式人生 > >【程式設計珠璣】第十五章--字串:用字尾陣列查詢最長不重疊的重複子串

【程式設計珠璣】第十五章--字串:用字尾陣列查詢最長不重疊的重複子串

問題:給定一個文字檔案作為輸入,查詢其中最長的重複子字串。例如:“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 2774 Time Limit Exceeded G++ 2094B 2013-11-15 16:33:03

結果是一直超時,可以看出,這個演算法在處理超長字串時的效率並非是那麼高了。因此一定還有更加快速的演算法。

未完待續。。。