Edit Distance(編輯距離)

前言

今天看了Stanford編輯距離代碼,感覺寫得不錯,寫一篇博客記錄下。

編輯距離的定義是:從字符串A到字符串B,中間需要的最少操作權重。這里的操作權重一般是:

  • 刪除一個字符(deletion)
  • 插入一個字符(insertion)
  • 替換一個字符(substitution)
  • 他們的權重都是1

編輯距離的算法一般用dp。很多博客寫到這里就結束了,因此十分晦澀難懂。因為沒有對其加主謂語,完全就是耍流氓。正確的說法應該是:

  • 刪除A末尾一個字符(deletion)
  • 用B末尾插入A末尾一個字符(insertion)
  • 把A末尾字符替換成B末尾的一個字符(substitution)

為什么?

算法及實現

我們舉一個實際例子

  • 長度為m的字符串A,len(A) = m
  • 長度為n的字符串B,len(B) = n

則A到B的編輯距離dp公式如下:

編輯距離DP公式

先不要急著看懂,我慢慢解釋。

  • Q2: 為什么d是一個[m+1][n+1]大小的二維數組,為什么d數組要比字符串長度大一?

  • A2: 考慮A、B都為空字符串,我們還是需要一個[1][1]大小的數組記錄其編輯距離為0。更進一步也就是說,我們假設字符串A為"AC",則我們需要考慮['', 'A', 'AC']三種情況。

  • Q1: 如何理解d[i][j]的計算公式?

  • A1: 第(i,j)個位置的計算需要依賴于和它相鄰的三個元素(i-1,j)、(i, j-1)和(i-1,j-1),關鍵是哪一個對應刪除,哪一個對應于插入,哪一個對應于替換?如果此時A[i]不等于B[j],則(下面為全文最重要部分):

    • 對于(i-1,j-1)時,d(i-1, j-1)表示完成從A[0,i-1]到B[0,j-1]的編輯次數,即現在A[0,i-1]=B[0,j-1],對于(i,j),我們直接把A[i]替換成B[j]即完成編輯。因此(i-1,j-1)對應于把A[i]用B[j]替換的一次操作
    • 對于(i-1, j)時,d(i-1, j)表示完成從A[0, i-1]到B[0, j]的編輯次數,即現在A[0,i-1]=B[0,j],對于(i,j),我們直接把A[i]刪除即可完成編輯,因此(i-1,j)對應于把A[i]刪除的一次操作
    • 對于(i, j-1)時,d(i, j-1)表示完成從A[0, i]到B[0, j-1]的編輯次數,即現在A[0,i]=B[0,j-1],對于(i,j),我們直接用B[j]插入到A[i]的位置即可完成編輯,因此(i,j-1)對應于把B[j]插到A[i]的一次操作

理解了上面的文字就理解編輯距離DP算法了,寫得有點冗長。

這里給一個帶Damerau–Levenshteindistance距離的代碼,其中添加了一種操作:

  • 置換兩個字符(transposition),也就是說'ab'到'ba'的操作消耗值為1

代碼地址:https://gist.github.com/nlpjoe

核心部分為score_edit_distance(self, source, target):

def score_edit_distance(self, source, target):
   if source == target:
       return 0
   s_pos = len(source)
   t_pos = len(target)
   self.clear(s_pos, t_pos)
   for i in range(s_pos + 1):
       for j in range(t_pos + 1):
           b_score = self.score[i][j]
           if b_score != self.worse():
               continue
           if i == 0 and j == 0:  # 0,0位置為空,默認為正確
               b_score = self.best()
           else:
               if i > 0:  # 刪除權重
                   b_score = min(b_score, self.score[i-1][j] + self.delete_cost(source[i-1]))
               if j > 0:  # 插入權重
                   b_score = min(b_score, self.score[i][j-1] + self.insert_cost(target[j-1]))
               if i > 0 and j > 0:  # 替換權重
                   b_score = min(b_score, self.score[i-1][j-1] + self.substitute_cost(source[i-1], target[j-1]))
               if i > 1 and j > 1:  # 置換權重
                   b_score = min(b_score, self.score[i-2][j-2] + self.transpose_cost(source[i-2], source[i-1], target[j-2], target[j-1]))
           self.score[i][j] = b_score
   return self.score[s_pos][t_pos]

輸出結果為:

0
5.0
1.0

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容