什么是动态规划
动态规划的英文名叫Dynamic Programming,是一种分阶段求解决策问题的数学思想。
动态规划的基本模型如下:
- 确定问题的决策对象
- 对决策过程划分阶段
- 对各个阶段确定状态变量
- 根据状态变量确定费用函数和目标函数
- 确定状态转移方程
动态规划方法的起源
在20世纪50年代初,美国数学家理查德·贝尔曼(Richard E. Bellman) 等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题逐个求解,创立了解决这类过程优化问题的新方法——动态规划。
贝尔曼在1958提出Bellman - Ford 算法,Bellman - ford算法是求含负权图的单源最短路径的一种算法
动态规划适用条件
适合应用动态规划方法求解的最优化问题,应该具备两个要素:最优子结构和子问题重叠
- 最优子结构
如果一个问题的最优解包含其子问题的最优解,就称此问题具有最优子结构性质。
某个问题是否能用动态规划方法解决,是否具有最优子结构是关键因素
利用反正法,来证明问题的一个最优解中,使用的子问题的解本身也是最优的。如果存在更优的子问题的解,构造的问题的解就不是最优的
- 重叠子问题
子问题重叠的意思是问题的递归算法会反复求解相同的子问题
动态规划算法总是充分利用重叠子问题,通过每个子问题只解一次,把解保存在一个需要时就可以查看的表中,每次查表的时间为常数
题目
国王和金矿
有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是10人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?
金矿编号 | 储量 | 工人数 |
---|---|---|
1 | 400 | 5 |
2 | 500 | 5 |
3 | 200 | 3 |
4 | 300 | 4 |
5 | 350 | 3 |
解题过程:
- 确定决策对象:得到尽可能多的黄金
- 分阶段:分成5个阶段,每个阶段处理一座金矿
- 确定状态变量: 当前处理的金矿编号和当前的工人数
- 确定目标函数: F(5,10) 是10个人处理第5座金矿时得到的最大的黄金数
- 确定状态转移方程:根据状态变量的范围列方程
- F(N,W) = 0 (N <= 1, W < P[0])
- F(N,W) = G[0] (N == 1, W >= P[0])
- F(N,W) = F(N - 1, W) (N > 1, W < P[N - 1])
- F(N,W) = MAX(F(N - 1, W) , F(N - 1, W - P[N - 1]) + G[N - 1]) (N > 1, W >= P[N - 1])
go的实现代码
package main
import (
"fmt"
"math"
)
//DigGold 挖金矿
func DigGold(g []int, p []int, w int) int {
if len(g) == 0 || len(p) == 0 || w == 0 {
return 0
}
lenGold := len(g) //金矿数量
pre := make([]int, w+1, w+1) //上一阶段的结果,坐标从1开始
result := make([]int, w+1, w+1) //当前阶段的结果
//外层循环是金矿的数量,内层循环是工人数
for i := 0; i < lenGold; i++ {
for j := 1; j <= w; j++ {
if i == 0 {
if j < p[0] {
result[j] = 0
} else {
result[j] = g[0]
}
} else {
if j < p[i] {
result[j] = pre[j]
} else {
neww := j - p[i]
result[j] = int(math.Max(float64(pre[j]), float64(pre[neww]+g[i])))
}
}
}
fmt.Println(result)
if i < lenGold-1 {
pre, result = result, pre
}
}
return result[w]
}
func main() {
p := []int{5, 5, 3, 4, 3}
g := []int{400, 500, 200, 300, 350}
fmt.Println(DigGold(g, p, 10))
}
输出结果
阶段 | 1工人 | 2工人 | 3工人 | 4工人 | 5工人 | 6工人 | 7工人 | 8工人 | 9工人 | 10工人 |
---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 400 | 400 | 400 | 400 | 400 | 400 |
2 | 0 | 0 | 0 | 0 | 500 | 500 | 500 | 500 | 500 | 900 |
3 | 0 | 0 | 200 | 200 | 500 | 500 | 500 | 700 | 700 | 900 |
4 | 0 | 0 | 200 | 300 | 500 | 500 | 500 | 700 | 800 | 900 |
5 | 0 | 0 | 350 | 350 | 500 | 550 | 650 | 850 | 850 | 900 |
答案是900
跟分治法比较
分治法(divide-and-conquer):将原问题划分成n个规模较小而结构与原问题相似的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
分治法则会重复地求解公共的子问题,动态规划对每个子子问题只求解一次,将其结果保存在一张表中。
跟贪心算法比较
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择,做出的是在某种意义上的局部最优解。
贪心算法中,由上一步的最优解推导下一步的最优解,而上一部之前的最优解则不作保留。
动态规划算法中,需要记录之前的所有最优解,由这些最优解推导全局最优解。
参考链接
动态规划漫画