做完才发现,分支限界并不适合做0-1背包。。。直接把一道算法题做成数据结构题了。。
众所周知啊,分支限界法的本质其实就是广度优先遍历。再通过剪枝操作,实现快速查找的目的。但是0-1背包给的两个数组,一个是货物体积,一个是货物价格。我们想要广度优先遍历,就要先构造一颗二叉树。这棵二叉树长这个样子:
每一层的节点都代表备选货物中的一个。节点中包含该货物的容量,价值信息。左子节点代表没有选该层货物,右子节点代表选了该层货物。这样就构造出了一颗类似决策树的二叉树,叶子结点代表了所有可能。
为了构建出这样一颗二叉树,我们需要把原有数组转换成这样的一颗完美二叉树。这里我们就需要引入TreeNodify函数:
public TreeNode treeNodify(int[] nums,int[] values){
TreeNode root = new TreeNode();
Deque<TreeNode> dq = new LinkedList<>();
dq.addLast(root);
Map<TreeNode,Integer> map = new HashMap<>();
map.put(root,0);
int cnt = nums.length;
while(!dq.isEmpty()){
TreeNode t = dq.poll();
TreeNode left = new TreeNode();
int idx = map.get(t)<cnt ? map.get(t) : 0;
TreeNode right = new TreeNode(nums[idx],values[idx]);
int tag = map.get(t) + 1;
map.put(left,tag);
map.put(right,tag);
if(tag <= cnt){
t.left = left;
t.right = right;
dq.offer(left);
dq.offer(right);
}
}
return root;
}
这段不是中断,跳过不讲。
但是这样全部走一遍也是很麻烦的,因此我们需要对这棵二叉树进行剪枝。怎么剪枝呢?我们都知道,当下面的节点加入背包后会撑爆背包时,我们就会跳过这个货物。因为就算加入了之后的操作也是徒劳。针对这个,我们可以以 当前节点体积 + 原有体积 是否小于背包总容量来剪枝。由于是广度优先,用队列模拟的,所以当不满足条件时,我们就不操作。这样,我们就完成了剪枝。
但是问题又来了,我们该怎样得到最大值呢? 这里我们采用在节点尾部判断最大值加入即可。因为不管最后一个选不选,两个叶子结点一定是保存了结果的,所以直接在叶子结点找值。
具体操作时,我们给每一个节点都用HashMap绑定两个值,一个是剩余空间,用来剪枝。另一个是当前最大价值。每一步都借用上一节点的这两个值便可以得到递推。
public int branchNBound(TreeNode root,int maxW){
int ans = 0;
int curW = 0;
Map<TreeNode,Integer> price = new HashMap<>();
Map<TreeNode,Integer> space = new HashMap<>();
Deque<TreeNode> dq = new LinkedList<>();
price.put(root,0);
space.put(root,maxW);
dq.addLast(root);
while(!dq.isEmpty()){
TreeNode treeNode = dq.poll();
if(treeNode.left == null || treeNode.right == null){
ans = Math.max(ans,price.get(treeNode));
continue;
}
price.put(treeNode.left,price.get(treeNode));
space.put(treeNode.left,space.get(treeNode));
dq.offer(treeNode.left);
curW = space.get(treeNode);
if(curW - treeNode.right.weight >= 0){
dq.offer(treeNode.right);
price.put(treeNode.right,price.get(treeNode)+treeNode.right.val);
space.put(treeNode.right,curW- treeNode.right.weight);
}
}
return ans;
}