0-1背包详解与算法实现(中)
仅以此记录我的学习过程
分支限界法
求解目标是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
以广度优先或以最小耗费优先的方式搜索解空间树。
基本思想
1.扩展结点处,先生成所有儿子结点,再从当前活结点表中选择下一个扩展结点。
2.为有效选择下一扩展结点,在每一活结点处,计算一个函数值(bound),根据最优值进行选择。
3.产生所有儿子节点后,导致不可行解或导致非最优解的儿子结点被舍弃,其余的加入活结点表。
4.重复直到找到所需解或活结点表为空。
解法描述
将数据按照单位价值排序,从编号1到编号4
物编 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
物重 | 5 | 4 | 3 | 2 |
物价 | 8 | 5 | 4 | 3 |
根据例题数据。1 为取该节点,2 为不可行 , 0 为不取。
c 为此时背包中当前价值,
u 为理想的上界(bound),背包装满时候价值 ,按照单位价值顺序装入。
bestP为当前的最好值(随着c而变化)。
当u < bestP时,展开无意义,即为非最优解儿子结点,不展开。
当取值超出背包容量时,也不展开,即不可行解,即为 2 的情况。
算法实现
定义一个结点类
//定义结点类
class BBnode{
BBnode parent; //父结点
boolean leftChild; //左孩子
BBnode(BBnode par , boolean ch){
parent = par;
leftChild = ch;
}
}
//活结点,这里用最大堆来实现,以up最为优先级
class HeapNode implements Comparable{
double upperProfit; // 结点的价值上界
double profit; //结点的价值
double weight; //结点的重量
int level; //结点当前的层号
BBnode ptr;//指向活结点在子集树中相应结点的指针
//构造方法
HeapNode(double up , double pp,double ww , int lev,BBnode ptr){
upperProfit = up;
profit = pp;
weight = ww;
level = lev;
this.ptr = ptr;
}
//比较价值上界,判断是否为扩展结点,是否展开
@Override
public int compareTo(Object x) {
double xup = ((HeapNode)x).upperProfit;
if(upperProfit<xup) return -1;
if(upperProfit==xup) return 0;
return 1;
}
}
物品类
//物品类
class Element implements Comparable{
int id; //物品编号
double v_w; //物品单位重量
Element(int id , double dd){
id = id;
v_w = dd;
}
@Override
public int compareTo(Object x) {
double xd = ((Element)x).v_w;
if(v_w < xd) return -1;
if(v_w == xd) return 0;
return 1;
}
public boolean equals(Object x){
return v_w == ((Element)x).v_w;
}
}
全局变量
public static int c; // 背包容量
public static int n; // 物品个数
public static double[] w; // 重量数组
public static double[] v; // 价值数组
public static double cw; //当前重量
public static double cv; //当前价值
public static int[] bestx; //最优解
public static BBnode E; //活结点优先队列
public static LinkedList<HeapNode> Heap;//活结点优先队列(最大堆);
计算结点所相应价值的上界
public static double bound(int i) {
double cleft = c - cw; //剩余容量
double b = cv; //价值上界
//以物品单位重量价值递减装填剩余容量
while (i <= n && w[i] <= cleft) {
cleft -= w[i];
b += v[i];
i++;
}
//填装剩余容量装满背包
if (i <= n)
b += v[i] / w[i] * cleft;
return b;
}
将一个新的活结点插入到优先队列中
实现对子集树的优先队列分支界限搜索,假定各物品依单位价值降序排列
//将一个新的活结点插入到优先队列中
public static void addLiveNode(double up, double pp, double ww, int lev, boolean ch) {
//将一个新的活结点插入到子集树和活结点序列中
BBnode b = new BBnode(E, ch);
HeapNode node = new HeapNode(up, pp, ww, lev, b);
Heap.add(node);
Collections.sort(Heap);
}
//实现对子集树的优先队列分支界限搜索,假定各物品依单位价值降序排列
public static double bbKnapsack() {
Heap = new LinkedList<>(); //初始化最大堆
//返回最大价值,bestx返回最优解
bestx = new int[n+1];
E = null;
int i = 1;
cw = cv = 0.0;
double bestp = 0.0; //当前最优值
double up = bound(1); //价值上界
//while搜索子集空间树,直到子集树的一个叶结点成为扩展结点为止,
//先检查当前结点左儿子结点的可行性,如果可行,则加入;当且仅当右儿子结点满足上界约束时才加入
while (i != n + 1) {
double wt = cw + w[i];
if (wt <= c) {
//左儿子为可行结点
if (cv + v[i] > bestp) {
bestp = cv + v[i]; //更新当前最优值
}
addLiveNode(up, cv + v[i], cw + w[i], i + 1, true); //leftChild = true
}
up = bound(i + 1); //不论左结点是否可取,都进行下一个结点上界计算
if (up >= bestp) // 右子树可能含有最优解
addLiveNode(up, cv, cw, i + 1, false);
//取下一个扩展结点
HeapNode node_h = Heap.poll();
E = node_h.ptr;
cw = node_h.weight;
cv = node_h.profit;
up = node_h.upperProfit;
i = node_h.level;
}
for (int j = n; j > 0; j--) {
bestx[j] = E.leftChild ? 1 : 0;
E = E.parent;
}
return cv;
}
对输入数据进行预处理
//对输入数据进行预处理
public static int knapsack(int[] pp, int[] ww, int cc, int nn, int[] xx){
//初始化
int ws = 0,//装包物品重量
ps = 0;//装包物品价值
Element[] Q = new Element[nn];//定义依单位重量价值排序的物品数组
for (int i = 1; i <= nn; i++) {
//单位重量价值数组
Q[i-1] = new Element(i,pp[i]/ww[i]);
ps += pp[i];
ws += ww[i];
}
// for (Element element : Q) {
// System.out.println(element.ID + ": " + element.d);
// }
//所有物品重之和<=最大容量C,即可全部物品装包
if (ws <= cc) {
for(int i = 1; i <= nn; i++){
xx[i] = 1;
}
return ps;
}
//依单位重量价值排序
Arrays.sort(Q);
// for (Element element : Q) {
// System.out.println(element.ID + ": " + element.d);
// }
//初始化数据成员
p = new int[nn + 1];
w = new int[nn + 1];
for (int i = 1; i <= nn; i++) {
p[i] = pp[Q[i-1].ID];
w[i] = ww[Q[i-1].ID];
}
cp = 0;
cw = 0;
c = cc;
n = nn;
int bestp = maxKnapsack();
for (int j = 1; j <= nn; j++) {
xx[Q[j-1].ID] = bestx[j];
}
return bestp;
}
主函数实现
public static void main(String[] args) {
//测试数据*1
c = 8;
n = 4;
w = new double[]{0, 5, 4, 3,2};
v = new double[]{0, 8, 5, 4,3};
int[] xx = new int[n + 1];
double maxp = knapsack(v, w, c, n, xx);
System.out.println("装入背包中物品总价值最大为:" + maxp);
System.out.print("装入的物品的最优序列为:");
for(int i = 1; i <= n; i++){
System.out.print(bestx[i] + " ");
}
结果