如何解决Levenshtein Distance:从矩阵推断编辑操作
|| 我用C ++编写了Levenshtein算法 如果我输入: 字符串s:民主人士 字符串t:共和党 我得到矩阵D填满的内容,并且可以在D [10] [8] = 8中读取操作次数(Levenshtein距离) 除了填充的矩阵外,我还想构建最佳解决方案。如何看待这个解决方案?我没主意 请只写信给我这个例子。解决方法
问题是
给定由Levenshtein算法生成的矩阵,如何找到“最佳解”?
即,我们如何找到字符串操作的精确顺序:插入,删除和替换[单个字母],这是将\'的字符串\'转换为\'t字符串\'所必需的?
首先,应该指出的是,在许多情况下,存在几种最优解。尽管Levenshtein算法提供的操作次数最少(在民主人士/共和党的例子中为8个),但有很多序列(8个操作)可以产生这种转换。
通过“解码” Levenshtein矩阵,可以枚举所有这样的最优序列。
总体思路是,最优解都从左上角到右下角(或沿另一个方向)遵循“路径”,从而该路径上的矩阵像元值保持不变或增加一个(或从相反方向减少1),从0开始,到针对所讨论的字符串的最佳操作次数结束(0到8个民主人士/共和党的情况)。当需要进行操作时,数字增加,当字符串中相应位置的字母相同时,数字保持不变。
产生这样的路径的算法很容易产生(产生所有可能的路径要稍微复杂一些),并且从这样的路径推论出运算的顺序。
此路径查找算法应从右下角开始,然后向后进行。之所以采用这种方法,是因为我们知道,要成为最佳解决方案,它必须在此角处结束,并且要在此角处结束,它必须来自三个单元格之一,即紧靠其左侧,紧靠上方它或立即对角线。通过在这三个像元中选择一个像元,该一个像元满足“相同值或减少一个”的要求,我们可以在最佳路径之一上有效地选择一个像元。通过重复该操作直到我们到达左上角(或者实际上直到到达值为0的单元格),我们才能有效地在最佳路径上回溯。
与民主党的插图-共和党的例子
还应注意,可以用以下两种方式之一构建矩阵:水平或垂直使用“民主人士”。这既不会改变Levenshtein距离的计算,也不会改变所需的操作列表。它只会改变我们解释矩阵的方式,例如在\“ path \”上水平移动要么意味着插入一个字符(从t字符串),要么删除一个字符(从s字符串),这取决于是否是'string s \'在矩阵中为“水平”或“垂直”。
我将使用以下矩阵。因此,约定是(仅沿从左到右和/或从上到下的方向)
横向移动是插入来自“不是字符串”的字母
垂直移动是从\'的字符串\'中删除字母
对角移动是:
无操作(两个位置的字母都相同);数字不变
替补(在各个位置的字母是不同的);这个数字增加一。
s = \“民主\”,t = \“共和党\”的Levenshtein矩阵
r e p u b l i c a n
0 1 2 3 4 5 6 7 8 9 10
d 1 1 2 3 4 5 6 7 8 9 10
e 2 2 1 2 3 4 5 6 7 8 9
m 3 3 2 2 3 4 5 6 7 8 9
o 4 4 3 3 3 4 5 6 7 8 9
c 5 5 4 4 4 4 5 6 6 7 8
r 6 5 5 5 5 5 5 6 7 7 8
a 7 6 6 6 6 6 6 6 7 7 8
t 8 7 7 7 7 7 7 7 7 8 8
下面粗略介绍了我用来在几种可能的最佳路径中选择一条路径的任意方法:
Starting at the bottom-rightmost cell,and working our way backward toward
the top left.
For each \"backward\" step,consider the 3 cells directly adjacent to the current
cell (in the left,top or left+top directions)
if the value in the diagonal cell (going up+left) is smaller or equal to the
values found in the other two cells
AND
if this is same or 1 minus the value of the current cell
then \"take the diagonal cell\"
if the value of the diagonal cell is one less than the current cell:
Add a SUBSTITUTION operation (from the letters corresponding to
the _current_ cell)
otherwise: do not add an operation this was a no-operation.
elseif the value in the cell to the left is smaller of equal to the value of
the of the cell above current cell
AND
if this value is same or 1 minus the value of the current cell
then \"take the cell to left\",and
add an INSERTION of the letter corresponding to the cell
else
take the cell above,add
Add a DELETION operation of the letter in \'s string\'
遵循此非正式的伪代码,我们得到以下信息:
从右下角的“ n”,“ t”单元格开始。
选择[对角线] \“ a \”,\“ a \”单元格作为下一个目标,因为它小于其他两个(并满足相同或-1条件)。
请注意,新单元格比当前单元格少一个
因此,第8步将\“ t \”替换为\“ n \”:democra N
继续\“ a \”,\“ a \”单元格,
选择[对角线] \“ c \”,\“ r \”单元格作为下一个目的地...
请注意,新单元格的值与当前单元格==>的值相同,无需进行任何操作。
继续使用\“ c \”,\“ r \”单元格,
选择[对角线] \“ i \”,\“ c \”单元格作为下一个目的地...
请注意,新单元格比当前单元格少一个
因此,第7步将\“ r \”替换为\“ c \”:democ C an
继续\“ i \”,\“ c \”单元格,
选择[对角线] \“ l \”,\“ o \”单元格作为下一个目的地...
请注意,新单元格比当前单元格少一个
因此第6步将\“ c \”替换为\“ i \”:演示我可以
继续\“ l \”,\“ o \”单元格,
选择[对角线] \“ b \”,\“ m \”单元格作为下一个目的地...
请注意,新单元格比当前单元格少一个
因此第5步将\“ o \”替换为\“ l \”:dem L ican
继续\“ b \”,\“ m \”单元格,
选择[对角线] \“ u \”,\“ e \”单元格作为下一个目的地...
请注意,新单元格比当前单元格少一个
因此第4步将\“ m \”替换为\“ b \”:de B lican
继续使用\“ u \”,\“ e \”单元格,
请注意,\“对角\”单元格不合格,因为\“ left \”单元格小于它。
选择[left] \“ p \”,\“ e \”单元格作为下一个目的地...
因此第3步在\“ e \”之后插入\“ u \”:de U blican
继续使用\“ p \”,\“ e \”单元格,
同样,“对角线”单元格不符合条件
选择[left] \“ e \”,\“ e \”单元格作为下一个目的地...
因此,步骤2是在\“ e \”之后插入\“ p \”:de Publican
继续使用\“ e \”,\“ e \”单元格,
选择[对角线] \“ r \”,\“ d \”单元格作为下一个目的地...
请注意,新单元格的值与当前单元格==>的值相同,无需进行任何操作。
继续\“ r \”,\“ d \”单元格,
选择[对角线] \“开始\”单元格作为下一个目标...
请注意,新单元格比当前单元格少一个
因此第1步将\“ d \”替换为\“ r \”:R epublican
您已经到达一个值为0的单元格:您的工作已经完成!
,自从我玩了一段时间以来,但是在我看来矩阵应该看起来像这样:
. . r e p u b l i c a n
. 0 1 2 3 4 5 6 7 8 9 10
d 1 1 2 3 4 5 6 7 8 9 10
e 2 2 1 2 3 4 5 6 7 8 9
m 3 3 2 2 3 4 5 6 7 8 9
o 4 4 3 3 3 4 5 6 7 8 9
c 5 5 4 4 4 4 5 6 7 8 9
r 6 5 5 5 5 5 5 6 7 8 9
a 7 6 6 6 6 6 6 6 7 7 8
t 8 7 7 7 7 7 7 7 7 7 8
不过不要认为这是理所当然的。
,这是基于mjv的答案的VBA算法。
(解释得很好,但缺少一些案例)。
Sub TU_Levenshtein()
Call Levenshtein(\"democrat\",\"republican\")
Call Levenshtein(\"ooo\",\"u\")
Call Levenshtein(\"ceci est un test\",\"ceci n\'est pas un test\")
End Sub
Sub Levenshtein(ByVal string1 As String,ByVal string2 As String)
\' Fill Matrix Levenshtein (-> array \'Distance\')
Dim i As Long,j As Long
Dim string1_length As Long
Dim string2_length As Long
Dim distance() As Long
string1_length = Len(string1)
string2_length = Len(string2)
ReDim distance(string1_length,string2_length)
For i = 0 To string1_length
distance(i,0) = i
Next
For j = 0 To string2_length
distance(0,j) = j
Next
For i = 1 To string1_length
For j = 1 To string2_length
If Asc(Mid$(string1,i,1)) = Asc(Mid$(string2,j,1)) Then
distance(i,j) = distance(i - 1,j - 1)
Else
distance(i,j) = Application.WorksheetFunction.min _
(distance(i - 1,j) + 1,_
distance(i,j - 1) + 1,_
distance(i - 1,j - 1) + 1)
End If
Next
Next
LevenshteinDistance = distance(string1_length,string2_length) \' for information only
\' Write Matrix on VBA sheets (only for visuation,not used in calculus)
Cells.Clear
For i = 1 To UBound(distance,1)
Cells(i + 2,1).Value = Mid(string1,1)
Next i
For i = 1 To UBound(distance,2)
Cells(1,i + 2).Value = Mid(string2,1)
Next i
For i = 0 To UBound(distance,1)
For j = 0 To UBound(distance,2)
Cells(i + 2,j + 2) = distance(i,j)
Next j
Next i
\' One solution
current_posx = UBound(distance,1)
current_posy = UBound(distance,2)
Do
cc = distance(current_posx,current_posy)
Cells(current_posx + 1,current_posy + 1).Interior.Color = vbYellow \' visualisation again
\' Manage border case
If current_posy - 1 < 0 Then
MsgBox (\"deletion. \" & Mid(string1,current_posx,1))
current_posx = current_posx - 1
current_posy = current_posy
GoTo suivant
End If
If current_posx - 1 < 0 Then
MsgBox (\"insertion. \" & Mid(string2,current_posy,1))
current_posx = current_posx
current_posy = current_posy - 1
GoTo suivant
End If
\' Middle cases
cc_L = distance(current_posx,current_posy - 1)
cc_U = distance(current_posx - 1,current_posy)
cc_D = distance(current_posx - 1,current_posy - 1)
If (cc_D <= cc_L And cc_D <= cc_U) And (cc_D = cc - 1 Or cc_D = cc) Then
If (cc_D = cc - 1) Then
MsgBox \"substitution. \" & Mid(string1,1) & \" by \" & Mid(string2,1)
current_posx = current_posx - 1
current_posy = current_posy - 1
GoTo suivant
Else
MsgBox \"no operation\"
current_posx = current_posx - 1
current_posy = current_posy - 1
GoTo suivant
End If
ElseIf cc_L <= cc_D And cc_L = cc - 1 Then
MsgBox (\"insertion. \" & Mid(string2,1))
current_posx = current_posx
current_posy = current_posy - 1
GoTo suivant
Else
MsgBox (\"deletion.\" & Mid(string1,1))
current_posx = current_posx
current_posy = current_posy - 1
GoTo suivant
End If
suivant:
Loop While Not (current_posx = 0 And current_posy = 0)
End Sub
,我最近使用Levenshtein距离算法的矩阵做了一些工作。我需要产生将一个列表转换为另一个列表的操作。 (这也适用于字符串。)
以下(vows)测试是否显示您正在寻找的功能?
,\"lev - complex 2\"
: { topic
: lev.diff([13,6,5,1,8,9,2,15,12,7,11],[9,13,11]),\"check actions\"
: function(topic) { assert.deepEqual(topic,[{ op: \'delete\',pos: 9,val: 7 },{ op: \'delete\',pos: 5,val: 9 },{ op: \'insert\',pos: 0,]); }
},\"lev - complex 3\"
: { topic
: lev.diff([9,[13,val: 7 }
]); }
},\"lev - complex 4\"
: { topic
: lev.diff([9,11,16],17]),{ op: \'replace\',pos: 11,val: 17 }
]); }
}
,这是一些Matlab代码,您认为这正确吗?似乎给出正确的结果:)
clear all
s = char(\'democrat\');
t = char(\'republican\');
% Edit Matrix
m=length(s);
n=length(t);
mat=zeros(m+1,n+1);
for i=1:1:m
mat(i+1,1)=i;
end
for j=1:1:n
mat(1,j+1)=j;
end
for i=1:m
for j=1:n
if (s(i) == t(j))
mat(i+1,j+1)=mat(i,j);
else
mat(i+1,j+1)=1+min(min(mat(i+1,j),mat(i,j+1)),j));
end
end
end
% Edit Sequence
s = char(\'democrat\');
t = char(\'republican\');
i = m+1;
j = n+1;
display([s \' --> \' t])
while(i ~= 1 && j ~= 1)
temp = min(min(mat(i-1,j-1),j-1)),mat(i-1,j));
if(mat(i-1,j) == temp)
i = i - 1;
t = [t(1:j-1) s(i) t(j:end)];
disp(strcat([\'iinsertion: i=\' int2str(i) \',j=\' int2str(j) \' ; \' s \' --> \' t]))
elseif(mat(i-1,j-1) == temp)
if(mat(i-1,j-1) == mat(i,j))
i = i - 1;
j = j - 1;
disp(strcat([\'uunchanged: i=\' int2str(i) \',j=\' int2str(j) \' ; \' s \' --> \' t]))
else
i = i - 1;
j = j - 1;
t(j) = s(i);
disp(strcat([\'substition: i=\' int2str(i) \',j=\' int2str(j) \' ; \' s \' --> \' t]))
end
elseif(mat(i,j-1) == temp)
j = j - 1;
t(j) = [];
disp(strcat([\'dddeletion: i=\' int2str(i) \',j=\' int2str(j) \' ; \' s \' --> \' t]))
end
end
,JackIsJack的C#实现会进行一些更改:
操作以“前进”顺序输出(JackIsJack输出以相反顺序);
原始答案中的最后一个'else \'子句工作不正确(看起来像复制粘贴错误)。
控制台应用程序代码:
class Program
{
static void Main(string[] args)
{
Levenshtein(\"1\",\"1234567890\");
Levenshtein( \"1234567890\",\"1\");
Levenshtein(\"kitten\",\"mittens\");
Levenshtein(\"mittens\",\"kitten\");
Levenshtein(\"kitten\",\"sitting\");
Levenshtein(\"sitting\",\"kitten\");
Levenshtein(\"1234567890\",\"12356790\");
Levenshtein(\"12356790\",\"1234567890\");
Levenshtein(\"ceci est un test\",\"ceci n\'est pas un test\");
Levenshtein(\"ceci n\'est pas un test\",\"ceci est un test\");
}
static void Levenshtein(string string1,string string2)
{
Console.WriteLine(\"Levenstein \'\" + string1 + \"\' => \'\" + string2 + \"\'\");
var string1_length = string1.Length;
var string2_length = string2.Length;
int[,] distance = new int[string1_length + 1,string2_length + 1];
for (int i = 0; i <= string1_length; i++)
{
distance[i,0] = i;
}
for (int j = 0; j <= string2_length; j++)
{
distance[0,j] = j;
}
for (int i = 1; i <= string1_length; i++)
{
for (int j = 1; j <= string2_length; j++)
{
if (string1[i - 1] == string2[j - 1])
{
distance[i,j] = distance[i - 1,j - 1];
}
else
{
distance[i,j] = Math.Min(distance[i - 1,j] + 1,Math.Min(
distance[i,j - 1] + 1,distance[i - 1,j - 1] + 1));
}
}
}
var LevenshteinDistance = distance[string1_length,string2_length];// for information only
Console.WriteLine($\"Levernstein distance: {LevenshteinDistance}\");
// List of operations
var current_posx = string1_length;
var current_posy = string2_length;
var stack = new Stack<string>(); // for outputting messages in forward direction
while (current_posx != 0 || current_posy != 0)
{
var cc = distance[current_posx,current_posy];
// edge cases
if (current_posy - 1 < 0)
{
stack.Push(\"Delete \'\" + string1[current_posx - 1] + \"\'\");
current_posx--;
continue;
}
if (current_posx - 1 < 0)
{
stack.Push(\"Insert \'\" + string2[current_posy - 1] + \"\'\");
current_posy--;
continue;
}
// Middle cases
var cc_L = distance[current_posx,current_posy - 1];
var cc_U = distance[current_posx - 1,current_posy];
var cc_D = distance[current_posx - 1,current_posy - 1];
if ((cc_D <= cc_L && cc_D <= cc_U) && (cc_D == cc - 1 || cc_D == cc))
{
if (cc_D == cc - 1)
{
stack.Push(\"Substitute \'\" + string1[current_posx - 1] + \"\' by \'\" + string2[current_posy - 1] + \"\'\");
current_posx--;
current_posy--;
}
else
{
stack.Push(\"Keep \'\" + string1[current_posx - 1] + \"\'\");
current_posx--;
current_posy--;
}
}
else if (cc_L <= cc_D && cc_L == cc - 1)
{
stack.Push(\"Insert \'\" + string2[current_posy - 1] + \"\'\");
current_posy--;
}
else
{
stack.Push(\"Delete \'\" + string1[current_posx - 1]+\"\'\");
current_posx--;
}
}
while(stack.Count > 0)
{
Console.WriteLine(stack.Pop());
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。