编辑距离(C++)

给定两个字符串s1和s2,计算出将s1转换成s2所用的最少操作数。可以对一个字符串进行如下三种操作:1.插入一个字符;2.删除一个字符;3.替换一个字符。

递归法的解题方式:
两个字符串从后(i=s1.length()-1,j=s2.length()-1)往前比较,递归三要素之一——终止条件base case:i=-1或j=-1结束;递归三要素之二——终止时的处理:返回另一个字符剩下的长度。即base case是i走完s1或j走完s2,可以直接返回另一个字符串剩下的长度,这是因为当i走完s1,j没走完s2时,用插入操作把s2剩下的字符插入s1,需要j+1步(因为j从0开始);而当j走完s2而i没走完s1时,用删除操作把s1前面没处理的字符删掉,需要i+1步(因为j从0开始)。
递归要素之三————提取重复逻辑。当比较的两个字符相同时,两字符串都向前搜索;而两个字符不同时,判断插入、删除、替换后哪个步骤最少,就从选那条路径。不能理解的读者可以看以前的一篇博文适合新手的递归理解。插入、删除、替换是怎么操作的可以看下列代码及注释。

int dp(string s1, string s2, int i, int j)
{
	//i走完s1,j没走完s2,用插入操作把s2剩下的字符插入s1,需要j+1步(因为j从0开始)
	if (i == -1)
		return j + 1;
	//j走完s2而i没走完s1,用删除操作把s1前面没处理的字符删掉,需要i+1步(因为j从0开始)
	if (j == -1)
		return i + 1;
	//两个字符相同,直接往前移动i,j即可
	if (s1[i] == s2[j])
		return dp(s1, s2, i - 1, j - 1);
	else
		//插入:i不往前移,j往前移,说明只处理了s2,这是往s1中插入一个字符导致的。处理步骤+1。
		//删除:i往前移,j不往前移,说明s1处理了一个字符而s2没处理,这是s1中删除了一个字符。处理步骤+1。
		//替换:i往前移,j往前移,说明既处理了s1,也处理了s2,是为替换字符。处理步骤+1。
		//递归步骤最少的那种(即把其他两种步骤多的排除了)。
		return min(min(dp(s1, s2, i, j - 1) + 1, dp(s1, s2, i - 1, j) + 1), dp(s1, s2, i - 1, j - 1) + 1);
}

动态规划代码:
单纯的递归代码会有很多重复计算的步骤,画出树形结构就能直观看出,因此采用动态规划法,利用备忘录数组dp[][]来保存结果,
动态规划代码首先遇到的问题是数组的初始值,当s2为空,那么s1的长度就是要删除的步骤数,即dp[i][0]=i;当s1为空,s2的长度就是s1要插入的步骤数,即dp[0][j]=j。然后自底向上求解,递归是自顶向下。

int minDistance2(string s1, string s2)
{
	int m = s1.length();
	int n = s2.length();
	vector<vector<int>> dp(m + 1, vector<int>(n + 1));
	for (int i = 1; i <= m; i++)//<=说明遍历的次数是m-1+1次,即字符串的长度
	{
		dp[i][0] = i;
	}
	for (int j = 1; j <= n; j++)
	{
		dp[0][j] = j;
	}
	for (int i = 1; i <= m; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			//if (s1[i] == s2[j])//运行结果不对,因为第i个字符的索引为i-1
			if (s1[i-1] == s2[j-1])
			{
				dp[i][j] = dp[i - 1][j - 1];//第i行j列的步骤数等于第i-1行j-1列,因为字符相同不需什么操作,所以不用+1
			}
			else
			{
				dp[i][j] = min(min(dp[i - 1][j]+1, dp[i][j - 1]+1), dp[i - 1][j - 1]+1);//+1表示经过了一次操作
			}
		}
	}
	return dp[m][n];
}
posted @ 2020-07-07 11:27  江中之苇  阅读(1330)  评论(0编辑  收藏  举报