求兩個字符串的最長公共子串


From:http://blog.csdn.net/steven30832/article/details/8260189

動態規划有一個經典問題是最長公共子序列,但是這里的子序列不要求連續,如果要求序列是連續的,我們叫公共子串,那應該如何得到這個串呢?

 

最簡單的方法就是依次比較,以某個串為母串,然后生成另一個串的所有長度的子串,依次去母串中比較查找,這里可以采用先從最長的子串開始,減少比較次數,但是復雜度依然很高!

 

然后重新看一下這個問題,我們建立一個比較矩陣來比較兩個字符串str1和str2

 

定義 lcs(i,j) ,當str1[i] = str2[j]時lcs(i,j)=1,否則等於0。

example:

str1 = "bab"

str2 = "caba"

 

建立矩陣

--b  a  b

c 0  0  0

a 0  1  0

b 1  0  1

a 0  1  0

 

連續i子串的特點就是如果str1[i]和str2[j]是屬於某公共子串的最后一個字符,那么一定有str1[i]=str2[j] && str1[i-1] = str2[j-1],從矩陣中直觀的看,就是由“1”構成的“斜線”代表的序列都是公共子串,那么最長公共子串肯定就是斜線“1”最長的那個串。

那么現在問題就可以轉化了,只要構造出如上的一個矩陣,用n^2的時間就可以得到矩陣,然后再到矩陣中去尋找最長的那個“1”構成的斜線就可以了!那么,現在又有了新的問題?如何快速的找到那個“1”構成的最長斜線呢?

采用DP的思想,如果str1[i] = str2[j],那么此處的包含str1[i] 和 str2[j]公共子串的長度必然是包含str1[i-1]和str2[j-1]的公共子串的長度加1,那么現在我們可以重新定義lcs(i,j),即是lcs(i,j) = lcs(i-1,j-1) + 1,反之,lcs(i,j) = 0。那么上面的矩陣就變成了如下的樣子:

 

--b  a  b

c 0  0  0

a 0  1  0

b 1  0  2

a 0  2  0

現在問題又變簡單了,只需要花n^2的時間構造這樣一個矩陣,再花n^2的時間去找到矩陣中最大的那個值,對應的就是最長公共子串的長度,而最大值對應的位置對應的字符,就是最長公共子串的最末字符。

 

算法還可以改進,我們可以將查找最大長度和對應字符的工作放在構造矩陣的過程中完成,一邊構造一邊記錄當前的最大長度和對應位置,這樣就節省了n^2的查找時間。

空間上也可以做改進,如果按照如上的方式構造,我們發現,當矩陣的第i+1行的值計算完成后,第i行的值就沒有用了,即便是最長的長度出現在第i行,我們也已經用變量記錄下來了。因此,可以將矩陣縮減成一個向量來處理,向量的當前值對應第i行,向量的下一個循環后的值對應第i+1行。

//    最長公共子串(連續)    LCS
//    Deng Chao
//     2012.12.4

#include <iostream>
#include <cstring>
using namespace std;


//    查找公共子串
//    lcs記錄公共子串
//    return    公共子串長度
int LCS(const char *str1  , int len1 , const char *str2 , int len2 , char *&lcs)
{
    if(NULL == str1 || NULL == str2)
    {
        return -1;    //空參數
    }
    
    //    壓縮后的最長子串記錄向量
    int *c = new int[len2+1];
    for(int i = 0 ; i < len2 ; ++i)
    {
        c[i] = 0;
    }
    int max_len = 0;    //匹配的長度
    int pos = 0;        //在str2上的匹配最末位置
    for(int i = 0 ; i < len1 ; ++i)
    {
        for(int j = len2 ; j > 0 ; --j)    //更新時從后往前遍歷
        { 
            if(str1[i] == str2[j-1])
            {
                c[j] = c[j-1] + 1;
                if(c[j] > max_len)
                {
                    max_len = c[j];
                    pos = j-1;
                }
            }
            else
            {
                c[j] = 0;
            }
        }
    }
    
    if(0 == max_len)
    {
                delete [] c;
        return 0;
    }
    
    //    得到公共子串
    lcs = new char[max_len];
    for(int i = 0 ; i < max_len ; ++i)
    {
        lcs[i] = str2[pos-max_len+1+i];
    }
    cout<<"pos = "<<pos<<endl;
    delete [] c;
        delete [] lcs;
    return max_len;
    
}

//    test
int main()
{
    const char *str1 = "abacaba";
    const char *str2 = "caba";
    int len1 = strlen(str1);
    int len2 = strlen(str2);
    
    char *lcs;
    
    int len = LCS(str1 , len1 , str2 , len2 , lcs);
    cout<<"max length = "<<len<<endl;
    for(int i = 0 ; i < len ; ++i)
    {
        cout<<lcs[i]<<" ";
    }
}

注意:

 

在更新最長子串長度的向量時,是從后往前遍歷更新的,為什么???從前往后遍歷更新會出現什么情況?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM