算法之动态规划理论

2023-11-05

目录

前言

 一个模型三个特征理论讲解

1.最优子结构

2.无后效性 

3.重复子问题 

一个模型三个特征实例剖析     

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

1.状态转移表法

2.状态转移方程法

四种算法思想比较分析

总结:

参考资料


前言

     本篇博文主要讲解动态规划的理论,主要可以帮我们解决这样的几个问题?什么样的问题可以用动态规划解决?解决动态规划问题的一般思考过程是什么样的?贪心、分治、回溯、动态规划这四种算法思想又有什么区别和联系?


 一个模型三个特征理论讲解

      什么样的问题适合用动态规划来解决呢?实际上,动态规划作为一个非常成熟的算法思想,很多人对此已经做了非常全面的总结。我把这部分理论总结为一个模型三个特征

      什么是一个模型?它指的是动态规划适合解决的问题的模型。我把这个模型定义为多阶段决策最优解模型

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

      什么是三个特征?它们分别是最优子结构、无后效性和重复子问题

1.最优子结构

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

2.无后效性 

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

3.重复子问题 

     用一句话概括,那就是,不同的决策序列,到达某个相同的阶段时,可能会产生重复的状态。

一个模型三个特征实例剖析     

案例分析:

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

我们再来看,这个问题是否符合三个特征

        我们可以用回溯算法来解决这个问题。如果你自己写一下代码,画一下递归树,就会发现,递归树中有重复的节点。重复的节点表示,从左上角到节点对应的位置,有多种路线,这也能说明这个问题中存在重复子问题。

         如果我们走到 (i, j) 这个位置,我们只能通过 (i-1, j)(i, j-1) 这两个位置移动过来,也就是说,我们想要计算 (i, j) 位置对应的状态,只需要关心 (i-1, j)(i, j-1) 两个位置对应的状态,并不关心棋子是通过什么样的路线到达这两个位置的。而且,我们仅仅允许往下和往右移动,不允许后退,所以,前面阶段的状态确定之后,不会被后面阶段的决策所改变,所以,这个问题符合无后效性这一特征。

         刚刚定义状态的时候,我们把从起始位置 (0, 0) (i, j) 的最小路径,记作 min_dist(i, j)。因为我们只能往右或往下移动,所以,我们只有可能从 (i, j-1) 或者 (i-1, j) 两个位置到达 (i, j)。也就是说,到达 (i, j) 的最短路径要么经过 (i, j-1),要么经过 (i-1, j),而且到达 (i, j) 的最短路径肯定包含到达这两个位置的最短路径之一。换句话说就是,min_dist(i, j) 可以通过 min_dist(i, j-1) min_dist(i-1, j) 两个状态推导出来。这就说明,这个问题符合最优子结构


min_dist(i, j) = w[i][j] + min(min_dist(i, j-1), min_dist(i-1, j))

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

      动态规划解题的一般思路,让你面对动态规划问题的时候,能够有章可循,不至于束手无策。

解决动态规划问题,一般有两种思路。我把它们分别叫作,状态转移表法和状态转移方程法。

 1.状态转移表法

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

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

      现在,我们来看一下,如何套用这个状态转移表法,来解决之前那个矩阵最短路径的问题?

      从起点到终点,我们有很多种不同的走法。我们可以穷举所有走法,然后对比找出一个最短走法。不过如何才能无重复又不遗漏地穷举出所有走法呢?我们可以用回溯算法这个比较有规律的穷举算法。 


