问题描述
有n个重量分别为w1 、 w2 、w3、w4···wn的物品,编号1~n,它们的价值为v1 、 v2 、v3、v4···vn。现有一容量为W的背包,求尽可能的把背包装满并使价值最大
下面不妨以n=4 W=6为例
物品编号 | 重量 | 价值 |
---|---|---|
1 | 5 | 4 |
2 | 3 | 4 |
3 | 2 | 3 |
4 | 1 | 1 |
蛮力法–求幂集搜索
思路大概是用蛮力法找出n的所有幂集,然后遍历一遍,找到最优解
复杂度为O(2n)
#include<iostream>
#include<vector>
#include<stdio.h>
#include<map>
#include<algorithm>
using namespace std;
/**
求解简单0/1背包问题
2019-8-12
*/
vector<vector<int> > ps;
void PSet(int n) // 求1-n的幂集
{
vector<vector<int> > ps1; // 子幂集
vector<vector<int> >::iterator it;
vector<int> s;
ps.push_back(s); // 添加空集合元素
for(int i=1;i<=n;i++)
{
ps1 = ps;
for(it=ps1.begin();it!=ps1.end();it++)
(*it).push_back(i);
for(it=ps1.begin();it!=ps1.end();it++)
ps.push_back(*it);
}
}
void Knap(int w[],int v[], int W)
{
int count = 0;
int sumw,sumv;
int maxi,maxsumw=0,maxsumv=0;
vector<vector<int> >::iterator it;
vector<int>::iterator sit;
printf(" 序号 选中物品 总重量 总价值 能否装入\n");
for(it=ps.begin();it!=ps.end();it++)
{
printf(" %d\t",count++);
sumw=sumv=0;
printf(" {");
for(sit=(*it).begin();sit!=(*it).end();sit++)
{
printf("%d ",*sit);
sumw += w[*sit-1];
sumv += v[*sit-1];
}
printf("}\t\t%d\t%d ",sumw,sumv);
if(sumw<=W)
{
printf("能\n");
if(sumv>maxsumv)
{
maxsumw = sumw;
maxsumv = sumv;
maxi = count-1;
}
}
else cout << "否\n";
// count++;
}
printf("最佳方案为:");
printf("选中物品");
printf("{ ");
for(sit=ps[maxi].begin();sit!=ps[maxi].end();sit++)
printf("%d ",*sit);
printf("},");
printf("总重量:%d,总价值:%d\n",maxsumw,maxsumv);
}
int main()
{
int n = 4, W = 6;
int w[] = {5,3,2,1};
int v[] = {4,4,3,1};
PSet(n);
printf("0/1背包解决方案\n",n);
Knap(w,v,W);
return 0;
}
队列式分支界限法
#include<iostream>
#include<queue>
using namespace std;
#define MAXV 20
int maxv = -9999;
int bestx[20]; // 存放最优解,全局变量
int total = 1;
struct NodeType{
int num; // 结点编号
int i; // 当前结点在搜索空间中的层次
int w; // 当前结点的总权重
int v; // 当前结点的总价值
int x[MAXV]; // 当前结点包含的解向量
double ub; // 上界
};
int n =5,W = 6;
int w[] = {0,5,3,2,1};
int v[] = {0,4,4,3,1};
void bound(NodeType &e){ // 计算分支结点e的上界
int i=e.i+1;
int sumw = e.w;
double sumv = e.v;
while((sumw+w[i]<=W)&& i<=n){
sumw += w[i];
sumv += v[i];
i++;
}
if(i<=n)
e.ub = sumv+(W-sumw)*v[i]/w[i];
else e.ub = sumv;
}
void EnQueue(NodeType e,queue<NodeType> &qu)
{
if(e.i==n)
{
if(e.v>maxv)
{
maxv = e.v;
for(int j=1;j<=n;j++)
bestx[j] = e.x[j];
}
}
else qu.push(e);
}
void bfs()
{
int j;
NodeType e,e1,e2;
queue<NodeType> qu;
e.i = 0;
e.w = 0;
e.v = 0;
e.num = total++;
for(j=1;j<=n;j++)
e.x[j] = 0;
bound(e);
qu.push(e);
while(!qu.empty())
{
e = qu.front();
qu.pop();
if(e.w+w[e.i+1]<=W) // 剪枝:检查左孩子结点
{
e1.num = total++;
e1.i = e.i+1;
e1.w = e.w+w[e1.i];
e1.v = e.v+v[e1.i];
for(j=1;j<=n;j++)
e1.x[j] = e.x[j];
e1.x[e1.i] = 1;
bound(e1);
EnQueue(e1,qu);
}
e2.num = total++;
e2.i = e.i+1;
e2.w = e.w;
e2.v = e.v;
for(j=2;j<=n;j++)
e2.x[j] = e.x[j];
e2.x[e2.i]= 0;
bound(e2);
if(e2.ub>maxv)
EnQueue(e2,qu);
}
}
int main()
{
bfs();
printf("分支界限法求解0/1背包问题:\n X=[");
for(int i=1;i<=n;i++)
printf("%2d",bestx[i]);
printf("],装入总价值为%d\n",maxv);
return 0;
}
未完待续~~
参考: 李春葆 .算法设计与分析(第二版)