编辑距离算法和Levenshtein距离算法

前言

最近在研究diff工具的实现,已经写了一个简单的demo,不过目前这个demo只是把Levenshtein距离算法的结果用Qt可视化了出来而已,还没有实用价值,界面如下:

qdiff

各种diff工具的核心基本都是编辑距离算法,网上许多文章把编辑距离算法等同于Levenshtein距离算法,但实际上Levenshtein距离算法只是各种编辑距离算法其中之一。各种编辑距离算法会使用不同的编辑操作种类,例如最常见的,Levenshtein距离算法使用复制、插入、删除和替换4种操作,而LCS距离算法使用复制、插入和删除3种操作。

编辑距离算法

在我看到的关于编辑距离算法的各种表述中,最严谨和容易理解的是在《算法导论》中的思考题15-5,详细的内容可参考该书,精简后的表述为:

给一个源文本串x[1…m]和一个目标文本串y[1…n],以及一组可用的编辑操作集合。编辑操作的结果保存在z,z是一个初始为空的足够大的缓冲区,要求编辑结束时z与y相同。我们维护两个下标i和j,分别指向x种位置和z中位置,变换操作允许改变z的内容和这两个下标。初始时,i=j=1。在编辑过程中应处理x的所有字符。可用的编辑操作有6种:

  1. 复制:从x复制一个字符到z,即进行赋值z[j]=x[i],然后i和j都自增1。此操作处理了x[i]。
  2. 替换:将x中一个字符替换为另一个字符c,z[j]=c,然后i和j都自增1。此操作处理了x[i]。
  3. 删除:删除x中一个字符,即将i增1,j不变。此操作处理了x[i]。
  4. 插入:将字符c插入z中,z[j]=c,将j增1,i不变。此操作未处理x中字符。
  5. 旋转:将x中下两个字符复制到z中,但交换顺序,z[j]=x[i+1]且z[j+1]=x[i],将i和j都自增2。此操作处理了x[i]和x[i+1]。
  6. 终止:删除x中剩余字符,令i=m+1。此操作处理了x中所有尚未处理的字符。如果执行此操作,则编辑过程结束。
    每个编辑操作都有相应的成本,具体的代价依赖于特定的应用,在此假定每个操作的成本是一个已知的常量。若源文本串、目标文本串、可用编辑操作及其代价已知,求一组可行的编辑操作序列a[1…k],其操作成本和S为最优(小)。

算法分析

现分析如下,为简单起见,就拿Levenshtein距离算法来分析,Levenshtein距离算法支持复制、替换、删除和插入4种编辑操作,若其成本分别为0、1、1、1,那么最后的成本和就是替换、删除和插入的操作次数。

记S(m,n)为将x[1…m]编辑为y[1…n]的最优成本和。最优编辑操作序列a[1…k]的每个操作一定为4种操作其中的一种,操作序列最后一个操作为a[k]:

  1. 若a[k]为复制,则要求x[m]=y[n],除去这次操作,之前操作的用于将x[1…m-1]编辑为y[1…n-1]。那么总的成本和S(m,n)’=S(m-1,n-1)+0。

  2. 若a[k]为替换,则要求x[m]!=y[n],除去这次操作,之前操作的用于将x[1…m-1]编辑为y[1…n-1]。总成本和为S(m,n)’’=S(m-1,n-1)+1。

  3. 若a[k]为删除,除去这次操作,之前的操作用于将x[1…m-1]编辑为y[1…n]。总成本和为S(m,n)’’’=S(m-1,n)+1。

  4. 若a[k]为插入,除去这次操作,之前的操作用于将x[1…n]编辑为y[1…n-1]。总成本和为S(m,n)’’’’=S(m,n-1)+1。

有了对a[k]的所有可能情况的分析,我们就可以得到S(m,n)=min((m,n)’,S,S(m,n)’’’,S(m,n)’’’’)或S(m,n)=min((m,n)’’,S,S(m,n)’’’,S(m,n)’’’’),适用前者还是后者取决于x[m]是否等于y[n]。这是一个递归式,S(m,n)依赖于S(m-1,n-1)、S(m-1,n)、S(m,n-1),因此还需要一个终止条件,这个终止条件是m=0或n=0,此时有S(0,n)=n和S(m,0)=m。

此处的分析使用了递归表述,为了避免重复计算,需要转换为使用动态规划,这个转换很简单,不再表述。

后记

编辑距离算法很慢,如果字符串的基本单位是字符,时间复杂度是O(mn)。如果字符串的基本单位是行,那还要乘上比较两行字符串的复杂度,这个复杂度可能是另一个O(n),也可能是一个比较大的整数(取决于对每行长度的限制是多少)。因此想要写一个实用的diff工具的话,要寻找其他优化过的算法。

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