动态规划入门详解 内含12道经典动态规划编程题

动态规划入门详解

一 什么是动态规划??

算法导论中介绍,动态规划和分治方法类似,都是听过子问题的解来解决原问题。下面说一下这2者之间的分别,分治方法将原问题划分为互不相交的子问题,而后将子问题组合之后求出原问题的解,而动态规划是相反的,动态规划利用子问题之间的重叠,写出原问题和子问题之间的状态转移方程,转化为更小的子问题的求解,直到到达边界条件为止,有点类似于递归。

①动态规划和分治的区别 分治的子问题是不重叠的 如归并排序 ,快排 动态规划是有重叠的
②贪心和动态规划的区别 贪心是自顶向下的 ,不会等子问题求解完毕后求原问题

二 动态规划问题求解的一般的步骤 (具体的步骤千变万化,但遵循的思路大体一致)

首先 要知道动态规划是用来解决最优解的(最大,最小,最长等问题)

1 将原问题转化为一个个小问题,而且每一个小问题之间是有重叠的,而且这些小问题满足最优的子结构 (就是这些小问题对后面的没有重叠的影响 且 这小问题的最优解的组合可以原问题的最优解)
2用数组dp存这些小问题的答案,确定存的内容是什么 可以比较直观的解题,dp中的状态必须有无后效性,意思就是已经记录的状态不会发生改变,而且而且未来的状态只能在现在的状态中组合产生。(就是转移方程)
3找到这些小问题的边界,注意保证,在这个边界下,大问题求解的每一步所要用到的小问题的结果都是已经存入数组dp里,否则无法保证最后问题求解的准确性,(2,3一般是一起完成的)
4找到原问题和小问题之间的联系,,建立状态转移方程,
5先写出赋值边界的代码,而后写出状态转移的代码
……

下面用题目来看动态规划
题目1 第一题较为简单和直观
斐波那契数列 f0=1,f1=1,f2=2,f3=2 f4=5…… fn=f(n-1)+f(n-2)
求fn的递归写法

int f(n)
{
   
	if(n==0 || n==1)  return 1;
	else return f(n-1)+f(n-2);
}

递归写法很简单1 但是效率很低很低,因为当n变大时,存在很多重复的计算 例如 求f10
求f10 那就要求f9 和f8 求f9 要求f8和f7 求f8要求f7和f6 …… 可以看出 f8已经求了2次了 易得越小的n求得重复的次数越多,因为每一个大的数,都是通过小的得到的

下面看一下 动态规划的解法 因为fn由相邻的2个数得到 那就用dp[n] 存所有的已经计算出来的f【n】的结果 dp【n】== -1表示没有计算

int f(n)
{
   
	if(n==0 || n==1) return 1;
	if(dp[n] != -1)  return dp[n];
	else{
   
		dp[n]=dp[n-1] +dp[n-2];  //关键语句
		return dp[n];
	}
	
}

乍一看这和递归没有什么区别;看关键语句,这里的求法,其实和递归是完全不一样的 因为这里用的是数组里面的值的相加就OK了
而递归用的调用函数去求解 ,在动态规划中,每一个fn的值求一次就被记录了 之后用到就直接用,而递归是每一次都要求一遍 时间复杂度是完全不一样的
相当于用一个On的空间,把指数级的时间复杂度降到了线性级 这是很合算的。。。。。。。。。。。。。

题目二 数塔问题
先看题目:如下图(图片来自百度图片)是一个数塔,从顶部出发在每一个节点可以选择向左或者向右走,一直走到底层, 求找出一条路径,使得路径上的数字之和最大

在这里插入图片描述
显然 用f[i][j]表示i行 j个 ,从1开始
那么f【1】【1】的最大和 就是max(下面2个的最大和)+ f[1][1]; 显然可以划分为子问题 而且有重复存在
那么令dp【i】【j】记录到 f【i】【j】的最大值和
则边界条件为最下层的dp值 所有的上层可以由下层得到
状态转移方程: dp【i】【j】= max(dp【i+1】【j】 ,dp【i+1】【j+1】)+f【i】【j】
代码如下

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn =1010;
int f[maxn][maxn],dp[maxn][maxn];
int main()
{
   
    int n;
    scanf("%d,&n");
    for(int i=1;i<=n;i++)
    {
   
        for(int j=1;j<=i;j++)
        {
   
            scanf("%d",&f[i][j]);
        }
    }
    //边界
    for(int j=1;j<=n;j++)
    {
   
        dp[n][j]=f[n][j];
    }
    //从第一层往上计算n-1层
    for(int i=n-1;i>=1;i--)
    {
   
        for(int j=1;j<=i;j++)
        {
   
            dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+f[i][j];
        }
    }
    printf("%d",dp[1][1]);
    return 0;
}

这个问题中,求dp【1】【1】转化为求子问题的最优解 而且可以由子问题的最优解的得到原问题的解 所以可解

题目三 最大连续子序列和
题目描述:给定一个数字序列A1,A2,…,An,求i,j(1≤i≤j≤n),使得Ai+,…,+Aj最大,输出这个最大和。
例子
-2 11 -4 13 -5 -2

显然11+(-4)+3=20为和最大的选值情况,因此最大和为20
解题思路:用dp【i】存以i为结尾的最大的的子序列的和 则每一个dp【i】和dp【i-1】有关 dp【i】=max(dp【i-1】+A【i】,A【i】)
因为是连续的 要么就是加上前一个之后更大,要么就是自己单独一个 那么dp中最大的那个就是答案,因为每一个都和前一个
相关,所以边界就是第一个 即dp【0】。到此,边界和状态转移方程都有了
代码如下

#include <iostream>
#include <cstdio>
#incl
  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值