掌握动态规划,从 “什么问题适合用” 及 “解题思路” 入手

ccc908 2023-04-25 16:51:12

一,“一个模型三个特征” 理论讲解

一个模型指的是适合用动态规划算法解决的问题的模型,这个模型也被定义为 “多阶段决策最优解模型”。具体解释如下:

一般是用动态规划来解决最优问题。而解决问题的过程,需要经历多个决策阶段。每个决策阶段都对应着一组状态。然后我们寻找一组决策序列,经过这组决策序列,能够产生最终期望求解的最优值。

1. 最优子结构

最优子结构指的是,问题的最优解包含子问题的最优解。反过来说就是,我们可以通过子问题的最优解,推导出问题的最优解。把最优子结构,对应到前面定义的动态规划问题模型上,就是后面阶段的状态可以通过前面阶段的状态推导出来

2. 无后效性

无后效性有两层含义,第一层含义是,在推导后面阶段的状态的时候,我们只关心前面阶段的状态值,不关心这个状态是怎么一步一步推导出来的。第二层含义是,某阶段状态一旦确定,就不受之后阶段的决策影响。无后效性是一个非常 “宽松” 的要求。只要满足前面提到的动态规划问题模型,其实基本上都会满足无后效性。

3. 重复子问题

不同的决策序列,到达某个相同的阶段时,可能会产生重复的状态。

4.“一个模型三个特征” 实例剖析

结合一个具体的动态规划问题更能详细理解上述理论,示例问题描述如下

假设我们有一个 n 乘以 n 的矩阵 w [n][n]。矩阵存储的都是正整数。棋子起始位置在左上角,终止位置在右下角。我们将棋子从左上角移动到右下角。每次只能向右或者向下移动一位。从左上角到右下角,会有很多不同的路径可以走。我们把每条路径经过的数字加起来看作路径的长度。那从左上角移动到右下角的最短路径长度是多少呢?

min_dist (i, j) 可以通过 min_dist (i, j-1) 和 min_dist (i-1, j) 两个状态推导出来,所以这个问题符合 “最优子结构”。

min_dist(i, j) = min(min_dist(i-1,j), min_dist(i, j-1))

二,两种动态规划解题思路总结

知道了如何鉴别一个问题是否可以用动态规划来解决,接下来就是总结动态规划解决问题的一般思路。解决动态规划问题,一般有两种思路。分别叫作:状态转移表法和状态转移方程法。

1. 状态转移表法

一般能用动态规划解决的问题,都可以使用回溯算法的暴力搜索解决。所以,当我们拿到问题的时候,我们可以先用简单的回溯算法解决,然后定义状态,每个状态表示一个节点,然后对应画出递归树。从递归树中,我们很容易可以看出来,是否存在重复子问题,以及重复子问题是如何产生的。以此来寻找规律,看是否能用动态规划解决。

找到重复子问题之后,接下来,我们有两种处理思路,第一种是直接用回溯加 “备忘录” 的方法,来避免重复子问题。从执行效率上来讲,这跟动态规划的解决思路没有差别。第二种是使用动态规划的解决方法,状态转移表法

我们先画出一个状态表。状态表一般都是二维的,所以你可以把它想象成二维数组。其中,每个状态包含三个变量,行、列、数组值。我们根据决策的先后过程,从前往后,根据递推关系,分阶段填充状态表中的每个状态。最后,我们将这个递推填表的过程,翻译成代码,就是动态规划代码了。

适合状态是二维的情况,再多维的话就不适合了,毕竟人脑不适合处理高维度的问题。

起点到终点,有很多种不同的走法,回溯算法比较适合无重复又不遗漏地穷举出所有走法,从而对比找出一个最短走法。

(1)回溯解法的 C++ 代码如下:

// leetcode64. 最小路径和. 回溯法-会超出时间限制
class Solution {
private:
    int minDist = 10000;
 void minDistBT(vector<vector<int>>& grid, int i, int j, int dist, int m, int n) {
 if (i == 0 && j == 0) dist = grid[0][0];
 if (i == m-1 && j == n-1) {
 if (dist < minDist) minDist = dist;
 return;
 }
 if (i < m-1) {
 minDistBT(grid, i + 1, j, dist + grid[i+1][j], m, n); // 向右走
 }
 if (j < n-1) {
 minDistBT(grid, i, j + 1, dist + grid[i][j+1], m, n); // 向下走
 }
 }
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        int dist = 0;
 minDistBT(grid, 0, 0, dist, m, n);
 return minDist;
 }
};

有了回溯代码之后,接下来,自然要画出递归树,以此来寻找重复子问题。在递归树中,一个状态(也就是一个节点)包含三个变量 (i, j, dist),其中 i,j 分别表示行和列,dist 表示从起点到达 (i, j) 的路径长度。从图中,可以看出,尽管 (i, j, dist) 不存在重复,但是 (i, j) 重复的有很多。对于 (i, j) 重复的节点,我们只需要选择 dist 最小的节点,继续递归求解,其他节点就可以舍弃了。

(2)动态规划解法的 C++ 代码如下:

// 对应 leetcode64. 最小路径和
class Solution { // 动态规划:状态转移表法
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        vector<vector<int> > states(m, vector<int>(n, 0));
 // 第一个阶段初始化
        int sum = 0;
 for(int i=0; i<n;i++){ // 初始化 states 的第一行数据
            sum += grid[0][i];
            states[0][i] = sum;
 }
        sum = 0;
 for(int j=0; j<m; j++){ // 初始化 states 的第一列数据
            sum += grid[j][0];
            states[j][0] = sum;
 }
 // 分阶段求解,下层状态的值是基于上一层状态来的
 for(int i=1; i<m; i++){
 for(int j=1; j<n; j++){
                states[i][j] = grid[i][j] + std::min(states[i-1][j],states[i][j-1]);
 }
 }
 return states[m-1][n-1];
 }
};

2. 状态转移方程法

根据最优子结构,写出递归公式,也就是所谓的状态转移方程。状态转移方程,或者说递归公式是解决动态规划的关键。递归加 “备忘录” 的方式,将状态转移方程翻译成来 C++ 代码。

// 状态转移方程
min_dist(i, j) = w[i][j] + min(min_dist(i, j-1), min_dist(i-1, j))
// 对应 leetcode64. 最小路径和
class Solution { // 状态转移方程法
private:
    int minDist(int i, int j, vector<vector<int> >& matrix, vector<vector<int> >& mem) { // 调用minDist(n-1, n-1);
 if (i == 0 && j == 0) return matrix[0][0];
 if (mem[i][j] > 0) return mem[i][j];
        int minUp = 10000;
 if (i - 1 >= 0) minUp = minDist(i - 1, j, matrix, mem);
        int minLeft = 10000;
 if (j - 1 >= 0) minLeft = minDist(i, j - 1, matrix, mem);
        int currMinDist = matrix[i][j] + std::min(minUp, minLeft);
        mem[i][j] = currMinDist;
 return currMinDist;
 }
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        vector<vector<int> > mem(m, vector<int>(n, -1));
 return minDist(m - 1, n - 1, grid, mem);
 }
};

三,四种算法比较分析

如果将这四种算法思想分一下类,那贪心、回溯、动态规划可以归为一类,而分治单独可以作为一类,因为它跟其他三个都不大一样。为什么这么说呢?因为前三个算法解决问题的模型,都可以抽象成多阶段决策最优解模型,而分治算法解决的问题尽管大部分也是最优解问题,但是,大部分都不能抽象成多阶段决策模型。

尽管动态规划比回溯算法高效,但是,并不是所有问题,都可以用动态规划来解决。能用动态规划解决的问题,需要满足三个特征,最优子结构、无后效性和重复子问题。在重复子问题这一点上,动态规划和分治算法的区分非常明显。分治算法要求分割成的子问题,不能有重复子问题,而动态规划正好相反,动态规划之所以高效,就是因为回溯算法实现中存在大量的重复子问题。

贪心算法实际上是动态规划算法的一种特殊情况。它解决问题起来更加高效,代码实现也更加简洁。不过,它可以解决的问题也更加有限。它能解决的问题需要满足三个条件,最优子结构、无后效性和贪心选择性(这里我们不怎么强调重复子问题)。其中,最优子结构、无后效性跟动态规划中的无异。“贪心选择性” 的意思是,通过局部最优的选择,能产生全局的最优选择。每一个阶段,我们都选择当前看起来最优的决策,所有阶段的决策完成之后,最终由这些局部最优解构成全局最优解。

四,内容总结

什么样的问题适合用动态规划解决?这些问题可以总结概括为 “一个模型三个特征”。其中,“一个模型” 指的是,问题可以抽象成分阶段决策最优解模型。“三个特征” 指的是最优子结构、无后效性和重复子问题。

哪两种动态规划的解题思路?它们分别是状态转移表法和状态转移方程法。其中,状态转移表法解题思路大致可以概括为,回溯算法实现 - 定义状态 - 画递归树 - 找重复子问题 - 画状态转移表 - 根据递推关系填表 - 将填表过程翻译成代码。状态转移方程法的大致思路可以概括为,找最优子结构 - 写状态转移方程 - 将状态转移方程翻译成代码

练习题

假设我们有几种不同币值的硬币 v1,v2,……,vn(单位是元)。如果我们要支付 w 元,求最少需要多少个硬币。比如,我们有 3 种不同的硬币,1 元、3 元、5 元,我们要支付 9 元,最少需要 3 个硬币(3 个 3 元的硬币)。

---------------------------------------------------------------------------------------------------------------------------

每日小知识分享:每一个 HTML 文档中,都有一个不可或缺的标签:<head>,在几乎所有的HTML里, 我们都可以看到类似下面这段代码:

<head><meta charset=utf-8><meta http-equiv=content-type content=text/html; charset=utf-8><meta name=renderer content=webkit/><meta name=force-rendering content=webkit/><meta http-equiv=X-UA-Compatible content=IE=edge,chrome=1/><meta http-equiv=Content-Type content=www.llyz.net imtoken;charset=gb2312><meta name=viewport content=width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no></head>

head标签作为一个容器,主要包含了用于描述 HTML 文档自身信息(元数据)的标签,这些标签一般不会在页面中被显示出来,主要告知搜索引擎本页面的关键字以及对应网址,在SEO中传递相关权重起到非常重要的作用。

