计算字符串的相似度
提出问题
许多程序会大量使用字符串。对于不同的字符串,我们希望能够有办法判断其相似程度。我们定义了一套操作方法来把两个不相同的字符串变得相同,具体的操作方法为:
1.修改一个字符(如把“a”替换为“b”)。
2.增加一个字符(如把“abdd”变为“aebdd”)。
3.删除一个字符(如把“travelling”变为“traveling”)。
比如,对于“abcdefg”和“abcdef”两个字符串来说,我们认为可以通过增加/减少一个“g“的方式来达到目的。上面的两种方案,都仅需要一次操作。把这个操作所需要的次数定义为两个字符串的距离,给定任意两个字符串,你是否能写出一个算法来计算出它们的距离?
分析与解法
不难看出,两个字符串的距离肯定不超过它们的长度之和(我们可以通过删除操作把两个串都转化为空串)。虽然这个结论对结果没有帮助,但至少可以知道,任意两个字符串的距离都是有限的。
我们还是应该集中考虑如何才能把这个问题转化成规模较小的同样的问题。如果有两个串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]变成相同字符串。
这样,很快就可以完成一个递归程序。
递归解法:
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int MinValue(int a,int b,int c)
{
int min=a;
if(b<min)
min=b;
if(c<min)
min=c;
return min;
}
int Distance(string strA,int pABegin,int pAEnd,
string strB,int pBBegin,int pBEnd)
{
if(pABegin>pAEnd)
{
if(pBBegin>pBEnd)
return 0;
else
return pBEnd-pABegin+1;
}
if(pBBegin>pBEnd)
{
if(pABegin>pAEnd)
return 0;
else
return pAEnd-pABegin+1;
}
if(strA[pABegin]==strB[pBBegin])
return Distance(strA,pABegin+1,pAEnd,strB,pBBegin+1,pBEnd);
else
{
int t1=Distance(strA,pABegin+1,pAEnd,strB,pBBegin,pBEnd);
int t2=Distance(strA,pABegin,pAEnd,strB,pBBegin+1,pBEnd);
int t3=Distance(strA,pABegin+1,pAEnd,strB,pBBegin+1,pBEnd);
return MinValue(t1,t2,t3)+1;
}
}
int main(int argc,char *argv[])
{
string strA,strB;
cin>>strA;
cin>>strB;
int ans=Distance(strA,0,strA.length()-1,strB,0,strB.length()-1);
cout<<ans<<endl;
}
有什么地方需要改进的呢?问题在于:在递归的过程中,有些数据被重复计算了。
为了避免重复计算,我们可以将计算后的结果存储起来。
如下表所示
|
|
|
j |
|
|
|
C(i-1,j-1) |
C(i-1,j) |
|
i |
|
C(i,j-1) |
C(i,j) |
|
|
|
|
|
|
|
|
|
|
|
我们注意到,当我们要计算distance(A[1,i],B[1,j])时,该值仅与红色部分的值相关。因此,我们只需要保存这些值即可。
所以动态规划是个很好地选择;
动态规划法:
#include<iostream>
#include<string>
#include<algorithm>
#define MAX 1000
using namespace std;
int dp[MAX][MAX];
int MinValue(int a,int b,int c)
{
int min=a;
if(b<min)
min=b;
if(c<min)
min=c;
return min;
}
int Distance(string strA,string strB)
{
int i,j;
int lenA=strA.length()+1;
int lenB=strB.length()+1;
for(i=0;i<lenA;i++)
dp[i][0]=i;
for(j=0;j<lenB;j++)
dp[0][j]=j;
dp[0][0]=0;
for(i=1;i<lenA;i++)
{
for(j=1;j<lenB;j++)
{
if(strA[i-1]==strB[j-1])
dp[i][j]=dp[i-1][j-1];
else
dp[i][j]=MinValue(dp[i][j-1],dp[i-1][j],dp[i-1][j-1])+1;
}
}
return dp[lenA-1][lenB-1];
}
int main(int argc,char *argv[])
{
string strA,strB;
cin>>strA;
cin>>strB;
int ans=Distance(strA,strB);
cout<<ans<<endl;
}
与动态规划类似的
备忘录法:
#include<iostream>
#include<string>
#include<algorithm>
#define MAX 1000
using namespace std;
int dis[MAX][MAX];
int MinValue(int a,int b,int c)
{
int min=a;
if(b<min)
min=b;
if(c<min)
min=c;
return min;
}
int Distance(string strA,int pABegin,int pAEnd,
string strB,int pBBegin,int pBEnd)
{
if(dis[pABegin][pBBegin]>=0)
return dis[pABegin][pBBegin];
if(pABegin>pAEnd)
{
if(pBBegin>pBEnd)
dis[pABegin][pBBegin]=0;
else
dis[pABegin][pBBegin]=pBEnd-pABegin+1;
return dis[pABegin][pBBegin];
}
if(pBBegin>pBEnd)
{
if(pABegin>pAEnd)
dis[pABegin][pBBegin]=0;
else
dis[pABegin][pBBegin]=pAEnd-pABegin+1;
return dis[pABegin][pBBegin];
}
if(strA[pABegin]==strB[pBBegin])
{
dis[pABegin][pBBegin]=Distance(strA,pABegin+1,pAEnd,
strB,pBBegin+1,pBEnd);
return dis[pABegin][pBBegin];
}
else
{
int t1=Distance(strA,pABegin+1,pAEnd,strB,pBBegin,pBEnd);
int t2=Distance(strA,pABegin,pAEnd,strB,pBBegin+1,pBEnd);
int t3=Distance(strA,pABegin+1,pAEnd,strB,pBBegin+1,pBEnd);
dis[pABegin][pBBegin]=MinValue(t1,t2,t3)+1;
return dis[pABegin][pBBegin];
}
}
int main(int argc,char *argv[])
{
int i,j;
string strA,strB;
cin>>strA;
cin>>strB;
for(i=0;i<MAX;i++)
for(j=0;j<MAX;j++)
dis[i][j]=-1;
int ans=Distance(strA,0,strA.length()-1,strB,0,strB.length()-1);
cout<<ans<<endl;
}