演算法題(2) Levenshtein編輯距離
技術標籤:演算法題
Levenshtein編輯距離
問題描述:在應用領域中,經常會遇到對兩個字串進行比較的問題,比如在自然語言處理中,需要比較兩個句子的相似度,高階點的方法有神經網路、TF-IDF文字相似度等,最基礎的方法就是編輯距離了,最初它是由俄羅斯科學家Vladimir Levenshtein在1965年提出來的。它的解釋是給定一個原字串和一個目標字串,計算將原字串修改為目標字串時所編輯的最小次數。編輯可以是“增加”、“刪除”和“修改”三種。
思路:本題需要動態規劃思想,即對兩個長字串分別由小到大“部分計算”,直到最後完成全部的編輯。首先我們建立一個表格(可以用二維陣列儲存),其規格為(m+1)×(n+1),m、n分別為兩個字串的長度。我們假設原字串是“kitten”,目標字串是“sitting”,將原字串設定為橫向表頭,將目標字串設定為縱向表頭(也可以反過來設定),則我們建立一個7×8的表格,如下所示:
<\b> | k | i | t | t | e | n | |
---|---|---|---|---|---|---|---|
<\b> | |||||||
s | |||||||
i | |||||||
t | |||||||
t | |||||||
i | |||||||
n | |||||||
g |
表格建立完成後,我們開始編輯距離演算法。
1、初始化: 首先我們對錶格執行初始化,方法是將<\b>所對應的每一行、列都從0開始,置為遞增的整數,即0,1,2…m或n. 如下圖:
Edit-Dist | <\b> | k | i | t | t | e | n |
---|---|---|---|---|---|---|---|
<\b> | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
s | 1 | ||||||
i | 2 | ||||||
t | 3 | ||||||
t | 4 | ||||||
i | 5 | ||||||
n | 6 | ||||||
g | 7 |
解釋:表格中的數字表示當前步驟時所編輯的次數,由於我們設定橫向為原字串,縱向為目標字串,因此<\b>行的數字代表刪除操作,<\b>列的部分代表插入操作。比如"kitten"中的"e"所對應的數字"5",指的是在編輯時先將"kitten"中的“kitte”刪除時的5個刪除操作;“sitting”中的"n"對應的“6”代表編輯時先插入“sittin”時的6次插入操作。
2、填充表格: 接下來我們填充表格剩餘的空白部分,方法如下:首先比較當前位置Matrix[i][j]所對應的列頭、行頭的字元是否相同,相同則當前位置取Matrix[i][j-1]+1、Matrix[j][i-1]+1和Matrix[i-1][j-1]的最小值,否則取Matrix[i][j-1]+1、Matrix[j][i-1]+1和Matrix[i-1][j-1]+1的最小值。如下圖:
Edit-Dist | <\b> | k | i | t | t | e | n |
---|---|---|---|---|---|---|---|
<\b> | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
s | 1 | 1 | 2 | 3 | 4 | 5 | 6 |
i | 2 | 2 | 1 | 2 | 3 | 4 | 5 |
t | 3 | 3 | 2 | 1 | 2 | 3 | 4 |
t | 4 | 4 | 3 | 2 | 1 | 2 | 3 |
i | 5 | 5 | 4 | 3 | 2 | 2 | 3 |
n | 6 | 6 | 5 | 4 | 3 | 3 | 2 |
g | 7 | 7 | 6 | 5 | 4 | 4 | 3 |
解釋:由Matrix[i][j-1]至Matrix[i][j]的為插入操作,由Matrix[i-1][j]至Matrix[i][j]的為刪除操作,由Matrix[i-1][j-1]至Matrix[i][j]的為修改操作(字元相同時不必修改,編輯步數不改變)。
3、最小編輯距離:
我們通過Levenshtein距離演算法對錶格實現了填充,編輯距離為表格最右下角的數字。因此"kitten"到"sitting"的最小編輯距離是3.
下面我們展示Levenshtein編輯距離的Python實現程式碼:
class solution:
def __init__(self, source, target):
self.source = source
self.target = target
self.Matrix = []
self.edit_distance(self.source, self.target)
def edit_distance(self, source, target):
if len(target) == 0 and len(source) == 0:
return 0
row = []
for i in range(len(target)+1):
row.append(i)
for j in range(len(source)):
if i == 0 :
row.append(j+1)
continue
row.append(0)
self.Matrix.append(row)
row=[]
for i in range(1, len(target)+1):
for j in range(1, len(source)+1):
if target[i-1] == source[j-1] : edit = 0
else : edit = 1
self.Matrix[i][j] = min(self.Matrix[i-1][j]+1, self.Matrix[i][j-1]+1, self.Matrix[i-1][j-1]+edit)
for i in range(len(target)+1):
print(self.Matrix[i])
if __name__ == '__main__':
edit = solution('kitten','sitting')
程式碼可以輸出編輯狀態矩陣。執行結果如下:
注:字串的編輯距離都是對稱的,即字串A到字串B的最小編輯距離也是字串B到字串A的最小編輯距離。如果我們在程式碼中將“kitten”和“sitting”的位置互換,則編輯狀態矩陣變為原矩陣的轉置。