编辑距离问题

编辑距离问题

        1.最小操作次数

        2.优化(每一次具体的操作)

        3.思考:操作权重,增加操作---交换字符

1.最小操作次数

将一个字符串A转换成字符串B

 可能会有两种情况        1.s[i]==s[j]

                                      2.s[i]!=s[j]

会有四种操作        1.删除        2.替换        3.插入        4.跳过

我们可以得到一个基本模板

                                1.s[i]==s[j]

                                        跳过

                                2.s[i]!=s[j]

                                        删除,替换,插入

一张图来模拟一下这个操作

 如果想要计数完全就需要一定的初始化

        将dp[0...n][0]和dp[0][0...m]初始化

(1).记忆化搜索 

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
string s1, s2;
int dp[1001][1001];
int MIN(int a, int b, int c)
{
	int x;
	x = min(a, b);
	return x = min(x, c);
}
int dfs(int i, int j)
{
	if (dp[i][j])
		return dp[i][j];			//记忆化搜索
	/*由于是从下标0处开始存储字符串的,所以应该让i,j==-1的时候再返回,*/
	if (i == -1) return 0;	
	if (j == -1) return 0;
	if (s1[i] == s2[j])
	{
		dp[i][j]= dfs(i - 1, j - 1);
	}
	else
	{
		dp[i][j]= MIN(dfs(i - 1, j - 1), dfs(i, j - 1), dfs(i - 1, j)) + 1;
		/*
				if(s1[i]!=s2[j])
		插入:  在s1[i]中插入一个和s2[j]一样的字符 s1.size()+1  j向前,i不变继续匹配
		删除: 把s1[i]这个字符删掉,s1.size()-1,i会自动前移,j不变继续匹配
		替换: 把s1[i]这个字符替换为s2[j],这样他们就匹配了,然后i,j同时前移
		*/
	}
	return dp[i][j];
}
int main()
{
	cin >> s1 >> s2;
	cout<<dfs(s1.size(), s2.size());
	
}

(2)dp(状态转移)

在记忆化搜索部分中,我们已经列出了可能的四种状态转移情况,那么改写dp的过程中我们的操作就简单的多了,值得一提的是,dp的时间复杂度是O(s1.size()*s2.size())

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
string s1, s2;
int dp[1001][1001];
int MIN(int a, int b, int c)
{
	int x;
	x = min(a, b);
	return x = min(x, c);
}
/*for循环和dfs搜索的区别是
dfs搜索的时间复杂度更高,他是从字符串的尾部开始进入搜索的,等到递归返回的时候才开始计数
for循环的时间复杂度较小,是从字符串的头部开始进入查找的,边选择操作边计数*/
int main()
{
	cin >> s1 >> s2;
	int n = s1.size();
	int m = s2.size();
	for(int i=1;i<=n;i++)
		for (int j = 1;j <= m;j++)
		{
			if (s1[i-1] == s2[j-1])
				dp[i][j] = dp[i - 1][j - 1];
			else
			{
				dp[i][j] = MIN(dp[i - 1][j], dp[i - 1][j - 1], dp[i][j - 1]) + 1;
			}
		}
	cout << dp[n][m];
}

2.优化(每一步具体的操作)

在知道了最小操作数之后,我们肯定还想知道每一步要怎么做才能够得到最小操作数呢?

在每次选到最小值的时候记录下当前最小值是使用了哪个操作即可

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
string s1, s2;
struct node
{
	int value;				//最小操作数
	int choice;				//选择
	/*
	* 0:跳过
	* 1:插入
	* 2:替换
	* 3:删除
	*/
	node()
	{
		value = 0;
		choice = 0;
	}
	node(int a, int b)
	{
		value = a;
		choice = b;
	}
}dp[1001][1001];
node MIN(node a, node b, node c)
{
	node x;
	if (a.value < b.value)
		x.value = a.value, x.choice = 3;
	else
		x.value = b.value, x.choice = 2;
	if (x.value > c.value)
		x.value = c.value, c.choice = 1;
	return x;
}
void print()
{
	int i = s1.size();
	int j = s2.size();
	cout << "将字符串" << s1 << "改写为" << s2 << '\n';
	while (i != 0 && j != 0)
	{//这里switch使用的是i,j	而switch里面写的i-1,j-1
	//是因为dp[i][j]记录的是上一步的选择,而上一步的操作对象是i-1,j-1等
		switch (dp[i][j].choice)
		{
		case 0:cout << "skip" << endl;i--, j--;
			break;
		case 1:cout << "insert" << ' ' << s2[j-1] << endl;i, j--;
			break;
		case 2:cout << "replace" << ' ' << s1[i-1] << " to " << s2[j] << endl;i--, j--;
			break;
		case 3:cout << "delete" << ' ' << s1[i-1] << endl;i--, j;
			break;
		}
	}
	while (i > 0)			//s1还没有走完,说明剩下的都是要删除的
	{
		cout << "delete" << s1[i] << endl;
		i--;
	}
	while (j > 0)			//s2还没有走完,说明剩下的都是要插入的
	{
		cout << "insert" << s2[j] << endl;
		j--;
	}
}
int main()
{	
	cin >> s1 >> s2;
	int n = s1.size();
	int m = s2.size();
	for (int i = 0;i <= n;i++)				//插入初始化
		dp[i][0]=node(i,2);					//dp[i][0]表示s1比s2长i,说明要删除
	for (int j = 0;j <= m;j++)				//删除初始化
		dp[0][j] = node(j, 1);				//dp[0][i]表示s2比s1长i,说明要插入
	for(int i=1;i<=n;i++)
		for (int j = 1;j <= m;j++)
		{
			if (s1[i-1] == s2[j-1])
			{
				dp[i][j].value = dp[i - 1][j - 1].value;
				dp[i][j].choice = 0;
			}
			else
			{
				dp[i][j] = MIN(dp[i - 1][j], dp[i - 1][j - 1], dp[i][j - 1]);
				dp[i][j].value++;
			}
		}
	cout << "最小操作数是:" << dp[n][m].value<<endl;
	print();
}

   3.思考:操作权重,增加操作---交换字符

增加权重比较好操作,我们可以定义一些新的数据结构来替换choice,然后再定义对应的weight,如果要知道操作权重,只需要查看数据结构.weigh就好了

新的操作--交换字符

交换字符可以有两种情况

1.s1[i-1]!=s2[j-1]        s1[i]!=s2[j]

交换后至少一个相等可以交换1

2.交换后让i--,j--而不是++,这样子需要时刻记录s1被改变成什么样子了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值