計算字符串的相似度(編輯距離)


問題

許多程序會大量使用字符串。對於不同的字符串,我們希望能夠有辦法判斷其相似程度。我們定義了一套操作方法來把兩個不相同的字符串變得相同,具體的操作方法為:
1.修改一個字符(如把“a”替換為“b”)。
2.增加一個字符(如把“abdd”變為“aebdd”)。
3.刪除一個字符(如把“travelling”變為“traveling”)。
比如,對於“abcdefg”和“abcdef”兩個字符串來說,我們認為可以通過增加/減少一個“g“的方式來達到目的。上面的兩種方案,都僅需要一次操作。把這個操作所需要的次數定義為兩個字符串的距離,給定任意兩個字符串,你是否能寫出一個算法來計算出它們的距離?

分析與解法

不難看出,兩個字符串的距離肯定不超過它們的長度之和(我們可以通過刪除操作把兩個串都轉化為空串)。雖然這個結論對結果沒有幫助,但至少可以知道,任意兩個字符串的距離都是有限的。
我們還是應該集中考慮如何才能把這個問題轉化成規模較小的同樣的問題。如果有兩個串A=xabcdae和B=xfdfa,它們的第一個字符是相同的,只要計算A[2,…,7]=abcdae和B[2,…,5]=fdfa的距離就可以了。但是如果兩個串的第一個字符不相同,那么可以進行如下的操作(lenA和lenB分別是A串和B串的長度):
1.刪除A串的第一個字符,然后計算A[2,…,lenA]和B[1,…,lenB]的距離。
2.刪除B串的第一個字符,然后計算A[1,…,lenA]和B[2,…,lenB]的距離。
3.修改A串的第一個字符為B串的第一個字符,然后計算A[2,…,lenA]和B[2,…,lenB]的距離。
4.修改B串的第一個字符為A串的第一個字符,然后計算A[2,…,lenA]和B[2,…,lenB]的距離。
5.增加B串的第一個字符到A串的第一個字符之前,然后計算A[1,…,lenA]和B[2,…,lenB]的距離。
6.增加A串的第一個字符到B串的第一個字符之前,然后計算A[2,…,lenA]和B[1,…,lenB]的距離。

在這個題目中,我們並不在乎兩個字符串變得相等之后的字符串是怎樣的。所以,可以將上面6個操作合並為:
1.一步操作之后,再將A[2,…,lenA]和B[1,…,lenB]變成相同字符串。
2.一步操作之后,再將A[1,…,lenA]和B[2,…,lenB]變成相同字符串。
3.一步操作之后,再將A[2,…,lenA]和B[2,…,lenB]變成相同字符串。

這樣,很快就可以完成一個遞歸程序。

代碼實現:

int calStringDis(string strA, int pABegin,int pAEnd,string strB, int pBBegin,int pBEnd)  
{    
    if (pABegin > pAEnd)    
    {    
        if (pBBegin > pBEnd)    
            return 0;     
        else    
            return pBEnd - pBBegin + 1;    
    }    
    if (pBBegin > pBEnd)    
    {    
        if(pABegin > pAEnd)    
            return 0;    
        else    
            return pAEnd - pABegin + 1;    
    }    
    if (strA[pABegin] == strB[pBBegin])    
    {    
        return calStringDis(strA,pABegin+1,pAEnd,strB,pBBegin+1,pBEnd);    
    }    
    else    
    {    
        int t1 = calStringDis(strA,pABegin+1,pAEnd,strB,pBBegin+2,pBEnd);    
        int t2 = calStringDis(strA,pABegin+2,pAEnd,strB,pBBegin+1,pBEnd);    
        int t3 = calStringDis(strA,pABegin+2,pAEnd,strB,pBBegin+2,pBEnd);    
    
        return minValue(t1,t2,t3)+1;    
    }    
}  

以上解法來自《編程之美》,有什么地方需要改進的呢?問題在於:在遞歸的過程中,有些數據被重復計算了。

很經典的可使用動態規划方法解決的題目,和計算兩字符串的最長公共子序列相似。

設Ai為字符串A(a1a2a3 … am)的前i個字符(即為a1,a2,a3 … ai)
設Bj為字符串B(b1b2b3 … bn)的前j個字符(即為b1,b2,b3 … bj)

設 L(i,j)為使兩個字符串和Ai和Bj相等的最小操作次數。
當ai==bj時 顯然 L(i,j) = L(i-1,j-1)
當ai!=bj時 

  若將它們修改為相等,則對兩個字符串至少還要操作L(i-1,j-1)次
   若刪除ai或在bj后添加ai,則對兩個字符串至少還要操作L(i-1,j)次
   若刪除bj或在ai后添加bj,則對兩個字符串至少還要操作L(i,j-1)次
   此時L(i,j) = min( L(i-1,j-1), L(i-1,j), L(i,j-1) ) + 1 

顯然,L(i,0)=i,L(0,j)=j, 再利用上述的遞推公式,可以直接計算出L(i,j)值。

代碼實現:

int minValue(int a, int b, int c)
{
    int t = a <= b ? a:b;
    return t <= c ? t:c;
}

int calculateStringDistance(string strA, string strB)
{
    int lenA = (int)strA.length()+1;
    int lenB = (int)strB.length()+1;

    int **c = new int*[lenA];
    for(int i = 0; i < lenA; i++)
        c[i] = new int[lenB];

    for(int i = 0; i < lenA; i++) c[i][0] = i;
    for(int j = 0; j < lenB; j++) c[0][j] = j;
    c[0][0] = 0;
    for(int i = 1; i < lenA; i++)
    {
        for(int j = 1; j < lenB; j++)
        {
            if(strB[j-1] == strA[i-1])
                c[i][j] = c[i-1][j-1];
            else
                c[i][j] = minValue(c[i][j-1], c[i-1][j], c[i-1][j-1]) + 1;
        }
    }

    int ret =  c[lenA-1][lenB-1];

    for(int i = 0; i < lenA; i++)
        delete [] c[i];
    delete []c;

    return ret;
}

 

 

 

 


免責聲明!

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



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