概念
莱文斯坦距离(Levenshtein distance)是编辑距离的一种。它由俄罗斯科学家 弗拉基米尔·莱文斯坦在1965年提出。
两个字符串之间的莱文斯坦距离是将一个单词更改为另一个单词所需的最少编辑操作次数。莱文斯坦距离允许的编辑操作包括单个字符的 替换/插入/删除。
示例
"Saturday" 和 "Sundays" 之间的 Levenshtein 距离是 4
1.) Saturday → Sturday // 删除第一个 a
2.) Sturday → Surday // 删除第一个 t
3.) Surday → Sunday // 替换 r 为 n
4.) Sunday → Sundays // 结尾添加 s
计算方法
莱文斯坦距离本质上可以理解为一个动态规划问题,其状态转移方程为:
解释一下状态转移方程:
- 方程第一行:如果 min(i,j) = 0(即当前两个字符串至少有一个长度为0,即是空字符串)时,则此时的莱文斯坦距离就是两字符串的长度的最大值。这个很好理解,不赘述了
- 方程第二行:如果min(i,j) != 0,则取如下三个数字的最小值:
2.1. 将a字符串的长度i减小1,计算其与长度为j的b字符串的莱文斯坦距离,并将其结果再加1,这其实等价于将当前a字符串的i位置的字符删除了;
2.2. 将b字符串的长度j减小1,计算其与长度为i的a字符串的莱文斯坦距离,并将其结果再加1,这其实等价于在当前a字符串的i位置后面插入了一个字符(即插入了b字符串j位置的那个字符);
2.3. 将str_a、str_b字符串的长度同时减小1,计算两者的剩余字符串的莱文斯坦距离,此时如果str_a的i位置的字符和str_b的j位置的字符不同,则结果再加1,这其实等价于将str_a的i位置的字符改为str_b的j位置的那个字符;
计算过程
Java代码
public class LevenshteinUtil {
/**
* 编辑距离算法,(用于计算距离相近的字符串)
*
* @param word1
* @param word2
* @return
*/
public static int minDistance(String word1, String word2) {
int n = word1.length();
int m = word2.length();
if (n * m == 0)
return n + m;
int[][] d = new int[n + 1][m + 1];
for (int i = 0; i < n + 1; i++) {
d[i][0] = i;
}
for (int j = 0; j < m + 1; j++) {
d[0][j] = j;
}
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1; j++) {
int left = d[i - 1][j] + 1;
int down = d[i][j - 1] + 1;
int left_down = d[i - 1][j - 1];
if (word1.charAt(i - 1) != word2.charAt(j - 1))
left_down += 1;
d[i][j] = Math.min(left, Math.min(down, left_down));
}
}
return d[n][m];
}
/**
* 编辑距离算法,(用于计算距离相近的标签)
*
* @param tagList1
* @param tagList2
* @return
*/
public static int minDistance(List<String> tagList1, List<String> tagList2) {
int n = tagList1.size();
int m = tagList2.size();
if (n * m == 0)
return n + m;
int[][] d = new int[n + 1][m + 1];
for (int i = 0; i < n + 1; i++) {
d[i][0] = i;
}
for (int j = 0; j < m + 1; j++) {
d[0][j] = j;
}
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1; j++) {
int left = d[i - 1][j] + 1;
int down = d[i][j - 1] + 1;
int left_down = d[i - 1][j - 1];
if (!Objects.equals(tagList1.get(i - 1), tagList2.get(j - 1)))
left_down += 1;
d[i][j] = Math.min(left, Math.min(down, left_down));
}
}
return d[n][m];
}
public static int minDistanceByLabel(List<Label> tagList1, List<Label> tagList2) {
int n = tagList1.size();
int m = tagList2.size();
if (n * m == 0)
return n + m;
int[][] d = new int[n + 1][m + 1];
for (int i = 0; i < n + 1; i++) {
d[i][0] = i;
}
for (int j = 0; j < m + 1; j++) {
d[0][j] = j;
}
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1; j++) {
int left = d[i - 1][j] + 1;
int down = d[i][j - 1] + 1;
int left_down = d[i - 1][j - 1];
if (!Objects.equals(tagList1.get(i - 1).getName(), tagList2.get(j - 1).getName()))
left_down += 1;
d[i][j] = Math.min(left, Math.min(down, left_down));
}
}
return d[n][m];
}
}