...全文
22 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
系统架构师历年论文真题解析及精选范文实战汇编 在当今快速发展的科技时代,系统架构师的角色日益凸显其重要性。作为信息技术的核心驱动者,系统架构师不仅需要有深厚的技术功底,更需具备前瞻性的视野和卓越的创新能力。而历年论文真题,则是系统架构师们学习和提升的重要资源。 本书《系统架构师历年论文真题解析及精选范文实战汇编》的编撰,旨在为广大系统架构师和有志于成为系统架构师的技术人员提供一个系统、全面、深入的学习平台。通过对历年论文真题的深入解析,结合精选范文的实战演练,使读者能够更好地理解和掌握系统架构设计的精髓,提高解决实际问题的能力。 在本书的编撰过程中,我们注重了以下几个方面: 首先,真题解析。我们精选了历年系统架构师考试中的经典论文题目,并对每一道题目进行了深入剖析。不仅从题目的背景、考点、难点入手,还结合实际案例,对解题思路和方法进行了详细阐述。通过真题解析,读者能够清晰地把握系统架构设计的核心内容和考查要点。 其次,范文实战。我们精选了一批优秀的系统架构设计论文作为范文,并对每篇论文的写作背景、目的、方法、结论等进行了详细介绍。同时,我们还结合真题解析,对范文的写作技巧和注意
考研数学150分是这样练成的主持人:亲爱的各位网友大家晚上好,欢迎光临海文考研大讲堂。海文教育集团传媒中心田振宇向您问好,今天我们又相聚在雄心启动未来—2007考研全程策划第三十二期节目中。今天我们为大家请到现场的是海文考研高级辅导专家王平老师,请他为大家讲讲考研数学150分是怎样炼成的。王平:各位网友大家晚上好,今天我为大家讲讲怎样能取得考研数学150分。其实分数只是我们说的一个概念,实质上是要取得好的成绩。考研数学作为一种选拔性考试,必然具有一定的难度。但是从近几年的试题来看,随着研究生招生规模的扩大,其整体难度已有所下降,考研数学越来越接近标准化考试,即试题越来越基础,越来越注重考察考生对基本概念、基本方法和基本性质的掌握程度,以及运算能力、逻辑推理能力等基本数学素质。 在备考之前,对考研数学的基本命题趋势和试题难度要有比较深刻的认识,根据自己对考研数学的定位,复习备考的主要策略:紧扣考纲,扎实基础,注重联系,加强训练。 第一,紧扣考纲。考研数学作为标准化考试,其命题范围有明确的规定,我的第一轮复习主要就是依据考试大纲,详细了解考试的基本要求,题型、类别和难度特点,准确定位。对于考试大纲未作要求的内容和知识点,我都没有看。因为从历年试题来看,偏题怪题越来越少,超纲题基本没有,因此没有必要在这上面浪费过多的时间和精力。 第二,扎实基础。考研数学所考察的重点就是考生的数学基本功,在根据考试大纲要求循序渐进地进行全面系统的复习的过程中,应该重点加强对基本概念、基本定理的理解,以及对基本方法的掌握。只有深入理解基本概念,牢牢掌握基本定理和公式,才能迅速而准确地找到解题的突破口和切入点,我们在考试中失分的一个重要原因就是对基本概念、定理记不全、记不牢,理解不准确,解题不得要领。 对于基本知识、基本定理和基本方法,关键在理解,而且理解还存在程度的问题,不能仅仅停留在看懂了的层次上,对一些易推导的定理,有时间一定要动手推一推,对一些基本问题的描述,特别是微积分中的一些术语的描述,一定要自己动手写一写,这些基本功都很重要,到临场时就可以发挥作用了。 第三,注重联系。考研试题中一般不太可能单独考察某个知识点,一般都是几个知识点结合起来考察考生的综合分析能力,因此复习时就应该注意知识点之间的联系,一是学科内部知识点的纵向联系,例如微积分中级数的求和一般都要用到微分或积分。同时还要注意三大学科之间的横向联系,例如概率试题通常都会用到微积分的知识等等。这些在综合练习时都是应该总结和注意的地方。 第四,加强训练。数学学科的特点,决定了数学考试要想取得好成绩就离不开大量有效的练习,俗话说熟能生巧,对于数学的基本概念、公式、结论等只有在反复练习中才能真正理解与巩固。数学试题虽然千变万化,其知识结构却基本相同,题型也相对固定,往往存在一定的解题套路,熟练掌握后既能提高正确率,又能提高解题速度。 数学考研题的重要特征之一就是综合性强、知识覆盖面广,一些稍有难度的试题一般比较灵活,对知识点串联的要求比较高,只有通过逐步的训练,不断积累解题经验,在考试时才更有机会较快找到突破口。平时有针对性的训练也有利于进一步理解并彻底弄清楚知识点的纵向与横向联系,转化为自己真正掌握了的东西,能够在理解的基础上灵活运用、触类旁通。 数学复习只是有一些值得注意的策略和方法,而没有一蹴而就的捷径,关键在个人的努力。当然,如果基础较弱,或者时间紧张,参加一定的考研辅导班也是不错的选择,因为大家从小到大,已经习惯了课堂的学习氛围。而且专业的考研辅导可以使你的复习更具方向性和目的性,能使你较快地发现自己原来的薄弱环节并予以补救。 总体的说就是要:1:注重基础,这是许多人可能都听别人所过但又不知如何入手的一点,一定要耐得住性子,冰冻三尺非一日之寒,看到别人成功辉煌的同时你也应该更多的去思考他(她)成功背后付出的努力。考研本身也是一个人综合素质的测定,一个系统的工程。 2:着力于思维的锻炼,它对于成绩的提高是整体性的,也是最可靠的途经。3:选好辅导书。我做的题目肯定不算最多的,甚至相对许多人是比较少的,但有一点我看的书的种类是比较多的,数学的每一门我都分别选了一册我认为最好的辅导教材,这 样才是比较合理的选书方法,也能达到最好的复习效果,没有必要将赌注都压在一本书上,也没有必要一本书反反复复地看。 4:稳定心态,不论复习状态或效果是好是坏,都不要有太大的波动,这点上文中提到了比较多。 这中间经常有同学

792

社区成员

发帖
与我相关
我的任务
社区描述
区块链技术专区
区块链 技术论坛(原bbs)
社区管理员
  • 区块链技术
  • ccc908
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