动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。
能采用动态规划求解的问题的一般要具有3个性质:
动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。
动态规划一般都是使用表格法进行求解。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。
这里我已背包问题为例详细介绍如何利用动态规划解决实际问题:
假设有一个小偷,有一个承重容量为35kg的背包,可以盗窃的物品有三种,每种只有一个,包括:A(30kg,价值3000元)、B(20kg,价值2000元)、C(15kg,价值1500元)。怎么选择才能使装入背包的物品价值最高?
接下来我用表格演示动态规划过程,如下图:
通过以上三步,我们就得到最大的方案,也就是把B和C同时放入背包时价值最大,最大为3500元。在表格推理过程中我们隐藏了一个细节,在计算每个单元格的 价值时,都会遵循一下公式:
当 前 最 大 价 值 = max { 当 前 容 量 对 应 的 上 一 行 单 元 格 的 价 值 当 前 物 品 的 价 值 + 剩 余 容 量 对 应 的 上 一 行 单 元 格 对 应 的 价 值 当前最大价值= \max \begin{cases} 当前容量对应的上一行单元格的价值\\ 当前物品的价值 + 剩余容量对应的上一行单元格对应的价值 {} \end{cases} 当前最大价值=max{当前容量对应的上一行单元格的价值当前物品的价值+剩余容量对应的上一行单元格对应的价值
这里我们用数学公式来表达,方便程序编写。用 g i g_i gi表示物品种类,用 w i w_i wi表示物品对应的重量,用 v i v_i vi表示物品对应的价值。用 V V V表示当前表格,维度为 ( M (M (Mx N ) N) N),M为物品的种类,N为容量划分数量(可以根据自己需要划分), V m a x V_{max} Vmax表示当前的最大值, G G G表示背包容量,则用公式表示为:
V [ m ] [ n ] = max ( V [ m − 1 ] [ n ] , v [ m ] + V [ m − 1 ] [ G − g [ m ] ] ) V[m][n]=\max(V[m-1][n],\space\space v[m]+V[m-1][G-g[m]]) V[m][n]=max(V[m−1][n], v[m]+V[m−1][G−g[m]])
代码实现如下:
def package_1(weights, values, capacities):
"""
该函数是利用动态规划解决0/1背包问题
:param weights: 每个物品的重量
:param values: 每个物品对应的价值
:param capacities: 预先设定的容量变化列表
:return:
"""
table_value = [[ 0 for _ in range(len(capacities))] for _ in range(len(weights))]
# 填充表格第一行
for k in range(len(capacities)):
if weights[0] <= capacities[k]:
table_value[0][k] = values[0]
for i in range(1, len(weights)):
for j in range(len(capacities)):
if weights[i] < capacities[j]:
continue
# 假定放入第i个物品后,剩余容量对应的索引
ind = capacities.index(capacities[-1] - weights[i])
table_value[i][j] = max(table_value[i-1][j], values[i]+table_value[i-1][ind])
假设有一个小偷,有一个承重容量为10kg的背包,可以盗窃的物品有三种,每种都不限数量,包括:A(3kg,价值30元)、B(2kg,价值20元)、C(1kg,价值15元)。怎么选择才能使装入背包的物品价值最高?
接下来我用表格演示动态规划过程,如下图:
填写步骤和0/1背包问题相似,唯一不同的地方就是可以选择不同数量的值,也就是考虑0-n个某种物品的重量,具体表格步骤就不想说了,情况太多,不好描述,直接看表吧(注:这里的最优以第一次出现的最优为准)。
在表格推理过程中我们隐藏了一个细节,在计算每个单元格的 价值时,都会遵循一下公式:
当 前 最 大 价 值 = max { 当 前 容 量 对 应 的 上 一 行 单 元 格 的 价 值 当 前 k 个 物 品 的 价 值 + 剩 余 容 量 对 应 的 上 一 行 单 元 格 对 应 的 价 值 当前最大价值= \max \begin{cases} 当前容量对应的上一行单元格的价值\\ 当前k个物品的价值 + 剩余容量对应的上一行单元格对应的价值 {} \end{cases} 当前最大价值=max{当前容量对应的上一行单元格的价值当前k个物品的价值+剩余容量对应的上一行单元格对应的价值
这里我们用数学公式来表达,方便程序编写。用 g i g_i gi表示物品种类,用 w i w_i wi表示物品对应的重量,用 v i v_i vi表示物品对应的价值。用 V V V表示当前表格,维度为 ( M (M (Mx N ) N) N),M为物品的种类,N为容量划分数量(可以根据自己需要划分), V m a x V_{max} Vmax表示当前的最大值, G G G表示背包容量, k k k表示当前物品选取的数量,则用公式表示为:
V [ m ] [ n ] = max ( V [ m − 1 ] [ n ] , v [ m ] ∗ k + V [ m − 1 ] [ G − g [ m ] ∗ k ] ) V[m][n]=\max(V[m-1][n],\space\space v[m]*k+V[m-1][G-g[m]*k]) V[m][n]=max(V[m−1][n], v[m]∗k+V[m−1][G−g[m]∗k])
代码实现如下:
def package_2(weights, values, capacities):
"""
该函数是利用动态规划解决0/1背包问题
:param weights: 每个物品的重量
:param values: 每个物品对应的价值
:param capacities: 预先设定的容量变化列表
:return:
"""
table_value = [[0 for _ in range(len(capacities))] for _ in range(len(weights))]
# 填充表格第一行
for k in range(len(capacities)):
num = capacities[k] // weights[k]
table_value[0][k] = num * values[0]
for i in range(1, len(weights)):
for n in range(1000):
# 提前终止
if n * weights[i] > capacities[-1]:
break
for j in range(len(capacities)):
if weights[i]*n < capacities[j]:
continue
# 假定放入第i个物品后,剩余容量对应的索引
ind = capacities.index(capacities[-1] - weights[i]*n)
table_value[i][j] = max(table_value[i - 1][j], values[i] + table_value[i - 1][ind])
假设有一个小偷,有一个承重容量为10kg的背包,可以盗窃的物品有三种,每种物品数量都有限制,包括:A(3kg,价值30元,2个)、B(2kg,价值20元,3个)、C(1kg,价值15元,3个)。怎么选择才能使装入背包的物品价值最高?
接下来我用表格演示动态规划过程,如下图:
填写步骤和完全背包问题相似,唯一不同的地方就是物品的数量有限,也就是考虑0-所有个某种物品的重量,具体表格步骤就不想说了,情况太多,不好描述,直接看表吧(注:这里的最优以第一次出现的最优为准)。
在表格推理过程中我们隐藏了一个细节,在计算每个单元格的 价值时,都会遵循一下公式:
当 前 最 大 价 值 = max { 当 前 容 量 对 应 的 上 一 行 单 元 格 的 价 值 当 前 k 个 物 品 的 价 值 + 剩 余 容 量 对 应 的 上 一 行 单 元 格 对 应 的 价 值 当前最大价值= \max \begin{cases} 当前容量对应的上一行单元格的价值\\ 当前k个物品的价值 + 剩余容量对应的上一行单元格对应的价值 {} \end{cases} 当前最大价值=max{当前容量对应的上一行单元格的价值当前k个物品的价值+剩余容量对应的上一行单元格对应的价值
这里我们用数学公式来表达,方便程序编写。用 g i g_i gi表示物品种类,用 w i w_i wi表示物品对应的重量,用 v i v_i vi表示物品对应的价值,用 s i s_i si表示物品对应的最大数量。用 V V V表示当前表格,维度为 ( M (M (Mx N ) N) N),M为物品的种类,N为容量划分数量(可以根据自己需要划分), V m a x V_{max} Vmax表示当前的最大值, G G G表示背包容量, k k k( k < s i k
V [ m ] [ n ] = max ( V [ m − 1 ] [ n ] , v [ m ] ∗ k + V [ m − 1 ] [ G − g [ m ] ∗ k ] ) 其 中 : k < s [ m ] V[m][n]=\max(V[m-1][n],\space\space v[m]*k+V[m-1][G-g[m]*k]) \space\space\space\space 其中:k < s[m] V[m][n]=max(V[m−1][n], v[m]∗k+V[m−1][G−g[m]∗k]) 其中:k<s[m]
代码实现如下:
def package_3(weights, values, limits, capacities):
"""
该函数是利用动态规划解决0/1背包问题
:param weights: 每个物品的重量
:param values: 每个物品对应的价值
:param limits: 每个物品对应的数量
:param capacities: 预先设定的容量变化列表
:return:
"""
table_value = [[0 for _ in range(len(capacities))] for _ in range(len(weights))]
# 填充表格第一行
for k in range(len(capacities)):
num = capacities[k] // weights[k]
if num > limits[0]:
if k != 0:
table_value[0][k] = table_value[0][k-1]
else:
table_value[0][k] = num * values[0]
for i in range(1, len(weights)):
for n in range(limits[i]+1):
# 提前终止
if n * weights[i] > capacities[-1]:
break
for j in range(len(capacities)):
if weights[i]*n < capacities[j]:
continue
# 假定放入第i个物品后,剩余容量对应的索引
ind = capacities.index(capacities[-1] - weights[i]*n)
table_value[i][j] = max(table_value[i - 1][j], values[i] + table_value[i - 1][ind])
代码还有很大的优化空间,后续会继续优化,另外我将结合一个实际工程案例,更深入的理解动态规划,敬请期待!