您的位置:首页 > 其它

字符串编辑距离(相似度)

2016-07-18 22:06 525 查看

编辑距离及编辑距离算法

编辑距离概念描述:
编辑距离,又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。
例如将kitten一字转成sitting:

sitten (k→s)
sittin (e→i)
sitting (→g)

俄罗斯科学家Vladimir Levenshtein在1965年提出这个概念。

问题:找出字符串的编辑距离,即把一个字符串s1最少经过多少步操作变成编程字符串s2,操作有三种,添加一个字符,删除一个字符,修改一个字符

给定一个源串和目标串,能够对源串进行如下操作:

1.在给定位置上插入一个字符

2.替换任意字符

3.删除任意字符

写一个程序,返回最小操作数,使得对源串进行这些操作后等于目标串,源串和目标串的长度都小于2000。

思路

如果有两个串 A = xabcdae 和 B = xfdfa,它们的第一个字符是相同的,只要计算A[2…7] = abcdae 和 B[2…5] = fdfa的距离就可以了。但是如果两个串的第一个字符不相同,那么可以进行如下的操作(lenA和lenB分别是字符串A和B的长度):

(1)删除A串的第一个字符,然后计算A[2…lenA]和B[1…lenB]的距离。

(2)删除B串的第一个字符,然后计算A[1…lenA]和B[2…lenB]的距离。

(3)修改A串的第一个字符为B串的第一个字符,然后计算A[2…lenA]和B[2…lenB]的距离。

(4)修改B串的第一个字符为A串的第一个字符,然后计算A[2…lenA]和B[2…lenB]的距离。

(5)增加B串的第一个字符到A串的第一个字符之前,然后计算A[1…lenA]和B[2…lenB]的距离。

(6)增加A串的第一个字符到B串的第一个字符之前,然后计算A[2…lenA]和B[1…lenB]的距离。

在这个题目中,我们并不在乎两个字符串变得相等之后的字符串是什么样的。所以,我们可以将上面的6个步骤简化为:

(1)一步操作之后,再将A[2…lenA] 和 B[1…lenB]变成相同的字符串。

(2)一步操作之后,再将A[1…lenA] 和 B[2…lenB]变成相同的字符串。

(3)一步操作之后,再将A[2…lenA] 和 B[2…lenB]变成相同的字符串。

这样,很快就可以完成一个递归程序。

在递归的过程中,有些数据被重复计算了。比如,如果我们开始调用StrDistance(A,1,3,B,1,3)

下图是部分展开的递归调用:



可以看到,圈中的两个子问题被重复计算了。为了避免这种不必要的重复计算,可以把子问题计算后的解储存起来。

思路二

编辑距离是动态规划里面的经典题目。 Edit[i][j]为word1[0..i-1]和word2[0..j-1]的最小编辑数。

状态转移方程:



解析:
首先定义这样一个函数——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 ≥ 1 且 j ≥ 1 ,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。

0failing
0
s
a
i
l
n
0failing
001234567
s1
a2
i3
l4
n5
计算edit(1, 1),edit(0, 1) + 1 == 2,edit(1, 0) + 1 == 2,edit(0, 0) + f(1, 1) == 0 + 1 == 1,min(edit(0, 1),edit(1, 0),edit(0, 0) + f(1, 1))==1,因此edit(1, 1) == 1。 依次类推:
0failing
001234567
s11234567
a22
i3
l4
n5
edit(2, 1) + 1 == 3,edit(1, 2) + 1 == 3,edit(1, 1) + f(2, 2) == 1 + 0 == 1,其中s1[2] == 'a' 而 s2[1] == 'f'‘,两者不相同,所以交换相邻字符的操作不计入比较最小数中计算。以此计算,得出最后矩阵为:
0failing
001234567
s11234567
a22123456
i33212345
l44321234
n55432223
递归和edit的代码:
<span style="font-family:Microsoft YaHei;font-size:14px;">#include <iostream>
#include <string>
#include <algorithm>

using namespace std;

int min(int a, int b, int c)
{
int tmp = std::min(a, b);
return std::min(tmp, c);
}
//动态规划解法,数据未重复计算
int edit_distance(const string& str1, const string& str2)
{
int m = str1.size();
int n = str2.size();
if (m == 0 || n == 0) return 0;
//建立数组
int **c = new int*[m + 1];
for (int i = 0; i < m + 1; i++)
c[i] = new int[n + 1];
//初始化数组
for (int i = 0; i < m + 1; i++)
c[i][0] = i;
for (int i = 0; i < n + 1; i++)
c[0][i] = i;
//距离计算
for (int i = 1; i < m+1; i++)
{
for (int j = 1; j < n+1; j++)
{
int d = (str1[i-1] == str2[j-1] ? 0 : 1);
c[i][j] = min(c[i - 1][j] + 1, c[i][j - 1] + 1, c[i - 1][j - 1] + d);
}
}

return c[m]
;

}

//递归解法,数据重复计算
int calculateDistance(string str1, int begin1, int end1, string str2, int begin2, int end2)
{
if (begin1 > end1)//边界条件,str1处理完毕
{
if (begin2 > end2)
return 0;
else
return end2 - begin2 + 1;
}
if (begin2 > end2)//边界条件,str2处理完毕
{
if (begin1 > end1)
return 0;
else
return end1 - begin1 + 1;
}
if (str1[begin1] == str2[begin2])
return calculateDistance(str1, begin1 + 1, end1, str2, begin2 + 1, end2);
else
{
int dis1 = calculateDistance(str1, begin1 + 1, end1, str2, begin2, end2);
int dis2 = calculateDistance(str1, begin1, end1, str2, begin2 + 1, end2);
int dis3 = calculateDistance(str1, begin1 + 1, end1, str2, begin2 + 1, end2);

return min(dis1, dis2, dis3) + 1;
}
}

int main(void)
{
string str1 = "sailn";
string str2 = "failing";
//动态规划解法,数据未重复计算
int r = edit_distance(str1, str2);
cout << "the edit_distance is : " << r << endl;
//递归解法,数据重复计算
int l = calculateDistance(str1, 0, str1.size() - 1, str2, 0, str2.size() - 1);
cout << "the calculateDistance is : " << l << endl;

system("pause");
return 0;
}</span>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息