动态规划(Dynamic Programming,简称DP)是一种算法策略,用于解决具有重叠子问题和最优子结构特性的优化问题。它是一种将复杂问题分解成更简单的子问题,并通过存储这些子问题的解(通常在一个表格中),避免重复计算,从而提高效率的方法。
动态规划的原理:
-
重叠子问题:动态规划适用于那些子问题会重复出现的问题。每次遇到子问题时,都计算其解并将其存储起来,之后再次遇到时直接使用。
-
最优子结构:一个问题的最优解包含其子问题的最优解。这意味着可以通过子问题的最优解来构建原问题的最优解。
-
无后效性:一旦某个状态被确定,它就不受之后决策的影响。这意味着你可以在不担心未来决策的情况下,独立地解决每个子问题。
-
记忆化:动态规划通常涉及两个过程,自顶向下的“记忆化”递归和自底向上的迭代。在记忆化递归中,每次解决子问题时都会检查是否已经计算过该子问题的解;如果是,则直接使用存储的解。
动态规划的基本步骤:
-
定义状态:确定dp数组的含义,每个元素dp[i]通常代表一个子问题的解。
-
确定状态转移方程:找出状态之间的关系,即如何从已知状态得到新状态。
-
确定初始状态和边界条件:初始化dp数组,确定算法的起点。
-
确定计算顺序:确定如何计算dp数组,通常采用自底向上的方法。
例子:0/1背包问题
问题描述:
假设你有一个背包,它能承载的最大重量为W。现在有n个物品,每个物品有一定的重量wi和价值vi,你需要选择一些物品装入背包,使得背包中物品的总价值最大,但总重量不超过W。
动态规划解法:
-
定义状态:
dp[i][j]
表示在前i个物品中,背包容量为j时的最大价值。 -
状态转移方程:
- 如果不选择第i个物品:
dp[i][j] = dp[i-1][j]
- 如果选择第i个物品(前提是j >= wi):
dp[i][j] = dp[i-1][j-wi] + vi
- 综合两种情况:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-wi] + vi)
- 如果不选择第i个物品:
-
初始状态:
dp[0][j] = 0
对所有j,因为没有物品时价值为0。 -
边界条件:
dp[i][0] = 0
对所有i,因为背包容量为0时无法装任何物品。 -
计算顺序:按照i和j从小到大的顺序计算。
Python代码实现:
def knapSack(W, wt, val, n):
dp = [[0 for x in range(W+1)] for x in range(n+1)]
for i in range(1, n+1):
for w in range(1, W+1):
if wt[i-1] <= w:
dp[i][w] = max(val[i-1] + dp[i-1][w-wt[i-1]], dp[i-1][w])
else:
dp[i][w] = dp[i-1][w]
return dp[n][W]
# 物品重量
wt = [1, 3, 4, 5]
# 物品价值
val = [1, 4, 5, 7]
# 背包最大承载重量
W = 7
# 物品数量
n = len(wt)
print(knapSack(W, wt, val, n))
在这个例子中,我们使用了一个二维数组来存储子问题的解,并通过填充这个数组来找到最大价值。动态规划使我们避免了重复计算子问题的解,从而提高了算法的效率。