字符串的相似度

问题描述

    给定两个字符串s1和s2,测定它们之间的相似度。相似度的定义如下:假设s1 = "abcde",s2 = "abcd",那么我们可以经过一次编辑(删掉s1的字符e,或者在s2末尾插入字符e)将它们变为相同的,这个编辑次数称为距离,而相似度为1/(距离+1)。

    那么,我们只要求计算出任意两个字符串的距离,问题便迎刃而解了。关于编辑总共有三种操作,删除、插入或者修改(复制),在下面的分析我们统称为操作。


解法一:

    对于s1[i]和s2[j]有以下三种情况:

    1、相等(或者不等时将两者修改使之相等),那么我们继续检查s1[i+1...]和s2[j+1...];

    2、不等,但经过一次操作(删除s1[i]或者在s2[j]之前插入s1[i])之后,我们继续检查s1[i+1....]和s2[j...];

    3、不等,但经过一次操作(删除s2[j]或者在s1[i]之前插入s2[j])后,继续检查s1[i....]和s2[j+1....]。

上述括号里面的内容可以不看,那只是能够造成我们如后续处理字符串所可能执行的操作,对本题计算距离没有影响。


根据上述的解法步骤,我们可以比较容易的得到一个递归程序string_similarity,代码稍后给出。


解法二:

    该问题明显存在着最优子结构以及重叠子问题(参看算法导论),因而可以改变为动态规划算法,这样程序可以运行得更快,在这个问题并没有减少渐进时间,不过有效降低了常数因子和递归开销。状态转移函数如下:

根据上述转移函数,可以比较容易的写出程序。下面给出两个程序的代码,都含有比较详细的注释。

#include<iostream>
#include<vector>
#include<string>

using namespace std;

class string_similarity
{//求两个字符串的相似度
private:
	string s1;
	string s2;
	size_t similarity_recurse(size_t, size_t, size_t, size_t);
public:
	string_similarity(const string &s11, const string &s22) :s1(move(s11)), s2(move(s22)){}
	size_t similarity_recurse(){ return similarity_recurse(0, s1.size() - 1, 0, s2.size() - 1); }
	size_t similarity_dynamic();
};

size_t string_similarity::similarity_recurse(size_t beg1, size_t end1, size_t beg2, size_t end2)
{//解法一,递归法,指数时间复杂度,其中n = length[s1],m = length[s2]。
	if (beg1 > end1)
	{//若字符串s1已经遍历到头
		if (beg2 > end2) return 0;
		else return end2 - beg2 + 1;
	}
	if (beg2 > end2)
	{//若字符串s2已经遍历到头
		if (beg1 > end1) return 0;
		else return end1 - beg1 + 1;
	}
	if (s1[beg1] == s2[beg2])//若当前两字符相等
		return similarity_recurse(++beg1, end1, ++beg2, end2);//则递归计算字串
	else
	{//若不想等,分三种情况
		size_t l1 = similarity_recurse(beg1 + 1, end1, beg2, end2);
		size_t l2 = similarity_recurse(beg1, end1, beg2 + 1, end2);
		size_t l3 = similarity_recurse(beg1 + 1, end1, beg2 + 1, end2);
		return (l1 < l2 ? (l1 < l3 ? l1 : l3) : (l2 < l3 ? l2 : l3)) + 1;//取最小值再加1
	}
}

size_t string_similarity::similarity_dynamic()
{//动态规划实现,时间O(nm),但是相比于递归,减少了递归所需的开销,并且保存了子问题,因而
	//杜绝了不必要的重复子问题的计算,故在空间和时间(常数因子更小)方面都更加优越
	//dis[i][j]表示s1前i个字符(下标0,1...i - 1)和s2前j个字符(下标0,1...j - 1)之间的距离
	size_t n = s1.size(), m = s2.size();
	vector<vector<size_t>> dis(n + 1);
	for (size_t i = 0; i != dis.size(); ++i)
		dis[i].resize(m + 1);//dis的下标要达到dis[n][m]
	for (size_t i = 0; i <= n; ++i)//初始化
		dis[i][0] = i;//1、j = 0 时
	for (size_t j = 0; j <= m; ++j)
		dis[0][j] = j;//2、i = 0 时
	for (size_t i = 1; i <= n; ++i)
	{//s1的第i个字符
		for (size_t j = 1; j <= m; ++j)
		{//与s2的第j个字符
			if (s1[i - 1] == s2[j - 1])//3、若想等
				dis[i][j] = dis[i - 1][j - 1];
			else //4、否则。其实这三个距离对应递归程序的三种情况
				dis[i][j] = (dis[i - 1][j - 1] < dis[i - 1][j] ?
				(dis[i - 1][j - 1] < dis[i][j - 1] ? dis[i - 1][j - 1] : dis[i][j - 1]) :
				(dis[i - 1][j] < dis[i][j - 1] ? dis[i - 1][j]:dis[i][j - 1])) + 1;
		}
	}
	return dis[n][m];
}

int main()
{
	string s1 = "abcdef", s2 = "abdce";
	string_similarity ss(s1, s2);
	cout << ss.similarity_recurse() << endl;
	cout << ss.similarity_dynamic() << endl;
	system("pause");
	return 0;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值