动态规划基础知识

简介: 简介: 公众号:程序员学长,分享编程知识,这篇文章讲解了《动态规划基础知识》,如何喜欢,记得点个关注吧

动态规划基础知识

问题模型

动态规划一般是用来解决最优解问题,而在解决的过程中是需要经历多个阶段的决策,每个阶段都会对应一组状态。我们需要找到一组决策,经过这些决策后,能求出问题的最优解。我们把这类问题抽象成“多阶段决策最优模型”。
动态规划具有三大特征,分别是最优子结构、无后效性和重复子问题,只有当原问题满足这三大特征时,我们才能使用动态规划的算法思想来解决。下面我们来分别看一下这三个特征。

  • 最优子结构
    最优子结构规定了原问题和子问题的关系。原问题的最优解包含子问题的最优解。反过来说,我们可以通过子问题的最优解来求出原问题的最优解。
  • 无后效性
    (1)无后效性是指在推导后面阶段状态的时候,只依赖于前面阶段的的状态,而不会去关心这个状态是怎么一步步得来的。比如斐波那契数列 F(5)=F(4)+F(3),则可以看出 F(5) 只依赖于 F(4) 和 F(3) 这两个状态的值,而不用管他们是如何得来的。
    (2)一旦某个状态确定了,就不受之后阶段的决策影响。
  • 重复子问题
    就是原问题经过拆分成多个子问题时,子问题和子问题之间存在重复计算的情况。

下面我们结合实例,来比较透彻的理解一下上面的理论部分。

问题描述:

我们假设有一个 n*n 的矩阵 w[n] [n](矩阵中存储正整数)。棋子从矩阵的左上角开始移动到矩阵的右下角。棋子每次只能向下或者向右移动一步。棋子可以有很多不同的路径走完这个矩阵。我们把每条路径经过的数字相加作为路径长度,那最短的路径长度是多少呢?
image-20211222183754767
我们从 start 开始走,一直走到 end 的位置,一共需要走 2 (n-1) 步,也就对应着 2 (n-1) 个阶段,每个阶段都有向下走和向右走两种决策,不同的决策对应着不同的状态。所以这符合多阶段决策,而最后求解的是最短路径长度,所以符合动态规划的问题模型。
接下来我们看它是否符合动态规划的三个特征呢?如下所示,从(0,0)位置走到(1,1)位置有两种走法,所以符合重复子问题。
image-20211222184015066
下面我们再来看一下无后效性这个特征。我们要想走到(i,j)这个位置,我们只能通过(i-1,j)和(i,j-1)这两个位置移动过来,也就是说,我们只需要关心(i-1,j)和(i,j-1)这两个位置的状态,而不必关系是如何从(0,0)位置走到这个位置的。而且,我们这里只允许向下或者向右走,不允许后退,所以前面阶段的状态确定之后,就不会被后面阶段的决策所改变。所以这个问题是符合“无后效性”这个特征的。
我们把从(0,0)位置走到(i,j)位置的最短路径记为为 min(i,j),因为我们只能向下或者向右移动,所以我们只能从(i-1,j)和(i,j-1)两个位置到达(i,j)。换句话说,就是 min(i,j) 只能通过 min(i-1,j) 和 min(i,j-1) 两个状态推导出来。所以这个问题就符合“最优子结构”。

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

所以这个问题是符合动态规划问题模型的,所以我们可以采用动态规划思想来解决这个问题。

求解思路

解决动态规划问题,一般有状态转移表法和状态转移方程法这两种方法。

  1. 状态转移表法

我们先定义一个状态表,一般状态表都是二维的。我们根据决策的先后顺序,从前往后,根据递归关系,分阶段的填充状态表中的每个状态。最后,我们把递归填表的过程翻译成代码就完成了。
接下来我们来看一下如何用状态转移表法来求最短路径这个问题的。
首先创建一个二维数组,表中的行、列表示棋子所在的位置,表中的数值表示从起点到这个位置的最短路径。然后,我们按照决策过程依次去填表,前两步的走法如下图所示:
image-20211222184600442
代码实现如下所示:

def minDis(data,n):
    status=[[0 for _ in range(n)] for _ in range(n)]
    sum=0
    #第一行赋值
    for i in range(n):
         sum=sum+data[0][i]
         status[0][i]=sum
    #第一列赋值
    sum=0
    for i in range(n):
         sum=sum+data[i][0]
         status[i][0]=sum
    for i in range(1,n):
         for j in range(1,n):
              status[i][j]=data[i][j]+min(status[i-1][j],status[i][j-1])
    return status[n-1][n-1]
data=[[1,3,5,7,2],
      [3,6,5,2,1],
      [7,4,1,6,5],
      [1,3,8,2,3],
      [4,3,1,6,4]]
n=5
print(minDis(data,n))
  1. 状态转移方程法

状态转移方程法和递归的解题思路类似。我们根据最优子结构,写出递推公式,也就是所谓的状态转移方程。然后根据状态转移方程,实现代码就好了。这里一般有两种实现方法,一种是递归加备忘录方法,另一种是迭代递推。
对于最短路径这个例子,它的状态转移方程如下所示:

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

我们这里采用递归加备忘录的方法来实现,另一种迭代递推的实现和状态转移表法的代码实现是一致的,只是思路不同而已。

import sys
data=[[1,3,5,7,2],
      [3,6,5,2,1],
      [7,4,1,6,5],
      [1,3,8,2,3],
      [4,3,1,6,4]]
n=5
mem=[[0 for _ in range(5)] for _ in range(5)]
def minDis(i,j):
    if i==0 and j==0:
        return data[0][0]
    if mem[i][j]>0:
        return mem[i][j]
    minLeft = sys.maxsize
    if j-1>=0:
        minLeft=minDis(i,j-1)
    minUp = sys.maxsize
    if i-1>=0:
        minUp=minDis(i-1,j)
    current=data[i][j]+min(minLeft,minUp)
    mem[i][j]=current
    return current
print(minDis(n-1,n-1))
相关文章
|
7月前
|
算法
【AcWing算法基础课】第五章 动态规划(未完待续)(3)
当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。
80 0
|
1月前
|
算法 调度 C++
[数据结构与算法]贪心算法(原理+代码)
[数据结构与算法]贪心算法(原理+代码)
|
1月前
|
算法
数据结构与算法之动态规划
数据结构与算法之动态规划
36 2
|
1月前
|
人工智能 算法 程序员
「程序员必须掌握的算法」动态规划「上篇」
「程序员必须掌握的算法」动态规划「上篇」
|
8月前
|
算法
算法:动态规划的入门理解
算法:动态规划的入门理解
|
10月前
|
存储 机器学习/深度学习 算法
第 12 天_动态规划【算法入门】
第 12 天_动态规划【算法入门】
109 0
|
人工智能 算法
动态规划入门
动态规划入门
38 0
|
算法
数据结构与算法(五) 动态规划 下
数据结构与算法(五) 动态规划
52 0
|
算法
数据结构与算法(五) 动态规划 上
数据结构与算法(五) 动态规划
54 0
|
算法
数据结构与算法(九)动态规划
数据结构与算法(九)动态规划
69 0

热门文章

最新文章