詳解編輯距離(Edit Distance)及其代碼實現

概述

編輯距離(Minimum Edit Distance,MED),由俄羅斯科學家 Vladimir Levenshtein 在1965年提出,也因此而得名 Levenshtein Distance。

在信息論、語言學和計算機科學領域,Levenshtein Distance 是用來度量兩個序列相似程度的指標。通俗地來講,編輯距離指的是在兩個單詞之間,由其中一個單詞轉換爲另一個單詞所需要的最少單字符編輯操作次數。

在這裏定義的單字符編輯操作有且僅有三種:

  • 插入(Insertion)
  • 刪除(Deletion)
  • 替換(Substitution)

譬如,"kitten" 和 "sitting" 這兩個單詞,由 "kitten" 轉換爲 "sitting" 需要的最少單字符編輯操作有:

1.kitten → sitten (substitution of "s" for "k")
2.sitten → sittin (substitution of "i" for "e")
3.sittin → sitting (insertion of "g" at the end)

因此,"kitten" 和 "sitting" 這兩個單詞之間的編輯距離爲 3 。

形式化定義

我們將兩個字符串 的 Levenshtein Distance 表示爲 ,其中 和 分別對應 的長度。那麼,在這裏兩個字符串 的 Levenshtein Distance,即 可用如下的數學語言描述:

  • 定義 指的是 中前 個字符和 中前 個字符之間的距離。爲了方便理解,這裏的可以看作是的長度。這裏的字符串的第一個字符 index 從 1 開始(實際因爲在表上運算的時候字符串前需要補 0),因此最後的編輯距離便是 時的距離:

  • 當 的時候,對應着字符串 中前 個字符和 字符串 中前 個字符,此時的 有一個值爲 0 ,表示字符串 a 和 b 中有一個爲空串,那麼從 a 轉換到 b 只需要進行次單字符編輯操作即可,所以它們之間的編輯距離爲 ,即 中的最大者。

  • 當 的時候, 爲如下三種情況的最小值:
    1. 表示 刪除
    2. 表示 插入
    3. 表示 替換

  • 爲一個指示函數,表示當 的時候取 0 ;當 的時候,其值爲 1。

過程示例

以 和 爲例,建立一個矩陣,通過矩陣記錄計算好的距離:

 

當時,,根據此初始化矩陣的第一行和第一列:

 

第一行(index = 0)初始化:
min(0, 0) = 0 ->  lev_{a, b}(0, 0) = max(0, 0) = 0
min(0, 1) = 0 ->  lev_{a, b}(0, 1) = max(0, 1) = 1
min(0, 2) = 0 ->  lev_{a, b}(0, 2) = max(0, 2) = 2
min(0, 3) = 0 ->  lev_{a, b}(0, 3) = max(0, 3) = 3

第一列(index = 0)初始化:
min(0, 0) = 0 ->  lev_{a, b}(0, 0) = max(0, 0) = 0
min(1, 0) = 0 ->  lev_{a, b}(1, 0) = max(1, 0) = 1
min(2, 0) = 0 ->  lev_{a, b}(2, 0) = max(2, 0) = 2
min(3, 0) = 0 ->  lev_{a, b}(3, 0) = max(3, 0) = 3

依據上面的公式可以繼續推導出第二行:

第二行(index = 1)推導


 

繼續迭代,第三行(index = 2)推導


 

直至推導出最終結果:


 

算法實現

1 遞歸方式

 

def Levenshtein_Distance_Recursive(str1, str2):

    if len(str1) == 0:
        return len(str2)
    elif len(str2) == 0:
        return len(str1)
    elif str1 == str2:
        return 0

    if str1[len(str1)-1] == str2[len(str2)-1]:
        d = 0
    else:
        d = 1
    
    return min(Levenshtein_Distance_Recursive(str1, str2[:-1]) + 1,
                Levenshtein_Distance_Recursive(str1[:-1], str2) + 1,
                Levenshtein_Distance_Recursive(str1[:-1], str2[:-1]) + d)

print(Levenshtein_Distance_Recursive("abc", "bd"))
>>>
2

2 動態規劃
遞歸是從後向前分解,那與之相對的就是從前向後計算,逐漸推導出最終結果,此法被稱之爲動態規劃,動態規劃很適用於具有重疊計算性質的問題,但這個過程中會存儲大量的中間計算的結果,一個好的動態規劃算法會盡量減少空間複雜度。

 

def Levenshtein_Distance(str1, str2):
    """
    計算字符串 str1 和 str2 的編輯距離
    :param str1
    :param str2
    :return:
    """
    matrix = [[ i + j for j in range(len(str2) + 1)] for i in range(len(str1) + 1)]

    for i in range(1, len(str1)+1):
        for j in range(1, len(str2)+1):
            if(str1[i-1] == str2[j-1]):
                d = 0
            else:
                d = 1
            
            matrix[i][j] = min(matrix[i-1][j]+1, matrix[i][j-1]+1, matrix[i-1][j-1]+d)

    return matrix[len(str1)][len(str2)]


print(Levenshtein_Distance("abc", "bd"))

>>>
2

應用與思考

編輯距離是NLP基本的度量文本相似度的算法,可以作爲文本相似任務的重要特徵之一,其可應用於諸如拼寫檢查、論文查重、基因序列分析等多個方面。但是其缺點也很明顯,算法基於文本自身的結構去計算,並沒有辦法獲取到語義層面的信息。

由於需要利用矩陣,故空間複雜度爲O(MN)。這個在兩個字符串都比較短小的情況下,能獲得不錯的性能。不過,如果字符串比較長的情況下,就需要極大的空間存放矩陣。例如:兩個字符串都是20000字符,則 LD 矩陣的大小爲:20000 * 20000 * 2=800000000 Byte=800MB。

參考文獻

[1] https://blog.csdn.net/ghsau/article/details/78903076
[2] https://en.wikipedia.org/wiki/Levenshtein_distance
[3] https://www.dreamxu.com/books/dsa/dp/edit-distance.html
[4] https://www.jianshu.com/p/a96095aa92bc



作者:TSW1995
鏈接:https://www.jianshu.com/p/a617d20162cf
來源:簡書
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章