private int minDist = Integer.MAX_VALUE; // 全局变量或者成员变量
// 调用方式:minDistBacktracing(0, 0, 0, w, n);
public void minDistBT(int i, int j, int dist, int[][] w, int n) {
  // 到达了n-1, n-1这个位置了,这里看着有点奇怪哈,你自己举个例子看下
  if (i == n && j == n) {
    if (dist < minDist) minDist = dist;
    return;
  }
  if (i < n) { // 往下走,更新i=i+1, j=j
    minDistBT(i + 1, j, dist+w[i][j], w, n);
  }
  if (j < n) { // 往右走,更新i=i, j=j+1
    minDistBT(i, j+1, dist+w[i][j], w, n);
  }
}

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

 

         既然存在重复子问题,我们就可以尝试看下,是否可以用动态规划来解决呢?我们画出一个二维状态表,表中的行、列表示棋子所在的位置,表中的数值表示从起点到这个位置的最短路径。我们按照决策过程,通过不断状态递推演进,将状态表填好。为了方便代码实现,我们按行来进行依次填充。

  

2.状态转移方程法

       状态转移方程法有点类似递归的解题思路,我们需要分析,某个问题如何通过子问题来递归求解,也就是所谓的最优子结构。根据最优子结构,写出递归公式,也就是所谓的状态转移方程。有了状态转移方程,代码实现就非常简单了。一般情况下,我们有两种代码实现方法,一种是递归加备忘录,另一种是迭代递推。

刚刚的例子,状态方程如下:


min_dist(i, j) = w[i][j] + min(min_dist(i, j-1), min_dist(i-1, j))

       这里强调一下,状态转移方程是解决动态规划的关键。如果我们能写出状态转移方程,那动态规划问题基本上就解决一大半了,而翻译成代码非常简单。但是很多动态规划问题的状态本身就不好定义,状态转移方程也就更不好想到。

状态转移法的代码如下:


private int[][] matrix = 
         {{1,3,5,9}, {2,1,3,4},{5,2,6,7},{6,8,4,3}};
private int n = 4;
private int[][] mem = new int[4][4];
public int minDist(int i, int j) { // 调用minDist(n-1, n-1);
  if (i == 0 && j == 0) return matrix[0][0];
  if (mem[i][j] > 0) return mem[i][j];
  int minLeft = Integer.MAX_VALUE;
  if (j-1 >= 0) {
    minLeft = minDist(i, j-1);
  }
  int minUp = Integer.MAX_VALUE;
  if (i-1 >= 0) {
    minUp = minDist(i-1, j);
  }
  
  int currMinDist = matrix[i][j] + Math.min(minLeft, minUp);
  mem[i][j] = currMinDist;
  return currMinDist;
}

四种算法思想比较分析

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

        回溯算法是个万金油。基本上能用的动态规划、贪心解决的问题,我们都可以用回溯算法解决。回溯算法相当于穷举搜索。穷举所有的情况,然后对比得到最优解。不过,回溯算法的时间复杂度非常高,是指数级别的,只能用来解决小规模数据的问题。对于大规模数据的问题,用回溯算法解决的执行效率就很低了。

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

       贪心算法实际上是动态规划算法的一种特殊情况。它解决问题起来更加高效,代码实现也更加简洁。不过,它可以解决的问题也更加有限。它能解决的问题需要满足三个条件,最优子结构、无后效性和贪心选择性(这里我们不怎么强调重复子问题)。

      其中,最优子结构、无后效性跟动态规划中的无异。贪心选择性的意思是,通过局部最优的选择,能产生全局的最优选择。每一个阶段,我们都选择当前看起来最优的决策,所有阶段的决策完成之后,最终由这些局部最优解构成全局最优 

总结:

         “一个模型三个特征。其中,一个模型指的是,问题可以抽象成分阶段决策最优解模型。三个特征指的是最优子结构、无后效性和重复子问题。

        状态转移表法解题思路大致可以概括为,回溯算法实现 - 定义状态 - 画递归树 - 找重复子问题 - 画状态转移表 - 根据递推关系填表 - 将填表过程翻译成代码。状态转移方程法的大致思路可以概括为,找最优子结构 - 写状态转移方程 - 将状态转移方程翻译成代码。

       贪心、回溯、动态规划可以解决的问题模型类似,都可以抽象成多阶段决策最优解模型。尽管分治算法也能解决最优问题,但是大部分问题的背景都不适合抽象成多阶段决策模型。

