算法笔记:回溯法 0-1背包问题

虽然物品是一个一维的线性储存
但是我们决策的过程可以看为一个树状的过程
每个物品都有选和不选两种情况
那么我们决策的过程就是在从上往下遍历这个树

利用贪心算法,根据性价比给物品排序
大的在决策树的上面
一次往下选

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#define NUM 100 
using namespace std;

int cw;				// 当前重量 
int cv;				// 当前价值 
int bestv;			// 当前最优解 
// 每个物品的数据结构
struct good {
	int w;
	int v;
	double d;		// 性价比 
} goods[NUM];

bool cmp(good a, good b){
	if(a.d >= b.d) return true;
	else return false; 
} 

// 剪枝算法
int Bound(int i, int c, int n){
	int cleft = c - cw;		// 剩余容量
	int max_value = cv;				// 最大价值 
	
	// 尽量装满背包
	// 利用循环来预先观察后面节点是否超重,而不是等后面递归
	while(i < n && goods[i].w <= cleft){
		cleft -= goods[i].w;
		max_value += goods[i].v;
		i++;
	} 
	
	// 看剩余部分还能装多少价值
	if(i < n) max_value += cleft * goods[i].d;
	return max_value; 
} 

// 回溯算法
void backtrack(int i, int c, int n){
	// 到叶子结点跟新最优值
	if(i + 1 > n) {
		bestv = cv;
		return;
	} 
	// 左子树
	if(cw + goods[i].w <= c){
		cw += goods[i].w;
		cv += goods[i].v;

		backtrack(i+1,c,n);
		
		// 开始回溯,变成进入这个结点之前的情况 
		// 在为右节点做准备,因为右节点的意思不选这个物品
		cw -= goods[i].w;
		cv -= goods[i].v;
	} 
	
	// 右节点
	//	Bound是在求后面最多还能装多少价值的东西 
	// 如果比当前最后值还好的话,继续向后面的结点计算
	// 没有进入判断才是剪枝 
	if(Bound(i+1,c,n) > bestv) backtrack(i+1,c, n);		
}

int main() {
	int c = 5;							// 背包的容量 
	int w[] = {2, 1, 3, 2};
    int v[] = {12, 10, 20, 15};
    int n = sizeof(w) / sizeof(w[0]);
	
	for(int i = 0;i < n;i++){
		goods[i].v = v[i];
		goods[i].w = w[i];
		goods[i].d = 1.0 * goods[i].v / goods[i].w;
	}
	
	sort(goods, goods + n, cmp);
	
	backtrack(0, c, n);
	
	cout << bestv << endl;
} 

在这里插入图片描述


关于这个剪枝函数
如果我们不适用剪枝函数
在这里插入图片描述
直接往后递归的话
那每条决策的路都会被走一遍
所以递归出口的bestv代表每走到叶子结点,我们就返回这条决策的最后答案
那这样的话我们需要得到一个最好答案的数组
最后返回数组的最大值才是最终答案

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#define NUM 100 
using namespace std;

int cw;				// 当前重量 
int cv;				// 当前价值 
int bestv;			// 当前最优解 
// 每个物品的数据结构
struct good {
	int w;
	int v;
	double d;		// 性价比 
} goods[NUM];

bool cmp(good a, good b){
	if(a.d >= b.d) return true;
	else return false; 
} 

// 回溯算法
void backtrack(int i, int c, int n){
	// 到叶子结点跟新最优值
	if(i + 1 > n) {
		bestv = cv;
		cout << bestv << " ";
		return;
	} 
	// 左子树
	if(cw + goods[i].w <= c){
		cw += goods[i].w;
		cv += goods[i].v;
//	    cout << cv << endl;
		backtrack(i+1,c,n);
		
		// 开始回溯,变成进入这个结点之前的情况 
		cw -= goods[i].w;
		cv -= goods[i].v;
	} 
	
	// 右节点
	//	Bound是在求后面最多还能装多少价值的东西 
	// 如果比当前最后值还好的话,继续向后面的结点计算
	// 没有进入判断才是剪枝 
	// if(Bound(i+1,c,n) > bestv) 
	backtrack(i+1,c, n);		
}

int main() {
	int c = 5;							// 背包的容量 
	int w[] = {2, 1, 3};
    int v[] = {12, 10, 20};
    int n = sizeof(w) / sizeof(w[0]);
	
	for(int i = 0;i < n;i++){
		goods[i].v = v[i];
		goods[i].w = w[i];
		goods[i].d = 1.0 * goods[i].v / goods[i].w;
	}
	
	sort(goods, goods + n, cmp);
	
	cout << "每种决策的最终结果:" << endl; 
	backtrack(0, c, n);
	
	// cout << bestv << endl;
} 

在这里插入图片描述
这一次数据变成三个,更容易看出来
三个物品的话
总共八种情况
最后输出只有七种是因为第一种全选超重了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值