动态规划

动态规划(入门)

一、基本思想
动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。

二、适用条件

最优化原理(最优子结构性质)
最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质 。

无后效性
将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

子问题的重叠性
动态规划算法的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其他的算法。选择动态规划算法是因为动态规划算法在空间上可以承受,而搜索算法在时间上却无法承受,所以我们舍空间而取时间 。

三、动态规划的三大步骤

定义数组元素的含义。首先我们要定义一个新的数组,用来保存历史记录,此时最重要的是我们要清楚该数组元素所代表的含义。

找出数组元素之间的关系式(也称确定状态转移方程)。我们之前说动态规划就是把一个问题分成若干个子问题,将那些子问题求解后,最大的问题也解决了,这就说明最大问题与他的子问题存在某种关系,最重要的是我们要找到这种关系,这也是动态规划中最难的一个步骤,也是核心部分。

寻找边界条件。由于状态转移方程给出的是一个递推式,因此我们必须找出递推的边界条件,并且确实边界条件(或最小子问题的解)。

有了边界条件和数组各个元素之间的关系式,我们就可以得到所需的答案了。

四、案例分析

1.斐波那契数列
斐波那契数列(Fibonacci sequence),又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……。要求第n项,我们要知道第n-1和第n-2项,随着一步步往前推,我们就可以得到第n项。我们可以很容易的得出它的状态转移方程,即dp[n]=dp[n-1]+dp[n-2];此时还需要找到它的边界条件,当输入的n或n-1,n-2为非正数是是无意义的,所以我们要确定它第一项和第二项的值。

#include<stdio.h>
int main()
{
	int n,i;
	scanf("%d",&n);
	int dp[n+1];//先创建一个数组保存历史数据
	dp[1]=0;//边界条件
	dp[2]=1;
	for(i=3;i<=n;i++)//通过关系式计算出第n项
	{
		dp[i]=dp[i-1]+dp[i-2];
	}
	printf("%d",dp[n]);//返回第n项的值
	return 0;
 } 

2.不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
在这里插入图片描述
由于我们的目的是从左上角到右下角一共有多少种路径,我们就可以定义dp[i][j]的含义为:当机器人从左上角走到(i,j)这个位置时,一共有dp[i][j]种路径,所以我们要求的是dp[m-1][n-1]。
我们可以看到机器人有两种不同的方式走到(m-1,n-1)
① 从(m-1,n-2)到(m-1,n-1)
② 从(m-2,n-1)到(m-1,n-1)
那么我们只要求出到(m-1,n-2)和(m-2,n-1)总共有多少条路径就得到了到(m-1,n-1)有多少条路径。因此我们可以得到状态转移方程为:dp[i][j]=dp[i-i][j]+dp[i][j-1]。但是当i或就中有一个元素为0时,i-1或j-1就成为了负数,这显然是不可以的,此时就需要边界条件了。我们可以看出当机器人位于第一行时,他的前一步只能是它的左边,dp[0][j]只能等于1。当机器人位于最左侧时,它的前一步只能向上,dp[i][0]也只能唯一。这就是它的边界条件。

#include<stdio.h>
int main()
{
	int m,n;
	scanf("%d %d",&m,&n);
	if(m==0 || n==0)
	{
		return 0;
	}
	int i,j;
	int dp[m][n];
	for(i=0;i<m;i++)//边界条件:初始化最左侧一列
	{
		dp[i][0]=1;
	}
	for(i=0;i<n;i++)//边界条件:初始化最上面一行
	{
		dp[0][i]=1;
	}
	for(i=1;i<m;i++)
	{
		for(j=1;j<n;j++)
		{
			dp[i][j]=dp[i-1][j]+dp[i][j-1];//求出所求路径
		}
	}
	return dp[m-1][n-1];
}

3.最短路径
给定一个三角形 triangle ,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 i 或 i + 1 。
输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
2
3 4
6 5 7
4 1 8 3
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)

对于这道题和上面的些许不同,因为这道题我们不知道它的最后一步的位置,只知道它的起始位置。因此我们采用自下而上,直接在原数组上操作。我们可以看出在位置(i,j)时,它的下一步为(i+1,j)和(i+1,j+1)中较小者。因此状态转移方程为dp[i][j]=min(dp[i+1][j],dp[i+1][j+1])+dp[i][j].最终dp[0][0]即为我们所求的最小路径。我们可以先找出最后一行元素的最小值,然后往上逆推,最终会推到(0,0)的位置。(此题也可以按照前面两道题的方法做,因为我们不确定最终的位置是最后一行的哪一个,因此在最后需要从最后一行元素的dp[i][j]中找出最小值才是我们最终所需的答案。)

#include<stdio.h>
#define min(a,b) (a<b?a:b)

int minimumTotal(int** triangle, int triangleSize, int* triangleColSize){
    int i,j;
    if(triangle == NULL) 
	{
		return 0;
	}
    for(i = triangleSize-2;i >= 0;i--)
	{
        for(j = 0;j < triangleColSize[i];j++ )
		{
            triangle[i][j] = min(triangle[i+1][j],triangle[i+1][j+1])+triangle[i][j];
        }
    }
    return triangle[0][0];
}

五、总结
不管该子问题以后是否会被用到,只要它计算过,就将其结果填入表中。这就是动态规划的基本思路。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值