编辑距离
在计算机科学中,编辑距离是一种量化两个字符串差异程度的方法,也就是计算从一个字符串转换成另外一个字符串所需要的最少操作步骤。不同的编辑距离中定义了不同操作的集合。比较常用的莱温斯坦距离(Levenshtein distance)中定义了:删除、插入、替换操作。
算法描述
定义edit(i, j),表示第一个字符串的长度为i的子串到第二个字符串长度为j的子串的编辑距离。
- 如果用递归的算法,自顶向下依次简化问题:
if (i < 0 && j < 0), edit(i, j) = 0;
if (i < 0 && j >= 0), edit(i, j) = j;
if (i >= 0 && j < 0), edit(i, j) = i;
if (i >= 0 && j >= 0), edit(i, j) = min{edit(i - 1, j) + 1, edit(i, j - 1) + 1, edit(i - 1, j - 1) + f(i, j)}, 如果第一个字符串的第i个字符等于第二个字符串的第j个字符,那么f(i, j) = 1;否则,f(i, j) = 0。
因为字符串的开始坐标是从0开始的,然后利用递归的时候判断条件应该和0比较。
- 如果用动态规划的思想,自底向上依次计算,保留已经计算的结果:
table[i][j]表示第一个字符串的长度为i的子串与第二个字符串长度为j的子串的距离。
if (j == 0), table[0][j] = j;
if (i == 0), table[i][0] = i;
if (i >= 1 && j >= 1), table[i][j] = min({table[i - 1][j] + 1, table[i][j - 1] + 1, table[i - 1][j - 1] + (s1[i - 1] == s2[j - 1] ? 0 : 1)});
具体实现
#include <iostream>
using namespace std;
class EditDistance {
public:
int edit(string s1, string s2, int len1, int len2);
int dp_edit_distance(string s1, string s2, int len1, int len2);
};
// 递归
int EditDistance::edit(string s1, string s2, int len1, int len2) {
if (len1 < 0 && len2 < 0)
return 0;
if (len1 < 0 && len2 >= 0)
return len2 + 1;
if (len1 >= 0 && len2 < 0)
return len1 + 1;
if (len1 >= 0 && len2 >= 0) {
return min(min(edit(s1, s2, len1 - 1, len2) + 1, edit(s1, s2, len1, len2 - 1) + 1),
edit(s1, s2, len1 - 1, len2 - 1) + (s1[len1] == s2[len2] ? 0 : 1) );
}
}
// 动态规划
int EditDistance::dp_edit_distance(string s1, string s2, int len1, int len2) {
int max1 = s1.size();
int max2 = s2.size();
int** table = new int* [max1 + 1];
for (int i = 0; i < max1 + 1; i++) {
table[i] = new int[max2 + 1];
}
for (int i = 0; i < max1 + 1; i++) {
table[i][0] = i;
}
for (int j = 0; j < max2 + 1; j++) {
table[0][j] = j;
}
for (int i = 1; i < max1 + 1; i++) {
for (int j = 1; j < max2 + 1; j++) {
table[i][j] = min(min(table[i - 1][j] + 1, table[i][j - 1] + 1), table[i - 1][j - 1] + (s1[i - 1] == s2[j - 1] ? 0 : 1)); //注意s1[i - 1]不是s1[i]
}
}
int result = table[max1][max2];
// 释放内存
for(int i = 0; i < max1 + 1; i++)
{
delete[] table[i];
table[i] = NULL;
}
delete[] table;
table = NULL;
return result;
}
int main() {
string str1 = "failingppp";
string str2 = "sailnbbb";
EditDistance* editDistance = new EditDistance();
clock_t start, end;
start = clock();
for (int i = 0; i < 1000000; i++) {
//int result = editDistance->edit(str1, str2, str1.size() - 1, str2.size() - 1);
int result = editDistance->dp_edit_distance(str1, str2, str1.size() - 1, str2.size() - 1);
//cout << "edit distance of " << str1 << " and " << str2 << " is : " << result << endl;
}
end = clock();
cout << "time1: " << (end - start) / 1000000.0 << endl;
start = clock();
for (int i = 0; i < 100; i++) {
int result = editDistance->edit(str1, str2, str1.size() - 1, str2.size() - 1);
//int result = editDistance->dp_edit_distance(str1, str2, str1.size() - 1, str2.size() - 1);
//cout << "edit distance of " << str1 << " and " << str2 << " is : " << result << endl;
}
end = clock();
cout << "time2: " << (end - start) / 1000000.0 << endl;
return 0;
说明:通过上面程序对比,可以发现动态规划明显快于递归的,因为递归需要反复的程序进入与返回操作,而动态保留了之前计算的结果。