动态规划-Python
动态规划-Python
动态规划(Dynamic Programming,简称DP)是解决多阶段决策过程最优化问题的一种方法。
动态规划算法的基本思想是:
将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;
对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。
动态规划算法将问题的解决方案视为一系列决策的结果
# 自底向上迭代的动态规划
# 初始化 base case
dp[0][0][...] = base case
# 进行状态转移
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 求最值(选择1,选择2...)
# 自顶向下递归的动态规划
def dp(状态1, 状态2, ...):
for 选择 in 所有可能的选择:
# 此时的状态已经因为做了选择而改变
result = 求最值(result, dp(状态1, 状态2, ...))
return result
1.斐波那契数列
斐波那契数列公式为:
F(0) = 0, F(1) = 1
F(n) = F(n - 1) + F(n - 2), 其中 n > 1
求该数列第n项
递归解法
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
return self.fib(n - 1) + self.fib(n - 2)
动态规划解法
- 定义状态: 定义状态
dp[i]
,表示斐波那契数列的第 i 项的值 - 状态转移方程: 根据斐波那契数列的定义,可以得到状态转移方程:
dp[i] = dp[i - 1] + dp[i - 2]
- 边界条件: 初始条件为
dp[0] = 0
和dp[1] = 1
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
dp = [0 for i in range(n + 1)]
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
空间优化
dp[i]
只与dp[i - 1]
和dp[i - 2]
有关,只需两个变量滚动前进即可。
class Solution:
def fib(self, n: int) -> int:
if n < 2:
return n
a, b = 1, 1
for _ in range(2, n):
a, b = b, a + b
return b
2. 0/1背包问题
给定一个容量为 M 的背包,以及 n 个物品,每个物品有一个重量 w[i] 和一个价值 c[i] 。要求在不超过背包容量的前提下,选择物品使得总价值最大。
【输入】
第1行: 两个整数,M表示背包容量,n表示物品数量。
第2~n+1行: 每行两个整数wi、ci,表示每个物品的重量和价值。
【输入样例】
10 4
2 1
3 3
4 5
7 9
构建动态规划表
假设有以下物品和背包容量:
物品 | 重量 w[i] |
价值 c[i] |
---|---|---|
1 | 2 | 1 |
2 | 3 | 3 |
3 | 4 | 5 |
4 | 7 | 9 |
背包容量 M = 10
填充动态规划表来计算最大价值:
dp[i][j] |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
2 | 0 | 0 | 1 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
3 | 0 | 0 | 1 | 3 | 5 | 5 | 6 | 8 | 8 | 9 | 9 |
4 | 0 | 0 | 1 | 3 | 5 | 5 | 6 | 9 | 9 | 10 | 12 |
最终,dp[4][10] = 12
,表示在不超过重量 10 的情况下,能获得的最大价值为 12
- 定义状态:
dp[i][j]
表示前i个物品中,背包最大载重为j的情况下,最大价值的总和 - 状态转移方程:
如果不选择第i个物品dp[i][j]=dp[i - 1][j]
如果选择第i个物品dp[i][j]=dp[i - 1][j - w[i]] + c[i]
可得 $dp[i][j] = \max(dp[i-1][j], dp[i-1][j - w[i]] + c[i])$ - 边界条件:
dp[0][j] = 0
(没有物品时,最大价值为 0)
dp[i][0] = 0
(背包容量为 0 时,最大价值为 0)
代码实现
# 读取背包容量 M 和物品数量 n
M, n = map(int, input().split())
# 初始化物品重量和价值数组,长度为 n+1,初始值为0
w = [0] * (n + 1)
c = [0] * (n + 1)
# 读取每个物品的重量和价值
for i in range(1, n + 1):
w[i], c[i] = map(int, input().split())
# 初始化 dp 数组,dp[i][j] 表示前 i 个物品在容量为 j 时的最大价值
dp = [[0 for _ in range(M + 1)] for _ in range(n + 1)]
# 动态规划求解
for i in range(1, n + 1): # 遍历每个物品
for j in range(1, M + 1): # 遍历每个容量
if j < w[i]: # 当前容量不够放下第 i 个物品
dp[i][j] = dp[i - 1][j]
else: # 当前容量可以放下第 i 个物品,取最大值
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + c[i])
# 查看动态规划表
for i in range(n + 1):
for j in range(M + 1):
print(dp[i][j], end=" ")
print()
# 输出最大价值
print(dp[n][M])
参考资料: