在之前的文章中我讲解了用最长公共子序列和最长公共子串的方法来求两个字符串的相似度问题,本文来讲解如何通过最少编辑距离算法求解两个文本的相似度问题。
首先来了解一下什么是编辑距离,编辑距离是这样定义的
对于两个字符串,由其中一个字符串转化为另外一个字符串所需要的操作次数叫做编辑距离。
这里允许的操作只有三种
- 将一个字符替换为另一个字符
- 插入一个字符
- 删除一个字符
那么最少编辑距离就是需要操作次数最少。举个简单的例子,比如要将kitten转换为sitting,则进行如下3步操作
- 将字符串kitten中的k替换为s,得到sitten
- 将字符串sitten中的e替换为i,得到sittin
- 将字符串sittin末尾加上g,得到sitting
这样就完成了从kitten到sitting的转换,并且编辑操作最少,编辑距离为3。编辑距离又称Levenshtein距离,是俄罗斯科学家Vladimir Levenshtein在1965年提出的。
那么给定两个字符串,如何来求它们的最少编辑距离呢? 通过暴力方法不太行,看起来是一个比较难的问题。考虑动态规划法,设dp[i][j]表示字符串S=[s_0, s_1, ..., s_i]和字符串T=[t_0, t_1, ..., t_j]的最少编辑距离,那么
- 当s_i=t_j时,从dp[i - 1][j - 1]到dp[i][j]不需要做编辑,因为s_i已经和t_j相等了,dp[i - 1][j]或dp[i][j - 1]到dp[i][j]都需要做一次编辑,即删除一个字符或加上一个字符
- 当s_i≠t_j时,从dp[i - 1][j - 1]到dp[i][j]需要做一次字符替换的编辑,而dp[i - 1][j]或dp[i][j - 1]到dp[i][j]都需要做一次编辑,即删除一个字符或加上一个字符
- 最终dp[i][j]需要取三者中间最小的那个值
那么,按照这个思路,得到其状态转移方程如下
初始化条件为dp[i][0]=dp[0][i]=0,代码如下
int Lenv(char S[], char T[]) {
int n = strlen(S);
int m = strlen(T);
for(int i = 0; i <= n; i++)
dp[i][0] = i;
for(int i = 0; i <= m; i++)
dp[0][i] = i;
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + 1;
if(S[i-1] == T[j-1]) {
dp[i][j] = min(dp[i][j], dp[i-1][j-1]);
} else {
dp[i][j] = min(dp[i][j], dp[i-1][j-1] + 1);
}
}
}
return dp[n][m];
}
如果最少编辑距离越小,说明这两个字符串就越相似。通常编辑距离在面试中也可能会被问到