动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法
1 原理
问题的最优解如果可以由子问题的最优解推导得到,则可以先求解子问题最优解,再构造原问题的最优解;若子问题有较多的重复出现,则可以自底向上从最终子问题向原问题逐步求解
动态规划在经济管理、生产调度、工程技术和最优控制等方面得到了广泛的应用。例如最短路线、库存管理、资源分配、设备更新、排序、装载等问题,用动态规划方法比用其它方法求解更为方便。比如背包问题(Knapsack problem)、最长公共子串(LCS)等
2 背包问题
一个小偷背了一个背包潜进了金店,包就那么大,他如果保证他背出来所有物品加起来的价值最大,这就是背包问题的来源,背包问题分为01背包问题和完全背包问题
① 0-1背包问题:在最优解中,每个物品只有两种可能的情况,即在背包中或者不在背包中(背包中的该物品数为0或1)
② 完全背包问题:在放入物品时,不限定每个物品的数量,即是完全背包问题
3 问题抽象
现实问题解决的一个关键问题,就是抽象数据模型
①
n
n
n个数对
(
w
i
,
v
i
)
,
0
≤
i
≤
n
(w_i,v_i),0\le i \le n
(wi,vi),0≤i≤n
② 正数
C
C
C
③ 条件函数
m
a
x
∑
i
=
1
n
x
i
w
i
≤
C
,
x
i
∈
{
0
,
n
}
max \sum_{i=1}^{n}x_iw_i \le C, \quad x_i \in \{0,n\}
maxi=1∑nxiwi≤C,xi∈{0,n}
④ 目标函数(最大值)
∑
i
=
1
n
x
i
v
i
,
x
i
∈
{
0
,
n
}
\sum_{i=1}^{n}x_iv_i, \quad x_i \in \{0,n\}
i=1∑nxivi,xi∈{0,n}
4 状态转换方程
d
p
(
i
,
j
)
=
m
a
x
(
d
p
(
i
−
1
,
j
)
,
d
p
(
i
−
1
,
j
−
w
[
i
]
)
+
v
[
i
]
)
,
j
≥
w
[
i
]
dp(i,j) = max(dp(i-1, j), dp(i-1, j-w[i]) + v[i]),j \ge w[i]
dp(i,j)=max(dp(i−1,j),dp(i−1,j−w[i])+v[i]),j≥w[i]
其中:
i
i
i,第
i
i
i件商品
w
[
i
]
w[i]
w[i],第
i
i
i件商品重量
v
[
i
]
v[i]
v[i],第
i
i
i件商品价值
当
j
≤
w
[
i
]
j \le w[i]
j≤w[i]时,不计算
d
p
(
i
−
1
,
j
−
w
[
i
]
)
+
v
[
i
]
)
dp(i-1, j-w[i]) + v[i])
dp(i−1,j−w[i])+v[i])
5 算法例解(0-1背包问题)
条件:
① 四种商品
品类 | 重量(磅) | 价值(RMB) |
---|---|---|
音响 | 4 | 3000 |
笔记本电脑 | 3 | 2000 |
吉他 | 1 | 1500 |
② 目标值重量4,求最大价值
③ 流程
背包空间(磅) | ||||
放入商品 | 1 | 2 | 3 | 4 |
音响 (4, 3000) | 0 | 0 | 0 | 3000 |
笔记本电脑 (3, 2000) | 0 | 0 | 2000 | 3000 |
吉他 (1, 1500) | 1500 | 1500 | 2000 | 3500 |
④ 计算过程
放入第一个物体(音响,4 3000)
空
间
为
1
,
放
不
下
,
f
(
1
,
1
)
=
0
空间为1,放不下, f(1,1) = 0
空间为1,放不下,f(1,1)=0
空
间
为
2
,
放
不
下
,
f
(
1
,
2
)
=
0
空间为2,放不下, f(1,2) = 0
空间为2,放不下,f(1,2)=0
空
间
为
3
,
放
不
下
,
f
(
1
,
3
)
=
0
空间为3,放不下, f(1,3) = 0
空间为3,放不下,f(1,3)=0
空
间
为
4
,
放
得
下
,
f
(
1
,
4
)
=
3000
空间为4, 放得下,f(1,4) = 3000
空间为4,放得下,f(1,4)=3000
放入第二个物体(笔记本电脑, 3 2000)
空
间
为
1
,
f
(
2
,
1
)
=
m
a
x
(
f
(
1
,
1
)
,
f
(
1
,
−
2
)
+
2000
)
=
m
a
x
(
0
,
0
)
=
0
空间为1,f(2, 1) = max(f(1,1), f(1,-2) +2000) = max (0, 0) = 0
空间为1,f(2,1)=max(f(1,1),f(1,−2)+2000)=max(0,0)=0
空
间
为
2
,
f
(
2
,
2
)
=
m
a
x
(
f
(
1
,
2
)
,
f
(
1
,
−
1
)
+
2000
)
=
m
a
x
(
0
,
0
)
=
0
空间为2,f(2, 2) = max(f(1,2), f(1,-1) +2000) = max (0, 0) = 0
空间为2,f(2,2)=max(f(1,2),f(1,−1)+2000)=max(0,0)=0
空
间
为
3
,
f
(
2
,
3
)
=
m
a
x
(
f
(
1
,
3
)
,
f
(
1
,
0
)
+
2000
)
=
m
a
x
(
0
,
2000
)
=
2000
空间为3,f(2, 3) = max(f(1,3), f(1,0) +2000) = max (0, 2000) = 2000
空间为3,f(2,3)=max(f(1,3),f(1,0)+2000)=max(0,2000)=2000
空
间
为
4
,
f
(
2
,
4
)
=
m
a
x
(
f
(
1
,
4
)
,
f
(
1
,
1
)
+
2000
)
=
m
a
x
(
3000
,
2000
)
=
3000
空间为4,f(2, 4) = max(f(1,4), f(1,1) +2000) = max (3000, 2000) = 3000
空间为4,f(2,4)=max(f(1,4),f(1,1)+2000)=max(3000,2000)=3000
放入第三个物体(吉他,1 1500)
空
间
为
1
,
f
(
3
,
1
)
=
m
a
x
(
f
(
2
,
1
)
,
f
(
2
,
0
)
+
1500
)
=
m
a
x
(
0
,
1500
)
=
1500
空间为1,f(3, 1) = max(f(2,1), f(2,0) +1500) = max (0, 1500) = 1500
空间为1,f(3,1)=max(f(2,1),f(2,0)+1500)=max(0,1500)=1500
空
间
为
2
,
f
(
3
,
2
)
=
m
a
x
(
f
(
2
,
2
)
,
f
(
2
,
1
)
+
1500
)
=
m
a
x
(
0
,
1500
)
=
1500
空间为2,f(3, 2) = max(f(2,2), f(2,1) +1500) = max (0, 1500) = 1500
空间为2,f(3,2)=max(f(2,2),f(2,1)+1500)=max(0,1500)=1500
空
间
为
3
,
f
(
3
,
3
)
=
m
a
x
(
f
(
2
,
3
)
,
f
(
2
,
2
)
+
1500
)
=
m
a
x
(
2000
,
1500
)
=
2000
空间为3,f(3, 3) = max(f(2,3), f(2,2) +1500) = max (2000, 1500) = 2000
空间为3,f(3,3)=max(f(2,3),f(2,2)+1500)=max(2000,1500)=2000
空
间
为
4
,
f
(
3
,
4
)
=
m
a
x
(
f
(
2
,
4
)
,
f
(
2
,
3
)
+
1500
)
=
m
a
x
(
3000
,
3500
)
=
3500
空间为4,f(3, 4) = max(f(2,4), f(2,3) +1500) = max (3000, 3500) = 3500
空间为4,f(3,4)=max(f(2,4),f(2,3)+1500)=max(3000,3500)=3500
6 程序例解
按照【5】分析的条件,进行代码验证(注:该程序参考了网上相关代码)
package main
import "fmt"
const (
// 行
RAW int = 4
// 列
COL int = 5
)
var weight = [RAW]int{0, 3, 4, 1}
var value = [RAW]int{0, 2000, 3000, 1500}
var cells [RAW][COL]int
var selected [RAW]int
// 动态规划计算网格
func dp() {
for i := 1; i < len(value); i++ {
for j := 1; j < 5; j++ {
cells[i][j] = maxValue(i, j)
}
}
for i := 0; i < RAW; i++ {
fmt.Printf("raw is %v \n", cells[i])
}
findBack()
fmt.Printf("selected goods is %v \n", selected)
}
// 判断当前单元格最大价值方法
func maxValue(i, j int) int {
// 当前商品无法放入背包,返回当前背包所能容纳的最大价值
if j < weight[i] {
return cells[i-1][j]
}
// 可放进背包时候,计算放入当前商品后的最大价值
curr := value[i] + cells[i-1][j-weight[i]]
if curr >= cells[i-1][j] {
return curr
}
return cells[i-1][j]
}
// 回溯选择的商品方法
func findBack() {
col := COL - 1
for i := RAW - 1; i > 0; i-- {
if cells[i][col] > cells[i-1][col] {
selected[i] = 1
col = col - weight[i]
} else {
selected[i] = 0
}
}
}
func main() {
dp()
}
raw is [0 0 0 0 0]
raw is [0 0 0 2000 2000]
raw is [0 0 0 2000 3000]
raw is [0 1500 1500 2000 3500]
selected goods is [0 1 0 1]
sandbox> exited with status 0