1. 程式人生 > >求多個字串的最長公共子串

求多個字串的最長公共子串

最長公共子串

最長公共子串(Longest Common Substring ,簡稱LCS)問題,是指求給定的一組字串長度最大的共有的子串的問題。例如字串”abcb”,”bca”,”acbc”的LCS就是”bc”。

求多串的LCS,顯然窮舉法是極端低效的演算法。改進一些的演算法是用一個串的每個字尾對其他所有串進行部分匹配,用KMP演算法,時間複雜度為O(N*L^2),其中N為字串個數,L為每個串的長度。更優秀的有廣義字尾樹的方法,時間可以達到 O(N*L)。本文介紹一種基於字尾陣列的LCS解法,利用二分查詢技術,時間複雜度可以達到O(N*L*logL)。

最長公共子串問題的字尾陣列解法

關於字尾陣列的構建方法以及Height陣列的性質,本文不再具體介紹,可以參閱IOI國家集訓隊2004年論文《字尾陣列》(許智磊)

和IOI國家集訓隊2009年論文《字尾陣列——處理字串的有力工具》(羅穗騫)

回顧一下字尾陣列,SA[i]表示排名第i的字尾的位置,Height[i]表示字尾SA[i]和SA[i-1]的最長公共字首(Longest Common Prefix,LCP),簡記為Height[i]=LCP(SA[i],SA[i-1])。連續的一段字尾SA[i..j]的最長公共字首,就是H[i-1..j]的最小值,即LCP(SA[i..j])=Min(H[i-1..j])。

求N個串的最長公共子串,可以轉化為求一些字尾的最長公共字首的最大值,這些字尾應分屬於N個串。具體方法如下:

設N個串分別為S1,S2,S3,…,SN,首先建立一個串S,把這N個串用不同的分隔符連線起來。S=S1[P1]S2[P2]S3…SN-1[PN-1]SN,P1,P2,…PN-1應為不同

的N-1個不在字符集中的字元,作為分隔符(後面會解釋為什麼)。

接下來,求出字串S的字尾陣列和Height陣列,可以用倍增演算法,或DC3演算法。

然後二分列舉答案A,假設N個串可以有長度為A的公共字串,並對A的可行性進行驗證。如果驗證A可行,A’(A’<A)也一定可行,嘗試增大A,反之嘗試縮小A。最終可以取得A的最大可行值,就是這N個串的最長公共子串的長度。可以證明,嘗試次數是O(logL)的。

於是問題就集中到了,如何驗證給定的長度A是否為可行解。方法是,找出在Height陣列中找出連續的一段Height[i..j],使得i<=k<=j均滿足Height[k]>=A,並且i-1<=k<=j中,SA[k]分屬於原有N個串S1..SN。如果能找到這樣的一段,那麼A就是可行解,否則A不是可行解。

具體查詢i..j時,可以先從前到後列舉i的位置,如果發現Height[i]>=A,則開始從i向後列舉j的位置,直到找到了Height[j+1]<A,判斷[i..j]這個區間內SA是否分屬於S1..SN。如果滿足,則A為可行解,然後直接返回,否則令i=j+1繼續向後列舉。S中每個字元被訪問了O(1)次,S的長度為N*L+N-1,所以驗證的時間複雜度為O(N*L)。

到這裡,我們就可以理解為什麼分隔符P1..PN-1必須是不同的N-1個不在字符集中的字元了,因為這樣才能保證S的字尾的公共字首不會跨出一個原有串的範圍。

字尾陣列是一種處理字串的強大的資料結構,配合LCP函式與Height陣列的性質,字尾陣列更是如虎添翼。利用字尾陣列,容易地求出了多個串的LCS,而且時空複雜度也相當優秀了。雖然比起字尾樹的解法有所不如,但其簡明的思路和容易程式設計的特點卻在實際的應用中並不輸於後綴樹。

   /* Problem: POI2000 pow
    * Author: Guo Jiabao
    * Time: 2009.4.16 21:00
    * State: Solved
    * Memo: 多串最長公共子串 字尾陣列 二分答案
    */
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
    const int MAXL=10011,MAXN=6;
