1. 程式人生 > >最小編輯距離問題(Edition Distance)

最小編輯距離問題(Edition Distance)

注:這篇部落格討論的演算法是怎樣求解兩個字串的最小編輯距離,其目的是為了下一篇的虛擬DOM,來做一個預備工作,這裡主要討論的用LevenshteinDistance,主要通過的是動態規劃。

什麼是最小編輯距離:

給定一個長度為m和n的兩個字串,設有以下幾種操作:替換(R),插入(I)和刪除(D)且都是相同的操作。尋找到轉換一個字串插入到另一個需要修改的最小(操作)數量。這個數量就可以被視為最小編輯距離。如:acd與ace的EditionDistance距離為1,abc與cab的距離為1。

遞迴實現:

我們來討論一下情況:
A[i]B[i]相等時,我們只需要比較其A[i+1...l

en]B[i+1...len]
A[i]B[i]不等時,我們可以做如下操作:
1. 在A[i+1]增加一個字元,從而比較A[i...0]B[i1...0]
2. 在B[i+1]增加一個字元,從而比較B[i...0]A[i1...0]
3. 更改A[i]或者B[i]處字元,從而比較A[i1...0]B[i1...0]
4. 刪除A[i]處字元,從而比較A[i1...0]B[i...0]
5. 刪除B[i]處字元,從而比較B[i1...0]A[i...0]
情況搞清楚了,程式碼我們非常快的就能寫出:

#include <iostream>
#include <cmath>
using namespace std; inline int getMin (int a, int b, int c) { return min(min(a, b), c); } int calcDistance(char* A, char* B, int m, int n) { if (m == 0 && n == 0) return 0; if (m == 0) return n; if (n == 0) return m; int caseA = calcDistance(A, B, m - 1
, n) + 1; int caseB = calcDistance(A, B, m, n - 1) + 1; int caseC = calcDistance(A, B, m - 1, n - 1) + (A[m] != B[n]); return getMin(caseA, caseB, caseC); } int main () { char s1[20], s2[20]; cin>>s1>>s2; cout<<calcDistance(s1, s2, strlen(s1), strlen(s2))<<endl; return 0; }

但是這種方法的缺點在哪裡呢,就像求解斐波那契數列那樣,遞迴造成了重複計算(如:我們可以在不同的遞迴函式中呼叫已經呼叫過的函式),我們再來看看最小編輯距離問題具有重疊子問題,最優子結構,所以我們可以用動態規劃來進行求解,這樣就避免了重複計算的問題。

利用動態規劃:

動態規劃的方式,我們只是對遞迴進行一下變形,也就是對我們已經求得的值進行儲存,首先我們要初始化一張表出來,也就是一個二維陣列,N[A.length + 1][B.length + 1]
形成表如圖:(NA的長度,MB的長度)

0N...00M00
我們來總結一下我們上面所述幾種情況的表現,可以分為4類:
1. A或者B字元相等,N[i,j]等同於N[i,j]
2. 更改A或者B字元,N[i,j]等同於N[i1,j1]
3. 刪除A字元或者增加B字元,N[i,j]等同於N[i1][j]+1
4. 刪除B字元或者增加A字元,N[i,j]等同於N[i][j1]+1
最終N[i,j]的值就為min(情況1 || 情況2, 情況3, 情況4)的最小值,並且N[i,j]代表的是A[0i]B[0j]的最小編輯距離。
程式碼如下:
int calcDistanceDP (char* A, char* B) 
{
    int m = strlen(A), n = strlen(B);
    // 生成表
    int *T = (int *)malloc(m * n * sizeof(int));
    // 賦初始值
    for (int i = 0; i <= m; i++) 
        for (int j = 0; j <= n; j++)
            *(T + i * n + j) = 0;
    for (int i = 0; i <= m; i++)
        *(T + i * n) = i;
    for (int i = 0; i <= n; i++)
        *(T + i) = i;
    for (int i = 0; i < m; i++) 
    {
        for (int j = 0; j < n; j++) 
        {
            int cost = (int)A[i] != B[j];
            int caseA = *(T + i * n + j + 1) + 1;
            int caseB = *(T + (i + 1) * n + j) + 1;
            int caseC = *(T + i * n + j) + cost;
            *(T + (i + 1) * n + j + 1) = getMin(caseA, caseB, caseC);
        }
    }
    return *(T + m * n - 1);
}

演算法複雜度為O(MN)