编辑距离问题
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被改变成什么样子了