分支限界法解决0-1背包问题
这个学期上了算法设计与分析这一门课,认为手写的笔记不太方便,所以就打算将它写在这里,方便自己以后查阅。
(1)分支限界算法的求解目标是找出满足约束条件的一个解,或者是在满足约束条件的解中找出使某一目标值达到极大或者极小的解,即在某种意义上的最优解。
(2)在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被加入活结点表中。
此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所需的解或活结点表为空时为止。
(3)分支节点的选择-常见的两种分支限界法
从活结点表中选择下一扩展结点的不同方式导致不同的分支限界法:
a、队列式(FIFO)分支限界法:按照队列先进先出(FIFO)原则选取下一个节点为扩展节点。
b、优先队列式分支限界法:按照优先队列中规定的优先级选取优先级最高的节点成为当前扩展节点。
最大优先队列:使用最大堆,体现最大效益优先
最小优先队列:使用最小堆,体现最小费用优先
0-1背包
(1)题目需求分析
在0-1背包的优先队列式分支限界法中,活结点优先队列中结点元素N的优先级由该结点的上界函数bound计算的值uprofit给出。子集树以结点为根的子树任一节点的价值不超过node,profit.因此用一个最大堆来实现活结点优先队列。
(2)设计思路
对于0-1背包问题中的每个活结点只有两个儿子 结点,分别表示对物品i的选取和对物品i的舍去;在判断儿子结点是否能加入到活结点表中,有两个 函数需要满足,第一个称为约束函数,判断能否满足背包容量约束,第二个称为限界函数,判断是否可能有最优解。
每次选取下一个活结点成为扩展结点的判断依据是当前情况下最有可能找到最优解的下一个结点。因此,每次选择扩展结点的方法:当前情况下,在活结点表中选择活结点的上界uprofit(通过限界函数getBound求出)最大的活结点成为当前的扩展结点。 这一过程一直持续到找到所需的解或活结点表为空时为止。
为了在活结点表中选择拥有最大的上界uprofit的活结点,在活结点表上实现优先队列。(堆按照uprofit排序)
为了求出0-1背包问题的最优解,对于每一个在活结点表中的活结点创建一个树结点,树节点需要反映该结点的父节点和是否有左孩子(有左孩子 表示物品i选取了,没有左孩子表示物品i舍去了)。因此,可以构造一颗子集树,最优解就是从树根到叶子结点的路径,子集树的第i层的所有结点就是在不同情况下对物品i的取舍结点。构造最优解的顺序是从叶子结点到根结点的过程。
程序实现过程参考清华大学出版社《算法设计与分析》上的部分代码实现。
程序源代码(java实现)
package exam2;
import java.io.BufferedInputStream;
import java.util.*;
/**
* 使用分支界限法解决01背包问题
*/
//子集树的结构
class Node {
public Node parent; //记录子集树的父节点
public boolean lChild; // 记录是左儿子还是右儿子
public Node(Node parent, boolean lChild) {
super();
this.parent = parent;
this.lChild = lChild;
}
}
//对背包按照单位重量价值排序的类
class UnitW implements Comparable<UnitW> {
public int id;
public double d;
public UnitW(int id, double d) {
super();
this.id = id;
this.d = d;
}
@Override
public int compareTo(UnitW o) { //按照d降序排列
return -(Double.compare(d, o.d));
}
}
class HeapNode implements Comparable<HeapNode> {
public Node liveNode; //活结点
public int upProfit; //活结点的价值上界
public int profit; //结点所相应的价值
public int weight; //结点所相应的重量
public int level; //结点在子集树中的层数
public HeapNode(Node liveNode, int upProfit, int profit, int weight, int level) {
super();
this.liveNode = liveNode;
this.upProfit = upProfit;
this.profit = profit;
this.weight = weight;
this.level = level;
}
@Override
public int compareTo(HeapNode o) { //按照上界价值降序排列
return -(Integer.compare(upProfit, o.upProfit));
}
}
public class Main {
static int C;
static int n;
static int[] w;
static int[] v;
static int curW; //当前的重量
static int curVal; //当前的价值
static int[] bestX; //记录最优解
static PriorityQueue<HeapNode>heap;
//计算最优上界
static int getBound(int i) {
int cLeft = C - curW;
int b = curVal; //价值的上界
while (i < n && w[i] <= cLeft) { //以物品单位重量价值递减的顺序装填剩余容量
cLeft -= w[i];
b += v[i];
i++;
}
if (i < n) b += v[i] * 1.0 / w[i] * cLeft;
return b;
}
//生成一个活结点插入到子集树和最大堆中
static void addLiveNode(int up, int v, int w, int lev, Node par, boolean iCh) {
Node b = new Node(par, iCh);
HeapNode hN = new HeapNode(b, up, v, w, lev); //生成一个堆元素
heap.add(hN); //加入到堆中
}
//分支限界法求解
static int maxKnapsack() {
Node endNode = null;
int upProfit = getBound(0); //计算一开始的上界
int i = 0, bestV = 0;
while (i != n) {
if (curW + w[i] <= C) { //进入左子树
if (curVal + v[i] > bestV)
bestV = curVal + v[i];
addLiveNode(upProfit, curVal + v[i], curW + w[i], i + 1, endNode, true); //左子树插入到最大堆中
}
upProfit = getBound(i+1); //注意不是 (i) 计算下层的上界
if (upProfit >= bestV) //右子树可能含有最优解
addLiveNode(upProfit, curVal, curW, i + 1, endNode, false);
HeapNode top = heap.poll(); //把堆顶元素删掉
endNode = top.liveNode; //记录父亲结点
curW = top.weight;
curVal = top.profit;
upProfit = top.upProfit;
i = top.level;
}
//构造最优解
for (int j = n - 1; j >= 0; j--) {
bestX[j] = endNode.lChild ?1:0;
endNode = endNode.parent;
}
return curVal;
}
public static void main(String[] args) {
Scanner cin = new Scanner(new BufferedInputStream(System.in));
int T = cin.nextInt();
while(T> 0) {
int sumW =0, sumV=0;
n = cin.nextInt();
C = cin.nextInt();
int[] oriW = new int[n]; //体积
int[] oriV = new int[n]; //价值
for (int i = 0; i < n; i++) {
oriW[i] = cin.nextInt();
sumW += oriW[i];
}
for (int i = 0; i < n; i++) {
oriV[i] = cin.nextInt();
sumV += oriV[i];
}
if (sumW <= C) {
System.out.println(sumV);
continue;
}
UnitW[] unitWS = new UnitW[n];
for (int i = 0; i < n; i++)
unitWS[i] = new UnitW(i,oriV[i] * 1.0 / oriW[i]);
Arrays.sort(unitWS);
w = new int[n];
v = new int[n];
for (int i = 0; i < n; i++) {
w[i] = oriW[unitWS[i].id];
v[i] = oriV[unitWS[i].id];
}
bestX = new int[n];
curW = 0;
curVal = 0;
heap = new PriorityQueue<>();
int maxValue = maxKnapsack();
System.out.println("---背包的最大价值是----");
System.out.println(maxValue);
int[] res = new int[n + 1]; //保存结果
for (int i = 0; i < n; i++)
res[unitWS[i].id] = bestX[i]; //获取最优解
System.out.println("---被选中物品的序号(从1开始)----");
for (int i = 0; i < n; i++)
if (res[i] == 1)
System.out.print(i + 1 + " ");
System.out.println();
}
}
}