using namespace std;
struct SuffixArray
{
    struct RadixElement
    {
        int id,k[2];
    }RE[MAXL],RT[MAXL];
    int N,A[MAXL],SA[MAXL],Rank[MAXL],Height[MAXL],C[MAXL];
    void RadixSort()
    {
        int i,y;
        for (y=1;y>=0;y--)
        {
            memset(C,0,sizeof(C));
            for (i=1;i<=N;i++) C[RE[i].k[y]]++;
            for (i=1;i<MAXL;i++) C[i]+=C[i-1];
            for (i=N;i>=1;i--) RT[C[RE[i].k[y]]--]=RE[i];
            for (i=1;i<=N;i++) RE[i]=RT[i];
        }
        for (i=1;i<=N;i++)
        {
            Rank[ RE[i].id ]=Rank[ RE[i-1].id ];
            if (RE[i].k[0]!=RE[i-1].k[0] || RE[i].k[1]!=RE[i-1].k[1])
                Rank[ RE[i].id ]++;
        }
    }
    void CalcSA()
    {
        int i,k;
        RE[0].k[0]=-1;
        for (i=1;i<=N;i++)
            RE[i].id=i,RE[i].k[0]=A[i],RE[i].k[1]=0;
        RadixSort();
        for (k=1;k+1<=N;k*=2)
        {
            for (i=1;i<=N;i++)
                RE[i].id=i,RE[i].k[0]=Rank[i],RE[i].k[1]=i+k<=N?Rank[i+k]:0;
            RadixSort();
        }
        for (i=1;i<=N;i++)
            SA[ Rank[i] ]=i;
    }
    void CalcHeight()
    {
        int i,k,h=0;
        for (i=1;i<=N;i++)
        {
            if (Rank[i]==1)
                h=0;
            else
            {
                k=SA[Rank[i]-1];
                if (--h<0) h=0;
                for (;A[i+h]==A[k+h];h++);
            }
            Height[Rank[i]]=h;
        }
    }
}SA;
int N,Ans,Bel[MAXL];
char S[MAXL];
void init()
{
    int i;
    freopen("pow.in","r",stdin);
    freopen("pow.out","w",stdout);
    scanf("%d",&N);
    SA.N=0;
    for (i=1;i<=N;i++)
    {
        scanf("%s",S);
        for (char *p=S;*p;p++)
        {
            SA.A[++SA.N]=*p-'a'+1;
            Bel[SA.N]=i;
        }
        if (i<N)
            SA.A[++SA.N]=30+i;
    }
}
bool check(int A)
{
    int i,j,k;
    bool ba[MAXN];
    for (i=1;i<=SA.N;i++)
    {
        if (SA.Height[i]>=A)
        {
            for (j=i;SA.Height[j]>=A && j<=SA.N;j++);
            j--;
            memset(ba,0,sizeof(ba));
            for (k=i-1;k<=j;k++)
                ba[Bel[SA.SA[k]]]=true;
            for (k=1;ba[k] && k<=N;k++);
            if (k==N+1)
                return true;
            i=j;
        }
    }
    return false;
}
void solve()
{
    int a,b,m;
    SA.CalcSA();
    SA.CalcHeight();
    a=0;b=SA.N;
    while (a+1<b)
    {
        m=(a+b)/2;
        if (check(m))
            a=m;
        else
            b=m-1;
    }
    if (check(b))
        a=b;
    Ans=a;
}
int main()
{
    init();
    solve();
    printf("%dn",Ans);
    return 0;
}

相關推薦

《程式設計師程式碼面試指南》字串公共

