当前位置: 首页 > article >正文

动态规划简介:爱的初体验

动态规划是一个让人欲仙欲死、死去活来的科目,茫然无措时让你痛不欲生,柳暗花明时让你喜不自胜。

小伙,不能啊!

你要心怀有爱,你要用全身心的爱来迎接今天,迎接动态规划。

一、动态规划的概念

动态规划(dynamic programing)是利用子问题的解来解决整个问题的,这一点和分治法有些神似。

维基百科上说:

This is also usually done in a tabular form by iteratively generating solutions to bigger and bigger subproblems by using the solutions to small subproblems. 这通常以表格形式完成,通过使用小子问题的解来迭代生成越来越大的子问题的解。

这指出了动态规划的两个特点:

①列表格(tabular form):每个动态规划算法都从一个表格开始。

②以小解大:利用较小问题的解(using the solutions to small subproblems)去求较大问题的解(generating solutions to bigger and bigger subproblems),这样不停地迭代(iteratively)。小问题为什么能解大问题呢,这说明这些子问题之间是相互关联的。

就像砌墙一样,表格里的每个单元格就像一块块砖头,把所有砖头垒完,墙也就砌完了。同样,填满所有格子,也就得到了问题的答案。这就是动态规划的整个过程。

二、动态规划的优势

动态规划和分治法同样利用子问题来求解,但解题思路却是完全相反的。

分治法是从要求解的问题出发,将问题一层层划分成相对独立的子问题,递归地解决所有子问题,然后再合并子问题,最终解决原问题。从名字也可以看出,分治法的核心在于分解,就像分家一样,把问题打散了求解。它是自上而下的。

在这个分开求解的过程中,会做很多无用功,即重复地求解同样的子问题。比如有一个敲村里所有寡妇门的问题,村里有一个王寡妇特别招人烦,你要敲任何一个寡妇的门她就会说:“老娘没空,去敲王寡妇的门”。于是,如果你用分治法解决这道题,王寡妇家的门就被敲爆了。

动态规划的思路与分治法正相反,它是自下而上的,从最小的子问题开始,对每个子问题只求解一遍,并且将其结果保存在表中,下次用到时直接从表中取出来就行了,从而避免了子问题的重复计算。

这么看来,动态规划也没什么高深的,不就是列表格、查表格吗?

别说,还真就是这么回事。

只不过,真遇到问题,你就算吃出吃奶的劲儿,不,即便使出宇宙洪荒之力,也未必能列出这个表格。

山无棱、天地合,你也未必能列出这表格。

三、适用范围

动态规划常用于最优化问题,这类问题要求在给定的约束条件下找到最优解。

1.适用的主要问题类型

(1)图论问题

动态规划在图论中有广泛的应用,如最短路径问题(Bellman-Ford算法、Floyd-Warshall算法)、最小生成树问题(如Kruskal算法和Prim算法的部分优化)等。

(2)序列问题

序列比对、编辑距离、最长公共子序列、最长递增子序列等问题是动态规划的典型应用场景。

(3)资源分配问题

动态规划可用于解决资源分配问题,如背包问题、工厂生产调度问题等。

(4)游戏理论

在双人零和博弈、部分可观察马尔可夫决策过程(POMDP)等游戏中,可用动态规划来计算最优策略。

2.问题的主要适用特征

(1)重复子问题

动态规划通过存储已解决的子问题的结果(即记忆化),避免了重复计算。如果一个问题可以被分解为多个相似的子问题,那么动态规划通常是一个有效的解决方案。

(2)最优子结构

最优子结构指能通过解决子问题的最优解来找到原问题的最优解。也就是说如果解决了所有子问题,那么可以通过组合这些子问题的解来找到原问题的最优解。

(3)多阶段决策过程

多阶段决策指每个阶段都依赖于前一个阶段的决策。例如,在投资决策、资源分配或路径规划等问题中,每个决策点都依赖于之前的状态和决策。

3.适用条件

能用动态规划解决的问题必须要有无后效性(without Aftereffect)。无后效性也称子问题独立性(Independence of Subproblems)。

