动态规划基于一个递推公式、一个或多个初始状态。当前子问题的解由上一个子问题的解求出。
从斐波那契数列讲起:
def fib(n):
if n == 1 or n == 2:
print("计算 f({}) = 1".format(n))
return 1
else:
f1 = fib(n - 1)
f2 = fib(n - 2)
print("计算 f({}) = f({}) + f({}) = {}".format(n, n-1, n-2, f1+f2))
return f1 + f2
运行 fib(5)
(1) 递归调用fib(5) 自顶向下执行过程,从调用fib(5)开始到计算出 fib(5) 结束。
(2)计算过程中存在大量的重复计算,求 fib(5) 的过程存在两次重复计算 fib(3)。
避免重复计算,设计一个数组 dp,dp[i] 存放 fib(i) 的值。
dp = [0, 1, 1]
def fib(n):
if n in [1,2]:
print("计算 f({}) = 1".format(n))
else:
for i in range(3, n+1):
dp.append(dp[i-1] + dp[i-2])
print("计算 f({}) = f({}) + f({}) = {}".format(i, i-1, i-2, dp[i]))
return dp[n]
运行 fib(5)
执行过程改为自底向上,即先求出子问题解,将计算结果存放在一张表中,而且相同的子问题只计算一次。在后面需要时只需简单查表,避免大量的重复计算。
上例中的第二个算法属于动态规划法,数组 dp 为动态规划数组。
动态规划求解过程:
又被称为记录结果再利用方法。
动态规划原理
动态规划是一种解决多阶段决策问题的优化方法。把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解。
- 阶段
把原问题的求解分成 n 个阶段,有顺序地求解。 - 阶段变量
用 k1、k2、、… 、kn 标识 n 个阶段 - 状态
描述当前阶段决策特征的量,每个阶段只选取一个状态。可以是数字、字符。通常是上一个阶段的结束点或本阶段开始点组成的集合。 - 状态变量
通常记为 sk,一个阶段所有状态变量组成的集合称为状态集,Sk。 - 决策
处理某一阶段的状态时,面对下一阶段的状态做出的选择 / 决定。 - 策略
从当前阶段开始进行到下面某一阶段的决策的集合。 - 阶段转移方程
某个状态以及该状态下的决策与下一个状态间的指标函数间的关系。
指标函数:衡量对某一阶段决策过程进行控制的数量指标。
求最优解时,一般对应的指标函数为最优指标函数。
动态规划解法
对于一个分为 k 个阶段的问题而言,若
- 从第 k 个阶段向第 1 个阶段进行解题,称为逆序解法;
- 从第 1 个阶段向第 k 个阶段进行解题,称为顺序解法。
动态规划可解问题具有的性质:
最优子结构 | 问题的最优解包含它的子问题的最优解 |
无后效性 | 某阶段状态确定后,不受其后决策的影响 |
重叠子结构 | 子问题间不独立,即一个子问题在其后阶段决策中可能被用到 |
(重叠子结构不是动态规划的必要条件,但没有重叠子结构,动态规划算法与其他算法相比,也不具备优势)
动态规划处理的问题是多阶段决策问题。一般由初始状态开始,通过对中间决策阶段的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了一条完成整个过程的活动路线。所以动态规划算法有一定的模式,一般有以下阶段:
- 划分阶段
按照问题的的空间特征把问题分成多个阶段。划分时注意,划分的阶段应是有序的或可排序的,否则问题无法求解。 - 确定状态和状态变量
把问题发展到各个阶段时所处的各种客观情况,用不同的状态表示出来。状态的选择应满足无后效性。 - 确定决策并给出状态转移方程
状态转移就是根据上一阶段的决策和状态导出本阶段的状态。但是常常依据相邻两个状态间的关系确定决策方法和状态转移方程·。 - 寻找边界条件
很多状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
一般条件下,只要解决问题的阶段、状态和状态转移决策确定了,状态转移方程与边界条件就可以得出。
动态规划是一种求解思路,注重是决策过程。动态规划原理是利用递推关系求最优解。
动态规划与分治法
它们都将带求解问题分成若干个子问题(阶段),按顺序求解子问题,前一个子问题的解为后一个子问题的解提供有用信息
但是动态规划的子问题间重叠、不独立。各个子问题包含公共的子子问题;而分治法的子问题独立,不重叠。
推荐一篇文章告诉你动态规划的应用,让你更6,更奥里给!!!
点这里