編輯距離是對兩個字符串差異化的量化,其含義是將一個字符串轉化爲另一個字符串所需的最少操作次數,允許的編輯操作包括將一個字符替換爲另一個字符,插入一個字符,刪除一個字符。編輯距離可用在自然語言處理中,用於計算兩個文本之間的相似度。
算法的基本原理:對於字符串a[1:i]和字符串b[1:j]來說,用edit[i][j]表示它們間的編輯距離。
如果a[i]和b[j]相同,則edit[i][j]=edit[i-1][j-1]。
如果a[i]和b[j]不相同,則有如下情況:
1) a[1:i]經過多次操作轉化爲b[1:j-1],然後再在結尾插入字符b[j]即可,edit[i][j]=edit[i][j-1]+1;
2)a[1:i-1]經過多次操作轉化爲b[1:j],然後再將字符a[i]刪除即可,edit[i][j]=edit[i-1][j]+1;
3)a[1:i-1]經過多次操作轉化爲b[1:j-1],然後再將字符a[i]替換爲b[j]即可,edit[i][j]=edit[i-1][j-1]+1。
在這三種情況中取最小值即可。其中edit[0][j]表示將空串轉爲b[1:j]的操作次數,爲j;edit[i][0]表示將a[1:i]轉爲空串的操作次數,爲i。
核心思想:看到這裏,應該明白了這是一個動態規劃(dynamic programming)問題。動態規劃是一種通過求子問題的最優解,從而求得原問題的最優解的方法。其中子問題稱爲狀態,由子問題推出原問題的解稱爲狀態轉移方程。這是動態規劃算法的核心。
在本問題中,狀態定義爲求edit[i][j]這樣一個二維數組,而上述的多個求解edit[i][j]的公式則爲狀態轉移方程。
該問題的動態規劃方程可表示如下:
相似度計算:求出兩個字符串a,b間的編輯距離edit(a,b)後,字符串a和字符串b的相似即可這樣計算:
代碼實現:
class LevenshteinDistance(object):
def distance(self, a, b):
"""
計算字符串a和b之間的編輯距離
"""
edit = [[0 for j in range(len(b) + 1)] for i in range(len(a) + 1)]
for i in range(len(a)):
edit[i + 1][0] = i + 1
for j in range(len(b)):
edit[0][j + 1] = j + 1
for i in range(len(a) + 1)[1:]:
for j in range(len(b) + 1)[1:]:
flag = b[j - 1] == a[i - 1]
num = 0 if flag else 1
edit[i][j] = min(edit[i - 1][j] + 1, edit[i][j - 1] + 1, edit[i - 1][j - 1] + num)
return edit[-1][-1]
def similarity(self, a, b):
"""
計算字符串a和b之間的相似度
"""
m = max(len(a), len(b))
d = self.distance(a, b)
return (m - d) / m
測試用例:
if __name__ == '__main__':
edit = LevenshteinDistance()
print(edit.distance('kitten', 'sitting'))
print(edit.similarity('kitten', 'sitting'))
結果爲:
3
0.5714285714285714