0-1背包问题

问题描述: 0-1背包问题(无物品价值)

有一个能装入容量为C的背包,n件物品重(w1, w2, w3, ....., wn),从n件中选若干件,刚好装满背包,找出所有可能解的数量

问题分析:w1 w2 w3 w4 ...... wn共有如下的2^n中可能的情况,要求找出所有的可能解,就在这个解空间中搜索出所有满足要求的情况。如果,直接搜索时间复杂度太高O(2^n)

w_1\,\,w_2\,\,w_3\,...\,...\,w_n \par 0\,\,\,\,\,\,0\,\,\,\,\,\,0\,\,\,\,...\,...\,\,0\par 1\,\,\,\,\,\,0\,\,\,\,\,\,0\,\,\,\,...\,...\,\,0\par 0\,\,\,\,\,\,1\,\,\,\,\,\,0\,\,\,\,...\,...\,\,0\par 1\,\,\,\,\,\,1\,\,\,\,\,\,0\,\,\,\,...\,...\,\,0\par \vdots \par 1\,\,\,\,\,\,1\,\,\,\,\,\,1\,\,\,\,...\,...\,\,1

借助树中“回溯”的思想,可在搜索过程中快速排出一些不满足要求的情况,例如:如果C=10,假设w3=9,w4=5,此时如果选择9+5>10,那么所有选择了9+5的情况将不必再考虑,后面继续添加物体重量只会越大,显然不满足情况。

在树空间中搜索:

问题可变成在二乘树中搜索的问题

树中每层节点表示一种物品,节点的左left边表示选择该物品“1”,right边表示不选择该物品“0”,从根节点开始沿着树搜索完整棵树就可以找到可行解,与上面的暴力搜索所有的情况不同的是,此时的搜索是可“回溯”的。回溯存在两种情况:1.如果搜索到某个节点重量>C,则该节点后的所有情况不再考虑,立刻回到当前节点的上一层开始后面的搜索;2.如果搜索到某个节点重量恰好=C,即找到了可行解,此时也要回溯,因为继续往下搜索没有必要(后面节点都是0,即后面的物品都不选)。

树中的搜索过程如下:

算法确定了以后需要找到合适的数据结构,本文用栈作为记录搜索过程的数据结构,因为栈后进先出的原则,非常适合用于回溯,回溯就是向上回到离当前最近的节点。因此用栈记录背包中已经选择的物体。同时还好配合一个表达当前搜索位置的flag,flag指明了w1, w2, w3, ......, wn中正在判断的物品。栈配合flag可完成对以上二叉树的搜索,程序如下:

#include<iostream>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;
//用栈实现搜索
int search0(const vector<int> &w,const int &C )
{
	stack<int> decide;//决策取第几个物体
	int curindex = 0;//从当前编号开始依次查找
	int N = w.size();
	int sum_w = 0;
	int num_case=0;
	while (1){
		for (int i = curindex; i < N; ++i){
			if (!(w[i] + sum_w > C)){
				sum_w += w[i];
				decide.push(i);
				if (sum_w == C){
					//找到了满足要求的情况 记录 回溯
					num_case++;
					sum_w -= w[i];//接着往下找
					decide.pop();
				}
			}
		}
		//经过以上循环 访问到了最后一个元素
		if (decide.empty()){
			break;//并且栈中没有元素回溯 说明搜索到了树中的最后一种情况 结束搜索
		}
		else{
			//回溯
			curindex = decide.top();
			sum_w -= w[curindex];
			decide.pop();
			++curindex;
		}
	}
	return num_case;
}

bool cmp(int x,int y)
{
	return x > y;
}
 
int main()
{
	//测试 6 10 1 8 4 3 5 2 输出 4
	int N;//元素的个数
	int C;//背包的容量
	cin >> N >> C;
	vector<int> w(N);
	stack<int> decide;//决策取第几个物体
	int num_case =0;
	if ( N > 0 ){
		for (int i = 0; i < N;++i)//输入每个元素的重量
			cin >> w[i];
		//将输入数据排序
		sort(w.begin(),w.end(),cmp);
		num_case = search0(w, C);
	}
	cout << num_case<<std::endl;
	system("pause");
	return 0;
}

