1 背包问题:给定n个重量为w1,w2...wn,价值为v1,v2...vn的物品和一个承重量为W的背包,求这些物品中最有价值的一个子集,并且要能够装入背包当中。
2 动态规划:动态规划与分治法都要求原问题的最优子结构,都是将问题分而治之,,不同的是动态规划适用于交叠子问题的情况,分治法则适用于子问题相互独立的问题
3 解题思路:为设计一个动态规划的算法,需要推导出一个递推关系,用较小子实例的解的形式来表示背包问题的实例的解,让我们考虑一个由前 i 个物品(1 <= i <= n)定义的实例,物品的重量分别为w1,w2...wi,价值分别为v1,v2...vi,背包的承重量为 j (1 <= j <=W)。设F(i, j)是该实例的最优解的价值总和,在这里可以把前 i 个物品能够放入承重量为 j 的背包中的子集分成两个类别,即把第 i 个物品放入背包和不把第 i 个物品放入背包,如下。
(1)不把第 i 个物品放入背包中的子集中,最优子集的价值是 F(i-1, j)
(2)把第 i 个物品放入背包中的子集中,最优子集的价值vi + F(i-1, j-wi)
因此,在考虑前 i 个物品放入背包的情况中,需要从上面两个行为中选出价值大的子集,可得递推式:
F(i, j) = max( F(i-1, j) , vi + F(i-1, j-wi))
4 动态规划代码展示
CAPACITY = 5 # 背包容量 THINGS = 5 # 物品数量 w = [0, 3, 2, 1, 4, 5] # 物品重量 v = [0, 25, 20, 15, 40, 50] # 物品价值 def find_the_best_set(i, j): if i < 0 or j < 0: # 当背包容量或物品小于0时 return 0 if j - w[i] >= 0: # 判断第 i 个物品的质量与背包容量比较 return max(find_the_best_set(i-1, j), find_the_best_set(i-1, j-w[i])+v[i]) # 从两个情况中选择最优解 else: # 第 i 个 物品不能够放入背包中时 return find_the_best_set(i-1, j) # 从前 i-1 个物品中选择最优解 if __name__ == '__main__': best_values = find_the_best_set(THINGS, CAPACITY) print(best_values) # 输出最优价值
5 动态规划代码所涉及问题的解,满足一个用交叠的子问题来表示的递推关系,直接自顶向下这样一个递推关系要求一个算法不止一次的计算公共子问题,因此效率非常低,另一方面,经典的动态规划方法是自底向上的:它用所有较小的子问题的解填充表格,有些较小子问题的解常常不是必需的,由于这个缺点没有在自顶向下中表现出来,所以我们希望能够将自顶向下和自底向上的优势结合起来,将需要的子问题求解并只解一次,这种方法是存在的,它是以记忆功能为基础的
6 动态规划及记忆功能代码展示
CAPACITY = 5 # 背包容量 THINGS = 6 # 物品数量 W = [0, 2, 6, 1, 4, 5] # 物品重量 V = [0, 25, 20, 15, 40, 50] # 物品价值 F = [[-1 for _ in range(CAPACITY+1)]for _ in range(THINGS)] # 记忆数组 初始值为-1
def find_the_best_set(i, j): value = 0 if i < 0 or j < 0: # 当背包容量或物品小于0时 return value if F[i][j] < 0: # 判断该情况是否存在记忆数组中 if j < W[i]: # 判断第 i 个物品的质量与背包容量比较,第 i 个物品不能放入背包中时 value = find_the_best_set(i-1, j) #从前 i-1 个物品中选择最优解 else: value = max(find_the_best_set(i-1, j), find_the_best_set(i - 1, j - W[i]) + V[i]) #选择最优解 F[i][j] = value # 将该情况的最优解存入记忆数组,避免二次计算 return F[i][j] if __name__ == '__main__': value = find_the_best_set(THINGS-1, CAPACITY) print(value) print(F)
7 总结:以上仅为自己目前对于动态规划解决背包问题的一点浅薄看法,扩展的话还应该记录选择物品的编号并输出,但是目前并没有想到较好的解法,以后补充。