我认为在这种情况下更深入地理解算法非常重要.我将向您介绍算法的基本步骤,而不是给您一些伪代码,并向您展示您想要的数据如何在最终矩阵中“编码”.当然,如果你不需要推出自己的算法,那么显然你应该只使用别人的,如
MattH suggests!
大图
这看起来像是Wagner-Fischer algorithm的实现.基本思想是计算“附近”前缀之间的距离,取最小值,然后计算当前字符串对的距离.例如,假设您有两个字符串’i’和’h’.让我们沿着矩阵的垂直和水平轴放置它们,如下所示:
_ h
_ 0 1
i 1 1
这里,’_’表示空字符串,矩阵中的每个单元对应于一个输入(”或’i’)到输出(”或’h’)的编辑序列.
从空字符串到任何长度为L的字符串的距离是L,(需要L个插入).从任何长度为L的字符串到空字符串的距离也是L(需要L个删除).这涵盖了第一行和第一列中的值,它们只是递增.
从那里,您可以通过从上,左和左上角值中取最小值并添加一个来计算任何位置的值,或者,如果字符串中该点处的字母相同,则取上面的值-left值不变.对于上表中(1,1)的值,最小值为0(0,0),因此(1,1)处的值为1,这是从“i”到“h”的最小编辑距离'(一个替代).因此,通常,最小编辑距离始终位于矩阵的右下角.
现在让我们做另一个,比较是喜.同样,矩阵中的每个单元对应于一个编辑序列,该编辑序列将输入(”,’i’或’is’)输入到输出(”,’h’或’hi’).
_ h i
_ 0 1 2
i 1 1 #
s 2 # #
我们首先扩展矩阵,使用#作为我们尚不知道的值的占位符,并通过递增来扩展第一行和第一列.完成后,我们可以开始计算标记为#above的位置的结果.让我们从(2,1)开始(在(行,列)中,即行主要表示法).在上,左上和左边的值中,最小值是1.表中相应的字母是不同的 – s和h – 所以我们将一个加到该最小值得到2,并继续.
_ h i
_ 0 1 2
i 1 1 #
s 2 2 #
让我们继续(1,2)的值.现在情况有所不同,因为表中相应的字母是相同的 – 它们都是我.这意味着我们可以选择在左上角的单元格中取值而不添加一个.这里的指导性直觉是我们不必增加计数,因为在这个位置上两个字符串都添加了相同的字母.而且由于两根琴弦的长度都增加了一倍,我们会对角线移动.
_ h i
_ 0 1 2
i 1 1 1
s 2 2 #
在最后一个空单元格中,事情会恢复正常.相应的字母是s和i,所以我们再次取最小值并加一,得到2:
_ h i
_ 0 1 2
i 1 1 1
s 2 2 2
这是我们得到的表格,如果我们继续这个过程的两个较长的单词,以is和hi-isnt(忽略标点符号)和提示开头:
_ h i n t
_ 0 1 2 3 4
i 1 1 1 2 3
s 2 2 2 2 3
n 3 3 3 2 3
t 4 4 4 3 2
这个矩阵稍微复杂一点,但这里的最终最小编辑距离仍然只有2,因为这两个字符串的最后两个字母是相同的.方便!
重新创建编辑序列
那么我们如何从这个表中提取编辑类型呢?关键是要认识到桌子上的移动对应于特定类型的编辑.因此,例如,从(0,0)到(0,1)的向右移动使我们从_ – > _,不需要编辑,_ – > h,需要一次编辑,一次插入.同样,从(0,0)到(1,0)的向下移动使我们从_ – > _,不需要编辑,i – > _,需要一次编辑,一次删除.最后,从(0,0)到(1,1)的对角线移动使我们从_ – > _,不需要编辑,i – > h,需要一次编辑,一次替换.
所以现在我们所要做的就是颠倒我们的步骤,从上,左和左上角的单元格中追溯局部最小值回到原点,(0,0),记住如果当前值与最小值,然后我们必须转到左上角的单元格,因为这是唯一一种不会增加编辑距离的运动.
以下是您可以采取的步骤的详细说明.从已完成矩阵的右下角开始,重复以下操作,直至到达左上角:
>查看左上角的相邻单元格.如果它不存在,请转到步骤3.如果单元格确实存在,请记下存储在那里的值.
>左上角单元格中的值是否等于当前单元格中的值?如果是,请执行以下操作:
>记录空操作(即等于).在这种情况下不需要编辑,因为此位置的字符是相同的.
>更新当前单元格,向上和向左移动.
>返回第1步.
>这里有很多分支:
>如果左侧没有单元格而上面没有单元格,则表示您位于左上角,算法已完成.
>如果左侧没有单元格,请转到步骤4.(这将继续循环,直到您到达左上角.)
>如果上面没有单元格,请转到步骤5.(这将继续循环,直到到达左上角.)
>否则,在左边的单元格,左上角的单元格和上面的单元格之间进行三向比较.选择价值最小的那个.如果有多个候选人,您可以随机选择一个;它们在这个阶段都是有效的. (它们对应于具有相同总编辑距离的不同编辑路径.)
>如果您选择了上面的单元格,请转到步骤4.
>如果您选择了左侧的单元格,请转到步骤5.
>如果您选择了左上方的单元格,请转到步骤6.
>你正在向上移动.请执行下列操作:
>在当前单元格中记录输入字符的删除.
>更新当前单元格,向上移动.
>返回第1步.
>你向左移动.请执行下列操作:
>在当前单元格中记录输出字符的插入.
>更新当前单元格,向左移动.
>返回第1步.
>你正在对角线移动.请执行下列操作:
>在当前单元格中记录输入字符的替换,以代替当前单元格的输出字符.
>更新当前单元格,向上和向左移动.
>返回第1步.
把它放在一起
在上面的示例中,有两种可能的路径:
(4, 4) -> (3, 3) -> (2, 2) -> (1, 2) -> (0, 1) -> (0, 0)
和
(4, 4) -> (3, 3) -> (2, 2) -> (1, 1) -> (0, 0)
扭转他们,我们得到
(0, 0) -> (0, 1) -> (1, 2) -> (2, 2) -> (3, 3) -> (4, 4)
和
(0, 0) -> (1, 1) -> (2, 2) -> (3, 3) -> (4, 4)
所以对于第一个版本,我们的第一个操作是向右移动,即插入.插入的字母是h,因为我们正在从isnt变为提示. (这对应于详细输出中的Insert,h.)我们的下一步操作是对角线移动,即替换或无操作.在这种情况下,它是无操作,因为两个位置的编辑距离相同(即字母相同).所以平等,我,我.然后向下移动,对应于删除.删除的字母是s,因为我们再次从isnt变为暗示. (通常,要插入的字母来自输出字符串,而要删除的字母来自输入字符串.)所以这是删除,s.然后是两个没有变化的对角线运动:Equal,n,n和Equal,t,t.
结果:
Insert, h
Equal, i, i
Delete, s
Equal, n, n
Equal, t, t
执行这些说明不是:
isnt (No change)
hisnt (Insertion)
hisnt (No change)
hint (Deletion)
hint (No change)
hint (No change)
总编辑距离为2.
我将把第二条最小路径作为练习.请记住,两条路径完全相同;它们可能不同,但它们将导致相同的最小编辑距离为2,因此完全可以互换.当您在矩阵中向后工作时,如果您看到两个不同的可能的局部最小值,您可以选择其中一个,并且最终结果保证是正确的
一旦你了解了这一切,根本不应该编码.在这种情况下,关键是要首先深入理解算法.一旦你完成了它,编码就很容易了.
积累与重建
最后,您可以选择在填充矩阵时累积编辑.在这种情况下,矩阵中的每个单元格都可以是元组:(2,”ins’,’eq’,’del’,’eq’,’eq’)).您将增加长度,并附加与最小先前状态的移动相对应的操作.这消除了回溯,因此降低了代码的复杂性;但它占用了额外的内存.如果执行此操作,最终编辑序列将与矩阵右下角的最终编辑距离一起显示.