编辑距离,又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。Levenshtein距离可以通过下面这个状态方程求解:
这个式子还是比较好理解的:当字符串a为空,那么两个字符串之间的距离就是另一个字符串b的长度,因为可以通过删除strlen(b)个字符后编程空字符。其它三个方程类似。
在《编程之美》中使用了递归的方法,就是将上述方程原封不动的表示了出来,代码如下:
<span style="font-size:14px;">// 递归法计算字符串距离(Levenshtein距离)
int StringDistance(char *str1, char *str2)
{
// str1、str2不能为NULL
if (*str1 == '\0')
{
if (*str2 == '\0')
return 0;
else
return strlen(str2);
}
if (*str2 == '\0')
{
if (*str1 == '\0')
return 0;
else
return strlen(str1);
}
if (*str1 == *str2)
return StringDistance(str1 + 1, str2 + 1);
else
{
int d1 = StringDistance(str1 + 1, str2); // str2增加一个字符
int d2 = StringDistance(str1, str2 + 1); // str2删除一个字符
int d3 = StringDistance(str1 + 1, str2 + 1); // str2修改一个字符
return min(min(d1, d2), d3) + 1;
}
}</span>
但这样的代码是存在重复计算的,改进后的动态规划方法使用一个二维数组保存中间变量,代码如下:
<span style="font-size:14px;">// 动态规划计算字符串距离(Levenshtein距离)
int StringDistance_DP(char *str1, char *str2)
{
int len1 = strlen(str1);
int len2 = strlen(str2);
// D[i][j]表示str1[0]~str1[i-1]和str2[0]~str2[j-1]之间的字符串距离
int **D = new int*[len1 + 1];
for (int i = 0; i <= len1; i++)
D[i] = new int[len2 + 1];
// str2长度为0,距离为str1的长度
for (int i = 0; i <= len1; i++)
D[i][0] = i;
// str1长度为0,距离为str2的长度
for (int j = 0; j <= len2; j++)
D[0][j] = j;
for (int i = 1; i <= len1; i++)
{
for (int j = 1; j <= len2; j++)
{
// 若str1[i-1]和str2[j-1]相等,则距离不增加
if (str1[i - 1] == str2[j - 1])
D[i][j] = D[i - 1][j - 1];
else
// D[i][j - 1] + 1:删除str2[j-1],长度+1
// D[i - 1][j] + 1:删除str1[i-1],长度+1
// 若str1[i-1]和str2[j-1]不相等,则执行替换操作,距离+1
D[i][j] = min(min(D[i][j - 1] + 1, D[i - 1][j] + 1), D[i - 1][j - 1] + 1);
}
}
for (int i = 0; i <= len1; i++)
{
for (int j = 0; j <= len2; j++)
cout << D[i][j] << ' ';
cout << endl;
}
int res = D[len1][len2];
for (int i = 0; i <= len1; i++)
delete[] D[i];
delete[] D;
return res;
}</span>
当str1 = "Saturday",str2 = "Sunday"时,这样便形成如下矩阵:
程序运行结果和上面的矩阵相同,程序无误,右下角的3就是两个字符串的Levenshtein距离,这又是一个使用动态规划解题的典型例子。
参考:
维基百科:http://en.wikipedia.org/wiki/Levenshtein_distance
《编程之美》