您的位置:首页 > 编程语言

编程之美:第三章 结构之法 3.3计算字符串的相似度

2015-08-03 00:54 453 查看
/*
计算字符串的相似度:
对于不同的字符串。我们定义一套操作方法来把两个不相同的字符串变相同,具体方法:
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[1,...,lenA]和B[2,...,lenB]变成相同字符串
3一步操作之后,再将A[2,...,lenA]和B[2,...,lenB]变成相同字符串

这个递归程序有些类似于前序和中序建立后序二叉树
优化:采用记忆化搜索,设定一个剪枝数组,如果有这个值,直接返回

输入:
abcdefg abcdef
abcd ab
machao mayan
machao zhuwenping
输出:
0.50
0.33
0.25
0.09
*/

#include <stdio.h>
#include <string.h>
const int MAXSIZE = 10000;

int min(int a,int b,int c)
{
	int iMin = a < b ? a : b;
	return iMin < c ? iMin : c;
}

int calStrDistance(char* strA,char* strB,int iBegA,int iEndA,int iBegB,int iEndB,int g_iDisArr[][100])
{
	if(iBegA > iEndA)//如果起始位置大于终点位置,表明连最后一个字符也判断结束了,此时,计算另一个字符串中结束位置-开始位置+1,就是距离值
	{
		if(iBegB > iEndB)
		{
			return g_iDisArr[iBegA][iBegB] = 0;
		}
		else
		{
			return g_iDisArr[iBegA][iBegB] = (iEndB - iBegB + 1);
		}
	}
	if(iBegB > iEndB)
	{
		if(iBegA > iEndA)
		{
			return g_iDisArr[iBegA][iBegB] = 0;
		}
		else
		{
			return g_iDisArr[iBegA][iBegB] = (iEndA - iBegA + 1);
		}
	}
	if(strA[iBegA] == strB[iBegB])//如果两个字符相同,那么双方都继续向下各自遍历
	{
		if(g_iDisArr[iBegA+1][iBegB+1] != -1)//如果已经计算过了,那么直接返回
		{
			return g_iDisArr[iBegA+1][iBegB+1];
		}
		else
		{
			return g_iDisArr[iBegA+1][iBegB+1] = calStrDistance(strA,strB,iBegA+1,iEndA,iBegB+1,iEndB,g_iDisArr);
		}
	}
	else
	{
		int iDis1,iDis2,iDis3;
		if(g_iDisArr[iBegA+1][iBegB] != -1)
		{
			iDis1 = g_iDisArr[iBegA+1][iBegB];
		}
		else
		{
			iDis1 = g_iDisArr[iBegA+1][iBegB] = calStrDistance(strA,strB,iBegA+1,iEndA,iBegB,iEndB,g_iDisArr);//如果不同,参见删除另一个字符串的当前字符,然后双方继续向下比较
		}
		if(g_iDisArr[iBegA][iBegB+1] != -1)
		{
			iDis2 = g_iDisArr[iBegA][iBegB+1];
		}
		{
			iDis2 = g_iDisArr[iBegA][iBegB+1] = calStrDistance(strA,strB,iBegA,iEndA,iBegB+1,iEndB,g_iDisArr);//如果不同,参加把一个字符串的当前字符加到另一个字符串的首位置,继续比较
		}
		if(g_iDisArr[iBegA+1][iBegB+1] != -1)
		{
			iDis3 = g_iDisArr[iBegA+1][iBegB+1];
		}
		else
		{
			iDis3 = g_iDisArr[iBegA+1][iBegB+1] = calStrDistance(strA,strB,iBegA+1,iEndA,iBegB+1,iEndB,g_iDisArr);//如果不同,参见修改一个字符串的第一个字符为另一个字符串的第一个字符
		}
		return g_iDisArr[iBegA][iBegB] =  ( min(iDis1,iDis2,iDis3) + 1 );//注意因为本身已经有一个值不同了,因此这里要加上1
	}
}

void process()
{
	char strA[MAXSIZE],strB[MAXSIZE];
	while(EOF != scanf("%s %s",strA,strB))
	{
		int g_iDisArr[100][100];
		memset(g_iDisArr,-1,sizeof(g_iDisArr));
		int iDis = calStrDistance(strA,strB,0,strlen(strA)-1,0,strlen(strB)-1,g_iDisArr);
		printf("%.2f\n",1.0/(iDis + 1));//相似度 = 距离加1的倒数
	}
}

int main(int argc,char* argv[])
{
	process();
	getchar();
	return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: