基于回溯法和分支限界法求解01背包问题
问题描述
现有n个物品,1个背包。对物品i,其价值为viv_ivi ,重量为WiW_iWi,背包的容量为WWW,如何选取物品使得背包里转入物品的总价值最大?
在约束条件为:选取物品的重量小于等于背包重量的情况下,尽可能让背包中物品的总价值最大。
算法设计
01背包问题是自己选取问题,该问题的解空间可用子集树表明。设currentTotalValue
是当前已经装入背包的物品的总价值、residueValue
是剩余的未知是否装入物品的总价值,也就是未被搜索到物品的总价值、pbest
是当前已经找到的最优解的价值。
使用两个等长的一维数组存储物品的重量weights[n]weights[n]weights[n]和价值values[n]values[n]values[n],这里第iii个物品的重量就是weights[i]weights[i]weights[i],价值就是values[i]values[i]values[i]。
回溯法
回溯法的基本思想,在包含问题所有解的解空间树中,按照深度优先的策略,从根节点出发搜索解空间树。搜索解空间的任意一个节点时候,只要其左儿子节点是一个可行节点,搜索就进入其左子树。当右子树有可能包含最优解时才进入右子树搜索,否则将右子树剪去。
分支限界法
分支限界法解空间的定义、解空间的组织结构、剪枝函数都与回溯法相同。不同的是解空间的搜索方式,这里采用广度优先搜索。
- 针对当前扩展节点,一次性生成其所有孩子节点,如果孩子节点满足约束条件和限界条件,则将该孩子节点插入到活结点队列末尾;反之则舍弃。
- 从活结点对了中选取待扩展节点。
- 搜索过程直到活结点队列为空时停止。
算法描述
回溯法
backtrack(i, weights[n], values[n], W){
if i > n
pbest = currentTotalValue
return
if weights[i] + currentTotalWeight <= W
x[i] = 1
currentTotalWeight += weights[i]
currentTotalValue += values[i]
backtrack(i+1, weights[n], values[n], W-currentTotalWeight)
currentTotalWeight -= weights[i]
currentTotalValue -= values[i]
if bound(i+1, W-currentTotalWeight, currentTotalValue, weights[n], values[n]) > pbest
x[i] = 0
backtrack(i+1, weights[n], values[n], W-currentTotalWeight)
}
分支限界法
branch(weights[n],values[n],W){
i = 0, currentTotalWeight = 0, currentTotalValue = 0, queue = null
up = 0
while true{
if currentTotalWeight+w[i] < W
if currentTotalValue + values[i] > pbest
pbest = currentTotalValue + values[i]
up = bound(i+1)
if up > pbest
queue.push(new Node(currentTotalWeight, currentTotalValue,
up , false , i+1))
node = queue.back()
if(node = null)
breaks
currentTotalWeight = node.currentTotalWeight
currentTotalValue = node.currentTotalValue
up = node.up
i = node.level
if i == n+1
stNode = node.ptr
}
if(stNode != null)
for j=n to 1 by -1
x[j] = stNode.isLeft?1:0
stNode = stNode.parent
}
}
正确性证明
复杂性分析
回溯法
-
判断约束函数需要O(1)O(1)O(1),在最坏的情况下有2n−12^n-12n−1个左孩子,约束函数耗时最坏为O(2n)O(2^n)O(2n)。
-
计算上界界限函数需要O(n)O(n)O(n)时间,最坏情况下有2n−12^n-12