编辑距离(Edit Distance),这里指的是Levenshtein距离,也就是字符串S1通过插入、修改、删除三种操作最少能变换成字符串S2的次数。接下来介绍利用动态规划来求解字符串的编辑距离。
定义:$s_1$和$s_2$表示两字符串,$dist(i, j)$表示字符串$s_1$的前$i$个字符串和$s_2$的前$j$个字符串的编辑距离,$s_1(i)$和$s_2(j)$分别表示$s1$的第$i$个字符和$s2$的第$j$字符。
- 若$s_1(i) = s_2(j)$,则$dist(i, j)$就等于$s_1$的前$i-1$个字符串和$s_2$的前$j-1$个字符串的编辑距离即 $dist(i, j) = dist(i-1,j-1) $
- 若$s_1(i) \neq s_2(j)$,则为了使$s_1(i) = s_2(j)$,可以通过在$s_1$的第$i$个字符处插入$s_2$的第$j$个字符,或替换$s_1$的第$i$个字符为$s_2$的第$j$个字符,或删除$s_1(s_2)$的第$i(j)$个字符(即使删除之后还是会出现不同),但是上述都是在进行了一次字符的操作之后,将转化为字问题求解,如上述的替换使$s_1(i) = s_2(j)$,则 $dist(i, j) = dist(i-1,j-1) + 1$ ,插入和删除使 $dist(i, j) = dist(i-1,j) +1$或者 $dist(i, j) = dist(i,j-1) +1$ 。
基于上述的情况可以得出递推公式:
$$
dist(i,j) = \begin{equation}
\left\{
\begin{array}{lr}
i, & j = 0 \\
j, & i = 0 \\
dist(i-1,j-1), & s_1(i) = s_2(j) \\
min(d(i-1,j), d(i,j-1),d(i-1,j-1)), & s_1(i) \neq s_2(j) \\
\end{array}
\right.
\end{equation}
$$
例如:$s_1 = “abcd”$,$s_2 = “abfce”$,则利用动态规划求解的矩阵为
a | b | f | c | e | ||
---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | |
a | 1 | 0 | 1 | 2 | 3 | 4 |
b | 2 | 1 | 0 | 1 | 2 | 3 |
c | 3 | 2 | 1 | 1 | 1 | 2 |
d | 4 | 3 | 2 | 2 | 2 | 2 |
代码实现
1 | public static int levenshtein(String s1, String s2){ |
上述的方法其中有一个不足之处是当两个字符串太长时,则对应申请的数组占用的空间也变大。而上述的算法中,在更新$dist(i,j)$的过程中只用到了$dist(i-1,j)$、$dist(i,j-1)$、$dist(i-1,j-1)$这三个数,因此我们可以只存储更新的上一行的数据,这样就可以得到$dist(i-1,j)$,$dist(i-1,j-1)$这两个数,而$d(i,0) = i$,可以在此基础上根据上一行的$d(i-1,0)$和$d(i-1,1)$推出$d(i-1,1)$,在结合上一行的$d(i-1,1)$和$d(i-1,2)$推出$d(i-1,2)$…以此类推得到第$i$行的数据。接下来是改进后的代码实现
1 | public static int levenshteinImprove(String s1, String s2){ |
上述代码还可以改进一个小细节,当比较的字符串长短一个很长,一个很短时,我们可以比较其长短,取较短的字符串长度作为开辟的数组的长度。