问题定义:
给出源文本串X[l..m]和目标文本串y[1..n] 和 一些操作及代价,求X到Y的编辑距离:将串X转化为串Y的最"便宜"的转换序列的代价
六种变换操作:
1、删除(delete)操作:源串中的单个字符可被删除
2、替换(replace)操作:源串中的单个字符可被替换为任意字符
3、复制(copy)操作:源串中的单个字符被复制到目标串中
4、插入(insert)操作:源串中的单个字符字符可被插入到目标串的任意位置
5、交换(twiddle)操作:源串中的两个相邻字符可进行交换并复制到目标串中去
6、消灭(kill)操作:在完成其它所有操作之后,源串中余下的全部后缀可以全部删除至行末
六种变换操作对应也有相应的代价,如:
cost(delete)=3;
cost(replace)=6;
cost(copy)=5;
cost(insert)=4;
cost(twiddle)=4;
cost(kill)= 被删除的串长 *cost(delete)-1;
举例:
最小代价的转换方式:
代价:copy*3 + replace*2 + delete*1 + insert*3 + twiddle*1 + kill
= 5*3 + 6*2 + 3*1 + 4*3 + 4*1 + 1*3-1= 48
非最小代价转换方式:
代价:copy*3+replace*1+delete*1+insert*4+twiddle*1+kill
= 5*3+6*1+3*1+4*4+4*1+2*3-1 = 49
对每一个操作进行分析:
假设,转换后字符可以存在Z中。
复制操作:c[i,j] = c[i - 1,j - 1] + cost(copy) if x[i] = y[j]
表示:X的前i-1个字符已经和Y的前j-1个字符匹配,而x[i] = y[j]相等,则只需要将x[i]复制到 z中,就可保证X的前i个字符与Y的前j个字符串是匹配的
替换操作:c[i,j] = c[i - 1,j - 1] + cost(replace) if x[i] != y[j]
表示:X的前i-1个字符已经和Y的前j-1个字符匹配,而x[i] != y[j]相等,则只需要将y[j]替换x[i]而放入z中,就可保证X的前i个字符与Y的前j个字符串是匹配的
交换操作:c[i,j] = c[i - 2,j - 2] + cost(twiddle) if i,j >= 2 && x[i] = y[j - 1] &&x[i - 1] = y[j]
表示:X的前i-2个字符已经和Y的前j-2个字符匹配,而x[i] = y[j - 1] && x[i - 1] = y[j]相等,则只需要将x[i] 与x[i - 1]互换复制到z中,就可保证X的前i个字符与Y的前j个字符串是匹配的
删除操作:c[i,j] = c[i - 1,j] + cost(delete)
表示:X的前i-1个字符已经和Y的前j个字符匹配,此时X中的第i个字符时多余的,此时我们只需把x[i]删除,就可保证X的前i个字符与Y的前j个字符串是匹配的
插入操作:c[i,j] = c[i,j - 1] + cost(insert)
表示:X的前i个字符已经和Y的前j-1个字符匹配,而目标串Y中还有一个字符y[j]在X中不存在,则只需要将y[j]插入z中过来,就可保证X的前i个字符与Y的前j个字符串是匹配的
消灭操作:c[i][n] = MIN(c[m,n], MIN(c[i,n] + cost(kill)))
表示:X的前i个字符已经和Y串匹配,之后将串X中i + 1到m的字符直接删除,就可保证串X与串Y是匹配的
注意:
1、消灭操作在状态转移方程中用不到,在最后程序的最后在把它用上
2、在不考虑消灭操作时,为了让d[i][j]最小,我们需要枚举以上五个子问题,找最小的转换代价
3、也就是说,五个子问题中,只有一个是我们需要的(它是五者最小值),而为了取最小值,我们需要枚举三个子问题
状态转移方程:
c[i][j]:表示,把源文本串X的前i个字符 转换为 目标串Y的前j个字符串的最小代价
kill操作:c[i][n] = MIN(c[m,n], MIN(c[i,n] + cost(kill))),其中0<=i<m(从0还是存储)
注意:写状态转移方程时,需要考虑所有能转化为c[i][j]的情况
代码
#include <iostream>
using namespace std;
const int MaxLen = 20;
char X[] = " algorithm"; //源字符串,第一个为空格
char Y[] = " altruistic";//目标串
int deleteCost = 3; //删除一个字符的代价
int replaceCost = 6;
int copyCost = 5;
int insertCost = 4;
int twiddleCost = 4;
//cost(kill) = 被删除的串长*cost(delete)-1;
int d[MaxLen][MaxLen] = {{0}};
int Min(int x,int y)
{
if (x > y)
{
return y;
}
else
{
return x;
}
}
void PrintD(int lenX,int lenY)
{
for (int i = 0;i <= lenX;i++)
{
for (int j = 0;j <= lenY;j++)
{
cout<<d[i][j]<<" ";
}
cout<<endl;
}
}
int EditDistance(int lenX,int lenY)
{
int del = 0; //删除一个字符的代价
int replace = 0;
int copy = 0;
int insert = 0;
int twiddle = 0;
//初始化边界值
//原串存在,目标串不存在,把原串中每一个字符都删除
for (int i = 1;i <= lenX;i++)
{
d[i][0] = i * deleteCost;
}
//目标串存在,原串不存在,把目标串中每一个字符都插入一遍
for (int j = 1;j <= lenY;j++)
{
d[0][j] = j * insertCost;
}
//递推
for (int i = 1;i <= lenX;i++)
{
for (int j = 1;j <= lenY;j++)
{
d[i][j] = 100000000;
if (X[i] == Y[j])
{
copy = d[i - 1][j - 1] + copyCost;
d[i][j] = Min(d[i][j],copy);
//复制到另一个串中,而不是在源串上直接修改,故需要代价
}
else
{
replace = d[i - 1][j - 1] + replaceCost;
d[i][j] = Min(d[i][j],replace);
}
del = d[i - 1][j] + deleteCost;
d[i][j] = Min(d[i][j],del);
insert = d[i][j - 1] + insertCost;
d[i][j] = Min(d[i][j],insert);
if (i >= 2 && j >= 2 && X[i] == Y[j - 1] && X[i - 1] == Y[j])
{
twiddle = d[i - 2][j - 2] + twiddleCost;
d[i][j] = Min(d[i][j],twiddle);
}
}
}
//处理Kill
for (int i = 1;i < lenX;i++)
{
if (d[lenX][lenY] > d[i][lenY] + deleteCost * (lenX - i) - 1)
{
//cost(kill) = 被删除的串长*cost(delete)-1;
//X的1~i个串 转化成 Y串(1~lenY),X串中剩余的串(i+1~lenX)全部kill掉,
//删除长度为 lenX - (i + 1) + 1 = lenX - i
//注意:for循环中,i不能等于lenX,
//原因:当i = lenX时,即d[lenX][lenY]时,相当于X串和Y串全部匹配了,不用再Kill了
d[lenX][lenY] = d[i][lenY] + deleteCost * (lenX - i) - 1;
}
}
return d[lenX][lenY];
}
int main()
{
int lenX = strlen(X) - 1;
int lenY = strlen(Y) - 1;
cout<<EditDistance(lenX,lenY)<<endl;
//PrintD(lenX,lenY);
system("pause");
return 0;
}
结果输出:48
编辑距离与LCS的联系:
文本串X要变成文本串Y,要转换的次数:(最少|LenX - LenY|,最多max(LenX,Leny))
他们两个很相似的原因,在考虑状态转移方程时,需要考虑的地方很相似。我们在状态转移方程的比较可以看出
参考资料
http://blog.youkuaiyun.com/mishifangxiangdefeng/article/details/7925025
http://hi.baidu.com/agosits/item/41f42c39e1011c9bc2cf2977