依次输入物品数量N,总容量C,以及各个物品的重量,第二行输出可行解的数量: 

扩展:输出所有的可行解

以上程序使用了stack数据结构,由于stack数据结构只允许对栈顶元素的访问,当出现满足要求的情况时,不方便从stack中取出可行解的具体方案进行记录。为了方便记录可行解的具体取法,采用顺序栈来模拟栈的效果。顺序栈本质上是一个数组+flag,同flag标识符对数组进行访问达到栈的效果,其优点是可随时同数组一样变量顺序栈中的所有元素。顺序栈相关内容可参考http://data.biancheng.net/view/170.html

改进版的代码如下:

#include<iostream>
#include<vector>
#include<stack>
#include<algorithm>
#include<time.h>
using namespace std;
//用顺序栈实现搜索(用顺序表模拟栈)
int search1(const std::vector<int> & w, const int &C,std::vector<std::vector<int> > &case_recorder)
{
	int N = w.size();
	vector<int> decide(N);//决策取第几个物体
	int top_flag = 0;//记录当前栈顶前的一个的元素
	int curindex = 0;//从当前编号开始依次查找
	int sum_w = 0;
	int num_case = 0;
	while (1){
		for (int i = curindex; i < N; ++i){
			if (!(w[i] + sum_w > C)){
				sum_w += w[i];
				//decide.push(i);
				decide[top_flag++] = i;//模拟入栈
				if (sum_w == C){
					//找到了满足要求的情况 记录 回溯
					num_case++;
					std::vector<int> new_case(top_flag);
					for (int i = 0; i < top_flag; ++i)
						new_case[i] = w[decide[i]];
					case_recorder.push_back(new_case);
					sum_w -= w[i];//接着往下找
					//decide.pop();
					--top_flag;//模拟出栈 游标后退移动即可
				}
			}
		}
		//经过以上循环 访问到了最后一个元素
		if ( /*decide.empty()*/ top_flag==0 ){
			break;//并且栈中没有元素回溯 说明搜索到了树中的最后一种情况 结束搜索
		}
		else{
			//回溯
			//curindex = decide.top();
			curindex = decide[top_flag-1];//模拟取栈顶元素
			sum_w -= w[curindex];
			//decide.pop();
			--top_flag;//模拟出栈
			++curindex;
		}
	}
	return num_case;
}
bool cmp(int x,int y)
{
	return x > y;
}
 
int main()
{
	//测试 6 10 1 8 4 3 5 2
	int N;//元素的个数
	int C;//背包的容量
	cin >> N >> C;
	vector<int> w(N);
	vector<int> decide(N);//决策取第几个物体 初始化内存空间 最多有N位在栈中 因为对多每个元素都取
	int stack_top =0;//用一个顺序数组模拟栈
	vector<vector<int> > case_recorder ;//记录可行的取的方案
	int num_case =0;
	if ( N > 0 )
	{
		for (int i = 0; i < N;++i)//输入每个元素的重量
			cin >> w[i];
		//将输入数据排序
		sort(w.begin(),w.end(),cmp);
		num_case = search1(w, C, case_recorder);
	}
	std::cout << "可行解的数量:" << num_case << std::endl;
	cout << "依次为:" << endl;
	for (int i = 0; i < num_case;++i){
		for (int j = 0; j < case_recorder[i].size(); ++j ){
			cout <<case_recorder[i][j]<<" ";
		}
		cout << endl;
	}
	system("pause");
	return 0;
}

依次输入物品数量N,总容量C,以及各个物品的重量,第二行输出可行解的数量,后面每行输出可行解的具体取法:  

 

结束!^v^ 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值