一、问题描述
0-1背包问题可描述为:n个物体和一个背包。对物体i,其价值为value,重量为weight,背包的容量为W。如何选取物品装入背包,使背包中所装入的物品总价值最大?
二、算法设计
2.1 用到的数据结构
class Goods //定义货物数据类型
{
public:
int weight;//货物重量
int value;//货物价值
friend ostream& operator<<(ostream &os, const Goods &out);
};
class Knapsack//背包
{
private:
int capacity;//背包容量
int nGoodsNum;//物品数
vector<Goods> goods; //所有货物
int nMaxValue;//之前背包中装入的最大价值物品
int nCurrentWeight;//当前背包中装入物品的数量
int nCurrentValue;//当前背包中物品的价值
vector<bool> bestResult;//之前背包中物品最大价值时的物品
vector<bool> currentResult;//当前背包中的物品
}
2.2 算法步骤
1)定义解空间。(X0 , X1,X2,X3…..Xn),Xi的值为true或false。
(i = 0,1,2,3….n)
2)确定解空间。问题的解空间描述了2^n种可能的解,采用一个二叉满树组织,解
空间的深度为问题的规模。
3)搜索解空间
a.约束条件。背包中物品重量小于背包容量。
b.限界条件。nCurrentValue为当前背包中物品价值,nMaxValue之前背包中装
入的最大价值的物品。nP = bound(i + 1),第 i个物品之后的所有物品可
装入背包的最大价值。要求:nP + nCurrentValue > nMaxValue.
c.以深入优先的方式进行搜索.首先以根节点即第一个物品开始搜索.
三、算法描述
int bound(int i)//限界函数
{
int nLeftCapacity = capacity - nCurrentWeight;
int tempMaxValue = nCurrentValue;
while (i < nGoodsNum && goods[i].weight <= nLeftCapacity)
{
nLeftCapacity -= goods[i].weight;
tempMaxValue += goods[i].value;
}
if (i < nGoodsNum)
{
tempMaxValue += (float)(goods[i].value) / goods[i].weight * nLeftCapacity;
}
return tempMaxValue;
}
void backTrack(int t)//递归回溯
{
if (t >= nGoodsNum)
{
for (int i = 0; i < nGoodsNum; ++i)
{
bestResult[i] = currentResult[i];
}
nMaxValue = nCurrentValue;
return;
}
if (nCurrentWeight + goods[t].weight <= capacity)
{
currentResult[t] = true;
nCurrentWeight += goods[t].weight;
nCurrentValue += goods[t].value;
backTrack(t + 1);
nCurrentWeight -= goods[t].weight;
nCurrentValue -= goods[t].value;
}
if (bound(t + 1) > nMaxValue)
{
currentResult[t] = false;
backTrack(t + 1);
}
}
//寻找最优结果
int BacktrackingKnapsack0_1(AllGoods &allGoods, int nKnapSackCap)
{
Knapsack knap(allGoods,nKnapSackCap);
knap.printGoods();
knap.sortByUintValue();
cout << "sort" << endl;
knap.printGoods();
knap.backTrack(0);
knap.printResult();
return 0;
}
四、算法复杂性分析
时间复杂度:判断约束函数需O(1),最坏情况下有2^n - 1个左孩子,约束函数耗时最坏为O(2^n).计算上界函数需O(n),在最坏情况下有2^n – 1个右孩子,限界函数耗时最坏为O(n2^n)。则背包问题最坏的时间复杂度为O(n2^n)。
空间复杂度:创建Knapsack类对象,其中有三个长度为n的数组,而后调用backTrack()函数递归深度为n。所以,此算法的空间复杂度为O(n).
五、算法实现与测试
5.1 实现代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int NKNAPSACKCAP = 10;
class Goods //定义货物数据类型
{
public:
int weight;
int value;
friend ostream& operator<<(ostream &os, const Goods &out);
};
ostream& operator<<(ostream &os, const Goods &out)
{
os << "重量:" << out.weight << " 价值: " << out.value;
return os;
}
typedef vector<Goods> AllGoods;//定义所有货物数据类型
class Knapsack
{
private:
int capacity;//背包容量
int nGoodsNum;//物品数
vector<Goods> goods;
int nMaxValue;
int nCurrentWeight;
int nCurrentValue;
vector<bool> bestResult;
vector<bool> currentResult;
int bound(int i)
{
int nLeftCapacity = capacity - nCurrentWeight;
int tempMaxValue = nCurrentValue;
while (i < nGoodsNum && goods[i].weight <= nLeftCapacity)
{
nLeftCapacity -= goods[i].weight;
tempMaxValue += goods[i].value;
}
if (i < nGoodsNum)
{
tempMaxValue += (float)(goods[i].value) / goods[i].weight * nLeftCapacity;
}
return tempMaxValue;
}
public:
Knapsack(AllGoods &AllGoods, int nKnapsackCap)
{
nGoodsNum = AllGoods.size();
capacity = nKnapsackCap;
nCurrentWeight = 0;
nCurrentValue = 0;
nMaxValue = 0;
for (int i = 0; i < nGoodsNum; ++i)
{
goods.push_back(AllGoods[i]);
bestResult.push_back(false);
currentResult.push_back(false);
}
}
void sortByUintValue()
{
stable_sort(goods.begin(), goods.end(), [](const Goods& left, const Goods& right)
{return (left.value * right.weight > left.weight * right.value); });
}
void printGoods()
{
for (size_t i = 0; i < goods.size(); ++i)
{
cout << goods[i] << endl;
}
}
void printResult()
{
cout << "MAX VALUE: " << nMaxValue << endl;
for (int i = 0; i < nGoodsNum; ++i)
{
if (bestResult[i])
{
cout << goods[i] << endl;
}
}
}
void backTrack(int t)
{
if (t >= nGoodsNum)
{
for (int i = 0; i < nGoodsNum; ++i)
{
bestResult[i] = currentResult[i];
}
nMaxValue = nCurrentValue;
return;
}
if (nCurrentWeight + goods[t].weight <= capacity)
{
currentResult[t] = true;
nCurrentWeight += goods[t].weight;
nCurrentValue += goods[t].value;
backTrack(t + 1);
nCurrentWeight -= goods[t].weight;
nCurrentValue -= goods[t].value;
}
if (bound(t + 1) > nMaxValue)
{
currentResult[t] = false;
backTrack(t + 1);
}
}
};
//获取物品信息,此处只是将书上例子输入allGoods
void GetAllGoods(AllGoods &allGoods)
{
Goods goods;
goods.weight = 2;
goods.value = 6;
allGoods.push_back(goods);
goods.weight = 2;
goods.value = 3;
allGoods.push_back(goods);
goods.weight = 2;
goods.value = 8;
allGoods.push_back(goods);
goods.weight = 6;
goods.value = 5;
allGoods.push_back(goods);
goods.weight = 5;
goods.value = 4;
allGoods.push_back(goods);
goods.weight = 4;
goods.value = 6;
allGoods.push_back(goods);
}
int BacktrackingKnapsack0_1(AllGoods &allGoods, int nKnapSackCap)
{
Knapsack knap(allGoods,nKnapSackCap);
knap.printGoods();
knap.sortByUintValue();
cout << "sort" << endl;
knap.printGoods();
knap.backTrack(0);
knap.printResult();
return 0;
}
int main()
{
AllGoods allGoods;
GetAllGoods(allGoods); //要求按照单位物品价值由大到小排序
AllGoods::iterator it;
BacktrackingKnapsack0_1(allGoods, NKNAPSACKCAP);
return 0;
}
5.2 运行结果
注意:代码中可能会用到C++11新特性,请在支持C++11标准的环境下编译运行
(如VS2013)