参考资料

本章内容来源于对王争大佬的《数据结构与算法之美》的专栏。

41 | 动态规划理论:一篇文章带你彻底搞懂最优子结构、无后效性和重复子问题-极客时间

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

算法之动态规划理论 的相关文章

  • go 进阶 三方库之 go-redis

    目录 一 基础 初始化连接 使用示例 1 常用操作与string 2 操作hash类型 3 操作list 4 操作set 5 操作zset 6 发布与订阅 7 事物操作 8 执行Lua脚本 二 基于redis实现分布式锁 封装锁结构体 lu
  • 机器学习——生成分类数据的坐标系边界需要用到的技术方法

    0 前言 如果遇到一种应用场景需要将x轴数据和y轴数据所有点映射到坐标系中 需要得到坐标系中x和y映射的坐标点 就要用到meshgrid把x和y映射到坐标系中 然后把得到的结果用ravel把结果转成一维的 用np c 把x数据和y数据堆叠在
  • HTML01

    若有 double p x 10 int i 5 使指针变量 p指向元素 x 5 的语句为 正确答案 A 你的答案 A 正确 p x i p x p x i p x i 设函数fun和实参数组的说明形式为 void fun char ch

随机推荐

  • 解决Microsoft Visual C++ 14.0 or greater is required. Get it with “Microsoft C++ Build Tools“报错

    Microsoft Visual C 14 0 or greater is required Get it with Microsoft C Build Tools 具体报错如下 Building wheel for cyac pyproj
  • 老猿学5G:融合计费基于流计费的触发器Triggers

    前往老猿Python博文目录 一 概述 每个触发条件都是一个可计费事件 SMF中的功能体CTF在用户上网时达到一定条件就会向CHF上报流量 而CTF什么时候触发流量上报是由CTF中的触发器来控制的 当用户UE发起上网行为时 SMF中的CTF
  • 汇编逆向-Qt

    Qt源码解析 索引 汇编逆向 授权破解示例分析 问题模拟 运行环境 x64dbg Windows 10 Qt5 12 3 示例代码 使用Qt显示当前时间 模拟一般授权软件的时间判断逻辑 当时间超过授权日期后就提示授权过期 没有Qt经验的同学
  • Java中方法定义和调用的学习

    方法其实就是若干语句的功能集合 参数 原料 就是进入方法的数据 返回值 原产物 就是从方法中出来的数据 定义方法的完整格式 修饰符 返回值类型 方法名称 参数类型 参数名称 方法体 return 返回值 修饰符 现阶段的固定写法 publi
  • VSCode 之 设置 settings.json 配置文件

    这篇文章主要介绍了 VSCode settings json 配置 文中通过示例代码介绍的非常详细 对大家的学习或者工作具有一定的参考学习价值 VSCode 从插件库里安装 eslint 和 prettier 两个 插件 也 实现自动格式化
  • 微信小程序怎么和后台服务器交互

    要实现微信小程序和后台服务器之间的交互 可以使用以下方式 1 小程序发起HTTP请求 后台服务器接收和处理请求 返回相应结果 这是最常用的方式 可以使用小程序提供的wx request API来发送HTTP请求 后台服务器可以使用任何语言和
  • 获取动画状态机中动画片段的时间长度

    获取动画状态机中动画片段的长度 非常简单的代码 public float GetClipLength Animator animator string clipName if null animator string IsNullOrEmp
  • wps保存后怎么恢复

    单击窗口左上角的 WPS文字 或WPS表格 在出现的菜单中单击 备份管理 也可以通过任务窗格 文件菜单等 好多入口 单击右下角的 查看其他备份 按钮 找一下有没有你要的历史文档
  • jenkins学习笔记第十六篇 Jenkins·配置 Publish Over SSH 插件——访问远程服务器

    一般而言 Jenkins 不单单需要做到将远程仓库里的代码进行编译或者打包 还需要将编译后的代码上传到远程服务器 并且执行一些其他的命令 即 Github代码 编译得到war包 上传远程服务器 执行远程命令 Jenkins 是通过 SSH
  • STM32笔记15--串口通信基本原理

    15 1 串行通信接口背景知识 15 2 STM32F1串口框图讲解 参考资料 STM32开发指南 库函数 5 3 usart串口文件夹 第九章 串口实验 1 串行通信接口背景知识 首先 处理器与外部通信有两种常见方式 并行通信和串行通信
  • 架构的概念与介绍

    1 什么是架构和架构本质 在软件行业 对于什么是架构 都有很多的争论 每个人都有自己的理解 此君说的架构和彼君理解的架构未必是一回事 LInux有架构 MySQL有架构 JVM也有架构 使用Java开发 MySQL存储 跑在Linux上的业
  • r语言聚类分析_【SPSS数据分析】SPSS聚类分析(R型聚类)的软件操作与结果解读 ——【杏花开生物医药统计】...

    在上一讲中 我们讲述了针对样本进行聚类的分析方法 Q型聚类 今天我们将详细讲解针对变量数据进行的聚类分析 系统聚类之R型聚类 我们要将数据变量进行聚类 但不知道要分成几类 或者没有明确的分类指标的时候 就需要用到R型聚类 R型聚类分析不但可
  • 根据Sql生成ER图

    原文 https blog csdn net qq 17010367 article details 79212850 commentsedit 1 根据SQL文件生成ER图 首先准备好SQL文件 然后在PowerDesigner 里 点击
  • 字符串表达式校验&求值(C#实现) - 附代码

    一 参考文献 严蔚敏 数据结构 C语言版 二 功能演示 1 测试例子 2 测试结果 三 对表达式进行校验 怎么对输入的字符串表达式进行校验呢 1 对表达式按操作符进行拆分 返回一个字符串数组 代码 private static string
  • Oracle数据库删除重复数据

    Oracle数据库中如何删除重复数据 第一种情况 部分字段重复数据的删除 先查询出那些数据是重复的 select 字段1 字段2 count from 表名 group by 字段1 字段2 having count gt 1 将上面的大于
  • TIA博途S7-1200学习笔记——指令集

    目录 1 位逻辑运算操作 1 1 常开触点 1 2 常闭触点 1 3 取反触点 1 4 线圈 1 5 赋值取反 1 6 复位输出 1 7 置位输出 1 8 置位位域 1 9 复位位域 2 10 SR置位 复位触发器 1 11 RS复位 置位
  • 【activiti】网关

    activiti网关 网关是用来控制流程的走向的 1 排他网关 ExclusiveGateway 1 1 什么是排他网关 排他网关 用来在流程中实现决策 当执行到这个网关时 会根据判断条件去选择执行某一条分支 注意 排他网关只会选择一个为t
  • 5.5_数据的存储和排列

    文章目录 一 大小端模式 二 边界对齐 在这个小结中 我们要探讨的是 数据的存储和排列 一 大小端模式 首先来看一个之前提到过的问题 叫做大小端模式 我们在内存里经常会存储某一些多字节的数据 比如 c 语言里的 Int 型变量 在很多时候占
  • renren-fast 快速开发 Web 管理平台

    什么是 renren fast renren fast 是一个 Java 的开源项目 只需要对它进行简单修改 就能够应用到自己的项目中 大大简化开发流程 缩短开发周期 renren fast 是一个前后端分离开发的项目 前端基于 vue e
  • 算法之动态规划理论

    目录 前言 一个模型三个特征理论讲解 1 最优子结构 2 无后效性 3 重复子问题 一个模型三个特征实例剖析 两种动态规划解题思路总结 1 状态转移表法 2 状态转移方程法 四种算法思想比较分析 总结 参考资料 前言 本篇博文主要讲解动态规