背包问题(Knap sack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价值,在限定的总重量内,我们如何选择,才能使得物品的总价值最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。
0/1背包问题是背包问题的一个特例,在选择物品时只有两种选择,选或不选.解决0/1背包问题就是一个寻找最优解的过程,可以使用动态规划的方法解决.当然最简单的方法就是枚举,列出所有的解,然后计算满足约束的解中最优的解,其实我们可以在枚举的过程中,通过一定的筛选方法去掉那些不可能的解.再进一步思考我们在枚举的过程中是否可以使用一个二叉树来表示选择的过程,这不就是决策树吗?!!通过一定的筛选其实就是剪枝的过程,那么到底满足什么条件时才进行剪枝呢?这个时候约束条件就派上用场了!
用 wi 表示第i个物品的重量, vi 表示第i个物品的价值, pi=vi/wi 为单位i物品的价值,用M表示背包的容量, 则约束条件为:
∑i=1nwi≤M
目标是求价值最大:
max∑i=1naiwi,ai=0/1
贪心算法中,我们按照单位价值非递减的顺序排列即: v1≤v2≤v3...≤vn .剪枝时采取的策略是,如果该分支值能够获得的最大价值比当前记录的最大价值小或者相等的话可以直接剪掉,因为该分支并不能得到最优解(相等时可能有一个,但是为了简单我们只求一个).按照我们按照单位价值递增的排序的策略,我们可以定义一个求最大上界的方法,也就是求出某条路径按照贪心的策略获得最大的价值,如果最后一个商品加入后重量超标,则取部分商品放进包里,取最大价值.
代码如下:
def boundF(self, cp, cw, k, M):
""" :param cp: :param cw: :param M: :return: maxValue """
b = cp
c = cw
for i in range(k, self._n):
c += self._weight[i]
if c < M:
b += self._value[i]
else:
return b + (1 - (c-M)/self._weight[i])*self._value[i]
return b
有了裁剪分支的上界函数后,就可以通过回溯的方法遍历解的空间树,如果一个点满足约束条件就继续深度优先搜索,不满足的时候判断是否需要截枝进行回溯,思路还是简单的,具体过程如下
def backKnap(self, M):
cw = 0
cp = 0
k = 0
fp = -1
Y = [0 for i in range(self._n)]
X = [0 for i in range(self._n)]
while True:
while k < self._n and cw + self._weight[k] <= M:
cw = cw + self._weight[k]
cp = cp + self._value[k]
Y[k] = 1
k += 1
if k > self._n - 1:
fp = cp
k = self._n -1
X = Y[:]
self._solution.append(X)
else:
Y[k] = 0
while self.boundF(cp, cw, k+1, M) <= fp: # 必须是k+1,若是k第一次探索, fp=-1,boundF()永远不会小于等于-1,会死循环
while k != -1 and Y[k] != 1:
k -= 1
if k == -1:
return X
Y[k] = 0
cw -= self._weight[k]
cp -= self._value[k]
k += 1
上述的程序输出最优解,同时用self._solution保存了所有遍历到叶子节点的解.
完整代码:BackKnap