1. 程式人生 > >編輯距離算法

編輯距離算法

文本 page str1 保留 多少 兩個 分享 edi highlight

2018-04-12 21:20:30

編輯距離是針對二個字符串(例如英文字)的差異程度的量化量測,量測方式是看至少需要多少次的處理才能將一個字符串變成另一個字符串。編輯距離可以用在自然語言處理中,例如拼寫檢查可以根據一個拼錯的字和其他正確的字的編輯距離,判斷哪一個(或哪幾個)是比較可能的字。DNA也可以視為用A、C、G和T組成的字符串,因此編輯距離也用在生物信息學中,判斷二個DNA的類似程度。Unix 下的 diff 及 patch 即是利用編輯距離來進行文本編輯對比的例子。

常用的編輯距離算法有:

  • Levenshtein距離,在萊文斯坦距離中,可以刪除、加入、取代字符串中的任何一個字元,也是較常用的編輯距離定義,常常提到編輯距離時,指的就是萊文斯坦距離。
  • LCS(最長公共子序列)距離,只允許刪除、加入字元。

一、最長公共子序列 LCS

最長公共子序列問題是很經典的動態規劃問題,問題描述如下:

LCS是Longest Common Subsequence的縮寫,即最長公共子序列。一個序列,如果是兩個或多個已知序列的子序列,且是所有子序列中最長的,則為最長公共子序列。

子序列: 一個序列A = a1,a2,……an,中任意刪除若幹項,剩余的序列叫做A的一個子序列。也可以認為是從序列A按原順序保留任意若幹項得到的序列。

例如:對序列 1,3,5,4,2,6,8,7來說,序列3,4,8,7 是它的一個子序列。對於一個長度為n的序列,它一共有2^n 個子序列,有(2^n – 1)個非空子序列。

請註意:子序列不是子集,它和原始序列的元素順序是相關的。

時間復雜度:對於一般性的LCS問題(即任意數量的序列)是屬於NP-hard。但當序列的數量確定時,問題可以使用動態規劃(Dynamic Programming)在多項式時間內解決。

技術分享圖片

    public int LCS(String s1, String s2) {
        if (s1.length() == 0 || s2.length() == 0) return 0;
        int len1 = s1.length();
        int len2 = s2.length();
        int[][] dp = new int[len1 + 1][len2 + 1];
        for (int i = 0; i <= len2; i++) dp[0][i] = 0;
        for (int i = 0; i <= len1; i++) dp[i][0] = 0;
        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; j++) {
                int same = s1.charAt(i - 1) == s2.charAt(j - 1) ? 1 : 0;
                dp[i][j] = Math.max(Math.max(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1] + same);
            }
        }
        return dp[len1][len2];
    }

二、萊文斯坦距離 LevenshteinDistcance

萊文斯坦距離,又稱Levenshtein距離,是編輯距離的一種。指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數。允許的編輯操作包括將一個字符替換成另一個字符,插入一個字符,刪除一個字符。

例如將kitten一字轉成sitting:

  1. sitten (k→s)
  2. sittin (e→i)
  3. sitting (→g)

俄羅斯科學家弗拉基米爾·萊文斯坦在1965年提出這個概念。

為了進一步的度量兩個字符串的相似程度,我們在得到最少的操作次數後,進行如下的計算:

1 - res / max(len1, len2)

和LCS幾乎一樣,依然是使用動態規劃算法進行求解,以下分別是偽代碼和Java實現:

int LevenshteinDistcance(string str1[1..lenStr1], string str2[1..lenStr2])
    int d[0..lenStr1, 0..lenStr2]
    int i, j, cost
 
    for i = 0 to lenStr1
       d[i, 0] := i
    for j = 0 to lenStr2
       d[0, j] := j
 
    for i = 1 to lenStr1
        for j = 1 to lenStr2
            if str1[i] = str2[j] 
                cost := 0
            else 
                cost := 1
            d[i, j] := min(
                                d[i-1, j  ] + 1,     // 刪除
                                d[i  , j-1] + 1,     // 插入
                                d[i-1, j-1] + cost   // 替換
                            )
 
   return d[lenStr1, lenStr2]
    public double levenshteinDistcance(String s1, String s2) {
        if (s1.length() == 0) return s2.length();
        if (s2.length() == 0) return s1.length();
        int len1 = s1.length();
        int len2 = s2.length();
        int[][] d = new int[len1 + 1][len2 + 1];
        for (int i = 0; i <= len1; i++) d[i][0] = i;
        for (int i = 0; i <= len2; i++) d[0][i] = i;
        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; j++) {
                int same = s1.charAt(i - 1) == s2.charAt(j - 1) ? 0 : 1;
                d[i][j] = Math.min(Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1), d[i - 1][j - 1] + same);
            }
        }
        // 得到最少的操作次數後,計算公式為:
        // 1 - res / max(len1, len2)
        return 1 - d[len1][len2] * 1.0 / Math.max(len1, len2);
    }

編輯距離算法