1.实践题目
编辑距离问题
2. 问题描述
给出两个字符串,提供三种操作:增删改,求使两个字符串相等最少需要编辑多少次
3. 算法描述(不能粘贴程序)
总的分为两种情况:
A.当一个字符串为空的情况,比如说有字符串1和字符串2,当字符串1位空的时候,字符串1增加和字符串2的一样的字符就和字符串2相等;当字符串2为空的时候,字符串1删除所有字符就和字符串2相等了,所以第一种情况其中一个字符串为空,长度为零的时候,状态方程为:
for(int i = 0; i < 字符串1长度(行数)) dp[i][0] = i;
for(int i = 0; i < 字符串2长度(列数)) dp[0][i] = i;
B.当两个字符串都非空的时候,这个时候就有三种操作,增删改,我们当前的状态是由子状态的增删改这三种方法中最优的方法来决定的,这三种方法我们可以这样子来定义:
增:dp[i][j] = dp[i][j-1] + 1 (j字符串的子状态增加一个达到了目前的j字符串)
删:dp[i][j] = dp[i-1][j] + 1 (i字符串的子状态删除一个达到了目前的i字符串)
改:dp[i][j] = dp[i-1][j-1] + 1 (替换操作不改变字符串的长度)
还有种情况若是i j下标所指的字符串相同,则不用改变
看下面的表格可以更直观:
|
| A | B | C | D | E |
| 0(皆空) | 1 | 2 | 3 | 4 | 5 |
A | 1 | 0 | 1 | 2 |
|
|
B | 2 |
|
|
|
|
|
C | 3 |
|
|
|
|
|
D | 4 |
|
|
|
|
|
可以看到A,A坐标的那个0,首先我们先判断是否相等,相等的话就等于左上角的数值,也就是没有增没有减也没有替换。不相等的话我们才开始做三个操作取最优,就比如说B,A坐标的格子,AB和A字符相比,不一样,然后左,上,左上三个方向分别加一去左侧加一为最优也就是原有的A字符增加一个B
所以我们可以列出动归的状态方程:
dp[i][j] = str1[i]==str2[j] ? dp[i-1][j-1] :min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+1)
当我们遍历完两个字符串后才能得到最小编辑距离也就是右下角的dp[行][列],而每个状态的子状态来自左,上和左上,我们从左到右,从上到下去填表时不会影响状态方程的。
代码:
#include <iostream>
#include <stdio.h>
#include <cstring>
using namespace std;
int main(){
char str1[2005],str2[2005];
cin>>(str1+1);
cin>>(str2+1);
int ll, lr;
ll = strlen(str1+1);
lr = strlen(str2+1);
//i和j分别是字符串一和字符串二的下标索引,a[i][j]代表第一个字符串0到i和第二个字符串0到j
//空串判断,当i为零时,i添加j个元素与j相等,或者j为零的时候,j删除j个元素和i相等,这两种情况创造了动归的最临界状态
//临界状态外的判断:增加:第一个字符串增加最后一个元素和第二个字符串最后一个元素相同,那么两个字符串最后一个元素不需要考虑,i增加了一个,所以还是i,j归为j-1
//删除同理:状态方程:a[i][j] = 1 + a[i-1][j];
//替换状态方程:a[i][j] = 1 + a[i-1][j-1];
//第一步先构造临界状态
int **a=new int*[ll+1];
for(int i=0;i<=ll;i++) a[i]=new int[lr+1];
for(int i=0;i<=ll; i++){
a[i][0]=i;
}
for(int j = 0;j<= lr; j++){
a[0][j] = j;
}
//从左上到右下,逐步判断三个情况那个最小更新新的状态
for(int i=1;i<=ll;i++)
{
for(int j=1;j<=lr;j++)
{
//如果相等就不用操作
int flag;
if(str1[i]==str2[j]){
a[i][j] = a[i-1][j-1];
}else{
//取最小
a[i][j] = min(min(a[i-1][j]+1,a[i][j-1]+1),a[i-1][j-1]+1);
}
}
}
printf("%d",a[ll][lr]);
}
4. 算法时间及空间复杂度分析(要有分析过程)
可以观察到它调用了两层循环,然后每层大小分别是字符串1的长度m和字符串2的长度n,所以我们的时间复杂度是O(m*n);
调用了一个二维数组dp[m][n],空间复杂度为O(m*n);
5. 程序运行截图
6.心得体会(对本次实践收获及疑惑进行总结)
心得:
收获就是列表的时候,开头都是空出了两格,专门用来表示其中一个字符串为空的情况,也为后面的两个字符串非空的情况提供了临界状态,这是一个很取巧的技巧。