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

你可能感兴趣的:(Edit Distance(编辑距离))