基本介绍
Levenshtein distance是一种度量两个序列(字符串)差异大小的方法。
该方法定义如下:
两个序列(以单词为例,这里序列也可以表示一个句子)的Levenshtein distance是在使用一个单词修改为另一个单词时,通过编辑单个字符(如插入,删除,修改)所需要的最小次数。
这个概念由俄罗斯数学家Vladimir Levenshtein于1965年提出。目前这个距离常用来评价字符识别任务的好坏。
举个例子
将单词“kitten”修改为“sitting”最少需要3次单字符的操作:
- kitten -> sitten(将“k”改为“s”)
- sitten -> sittin(将“e”改为“i”)
- sittin -> sitting(将“g”删除)
原理
假设现在两个字符串A和B,其中A的长度为a,B的长度为b,现要计算A与B之间的Levenshtein distance
我们可以考虑使用动态规划的思想解决这个问题
假设
A
i
A_{i}
Ai和
B
j
B_{j}
Bj分别为字符串A、B的前
i
、
j
i、j
i、j个字符组成的子串,现在我们来看看将
A
i
:
A
[
1
]
A
[
2
]
.
.
.
A
[
i
−
1
]
A
[
i
]
A_{i}:A[1]\quad A[2] \quad ...\quad A[i-1]\quad A[i]
Ai:A[1]A[2]...A[i−1]A[i]
修改为
B
j
:
B
[
1
]
B
[
2
]
.
.
.
B
[
j
−
1
]
B
[
j
]
B_{j}:B[1]\quad B[2] \quad ... \quad B[j-1]\quad B[j]
Bj:B[1]B[2]...B[j−1]B[j]
需要的最少编辑次数,即两个子串的Levenshtein distance,下面我们来分别讨论三种操作的操作次数:
1.插入操作
假设将 A [ 1... i ] A[1...i] A[1...i]修改为 B [ 1... j − 1 ] B[1...j-1] B[1...j−1]需要操作数为 o p 1 op_{1} op1,那么在 A [ i ] A[i] A[i]后插入一个字符 B [ j ] B[j] B[j],这样就可以将 A [ 1... i ] A[1...i] A[1...i]修改为 B [ 1... j ] B[1...j] B[1...j],这时所需要的操作数为 o p 1 + 1 op_{1}+1 op1+1
2.删除操作
假设将 A [ 1... i − 1 ] A[1...i-1] A[1...i−1]修改为 B [ 1... j ] B[1...j] B[1...j]需要操作数为 o p 2 op_{2} op2,那么删除 A [ i ] A[i] A[i]就可以将 A [ 1... i ] A[1...i] A[1...i]修改为 B [ 1... j ] B[1...j] B[1...j],这时所需要的操作数为 o p 2 + 1 op_{2}+1 op2+1
3.修改操作
假设将 A [ 1... i − 1 ] A[1...i-1] A[1...i−1]修改为 B [ 1... j − 1 ] B[1...j-1] B[1...j−1]需要操作数为 o p 3 op_{3} op3,这时要将 A [ 1... i ] A[1...i] A[1...i]修改为 B [ 1... j ] B[1...j] B[1...j]分两种情况:
a. A [ i ] ≠ B [ j ] A[i]\ne B[j] A[i]̸=B[j],则将 A [ i ] A[i] A[i]替换成 B [ j ] B[j] B[j]即可完成修改,这时操作数为 o p 3 + 1 op_{3}+1 op3+1
b.
A
[
i
]
=
=
B
[
j
]
A[i]== B[j]
A[i]==B[j],则将不需要进行修改操作,操作数仍为
o
p
3
op_{3}
op3
最后可以得到状态转移方程如下
l
e
v
a
,
b
(
i
,
j
)
=
{
m
a
x
(
i
,
j
)
,
i
f
m
i
n
(
i
,
j
)
=
0
m
i
n
{
l
e
v
a
,
b
(
i
−
1
,
j
)
+
1
l
e
v
a
,
b
(
i
,
j
−
1
)
+
1
l
e
v
a
,
b
(
i
−
1
,
j
−
1
)
+
1
a
i
≠
b
j
,
o
t
h
e
r
w
i
s
e
lev_{a,b}(i, j)=\begin{cases} max(i, j),\quad if\ min(i, j) = 0 \\ min \begin{cases} lev_{a,b}(i-1,j)+1\\ lev_{a,b}(i,j-1)+1 \\ lev_{a,b}(i-1,j-1)+1_{a_{i}\ne b_{j}} \\ \end{cases} , otherwise \\ \end{cases}
leva,b(i,j)=⎩⎪⎪⎪⎨⎪⎪⎪⎧max(i,j),if min(i,j)=0min⎩⎪⎨⎪⎧leva,b(i−1,j)+1leva,b(i,j−1)+1leva,b(i−1,j−1)+1ai̸=bj,otherwise
上式中 1 a i ≠ b j 1_{a_{i}\ne b_{j}} 1ai̸=bj表示 a i ≠ b j a_{i}\ne b_{j} ai̸=bj表达式取0,否则取1
Python代码如下
得到上述转移方程后我们就很容易写出下面程序了
1.按照上述公式编写,没做优化的情况
import numpy as np
def Lev_distance():
A = "fafasa"
B = "faftreassa"
dp = np.zeros((len(A) + 1, len(B) + 1))
for i in xrange(len(A) + 1):
dp[i][0] = i
for j in xrange(len(B) + 1):
dp[0][j] = j
for i in xrange(1, len(A) + 1):
for j in xrange(1, len(B) + 1):
if A[i - 1] == B[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j])) + 1
print("Levenshtein distance: {}".format(dp[len(A)][len(B)]))
if __name__=="__main__":
Lev_distance()
2.使用滚动数组优化上述代码
import numpy as np
def Lev_distance():
A = "fafasa"
B = "faftreassa"
dp = np.array(np.arange(len(B)+1))
for i in xrange(1, len(A)+1):
temp1 = dp[0]
dp[0] += 1
for j in xrange(1, len(B)+1):
temp2 = dp[j]
if A[i-1] == B[j-1]:
dp[j] = temp1
else:
dp[j] = min(temp1, min(dp[j-1], dp[j]))+1
temp1 = temp2
print("Levenshtein distance: {}".format(dp[len(B)]))
if __name__=="__main__":
Lev_distance()
参考
[1] https://en.wikipedia.org/wiki/Levenshtein_distance
[2] http://www.cnblogs.com/BlackStorm/p/5400809.html
欢迎加入OCR交流群:785515057