目录
1、贪心算法:限制条件——背包容量;期望值——背包中物品总价值最大。
3、分支限界法:与回溯算法的区别在于分支限界只找出满足约束条件的一个解,或在满足约束条件的解中找出在某种意义下的最优解;回溯是深度优先搜索,分支限界是广度优先或以最小耗费优先搜索。
实验 5《算法综合实验》(选做)
ps.分支限界还是没有很清楚,可以考虑参考其他文章
一、实验目的
- 理解和复习所学各种算法的概念
- 掌握和复习所学各种算法的基本要素
- 掌握各种算法的优点和区别
- 通过应用范例掌握选择最佳算法的设计技巧与策略
二、实验内容
- 使用贪心算法、回溯法、分支限界法解决 0-1 背包问题;
- 通过上机实验进行算法实现;
- 保存和打印出程序的运行结果,并结合程序进行分析,上交实验报告。
三、算法思想分析:
1、贪心算法:限制条件——背包容量;期望值——背包中物品总价值最大。
每次选择能够装入背包的单位价值最大的物品装入。
其实用贪心算法并不能找出0-1背包的最优解,因为无法保证最终能将背包装满,部分闲置的背包空间使总价值降低了。
2、 回溯法:约束条件——当前剩余容量是否足以放下物品i;
限界条件——假设将剩余容量装满,背包总价值是否将大于当前最佳总价值,
若大于才继续向下扩展。
3、分支限界法:与回溯算法的区别在于分支限界只找出满足约束条件的一个解,或在满足约束条件的解中找出在某种意义下的最优解;回溯是深度优先搜索,分支限界是广度优先或以最小耗费优先搜索。
分支限界的基本思想:
- 每个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生所有儿子结点;
- 在这些儿子结点中,导致不可行解或导致非最优解的儿子节点被舍弃,其余儿子结点被加入活结点表中;
- 每次从活结点表中取下一个结点成为当前扩展结点,并重复上述结点扩展过程,直到找到所需解或活结点表为空时为止。
- 可以考虑用剪枝函数加快搜索
四、实验过程分析
-
遇到的问题及解决
- 回溯法中保存放入背包的物品时操作有误,最终输出为空,是否放入应该与bestV做比较时进行修改,而不是在满足限界条件时就修改,然后回溯时又拿出来,最终导致输出时包里啥也没有。
- 回溯法需要在i == n,即到达叶子结点时更新bestV和bestC,否则在之后的递归及回溯过程中cleft和currentV一直在变化,无法得到最优解
- 感觉分支和回溯真的很像,只是一个是深度优先,一个是广度优先,所以我刚开始想着能不能直接在回溯的基础上修改部分代码,将回溯改为分支。但是发现不太可行,分支需要用到活结点队列,另外我已经不太记得广度优先搜索具体应该怎么操作了,所以就放弃了这个想法,看了看教材上的分支限界解决01背包问题,按教材的思路实现算了,等之后对分支及广度优先搜索更熟悉之后再考虑看看将回溯改为分支的想法可不可行吧。-----书上用的C++,我实验都用的Java,还是去找找用Java写的代码看看吧。
-
实验体会
- 回溯法解决01背包问题中,既用到了约束条件又用到了限界函数,这个例子对于我们理解回溯算法的剪枝函数是非常有帮助的,刚开始我将约束条件与限界函数混为一谈,我就说老师上课说限界函数是用来剪去右子树的(应该只是对于01背包问题来说吧,并不是所有的限界函数都是用来剪去右子树的吧)是什么意思,原来是右子树代表这个物品没有放入背包,在这种情况下计算剩余可达到的最大价值(上界),如果小于当前bestV的话就可以不用考虑了,不过我觉得这里还是有点小问题,就是为什么是按顺序放,来计算剩余可达到的最大价值呢,为什么不像贪心算法那样,先考虑将单位价值最大的物品放入,这样有可能找到一个更大的剩余可达到的价值量啊---------好的,我发现PPT上有个小字,上述物品已经按单位价值排序。。。呃,所以要要求用户输入时先按物品单位价值排序吗,其实也可以用户输入后我帮他排个序,然后保存用户输入顺序与排序后的顺序的映射,找出答案后输出时做个转换就行了,这算是一个可改进之处吧。
五、算法源代码及用户屏幕
1.贪心算法解决0-1背包问题
源代码:
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Scanner;
public class Greedy_01 {
private static int n;//物品数量
private static int[]weight;//物品的重量
private static int[]value;//物品的价值
private static double[]unitValue;//单位价值
private static int capacity;//背包容量
private static boolean[]thing;//放入背包的物品
private static boolean[]besidesConsider;//存储是否考虑放入背包
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("贪心算法求解0—1背包问题");
System.out.print("请输入背包容量(要求为整数):");
capacity = input.nextInt();
System.out.print("请输入物品数量:");
n = input.nextInt();
weight = new int[n];
value = new int[n];
unitValue = new double[n];
thing = new boolean[n];
besidesConsider = new boolean[n];
System.out.println("请分别输入每个物品的重量及对应价值(每行表示一个物品的重量及对应价值):");
for(int i = 0; i < n; i++){
weight[i] = input.nextInt();
value[i] = input.nextInt();
BigDecimal bigDecimal = new BigDecimal((double) value[i] / weight[i]);//四舍五入
unitValue[i] = bigDecimal.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
}
printThings();
selectThing();
}
private static int getMaxIndex(){
double max = 0;
int maxIndex = -1;
for(int i = 0; i < n; i ++){
if(!besidesConsider[i] && unitValue[i] > max){//如果该物品并未放入背包且单位价值高于max,则更新
max = unitValue[i];
maxIndex = i;
}
}
return maxIndex;
}
private static void selectThing(){//选择物品放入背包
int weightCount = 0;
int valueCount = 0;
System.out.println("放入背包中的物品有:");
int maxIndex = getMaxIndex();//得到单位价值最大的物品
while(maxIndex != -1){//当maxIndex为-1时,则没有可以放入背包的物品了
if(weight[maxIndex] < capacity){
capacity -= weight[maxIndex];//将物品放入背包
thing[maxIndex] = true;
weightCount += weight[maxIndex];
valueCount += value[maxIndex];
System.out.println("第" + (maxIndex + 1) + "件物品,重量为" + weight[maxIndex] + ",价值为" + value[maxIndex] + ",单位价值为" + unitValue[maxIndex] + "。");
}
besidesConsider[maxIndex] = true;//不管有没有放入,这个物品之后都不用再考虑放入了
maxIndex = getMaxIndex();
}
System.out.println("总重量为" + weightCount + ",总价值为" + valueCount);
}
private static void printThings(){
System.out.println("待放入背包的物品如下:");
for(int i = 0; i < n; i++){
System.out.println("第" + (i + 1) + "件物品,重量为" + weight[i] + ",价值为" + value[i] + ",单位价值为" + unitValue[i] + "。");
}
}
}
用户屏幕:
2.回溯算法解决0-1背包问题
源代码:
import java.math.BigDecimal;
import java.util.Scanner;
public class Backtrack_01 {
private static int n;//物品数量
private static int[]weight;//物品的重量
private static int[]value;//物品的价值
private static int capacity;//背包容量
private static int bestV;//最优解时背包总价值
private static int bestC;//最优解时背包总重量
private static int currentV;//当前背包价值
private static int cleft;//背包剩余容量
private static boolean[]thing;//放入背包的物品
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("回溯算法求解0—1背包问题");
System.out.print("请输入背包容量(要求为整数):");
capacity = input.nextInt();
cleft = capacity;
System.out.print("请输入物品数量:");
n = input.nextInt();
weight = new int[n];
value = new int[n];
thing = new boolean[n];
System.out.println("请分别输入每个物品的重量及对应价值:");
for(int i = 0; i < n; i++){
weight[i] = input.nextInt();
value[i] = input.nextInt();
}
backtrack(0);
System.out.println("放入背包中的物品有:");
for(int i =0; i < n; i++){
if(thing[i])
System.out.println("第" + (i + 1) + "件物品,重量为" + weight[i] + ",价值为" + value[i]);
}
System.out.println("总重量为" + bestC + ",总价值为" + bestV);
}
private static void backtrack(int i){//i为递归深度,即第i+1个物品是否放入
if(i == n){
bestV = currentV;
bestC = capacity - cleft;
return;
}
if(cleft >= weight[i]){//搜索左子树
thing[i] = true;
cleft -= weight[i];
currentV += value[i];
backtrack(i+1);
//回溯
cleft += weight[i];
currentV -= value[i];
}
if(bound(i + 1)){//搜索右子树
thing[i] = false;
backtrack(i + 1);
}
}
private static boolean bound(int i){
int cV = currentV;
int cl = cleft;
while(i < n && weight[i] <= cl){
cl -= weight[i];
cV += value[i];
i++;
}
if(i < n){//装满背包
cV += value[i] / weight[i] * cl;
}
if(cV > bestV){
return true;
}else {
return false;
}
}
}
用户截图:
正确性:
3.分支限界算法解决0-1背包问题
源代码:
import java.util.PriorityQueue;
import java.util.Scanner;
//定义节点中的参数以及优先级设置的对象
class thingNode implements Comparable<thingNode>{
int weight;//该节点目前背包中的重量
double value;//该节点目前背包中的总价值
double upprofit;//该节点能够达到的价值上界
int Left; //该节点是否属于左节点(用于最终构造最优解)
int level; //该节点是第几个物品的选择
thingNode father; //该节点的父节点
public int compareTo(thingNode node){
if(this.upprofit<node.upprofit)
return 1;
else if(this.upprofit == node.upprofit)
return 0;
else
return -1;
}
}
public class BranchAndBound_01 {
private static int n;
private static int capacity;
private static int[] weight;
private static double[] value;
private static int maxValue = 0;
private static int[] bestWay;
public void getMaxValue() {
PriorityQueue<thingNode> pq = new PriorityQueue<thingNode>();
//构造一个初始化节点,属于-1层
thingNode initial = new thingNode();
initial.level = -1;
initial.upprofit = 26;
pq.add(initial);
while (!pq.isEmpty()) {
thingNode fatherNode = pq.poll();
//当已经搜索到叶子节点时
if (fatherNode.level == n - 1) {
if (fatherNode.value > maxValue) {
maxValue = (int) fatherNode.value;
for (int i = n - 1; i >= 0; i--) {
bestWay[i] = fatherNode.Left;
fatherNode = fatherNode.father;
}
}
} else {
//先统计其左节点信息,判断是否加入队列。
if (weight[fatherNode.level + 1] + fatherNode.weight <= capacity) {
thingNode newNode = new thingNode();
newNode.level = fatherNode.level + 1;
newNode.value = fatherNode.value + value[fatherNode.level + 1];
newNode.weight = weight[fatherNode.level + 1] + fatherNode.weight;
newNode.upprofit = Bound(newNode);
newNode.father = fatherNode;
newNode.Left = 1;
if (newNode.upprofit > maxValue)
pq.add(newNode);
}
//向右节点搜索,其能够取到的价值上界通过父亲节点的上界减去本层物品的价值。
if ((fatherNode.upprofit - value[fatherNode.level + 1]) > maxValue) {
thingNode newNode2 = new thingNode();
newNode2.level = fatherNode.level + 1;
newNode2.value = fatherNode.value;
newNode2.weight = fatherNode.weight;
newNode2.father = fatherNode;
newNode2.upprofit = fatherNode.upprofit - value[fatherNode.level + 1];
newNode2.Left = 0;
pq.add(newNode2);
}
}
}
}
//用于计算该节点的最高价值上界
public double Bound(thingNode no) {
double maxLeft = no.value;
int leftWeight = capacity - no.weight;
int templevel = no.level;
//尽力依照单位重量价值次序装剩余的物品
while (templevel <= n - 1 && leftWeight > weight[templevel]) {
leftWeight -= weight[templevel];
maxLeft += value[templevel];
templevel++;
}
//不能装时,用下一个物品的单位重量价值折算到剩余空间。
if (templevel <= n - 1) {
maxLeft += value[templevel] / weight[templevel] * leftWeight;
}
return maxLeft;
}
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("分支限界算法求解0—1背包问题");
System.out.print("请输入背包容量(要求为整数):");
capacity = input.nextInt();
System.out.print("请输入物品数量:");
n = input.nextInt();
weight = new int[n];
value = new double[n];
bestWay = new int[n];
System.out.println("请分别输入每个物品的重量及对应价值:");
for (int i = 0; i < n; i++) {
weight[i] = input.nextInt();
value[i] = input.nextInt();
}
BranchAndBound_01 b = new BranchAndBound_01();
b.getMaxValue();
System.out.println("放入背包中的物品有:");
int bestC = capacity;
for (int i = 0; i < n; i++) {
if (bestWay[i] == 1) {
bestC -= weight[i];
System.out.println("第" + (i + 1) + "件物品,重量为" + weight[i] + ",价值为" + value[i]);
}
}
System.out.println("总重量为" + bestC + ",总价值为" + maxValue);
}
}
用户截图: