本实验要求基于算法设计与分析的通常过程(即待求解问题的描述、算法设计、算法描述、算法正确性证实、算法分析、算法实现与测试),在针对0-1背包问题求解的实践中理解动态规划 (Dynamic Programming, DP) 方法的思想、求解策略及步骤。算法
做为挑战:能够考虑基于跳跃点的改进算法,以及对连续型物品重量/背包容量的支持。数组
理解问题,给出问题的描述。数据结构
n个物体,1个背包。对物品i,其价值为\(v_i\),重量为\(W_i\),背包的容量为 \(W\),如何选取物品,是的背包中装入的物品的总价值最大?函数
在约束条件为:选取物品的重量小于等于背包重量的状况下,尽量让背包中物品的总价值最大。测试
根据问题描述,设计以下的约束条件和目标函数:优化
约束条件:spa
\[ \begin{equation} \left\{ \begin{array}{**lr**} \sum_{i=1}^{n} w_ix_i \leq W \\ x_i \in \{0,1\}, 1 \leq i \leq n & \end{array} \right. \end{equation} \]设计
目标函数:code
\[ max\sum_{i=1}^{n} v_ix_i \]
问题如今等价于,寻找一个在知足约束条件状况下,并使目标函数达到最大的解 \(X=(x_1,x_2,...,x_n)\)
算法设计,包括策略与数据结构的选择
设计用二维数组对物品信息进行记录: \(C[i][j]\) 用来记录若是当前还有\(i\)个物品,背包容量还剩\(j\)的状况下,当前背包所能获得的最大价值。
很容易发现条件即:\[C[0][j] = C[i][0] = 0\]
递归定义应该为:
\[ \\ C[i][j]= \begin{equation} \left\{ \begin{array}{**lr**} C[i-1][j], & j < w_i \\ max\{C[i-1][j],C[i-1][j-w_i]+v_i\}, & j \ge w_i \end{array} \right. \end{equation} \]
能够这样理解,每一个物品我能够选择是否加入到背包中,首先判断,当前物品是否重量已经大于背包所能容纳的重量;若是能容纳该物体,则进行判断加入该物品\(C[i-1][j-w_i]+v_i\)获得的价值更高,仍是不加入该物品\(C[i-i][j]\)所能获得的物品的总价值更高。
描述算法。但愿采用源代码之外的形式,如伪代码或流程图等;
伪代码表示:
01PACKAGE(n,w,v,W) // n为物品的个数,w为重量, v为价值,W为背包容量 // C[1..n,1..n]为最优解 for i=1 to n: do C[i][0] = 0 for j=1 to W: do C[0][j] = 0 for i=1 to n: for j=1 to W: do if j < w[i]: then C[i][j]=C[i-1][j] else then C[i][j]=max{C[i-1][j],C[i-1][j-w[i]]+v[i]} return C
算法的正确性证实。须要这个环节,在理解的基础上对算法的正确性给予证实;
对该算法进行最优子结构的证实:
假设\(X=(x_1,x_2,x_3,...,x_n)\)是背包问题的最优解,那么\((x_2,..,x_n)\)是下面问题的一个最优解:
\[ \begin{equation} \left\{ \begin{array}{**lr**} \sum_{i=2}^{n} w_ix_i \leq W-w_1x_1 \\ x_i \in \{0,1\}, 2 \leq i \leq n & \end{array} \right. \end{equation} \]
目标函数: \[max\sum_{i=2}^nv_ix_i\]
即除去第一个物品之后的子问题
证实以下:(反证法)
设\(X=(x_2,...,x_n)\)不是上述子问题的最优解,设\(Y=(y_2,...,y_n)\)是上述问题的最优解,则\(Y\)所求的目标函数的值必定比X求得的目标函数的值更大,即:
\[ \sum_{i=2}^nv_iy_i > \sum_{i=2}^nv_ix_i \]
又\(Y\)知足约束条件:\(\sum_{i=2}^nw_iy_i \leq W-w_1x_1\),即 \(w_1x_1+\sum_{i=2}^nw_iy_i \leq W\),该不等式证实\((x_1,y_1,y_2,...,y_n)\)是原问题的一个解。
在公式 \((5)\) 中左右同时加上\(v_1x_1\),可得:\[ v_1x_1+\sum_{i=2}^nv_iy_i > v_1x_1+\sum_{i=2}^nv_ix_i\],说明\((x_1,y_1,y_2,...,y_n)\)要比\((x_1,x_2,...,x_n)\) 方案价值更高,因此\((x_1,x_2,...,x_n)\)不是最优解,产生了矛盾。
因此其最优子结构的性质得证。
算法复杂性分析,包括时间复杂性和空间复杂性;
时间复杂性分析:
因为只须要遍历进行,因此只须要考虑循环中的复杂度便可。
\[ T(n) =O(n)+O(W)+O(nW) \\ =O(nW) \]
空间复杂度,主要是生成数组时占用的空间。
\[ T(n)=O(n^2) \]
时间复杂度分析:
只须要一个循环便可。
\[ T(n)=O(n) \]
空间复杂度为:\[O(1)\]
算法实现与测试。附上代码或以附件的形式提交,同时贴上算法运行结果截图;
# -*- coding: utf-8 -*- """ Created on Fri Sep 28 12:44:40 2018 @theme: 算法准备-01背包问题 @author: pprp """ import numpy as np def solvePackage(n,w,v,W): """solve the 01 package problem""" C=np.zeros((n+1,W+1)) for i in range(n): C[i][0]=0 for i in range(W): C[0][i]=0 for i in range(1,n+1): for j in range(1,W+1): if j < w[i-1]: C[i][j]=C[i-1][j] else: C[i][j]=max(C[i-1][j],C[i-1][j-w[i-1]]+v[i-1]) return C def getSolution(n,w,W,C): j = W x = np.zeros(n) for i in range(n,0,-1): if C[i][j]==C[i-1][j]: x[i-1] = 0 else: x[i-1] = 1 j -= w[i-1] return x if __name__ == "__main__": n = 11 w = np.array([2, 6, 3, 4, 2, 8, 2, 4, 7, 5, 1]) v = np.array([10,23,5,34,23,17,22,32,12,15,32]) W = 15 C=solvePackage(n,w,v,W) x=getSolution(n,w,W,C) print("packages:",x) print("Output:\n",C)
packages: [1. 0. 0. 1. 1. 0. 1. 1. 0. 0. 1.]
验证结果:10+34+23+22+32+32=153
动态规划算法一般是用来解决某种最优性质的问题。基本思想是将带求解问题划分为若干个子问题,先求解子问题,而后从子问题的解获得原问题的解。动态规划与分治法的区别在于,动态规划的子问题多是互相重叠的重复计算的,分治法则是相互独立的。能够用一个表来记录子问题是否已经求解,这样能够避免重复求解。
须要知足最优化原理、无后效性和重叠性。
最优化原理,一个最优化策略的子策略必定是最优的,就是知足最优子结构的性质。
重叠性,就是记录已经解决过的问题,须要存储已经解决过的问题,空间复杂度比较大,是一种以空间换时间的算法。
动态规划的难点在于,如何根据问题的最优子结构的性质,构造动态规划方法中的递归公式或动态规划方程。就好比本问题中,如何设计这个方程才是难点所在。
在逆向求解使用的哪几个背包的时候,因为对问题理解的不深入,致使汇总的时候发现计算结果出现了问题,也就是\(getSolution\)这个函数出现了问题。应该逆向进行求解问题,也就是从后往前进行推导,更改了循环的方向之后就能够获得最终的结果了。
只有在真正理解算法的基础上,而后加以伪代码的梳理,这时候写才能一鼓作气。另外还须要对代码计算出来的结果进行人工核查,防止某些问题被忽略掉。另外这个问题已经被老师分析的比较透彻,因此写起来没有太大的困难。可是碰见新的问题的时候,如何构造解决问题的方法才是难点所在。