1.最小编辑距离的概念
2.两字符串最小编辑距离的算法实现
关于编辑距离的求法,普遍的采用的是动态规划方法。动态规划其实就是把一个复杂的最优解问题分解成一系列较为简单的最优解问题,再将较为简单的最优解问题一步步分解,直到能够一眼看出为止。下面以"sailn"和"failing"这两个字符串作例子进行说明。定义这样一个函数——edit(i, j),它表示字符串x的长度为i的子串到字符串y的长度为j的子串的编辑距离。
算法开始要先初始化edit(0, j) = j(字符串1子串长度为0,字符串2子串有多少个字符,就作多少次增加操作;于是同理,edit(i, 0) = i)。这里,要注意到对于操作(d),即交换相邻字符串的操作,要把某个字符通过这个操作到另一个位置上,最多只能执行一次操作,即只能移动到邻位上。原因是什么呢?这是因为,移动两次的话,就没有优势了,它的操作等于两次替换操作的操作数。大于2次的时候,移动操作会更差。所以,操作(d),只发生在相邻的字符之间。
3.算法的正确性证明
(4)这个证明过程只能证明我们可以得到结果,但并没有证明结果是最小的(即我们得到的是最少的转换步骤)。所以算法中引进了一个函数min(),即edit[i,j]保存的是上述三种操作中操作数最小的一种。这就保证了我们获得的结果是最小的操作次数。
4.算法的时间复杂度分析
算法的主要任务是计算辅助数组各个元素的值。数组中共有(m+1)* (n+1)个元素,计算他们的值时间复杂度为O(m*n)。因此算法的时间复杂度为O(m*n),递归实现和非递归实现都是如此,但是递归过程涉及函数创建、堆栈的分配等过程,性能上会有所降低。实际的测试也说明了这一点。另外空间复杂度优化后的非递归实现也包含了一些其他的开销,所以性能比优化前也有所降低。下面是对随机产生的字符串进行测试所得的结果:
字符串X长度 |
字符串Y长度 |
最短编辑距离 |
递归时间(ms) |
非递归时间(ms) |
非递归(空间优化)时间(ms) |
---|---|---|---|---|---|
20 |
30 |
25 |
82 |
0 |
1 |
200 |
300 |
248 |
13335 |
3 |
5 |
500 |
1000 |
813 |
116793 |
29 |
35 |
2000 |
1000 |
1633 |
483133 |
135 |
159 |
5.完整源代码
#include
#include
#include
#include
#include
#include
using namespace std;
const int X = 20, Y = 30;
//1.递归实现
int edit_length_1(string x, string y, int edit[][Y+1], int xlen, int ylen, int i, int j){
if(i >= 0 && j >= 0 && i <=xlen && j <= ylen){
if(i == 0 || j == 0 || edit[i][j] != 100000)
return edit[i][j];
else{
if(x[i-1] == y[j-1]){
return edit[i][j] = min(min(edit_length_1(x,y,edit,xlen,ylen,i,j-1) + 1,
edit_length_1(x,y,edit,xlen,ylen,i-1,j) + 1),
edit_length_1(x,y,edit,xlen,ylen,i-1,j-1));
}else{
if(i >= 2 && j >= 2 && x[i-2] == y[j-1] && x[i-1] == y[j-2]){
return edit[i][j] = min(min(edit_length_1(x,y,edit,xlen,ylen,i,j-1) + 1,
edit_length_1(x,y,edit,xlen,ylen,i-1,j) + 1),
min(edit_length_1(x,y,edit,xlen,ylen,i-1,j-1) + 1,
edit_length_1(x,y,edit,xlen,ylen,i-2,j-2) +1));
}else{
return edit[i][j] = min(min(edit_length_1(x,y,edit,xlen,ylen,i,j-1) + 1,
edit_length_1(x,y,edit,xlen,ylen,i-1,j) + 1),
edit_length_1(x,y,edit,xlen,ylen,i-1,j-1) + 1);
}
}
}
}else{
return 0;
}
}
//2.非递归实现
int edit_length_2(string x, string y, int edit[][Y+1], int xlen, int ylen){
int i = 0, j = 0;
for(i = 0; i <= xlen; i++){
edit[i][0] = i;
}
for(j = 0; j <= ylen; j++){
edit[0][j] = j;
}
for(i = 1; i <= xlen; i++ ){
for(j = 1; j <= ylen; j++){
if(x[i-1] == y[j-1]){
edit[i][j] = min(min(edit[i][j-1] + 1, edit[i-1][j] + 1), edit[i-1][j-1]);
}else{
if(i >= 2 && j >= 2 && x[i-2] == y[j-1] && x[i-1] == y[j-2]){
edit[i][j] = min(min(edit[i][j-1] + 1, edit[i-1][j] + 1), min(edit[i-1][j-1] + 1, edit[i-2][j-2] + 1));
}else{
edit[i][j] = min(min(edit[i][j-1] + 1, edit[i-1][j] + 1), edit[i-1][j-1] + 1);
}
}
}
}
return edit[xlen][ylen];
}
//3.非递归实现,空间优化
int edit_length_3(string x, string y, int edit[][Y+1], int xlen, int ylen){
int i = 0, j = 0;
//首行元素初始化
for(j = 0; j <= ylen; j++){
edit[0][j] = j;
}
for(i = 1; i <= xlen; i++ ){
edit[i%3][0] = edit[(i-1)%3][0] + 1;
for(j = 1; j <= ylen; j++){
if(x[i-1] == y[j-1]){
edit[i%3][j] = min(min(edit[i%3][j-1] + 1, edit[(i-1)%3][j] + 1), edit[(i-1)%3][j-1]);
}else{
if(i >= 2 && j >= 2 && x[i-2] == y[j-1] && x[i-1] == y[j-2]){
edit[i%3][j] = min(min(edit[i%3][j-1] + 1, edit[(i-1)%3][j] + 1), min(edit[(i-1)%3][j-1] + 1, edit[(i-2)%3][j-2] + 1));
}else{
edit[i%3][j] = min(min(edit[i%3][j-1] + 1, edit[(i-1)%3][j] + 1), edit[(i-1)%3][j-1] + 1);
}
}
//cout << edit[i%3][j] << " ";
}
//cout << endl;
}
return edit[(i-1)%3][j-1];
}
void test_1(string x, string y){
int xlen = x.length();
int ylen = y.length();
int edit[X+1][Y+1] = {100};
for(int i = 0; i <= xlen; i++){
for(int j = 0; j <= ylen; j++){
edit[i][j] = 100000;
}
}
for(int i = 0; i <= xlen; i++){
edit[i][0] = i;
}
for(int j = 0; j <= ylen; j++){
edit[0][j] = j;
}
/*
cout << "分析前的数组状态" << endl;
for(int i = 0; i <= xlen; i++){
for(int j = 0; j <= ylen; j++){
cout << edit[i][j] << " ";
}
cout << endl;
}
cout << endl;
*/
int max_len = edit_length_1(x, y, edit, xlen, ylen, xlen, ylen);
/*
cout << "分析后的数组状态:" << endl;
for(int i = 0; i <= xlen; i++){
for(int j = 0; j <= ylen; j++){
cout << edit[i][j] << " ";
}
cout << endl;
}
cout << endl;
*/
cout << "1.递归分析这两个字符串的最短编辑距离为:" << max_len << endl;
}
void test_2(string x, string y){
int xlen = x.length();
int ylen = y.length();
int edit[X+1][Y+1];
memset(edit, 0, sizeof(edit));
//分析前的数组状态
/*
cout << "分析前的数组状态:" << endl;
for(int i = 0; i <= xlen; i++){
for(int j = 0; j <= ylen; j++){
cout << edit[i][j] << " ";
}
cout << endl;
}
cout << endl;
*/
int max_len = edit_length_2(x, y, edit, xlen, ylen);
/*
cout << "分析后的数组状态:" << endl;
for(int i = 0; i <= xlen; i++){
for(int j = 0; j <= ylen; j++){
cout << edit[i][j] << " ";
}
cout << endl;
}
cout << endl;
*/
cout << "2.非递归分析这两个字符串的最短编辑距离为:" << max_len << endl;
}
void test_3(string x, string y){
int xlen = x.length();
int ylen = y.length();
int edit[3][Y+1];
memset(edit, 0, sizeof(edit));
//分析前的数组状态
/*
cout << "分析前的数组状态:" << endl;
for(int i = 0; i <= 2; i++){
for(int j = 0; j <= ylen; j++){
cout << edit[i][j] << " ";
}
cout << endl;
}
cout << endl;
*/
int max_len = edit_length_3(x, y, edit, xlen, ylen);
/*
cout << "分析后的数组状态:" << endl;
for(int i = 0; i <= 2; i++){
for(int j = 0; j <= ylen; j++){
cout << edit[i][j] << " ";
}
cout << endl;
}
cout << endl;
*/
cout << "3.非递归(空间优化)分析这两个字符串的最短编辑距离为:" << max_len << endl;
}
char* rand_string(char *str, int len){
int i = 0;
srand((unsigned)time(NULL));
for(i = 0; i < len; i++)
str[i] = rand()%26 + 'a';
str[i] = '\0';
return str;
}
long getCurrentTime(){
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
int main(){
char str_1[X+1];
rand_string(str_1, X);
string x(str_1);
sleep(1);
char str_2[Y+1];
rand_string(str_2, Y);
string y(str_2);
cout << "string x = " << x << endl;
cout << " 字符串长度为:" << x.length() << endl;
cout << "string y = " << y << endl;
cout << " 字符串长度为:" << y.length() << endl;
long time_1 = getCurrentTime();
test_1(x, y);
long time_2 = getCurrentTime();
cout << " 用时:";
cout << time_2 - time_1 << " ms;" << endl;
long time_3 = getCurrentTime();
test_2(x, y);
long time_4 = getCurrentTime();
cout << " 用时:";
cout << time_4 - time_3 << " ms;" << endl;
long time_5 = getCurrentTime();
test_3(x, y);
long time_6 = getCurrentTime();
cout << " 用时:";
cout << time_6 - time_5 << " ms;" << endl;
}