/** * 題目: * 給定兩個字串 str1 和 str2,返回兩個字串的最長公共子串。 *舉例: * str1 = "1AB2345CD",str2 = "12345EF",返回"2345"。 */ /** * 解答: * 經典動態規劃的方法可以做到時間複

字串公共(動態規劃)

code如下: //Longest common sequence, dynamic programming method void FindLCS(char *str1, char *str2) { if(str1 == NULL || str2 == NULL)

LCS兩字串公共

LCS問題就是求兩個字串最長公共子串的問題。解法就是用一個矩陣來記錄兩個字串中所有位置的兩個字元之間的匹配情況,若是匹配則為1,否則為0。然後求出對角線最長的1序列,其對應的位置就是最長匹配子串的位置。 下面是字串21232523311324和字串31212322344

SPOJ 1812 Longest Common Substring II 字尾自動機字串公共

題意: 給若干字串,求它們的最長公共子串的長度。 題解:字尾自動機。 對第一個串建立SAM,並拓撲排序。 用後面的串分別匹配。 對於SAM,每個節點新增兩個值ml,ans; ml代表該節點滿足單一字串時的最大值,匹配完一個字串後重置為0; an

字串處理演算法(六)2字串公共部分

基礎演算法連結快速通道,不斷更新中: 整型陣列處理演算法部分: 整型陣列處理演算法(一)按照正態分佈來排列整型陣列元素 整型陣列處理演算法(二)檔案中有一組整數,要求排序後輸出到另一個檔案中 整型陣列處理演算法(三)把一個數組裡的所有元素,插入到另一個數組的指定位置 整型陣列

poj--3450 KMP字串公共

思路與前面的3080一樣 程式碼如下: #include<iostream> #include<cstdio> #include<cstring> using n

POJ 3450 Corporate Identity(kmp字串公共

http://poj.org/problem?id=3450 #include <stdio.h> #include <string.h> const int max_N=

字串公共

最長公共子串 最長公共子串(Longest Common Substring ,簡稱LCS)問題,是指求給定的一組字串長度最大的共有的子串的問題。例如字串”abcb”,”bca”,”acbc”的LCS就是”bc”。 求多串的LCS,顯然窮舉法是極端低效的演算法。改進一些的演

Blue Jeans POJ - 3080 (字串公共)

題意:       給定n個字串,要你求出這n個字串的最長公共子串,如果存在多個輸出字典序最小的那個. 分析:        KMP+暴力去列舉即可. #include<stdio.h> #incl

公共字串公共(C++)

思路: 1.按兩字串長度,兩層迴圈 2.每一次迴圈找出潛在的公共字串 (比較笨的方法,但比較好用,也很清楚,程式碼如下) #include<iostream> #include<stdio.h> #include<algorithm>

python實現:字串公共

假設s1 = 'abcdef'  ,s2 = 'mcdea',建立一個(len(s1)+1) x (len(s2)+1)的陣列,如下如所示的一個二維陣列,程式碼的操作流程是: # -*- coding

用字尾陣列字串公共

對於兩個字串,不好直接運用字尾陣列,所以我們可以把兩個子串串中間用一個在字串中不會出現的字元連線起 來,比如'$‘,計算字尾陣列,檢查字尾陣列中所有相鄰字尾。分屬於兩個字串的字尾的lcp的最大值就是答案。 因為字串的任何一個子串都是這個字串某個字尾的字首。求A和B 的最長公

利用KMP演算法,順序表儲存的兩字串公共

/*程式說明: * 程式是由順序表實現,功能為查詢兩字串中的最長公共子串; * 兩條字串分別由兩個順序表儲存; * 查詢思路是分解出短的那條字串的全部連續子串(例如abc分解為abc,ab,a,bc,b,c), * 再使用kmp演算法將該全部子串與長的那條字串進行匹配,找

Longest Common Substring II(字尾自動機公共

A string is finite sequence of characters over a non-empty finite set Σ. In this problem, Σ is the set of lowercase letters. Substring, also called factor

公共(1)--hdu1238(字串公共--暴力)

Substrings                                                 Time Limit:1000MS    Memory Limit:3276

兩組字符中的公共(可包含長度相同的公共

String#include <stdio.h>#include <string.h>main(){int i,j,k,n,h,m=0,count=0,count1=0,count2=0,count3=0;char str1[100], str2[100];int str3[100];

字符公共——Java實現

求解 ont ins oid info ++ 題意 短字符串 clas 要求:求兩個字符串的最長公共子串,如“abcdefg”和“adefgwgeweg”的最長公共子串為“defg”(子串必須是連續的) public class Main03{ // 求解兩個字符號的最

[Python]獲取2字串公共

原創文章,歡迎轉載。轉載請註明:轉載自 祥的部落格 原文連結:https://blog.csdn.net/humanking7/article/details/84645055 文章目錄 @[toc]

[OJ-java] 查詢兩字串a,b中的公共

目的 記錄自己做過的有價值的程式碼 題目 如題,即查詢兩個字串stringA和stringB中的最長公共子串。成功返回最大公共子串,不成功返回null。 程式碼 public static String iQueryMaxCommString(Str

如何字串公共

此演算法的時間複雜度我還沒想清楚, 程式碼如下: #include<stdio.h>#include<string.h> char * maxsamesubstring(char *s1,char *s2){ int i,j,len,maxlen,i