编辑距离DP算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ljt735029684/article/details/80869654

偶然看到这道经典题,顺便复习下DP, 


由于懒得做图,所以,需要图片或者其他讲法,请参考这篇https://blog.csdn.net/chichoxian/article/details/53944188。 可能你需要配合这位作者的图片才能更好理解


有两个字符串str1跟str2, 设他们的最短距离为str1变为str2的操作最小次数,有以下三种操作

1. 插入字符,如abc  -> abcd

2. 删除字符 ,如abc -> ac

3. 替换字符,   如abc -> adc

输入两个字符串,请计算两个字符串的最短距离


由于我们发现,一个字符串中,所有子字符串(或者说字符)都可能有以上操作,所有此问题具有重复子问题。

这是一道经典的dp算法, 设i为str1[:i], str2[:j](python表达方法,str[:i]意味着str[0]到str[i], 读者注意str[i]是一个字符,str[:i]是一个子字符串)

 状态

    dp[i][j]定义为str1[:i]到str2[:j]的最短距离

    思路是这样的,先考虑最后一步,此时i是str1的最后一个字符,j是str2的最后一个字符, 那么有几种可能

1.  str1[i] == str2[j],即最后一个字符串相等

          那么不进行任何操作,就使用前面最优解, dp[i][j] = dp[i-1][j-1] + 0 =  dp[i-1][j-1]

2.  str1[i] != str2[j], 那么就需要进行操作,即插入,删除,替换

    if len(str1) >len (str2)

        那么最优解明显是删除字符str[i],则代表 dp[i][j] = dp[i][j-1] + 1(意味着dp[i][j-1]时构建的最优字符串再删除str[i]才能达到此时dp[i][j]最优解)(i > j, 那么i -1 == j, 由于我们每次只操作一个字符,所以i >j时,i-1则==j)

    else if  len(str1) < len(str2)

        插入操作, 插入str[i], dp[i][j] = dp[i-1][j] + 1(意味着, dp[i-1][j]时构建的最优字符串,需要再插入str[i]才能达到最优解)(i < j, 那么 i+1 == j)

    else len(str1) == len(str2)

        替换操作,dp[i][j] = dp[i-1][j-1] + 1(意味着,dp[i-1][j-1]构建的字符串,再把str[i]变成str[j]才能达到最优解)(i==j, 那么i-1 == j-1)


    由于此问题具有重复子问题,所有从最优解回到一开始,我们考虑的这几种情况会重复运用到前面的所有情况,即相当于所有长度,字符相等或者不等,我们都考虑了,并解决了(使用插入,删除,替换等), 因此可以不用考虑中间某个字符的插入,删除,替换操作怎样,因为在这个字符作为末尾的时候就已经判断过了


当然,上面的条件判断长度关系, 是没有必要的,那是搜索时用的判断条件(否则直接搜索会因为搜索广度或者深度没有上限而不法直接搜索),使用dp时我们可以每次判断都直接使用三种插入方法,并取最优解。说长度长时删除,短时插入,只是用搜索方便来理解dp

状态转移方程

    即str1[i] != str2[j]时有

            dp[i][j] = min{dp[i-1][j-1], dp[i][j-1], dp[i-1][j]} + 1 

    str1[i] == str2[j]时, 有

            dp[i][j] = dp[i-1][j-1]


边界

    先说结论,边界是空字符串""到str1跟str2的距离。 为什么边界是""呢,本人一开始也是用str1[0]与str2[0],之类的作为边界,发现在矩阵dp[0][j]以及dp[i][0]时需要一个个用上面搜索的方法判断的窘境,因此,最后也是发现,多使用一行空字符串的话,就能很简单解决

因此, 边界为dp[0][j] = j, 以及dp[i][0] = i, 意味着从空字符串插入一步步构建str1跟str2子字符串的过程


你可能发现了一个问题, 矩阵dp[i][j]的第0行与第0列代表着空字符串到str1的最短距离以及到str2的最短距离。这个问题会导致一个注意点—— 矩阵的dp[i][j]其实意味着str[i-1]到str[j-1]的距离(因为dp矩阵多了一行一列), 因此,所有的判断字符,应该是str1[i-1]==str2[j-1]而不是str[i]到str[j], i,j的取值范围是[0, len(str)]闭区间


下面给出代码

/*
	定义状态:
		dp[i][j]为字符串str1的前i个字符,到str2的前j个字符的最短距离 
		
	状态转移:
		两个字符串相等时:
			相等:dp[i][j] = dp[i-1][j-1]
		不等时: 
			插入:dp[i][j] = dp[i][j-1] + 1  		//此时, i < j, 使用插入的i 
			删除: dp[i][j] = dp[i-1][j] + 1  		//此时,i > j,  i-1与j达到同长 
			替换:dp[i][j] = dp[i-1][j-1] + 1 		//此时  i == j,
		边界:
			从空字符串""到str,插入i次, 
			dp[i][0] = i
			dp[0][j] = j 
		
	注意:字符串是从0开始的,而0行0列考虑的是空字符串 
		 
*/
#include<iostream>
#include<string>
#include<algorithm> 
#define MAXV 256

using namespace std;

string str1, str2;
int dp[MAXV][MAXV];


int distance()
{
	int len1 = str1.length();
	int len2 = str2.length();
	 
	for(int i = 0; i <= len1; i++)
		dp[i][0] = i;
	for(int j = 0; j <= len2; j++)
		dp[0][j] = j;
	
	for(int i = 1; i <= len1; i++)
	{
		for(int j = 1; j <= len2; j++)
		{
			if(str1[i - 1] == str2[j - 1])
			{
				dp[i][j] = dp[i - 1][j - 1];
			}
			else
			{
				dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
			}
			
		}
		
	}
	
	for(int i = 0; i <= len1; i++)
	{
		for(int j =0; j <= len2; j++)
			cout << dp[i][j] << " ";
		cout << endl;	
	}
	
	return dp[len1][len2];
}


int main()
{
	cin >> str1 >> str2;
	
	cout << distance() << endl;
	
		
	
	return 0;
} 



猜你喜欢

转载自blog.csdn.net/ljt735029684/article/details/80869654