【程式設計珠璣】第十五章--字串:用字尾陣列查詢最長不重疊的重複子串
阿新 • • 發佈:2019-01-25
問題:給定一個文字檔案作為輸入,查詢其中最長的重複子字串。例如:“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 |
結果是一直超時,可以看出,這個演算法在處理超長字串時的效率並非是那麼高了。因此一定還有更加快速的演算法。
未完待續。。。