所谓的无后效性说白了就是不会“后反劲儿”,就像某人在路上被车闪了一下腰,该检查检查,该赔偿赔偿,赔偿完事就了了,不能说过个三年五载腰疼了再找人家算账。

也就是说,“未来与过去无关”,当前的状态是此前历史的一个完整总结,后续过程的演变不再受此前各种状态及决策的影响。

从子问题独立性角度看,是指每个子问题都是离散的,即不依赖于其他子问题,这样动态规划才管用。比如在伦敦的多个地方玩,每个地方都是独立的,这种情况可以用动态规划;但如果在伦敦玩完又要去巴黎玩,由于中间有一段伦敦到巴黎的路程,也需要时间,导致巴黎每个玩的地方游玩时间是相互依赖的(不是独立的,只有第一个去玩的地方要考虑伦敦到巴黎的时间),就不能用动态规划。

四、动态规划体验馆

纸上得来终觉浅,搞两个动态规划问题让列位体验一下看不懂的感觉。

1.子数组最大和

一个元素为整数(数值可正可负)的数组,求这个数组连续元素(被称为子数组)之和的最大值是多少?

例如:

数组{-9, 5, 2, -7, 3},和最大的子数组为{ 5, 2},最大值为7。

数组{-9, 5, 2, -7, 8},和最大的子数组为{ 5, 2, -7, 8},最大值为8。

之所以叫子数组,是因为它的孩子也得像数组一样一个挨一个,即选出来的元素必须是连续的。这个问题也被称为求“连续子序列的最大和”。

这个问题最直接的求解方法是用穷举法。设数组为A[],有n个元素,代码如下:

int MaxSubString(int* A, int n){
    int max = -1e9;  //初始值为负无穷大
    int sum;
    for(int i = 0; i < n; i++){
        sum = 0;
        for(int j = i; j < n; j++){
            sum += A[j];
            if(sum > max) max = sum;
        }
    }
    return max;
}

这种方法需要两重循环,时间复杂度为O(n^2),效率比较低。

这时动态规划就派上用场了,时间复杂度为O(n)。

思路是把数组A划分左右两部分:第一个元素A[0]和后面的其他部分After={A[1], A[2], …… , A[n-1]}。假设数组A中“和最大的子数组”为{A[i], …… ,A[j]),无非有以下几种情况:

①和最大的子数组只和A[0]有关。即和最大子的数组只有一个元素,就是A[0],此时0 = i = j。比如A[]={9, -5, -2, -7}时,和最大的子数组就是{9}。

②和最大的子数组和A[0]、After均有关,即和最大的子数组是由A[0]开始的子数组,此时0 = i < j。比如A[]={9, 5, -2, -7}时,和最大的子数组就是{9, 5}。注意,这时候After中的第一个元素一定是和最大的子数组中的元素,否则和最大的子数组必然与After无关。

③和最大的子数组只和After有关,即数组A和最大的子数组与数组After的和最大的子数组相同,此时0 < i。比如A[]={-9, -5, 2, 7}时,和最大的子数组就是{2, 7}。

数组A可以看成第一个元素(A[0])和后面的部分(After)组成,同理,数组After本身同样可以像这样分成两部分。于是,这就可以将一个大问题(N个元素数组)转化为一个较小的问题(N-1个元素的数组)。

假设已经知道数组After的子数组的最大和为All[1](All[i]指下标为i到n-1的数组的子数组的最大和),并且已经知道After中包含A[1]的子数组的最大和为Start[1](Start[i] 指下标为i到n-1的数组的包含第i个元素的子数组的最大和)。那么可以得出A的子数组的最大和All[0] = max{ A[0], A[0] + start[1], All[1] }。通过这样的分析,可以看出这个问题无后效性(A[0]对All[1]没有任何影响),可以用动态规划来解决。代码如下:

int MaxSubString(int* A, int n){
    int Start = A[n - 1];
    int All = A[n - 1];
    for(int i = n - 2; i >= 0; i--){ //从后向前遍历,反之亦可。
        Start = max( A[i], A[i] + Start);
        All = max(Start, All);
    }
    return All;
}

2背包问题

