主要理解得益于:https://blog.csdn.net/a784586/article/details/63262080
其通俗易懂的讲解着实厉害,部分内容也来自与这篇博文
动态规划
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中, 可能会有很多可行解,每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划可以将一个复杂问题分解问若干子问题,然后求解子问题,从而得到原始问题的解。
这本身是一种分治法的思想,但是动态规划分解后的子问题往往不是独立的,即下一个阶段的求解必须是建立在上一个子阶段的基础上的。可以简单理解为一个爬楼梯的过程,你要爬到第二格楼梯,一定要先爬第一格。
背包问题
有
种重量和价值分别为
,
的物品,假设有一个容量为
的背包,求怎么样装这个背包,才能使得
不超过背包的总重量,并获得最大的价值
。
这个问题确实很难,因为从物品的组合方式多种多样,我怎么知道该如何选才能达到最优?下面以一个简单的例子来说明动态规划在这里应该怎么用。
如上图,现在我们有一个能容纳重量为
的背包,和5种物品
,直观上确实很难理解怎么直接放置才能实现价值最大化。
1、假设这里每种物品至多只能取一次,并且我们先不管这个背包真实容量11,我们假设这个背包容量其实只有1,并且此时此刻可选物品也只有a,显然,在这种情况下,a能放入这个背包,并得到了价值1。这时候假设我们可选物品多了一件b,容量仍为1,但b的重量大于1,放不下,与此类似,我们可知,即使可选物品变成5件,因为其他物品的重量都超过了当前这个背包容量1,我们可放的物品仍然只有a,总的价值还是为1。
2、这时候我们假设背包容量变成了2,一开始还是只能放a,得到的价值为1。这时候,假设b也可选了,即此时ab均可选,那么因为b的重量小于等于2,此时价值最大的方式就是把刚刚放的a拿出来,放入b,就可以得到价值6。当 也可选,因为容量超限,显然不能再有更好的放置方案。
3、接着,我们假设背包容量变成了3,起初只有a一种物品,那么能放入,得到的价值为1,这时候再来一件b,a+b仍然不超过容量3,因此可以得到价值7。
我发现这个例子要举出点鲜明的变化至少要举到重量变成5的时候,但那时候文字太多了。我就文字补一波,希望看到这里的人能脑补出来。我们假设在重量为5的情况下,一件件看物品 物品,开始只有a,那么放得下,得到价值1。只有ab,也放得下,得到价值7。但出现c的时候,问题来了,因为c一个的价值就超过了ab,而且重量不超限,最好的方法就是把ab都干掉,只放c。当放了c,后来再看de,因为两个都超限,最终也只放c获得了最大价值18。
上面的内容非常废话,但却透露了一个思想,甚至隐含了一个公式。
我们假设
为前
种物品在重量
条件下任意组合所能达到的最大化价值(好好理解一下这句话理解了背包问题基本没问题),则有
有人说这不是废话么,请看回前面的第三点,假设在c刚出现的时候,我们要怎么才能知道如何实现价值最大化,那么就是比比a+b和单个c的价值,看看谁能最大化这个利益。
这里用区区几个小例子很难表达完整,我们拓展一下物品种类,假设现在有了50种物品 ,背包容量也拓展到了110。那你怎么知道当50种物品都可选即(=50),且背包容量为110的最优方案是什么。参考前面那一段落,我们现在假设已经知道当容量<=110,且物品<50(即最多49种)的最佳放置方案。且假设第50种物品的价值为 重量为 ,那么求解这个的办法即为,对比一下没出现 时候的最大利益 和把 也放进去的利益 。
这里很多人会晕,其实很简单,
也就是这个背包有重达
的空间给这个新成员占用了,并且通过这个成员增多了价值
。但你其余的49种只剩下
这么多空间进行分配了,所以也要让49种任意组合得到最大化价值。这个公式一般化,有:
这个公式很多人看起来就晕圈了,其实只要把i从1开始(手算都可以),慢慢推导过去,你就会发现,我去,那么简单?
问题具体化
这里再提一下上面那个例子
求解这个问题,其实画个表格,慢慢算即可,如下:
里面几个主要的数字我都标红了,前面都有提到,这个表只要慢慢算到右下角就可以了,最右下角就是当w为11,n为5时候的最优值。我用python跑出的完整表格如下:
01背包问题
其实0101,挺形象的,0就是不放,1就是至多放一个。也就是物品只有放或者不放进背包,要么就不放,要么就只放一个。前面提到的那个案例就是01背包问题,01背包问题是所有背包问题的基础,基本上理解了01问题,接下来的完全背包问题,多重背包问题都能很快明白。
像我前面提到了大堆文字,其实数学公式非常简洁明了,01问题的最优价值假设为
,那么具体迭代或者说,状态转移方程(你可以理解为这个阶段和上一个阶段之间缠绵的关系)如下:
直接上代码:
def main():
w = 11 # 背包容量
n = 5 # 物品种类/个数
# 对应为物品a,b,c,d,e
value = [1, 6, 18, 22, 28] # 价值
weight = [1, 2, 5, 6, 7] # 开销
# 01问题
zero_one_problem(w, n, weight, value)
# 01问题
def zero_one_problem(w, n, weight, value):
# 创建一个大小为w+1 * n+1 的全零矩阵,用来放结果
matix = [[0 for i in range(w + 1)] for j in range(n + 1)]
# 背包种类个数开始上升
for row in range(1, n + 1):
# 容量从1开始增长
for col in range(1, w + 1):
# 先看看新来的花费是不是大于当前容量
if (weight[row - 1] > col):
# 如果是,就在前面找最大值
matix[row][col] = matix[row - 1][col]
else:
# 如果不是,就看看新增一个物品价值牛逼点,还是不用这个物品也很厉害
matix[row][col] = max(matix[row - 1][col], value[row - 1] + matix[row - 1][col - weight[row - 1]])
print('0/1背包问题的各种条件下的最优值矩阵:')
for it in matix:
for i in it:
print(i, '\t', end='')
print()
# 看看对应某一个位置对应放了什么物品
res = [0 for heh in range(n)]
row = n # row和col为你要查的某个元素的坐标对应的物品放置列表
col = w # 可改
# 打印矩阵
while row - 1:
if matix[row][col] == (
matix[row - 1][col - weight[row - 1]] + value[row - 1]):
res[row - 1] = 1
col = col - weight[row - 1]
row -= 1
res[0] = int(matix[1][col] / value[0])
print('0/1背包问题坐标为%d,%d的背包物品存放列表:' % (n, w))
print(res)
print('-' * 50)
if __name__ == '__main__':
main()
完全背包问题
完全背包问题看上去高大上了很多,其实它只是,假设每一种物品的个数都变成了无穷个。也就是,只要你想放这个物品,那么想要多少有多少。虽然看上去难了很多了,其实只要把01问题的第二个方程去掉,然后最后一个方程
的第二个参数改成,让新来的物品个数k从0到
,之所以会有
这项,是因为即使物品是取之不尽的,背包容量总是有限的,
的得来就是
向下取整得到(即假设新来的物品尽可能放),公式变成:
其中
,因为要让新来的物品从0到最多都试一试,看看取几个能组合出最厉害的组合。
def main():
w = 11 # 背包容量
n = 5 # 物品种类/个数
# 对应为物品a,b,c,d,e
value = [1, 6, 18, 22, 28] # 物品价值
weight = [1, 2, 5, 6, 7] # 物品重量
# 完全背包问题
complete_problem(w, n, weight, value)
# 完全背包问题
def complete_problem(w, n, weight, value):
# 创建一个大小为w+1 * n+1 的全零矩阵,用来放结果
matix = [[0 for i in range(w + 1)] for j in range(n + 1)]
# 背包种类个数开始上升
for row in range(1, n + 1):
# 容量从1开始增长
for col in range(1, w + 1):
max_k = int(col / weight[row - 1]) # 不限个数,所以只要新增背包上限不超过总容量即可
for k in range(max_k + 1):
matix[row][col] = max(matix[row - 1][col],
k * value[row - 1] + matix[row - 1][col - k * weight[row - 1]])
print('完全背包问题的各种条件下的最优值矩阵:')
for it in matix:
for i in it:
print(i, '\t', end='')
print()
# 获取最优值矩阵中某个位置为行row,列col的点的物品放置列表
res = [0 for heh in range(n)]
row = n # row和col为你要查的某个元素的坐标对应的物品放置列表
col = w # 可改
# 打印矩阵
# 这个求解方法完全是反向思维,即上面求解倒着来,要多加理解
while row - 1:
count = int(col / weight[row - 1])
for k in range(count, 0, -1):
if matix[row][col] == (
matix[row - 1][col - weight[row - 1] * k] + k * value[row - 1]):
res[row - 1] = k
col = col - k * weight[row - 1]
break
row -= 1
res[0] = int(matix[1][col] / value[0])
print('完全背包问题坐标为%d,%d的背包物品存放列表:' % (n, w))
print(res)
print('-' * 50)
if __name__ == '__main__':
main()
多重背包问题
其实这个只是比完全背包问题的弱化版本,即可能物品能取的个数是有限制的。比如
每种均只能取3个,那么和完全背包问题的优化公式基本一样,只是
变成了
其中
代码:
def main():
w = 11 # 背包容量
n = 5 # 物品种类/个数
# 对应为物品a,b,c,d,e
value = [1, 6, 18, 22, 28] # 物品价值
weight = [1, 2, 5, 6, 7] # 物品重量
# 多重背包问题,多了一个数量限制
num = [1, 1, 1, 1, 1] # 背包个数,如果都是1,那么多重背包问题其实就是一个01背包问题
# num = [3, 3, 3, 3, 3] # 背包个数,上下两个切换注释看一看
multiple_problem(w, n, weight, value, num)
# 多重背包问题
def multiple_problem(w, n, weight, value, num):
# 创建一个大小为w+1 * n+1 的全零矩阵,用来放结果
matix = [[0 for i in range(w + 1)] for j in range(n + 1)]
# 背包种类个数开始上升
for row in range(1, n + 1):
# 容量从1开始增长
for col in range(1, w + 1):
# 有限个数,所以要在不超出背包容量和限制个数的条件下选一个值
# 最小值满足木桶的短板理论,即能放多少,取决于少的那个数
max_k = min(int(col / weight[row - 1]), num[row - 1])
for k in range(max_k + 1):
matix[row][col] = max(matix[row - 1][col],
k * value[row - 1] + matix[row - 1][col - k * weight[row - 1]])
print('多重背包问题的各种条件下的最优值矩阵:')
for it in matix:
for i in it:
print(i, '\t', end='')
print()
# 获取最优值矩阵中某个位置为行row,列col的点的物品放置列表
res = [0 for heh in range(n)]
row = n # row和col为你要查的某个元素的坐标对应的物品放置列表
col = w # 可改
# 打印矩阵
while row - 1:
count = min(int(col / weight[row - 1]), num[row - 1])
for k in range(count, 0, -1):
if matix[row][col] == (
matix[row - 1][col - weight[row - 1] * k] + k * value[row - 1]):
res[row - 1] = k
col = col - k * weight[row - 1]
break
row -= 1
res[0] = int(matix[1][col] / value[0])
print('多重背包问题坐标为%d,%d的背包物品存放列表:' % (n, w))
print(res)
if __name__ == '__main__':
main()
后话
其实我这些代码段都是写在一起的,只不过为了布局就把它们全部拆开了。另外动态规划解决这个问题我感觉太”贪心了”,因为如果是一个多重背包问题,且背包数量有很多个,那么第一个装得太满就导致后面的背包只能装一些歪瓜裂枣。解决办法有用模拟退火算法或者遗传算法。
虽然这次背包算法也还是没有给我带来什么惊喜,但总归是有所收获的。