首先需要解决的问题是:定义一个函数(cost function)计算两个字符串的相差程度。
一个合理的设计是:计算从一个字符串改成另一个字符串需要经过的步数。
更改的三种基本类型
- 替代:更改一个字符,如shot->spot
- 插入: 插入一个字符,如ago->agog
- 删除:删除一个字符,如hour->our
这样计算出从字符串P变到字符串T总共需要多少基本类型,就是编辑距离。
这种相似字符串的匹配算法似乎很难,但是借助DP思想,问题将非常简洁。又是见证奇迹的时候。
递归形式的编辑距离问题
以
i,j
为字符串前缀子串的最后一个字符。
字符串P(长度为m)变T(长度为n),我们以
Pi,Tj
表示下标分别为
i,j
的字符。
定义一个函数D[i,j]表示前缀子串的编辑距离。
P:[1…i] 1 < i < m
T:[1…j] 1< j < n
总共三种方式右对齐两个前缀字符串
如果 Pi=Tj , 子问题变为D[i-1,j-1], 否则为D[i-1,j-1]+1. 意思是:如果最右的字符匹配了,递归计算子串,否则需要替换,代价设为1.
D[i-1,j] + 1 表示的是删除 Pi ,再计算[1…i-1]与[1…j]的编辑距离。因为 Pi 与 Tj 不匹配,将指向P的指针前挪一位,指向T的指针不动。+1表示的代价是删除 Pi 的代价。
- D[i,j-1]+1表示的是在 Pi 后添加一个 Tj ,指向T的指针往左移动一位。因为当前的 Tj 通过对P的插入的方式已经匹配了,接着看下一个字符与 Pi 的对比操作。
这三类操作都是面对一个字符时候的选择策略。
D[i,j] = min{D[i-1,j]+Delete( ),D[i,j-1]+Insert( ), D[i-1,j-1]+Replace( )}
Delete:删除操作
Insert:插入操作
Replace:取代操作,如果字符相同,则为0
如果用递归算法,出口:
D[0,0] = 0;
D[i,0] = i;
D[0,j] = j;
// 递归方法实现
int EditDistanceRecursion( char *X, char *Y, int m, int n )
{
// 基本情况
if( m == 0 && n == 0 )
return 0;
if( m == 0 )
return n;
if( n == 0 )
return m;
// 递归
int left = EditDistanceRecursion(X, Y, m-1, n) + 1;
int right = EditDistanceRecursion(X, Y, m, n-1) + 1;
int corner = EditDistanceRecursion(X, Y, m-1, n-1) + (X[m-1] != Y[n-1]);
return Minimum(left, right, corner);
}
假设三个操作的代价都是1,则上面的算法是递归的算法实现。
比较值得注意的巧妙之处是用(X[m-1] != Y[n-1])来表示0,或者1,比单独比较实现代码简洁得多。
参考:http://www.acmerblog.com/dp5-edit-distance-4883.html
笔记主要来自:《算法设计手册》阅读