直接上干货。
什么叫编辑距离?从字符串str1到字符串str2需要经过多少次编辑。每次编辑只能增加 / 删除 / 替换1个字符。计算出从str1到str2最小的编辑距离(Minimum Edit Distance),用此可作为两个字符串之间的相似度的衡量。
定义符号:
对于两个字符串strA, strB,它俩的长度分别记为lenA, lenB.
MED:最小的编辑距离(Minimum Edit Distance)
记C[i, j] = MED{ strA[0…i], strB[0…j] }. 即C数组中存储的是 以i j 结尾的两个字符串的最小编辑距离。
现在开始分析:
对于strA[0…lenA-1] 和 strB[0…lenB-1],
当计算C[i][j]时,我们认为C数组之前的值都已经计算好了,我们认为经过C[i-1][j-1]次最少次数的编辑之后,处理过的strA[0…i-1] 与处理过的 strB[0…j-1] 相等。
当计算C[i][j]时,不要去管i与j后面的字符串。
若strA[i] == strB[j],则C[i][j] = C[i-1][j-1].这是最简单的情况。
当strA[i] != strB[j]时,就要分情况讨论了:
1.删除strA[i],此时等价于要计算MED{ strA[0…i-1], strB[0…j] },显然,这就是C[i-1][j],此时C[i][j] = C[i-1][j] + cost_Del。
2.删除strB[j],此时等价于要计算MED{ strA[0…i], strB[0…j-1] },显然,这就是此时C[i][j] = C[i][j-1] + cost_Del。
3.将strA[i]修改为strB[j],此时等价于要计算MED{ strA[0…i-1], strB[0…j-1] },显然,此时C[i][j] = C[i-1][j-1] + cost_Edit。
4.将strB[j]修改为strA[i],此时等价于要计算MED{ strA[0…i-1], strB[0…j-1] },显然,此时C[i][j] = C[i-1][j-1] + cost_Edit。
5.将strB[j]添加到strA[i]之后,此时等价于要计算MED{ strA[0…i], strB[0…j-1] },显然,此时C[i][j] = C[i][j-1] + cost_Insert。
6.将strA[i]添加到strB[j]之后,此时等价于要计算MED{ strA[0…i-1], strB[0…j] },显然,此时C[i][j] = C[i-1][j] + cost_Insert。
由上述六种情况分析可知,只有当删除和插入的代价相同时,才可以合并这六种情况为三种。
状态转移方程为
注意,因为我们要用C[0][j]和C[i][0]来表示其中一个串为空时的最小编辑距离。所以C[i][j] = MED{ strA[0…i-1], strB[0…j-1] }。
if(strA[i-1] == strB[j-1])
C[i][j] = C[i-1][j-1];
else
{
int C1 = C[i-1][j] + cost_Del;
int C2 = C[i][j-1] + cost_Del;
int C3 = C[i][j-1] + cost_Insert;
int C4 = C[i-1][j] + cost_Insert;
int C5 = C[i-1][j-1] + cost_Edit;
C[i][j] = std::min(C1, C2, C3, C4, C5);
}
进行一下优化,空间复杂度可以降为O(min{lenA, lenB}).
定义C[2][N],其中N = min{lenA, lenB}。
if(strA[i-1] == strB[j-1])
C[i&1][j] = C[(i-1)&1][j-1];
else
{
int C1 = C[(i-1)&1][j] + cost_Del;
int C2 = C[i&1][j-1] + cost_Del;
int C3 = C[i&1][j-1] + cost_Insert;
int C4 = C[(i-1)&1][j] + cost_Insert;
int C5 = C[(i-1)&1][j-1] + cost_Edit;
C[i][j] = std::min(C1, C2, C3, C4, C5);
}
leetcode上有类似的题,不过它默认是所有操作的代价都是相等的。所以只用比较三个值的大小即可。
https://leetcode.com/problems/edit-distance/
完整答案如下所示:
class Solution {
public:
int minDistance(const string& word1, const string& word2) {
int len1 = word1.size();
int len2 = word2.size();
if(0 == len1)
return len2;
else if(0 == len2)
return len1;
for(int j = 0; j <= len2; j++)
C[0][j] = j;
for(int i = 1; i < len1+1; i++)
{
C[(i-1)&1][0] = i-1;
C[i&1][0] = i;
for(int j = 1; j < len2 + 1; j++)
if(word1[i-1] == word2[j-1])
C[i&1][j] = C[(i-1)&1][j-1];
else
{
int C1 = C[(i-1)&1][j-1] + 1;
int C2 = C[i&1][j-1] + 1;
C2 = C2 < C1? C2: C1;
int C3 = C[(i-1)&1][j] + 1;
C[i&1][j] = C3 < C2? C3 : C2;
}
}
return C[len1&1][len2];
}
private:
int C[2][1000];
};