算法设计之动态规划

本文深入解析动态规划的基本思想,通过实例探讨最长公共子序列问题,包括子序列和子串的定义、两者关系以及如何利用动态规划解决。介绍递推公式建立过程,并提供一个代码实现和测试结果,展示如何在实际问题中应用动态规划来求解最优化问题。
摘要由CSDN通过智能技术生成

    通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。

一、基本思想

    一般来说,只要问题可以划分成规模更小的子问题,并且原问题的最优解中包含了子问题的最优解,则可以考虑用动态规划解决。动态规划的实质是分治思想和解决冗余,因此,动态规划是一种将问题实例分解为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优化问题的算法策略。
    由此可知,动态规划法与分治法和贪心法类似,它们都是将问题实例归纳为更小的、相似的子问题,并通过求解子问题产生一个全局最优解。
    其中贪心法的当前选择可能要依赖已经作出的所有选择,但不依赖于有待于做出的选择和子问题。因此贪心法自顶向下,一步一步地作出贪心选择;
    而分治法中的各个子问题是独立的 (即不包含公共的子子问题),因此一旦递归地求出各子问题的解后,便可自下而上地将子问题的解合并成问题的解。
    但不足的是,如果当前选择可能要依赖子问题的解时,则难以通过局部的贪心策略达到全局最优解;如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题。解决上述问题的办法是利用动态规划。
    该方法主要应用于最优化问题,这类问题会有多种可能的解,每个解都有一个值,而动态规划找出其中最优(最大或最小)值的解。若存在若干个取最优值的解的话,它只取其中的一个。在求解过程中,该方法也是通过求解局部子问题的解达到全局最优解,但与分治法和贪心法不同的是,动态规划允许这些子问题不独立,也允许其通过自身子问题的解作出选择,该方法对每一个子问题只解一次,并将结果保存起来,避免每次碰到时都要重复计算。因此,动态规划法所针对的问题有一个显著的特征,即它所对应的子问题树中的子问题呈现大量的重复。动态规划法的关键就在于,对于重复出现的子问题,只在第一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。

二、 案例之最长公共子序列问题

1.子序列和子串的定义:

子序列:一个给定的序列的子序列是在该序列中删除若干元素后得到的序列;
子串:字符串中任意个连续的字符组成的子序列称为该串的子串。

2.两者之间的关系:

从定义可以看出的是子串是比子序列的一个特殊情况,也就是说,子序列包含子串,而子串被包含。子串的定义出现在数据结构里面,子序列出现在算法设计与分析这本书里面。接下来用图说明。
在这里插入图片描述
    如上图,给定的字符序列: {a,b,c,d,e,f,g,h},它的子序列示例: {a,c,e,f} 即元素b,d,g,h被去掉后,保持原有的元素序列所得到的结果就是子序列。同理,{a,h},{c,d,e}等都是它的子序列。

    它的字串示例:{c,d,e,f} 即连续元素c,d,e,f组成的串是给定序列的字串。同理,{a,b,c,d},{g,h}等都是它的字串。

    这个问题说明白后,最长公共子序列(以下都简称LCS)就很好理解了。

    给定序列s1={1,3,4,5,6,7,7,8},s2={3,5,7,4,8,6,7,8,2},s1和s2的相同子序列,且该子序列的长度最长,即是LCS。
s1和s2的其中一个最长公共子序列是 {3,4,6,7,8}

三、算法分析

分析LCS

1.分析问题的最优子结构

在这里插入图片描述
    以例子(S1={1,3,4,5,6,7,7,8}和S2={3,5,7,4,8,6,7,8,2}),并结合上图来说:

    假如S1的最后一个元素 与 S2的最后一个元素相等,那么S1和S2的LCS就等于 {S1减去最后一个元素} 与 {S2减去最后一个元素} 的 LCS 再加上 S1和S2相等的最后一个元素。

    假如S1的最后一个元素 与 S2的最后一个元素不等(本例子就是属于这种情况),那么S1和S2的LCS就等于 : {S1减去最后一个元素} 与 S2 的LCS, {S2减去最后一个元素} 与 S1 的LCS 中的最大的那个序列。

