问题描述: 0-1背包问题(无物品价值)
有一个能装入容量为C的背包,n件物品重(w1, w2, w3, ....., wn),从n件中选若干件,刚好装满背包,找出所有可能解的数量
问题分析:w1 w2 w3 w4 ...... wn共有如下的中可能的情况,要求找出所有的可能解,就在这个解空间中搜索出所有满足要求的情况。如果,直接搜索时间复杂度太高。
借助树中“回溯”的思想,可在搜索过程中快速排出一些不满足要求的情况,例如:如果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^