背包问题属于动态规划中的明星产品,只要接触过动态规划,必然就会碰到背包问题。话说有n件物品和一个容量为v的背包。第i件物品的体积是c[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。

比如有5个商品,他们的体积为 c[5] = {3,5,2,7,4};价值为 v[5] = {2,4,1,6,5}; 背包的体积为10。

这是最基础的背包问题,每种物品仅有一件,可以选择放或不放。

思路和“子数组最大和”一样,也是分成两部分:第i件物品、前i-1件物品。设将前i件物品装入容量为j的背包的最大价值为f[i][j](注意这里说的是前i件物品,不是第i件物品。也就是说,f[i][j]指考虑从第1件一直到第i件物品装背包的最大价值)

分为两种情况:

①第i件物品不装入背包。显然最大价值和第i件物品无关,只和前i-1件物品有关。即此时的最大价值为前i-1件物品装入容量为v的背包的最大价值,其值为f[i-1][v]。

②第i件物品装入背包。此时最大价值肯定包含第i件物品,所以背包的容量分为两部分:第i件物品的体积c[i]、剩下的体积v-c[i]。对应地,背包内装入物品的最大价值也分为两部分:第i件物品的价值v[i]、前i-1件物品装入容量为v-c[i]的最大价值,即此时的最大价值为v[i] + f[i-1][v-c[i]]。

因为只有这两种情况,最后取①、②的最大值,就是要求的大价值。

根据以上分析可以递归地定义问题的最优解 f[i][v] = max{ f[i-1][v], v[i] + f[i-1][v - c[i]]},这个玩艺就叫状态转移方程。

代码如下:

#include<iostream>
 
#define max(a,b) ((a) > (b) ? a : b)
int c[5] = {3,5,2,7,4};
int v[5] = {2,4,1,6,5};
int f[6][10] = {0};
//f[i][v] = max{ f[i-1][v] , f[i-1][v - c[i]] + w[i]}
 
int main()
{
    for(int i = 1; i < 6; i++)
        for(int j = 1; j < 10 ;j++)
        {
            if(c[i] > j)//如果背包的容量,放不下c[i],则不选c[i]
                f[i][j] = f[i-1][j];        
            else
            {
                f[i][j] = max(f[i-1][j], f[i-1][j - c[i]] + v[i]);//转移方程式
            }
        }
        std::cout<<f[5][9];
    return 0;
}

01背包问题是最基本的动态规划问题,可以说是背包界最易懂的。不知你懂了没?

不懂怎么办?多看几遍喽!

这玩艺本来就是比较难,老金自认为已经讲得相对通俗易懂了,实在看不懂只能见谅了!


http://www.kler.cn/a/456047.html

相关文章:

  • SCSA:探索空间与通道注意力之间的协同效应
  • Oracle 11G还有新BUG?ORACLE 表空间迷案!
  • Spark生态圈
  • 【信息系统项目管理师】高分论文:论信息系统项目的资源管理(智慧储电站系统)
  • 进军AI大模型-环境配置
  • 拉取docker run hello-world失败
  • TCP 和 UDP 的区别:解析网络传输协议
  • 人生至淡,就是风清月朗
  • Ubuntu下PyTorch开发环境配置
  • redis的基础知识
  • linux检测U盘,网络是连接
  • 2024第一届Solar杯应急响应挑战赛
  • 目标检测中的正负样本是什么,是如何起作用的?
  • flask后端开发(3):html模板渲染
  • springboot、spring、springmvc有哪些注解
  • 微信流量主挑战:三天25用户!功能未完善?(新纪元4)
  • Pandas08
  • uniapp中wx.getFuzzyLocation报错如何解决
  • org.apache.zookeeper.server.quorum.QuorumPeerMain
  • AI助力古诗视频制作全流程化教程
  • MySQL版本升级(8.0.31->8.0.37)
  • 外键约束
  • 电路元件与电路基本定理
  • Xshell远程连接提示“找不到匹配的host key算法“问题处理
  • 【Java 数据结构】LinkedList 类 和 模拟实现链表
  • html+css+js网页设计 美食 全屏幕轮播美食1个页面