其实到这里你应该有点分治法的感觉了。

    假设我们用dp[i,j]表示Xi 和 Yj 的LCS的长度(直接保存最长公共子序列的中间结果不现实,需要先借助LCS的长度)。其中X = {x1 … xm},Y ={y1…yn},Xi = {x1 … xi},Yj={y1… yj}。

2.建立递推公式:

在这里插入图片描述
最长公共子序列填充表
在这里插入图片描述
最长公共子序列路径表:
在这里插入图片描述
路径表解释:
在这里插入图片描述
在这里插入图片描述
上图是别的例子拿过来作为参看。意思是只有s[i]==s[j]的时候才算入公共子序列中。

四、算法实现

1.代码
string GetLCS_Ty(string str1, string str2)
{


	if (str1.length() == 0||str2.length() == 0)
	{
		return "";
	}

	int nLen1 = str1.length() +1;
	int nLen2 = str2.length() +1;

	int **b = (int **)malloc(nLen1*sizeof(int *));
	for (int j = 0; j < nLen1;j++)
	{
		b[j] = (int *)malloc(sizeof(int)*nLen2);
		memset(b[j], 0, sizeof(int)*nLen2);
	}

	/*cout << "数组b初始化:\n";
	for (int i = 0; i < nLen1;i++)
	{
		for (int j = 0; j < nLen2; j++)
		{
			cout << b[i][j] << " ";
		}
		cout << endl;
	}*/



	int **dp = (int **)malloc(nLen1*sizeof(int *));
	memset(dp, 0, nLen1*sizeof(int *));
	for (int j = 0; j < nLen1; j++)
	{
		dp[j] = (int *)malloc(sizeof(int)*nLen2);
		memset(dp[j], 0, sizeof(int)*nLen2);
	}

/*
	cout << "数组dp初始化:\n";
	for (int i = 0; i < nLen1; i++)
	{
		for (int j = 0; j < nLen2; j++)
		{
			cout << dp[i][j] << " ";
		}
		cout << endl;
	}
*/

	

	for (int i = 1; i < nLen1; i++)
	{
		for (int j = 1; j < nLen2; j++)
		{
			if (str1[i - 1] == str2[j - 1])
			{
				dp[i][j] = dp[i - 1][j - 1] + 1;
				b[i][j] = 1; //↖
			}
			else if (dp[i - 1][j] > dp[i][j - 1])
			{
				dp[i][j] = dp[i - 1][j];
				b[i][j] = 2;//↑
			}
			else
			{
				dp[i][j] = dp[i][j - 1];
				b[i][j] = 3;//←
			}
		}
	}
	
	cout << "\n-----------------\n";

	int x = nLen1-1;
	int y = nLen2-1;

	int k = dp[x][y];
	int nclen = k;
	cout << "最长公共子序列填充表:\n";
	for (int i = 1; i < nLen1;i++)
	{
		for (int j = 1; j < nLen2;j++)
		{
			cout << dp[i][j] << " ";
		}
		cout << endl;
	}
	cout << endl;

	cout << "最长公共子序列路径表:\n";
	for (int i = 1; i < nLen1; i++)
	{
		for (int j = 1; j < nLen2; j++)
		{
			cout << b[i][j] << " ";
		}
		cout << endl;
	}
	cout << endl;
	
	char *LCS = new char[nclen+1];
	memset(LCS, 0, nclen + 1);
	while (x > 0 && y > 0)
	{
		if (b[x][y] == 1)
		{
			LCS[k - 1] = str1[x - 1];
			x--;
			y--;
			k--;
		}
		else if (b[x][y] == 2)
		{
			x--;
		}
		else
		{
			y--;
		}
	}

	string strtmp = LCS;
	free(LCS);
	LCS = NULL;


	
	for (int j = 0; j < nLen1; j++)
	{
		free(b[j]);
		b[j] = NULL;
	}
	delete b;

	for (int j = 0; j < nLen1; j++)
	{
		free(dp[j]);
		dp[j] = NULL;
	}
	delete dp;

	return strtmp;
}

void main()
{
	char s1[] = "1528936";
	char s2[] = "568937";
	string strLCS = GetLCS_Ty(s1, s2);
	cout <<"最长公共子序列:"<< strLCS.c_str() << endl;
	system("pause");
}
2.测试结果

在这里插入图片描述
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发如雪-ty

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值