许多程序会大量使用字符串。对于不同的字符串,我们希望能够有办法判断其相似程序。我们定义一套操作方法来把两个不相同的字符串变得相同,具体的操作方法为:
1.修改一个字符(如把“a”替换为“b”);
2.增加一个字符(如把“abdd”变为“aebdd”);
3.删除一个字符(如把“travelling”变为“traveling”);
比如,对于“abcdefg”和“abcdef”两个字符串来说,我们认为可以通过增加/减少一个“g”的方式来达到目的。上面的两种方案,都仅需要一 次 。把这个操作所需要的次数定义为两个字符串的距离,而相似度等于“距离+1”的倒数。也就是说,“abcdefg”和“abcdef”的距离为1,相似度 为1/2=0.5。
给定任意两个字符串,你是否能写出一个算法来计算它们的相似度呢?
原文的分析与解法
不难看出,两个字符串的距离肯定不超过它们的长度之和(我们可以通过删除操作把两个串都转化为空串)。虽然这个结论对结果没有帮助,但至少可以知道,任意两个字符串的距离都是有限的。
我们还是就住集中考虑如何才能把这个问题转化成规模较小的同样的子问题。如果有两个串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[2,…,lenA]和B[2,…,lenB]变成相字符串。
3.一步操作之后,再将A[1,…,lenA]和B[2,…,lenB]变成相字符串。
这样,很快就可以完成一个递归程序。
View Code
int calculateStringDistance(string strA, int pABegin, int pAEnd, string strB, int pBBegin, int pBEnd)
{
if(pABegin > pAEnd)
{
if(pBBegin > pBEnd)
return 0;
else
return pBEnd - pBBegin + 1;
}
if(pBBegin > pBEnd)
{
if(pABegin > pAEnd)
return 0;
else
return pAEnd - pABegin + 1;
}
if(strA[pABegin] == strB[pBBegin])
{
return calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);
}
else
{
int t1 = calculateStringDistance(strA, pABegin, pAEnd, strB, pBBegin+1, pBEnd);
int t2 = calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin, pBEnd);
int t3 = calculateStringDistance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);
return minValue(t1, t2, t3) + 1;
}
}
动态规划算法:
下面是一个简单直观的,备忘录版的动态规划,每次判断是否计算过了,如果计算过了直接拿来用,没有计算过就计算之。 当初看算法导论的时候觉得备忘录简直就是多次一举,现在发现其他它可是个好东西,就这题而言,备忘录版不比普通动态规划算法慢多少,但是实现上简单了很多,而且易于理解。记得高质量C/C++作者说过,程序应该在保证清晰、可读下,去提高算法效率,而不是一味的追求效率。
#include <string.h>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 100;
static int pData[M][M];
int CalculateStringDistance(char *strOne, int strOneBeginIndex, int strOneEndIndex, char *strTwo, int strTwoBeginIndex, int strTwoEndIndex);
int main(int argc, char* argv[])
{
char str1[] = "abcdefabcdefabcdefabcdefabcdef";
char str2[] = "abcdefgabcdefgabcdefgabcdefgabcdefg";
int m = sizeof(str1) / sizeof(str1[0]);
int n = sizeof(str1) / sizeof(str1[0]);
memset(pData, -1, sizeof(pData));
cout << 1.0 / (CalculateStringDistance(str1, 0, sizeof(str1)/sizeof(char) - 1, str2, 0, sizeof(str2)/sizeof(char) - 1) + 1 )<< endl;;
return 0;
}
int CalculateStringDistance(char *strOne, int strOneBeginIndex, int strOneEndIndex, char *strTwo, int strTwoBeginIndex, int strTwoEndIndex)
{
if (strOneBeginIndex > strTwoEndIndex)
{
if (strTwoBeginIndex > strTwoEndIndex)
{
return 0;
}
else
{
return strTwoEndIndex - strOneBeginIndex;
}
}
if ( strOne[strOneBeginIndex] == strTwo[strTwoBeginIndex])
{
if (pData[strOneBeginIndex][strTwoBeginIndex] == -1)
{
pData[strOneBeginIndex][strTwoBeginIndex] = CalculateStringDistance( strOne, strOneBeginIndex + 1, strOneEndIndex, strTwo, strTwoBeginIndex + 1, strTwoEndIndex);
}
return pData[strOneBeginIndex][strTwoBeginIndex] ;
}
else
{
if ( pData[strOneBeginIndex + 1][strTwoBeginIndex] == -1)
{
pData[strOneBeginIndex + 1][strTwoBeginIndex] =CalculateStringDistance(strOne, strOneBeginIndex + 1, strOneEndIndex, strTwo, strTwoBeginIndex, strTwoEndIndex);
}
int t1 = pData[strOneBeginIndex + 1][strTwoBeginIndex];
if ( pData[strOneBeginIndex + 1][strTwoBeginIndex + 1] == -1)
{
pData[strOneBeginIndex + 1][strTwoBeginIndex + 1] =CalculateStringDistance(strOne, strOneBeginIndex + 1, strOneEndIndex, strTwo, strTwoBeginIndex + 1, strTwoEndIndex);
}
int t2 = pData[strOneBeginIndex + 1][strTwoBeginIndex + 1];
if ( pData[strOneBeginIndex][strTwoBeginIndex + 1] == -1)
{
pData[strOneBeginIndex][strTwoBeginIndex + 1] =CalculateStringDistance(strOne, strOneBeginIndex , strOneEndIndex, strTwo, strTwoBeginIndex + 1, strTwoEndIndex);
}
int t3 = pData[strOneBeginIndex][strTwoBeginIndex + 1];
return min( min(t1, t2), t3) + 1;
}
}