一、問題描述
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 knapsack_0_1_branchAndBound()
{
list<Node*> activeNodes;
list<Node*> diedNodes;
sortByUintValue()
//判斷第一個物品是否可放入揹包
if (goods[0].weight < capacity)
{
activeNodes.push_back(new Node(nullptr, true, goods[0].weight, goods[0].value,0));
}
activeNodes.push_back(new Node(nullptr, false, 0,0,0));
Node *curNode = nullptr;
Node *preNode = nullptr;
int curId;
while (!activeNodes.empty())
{
//取出隊列中最靠前的節點
curNode = activeNodes.front();
activeNodes.pop_front();
diedNodes.push_back(curNode);//放入死節點隊列
if (curNode->id + 1 == nGoodsNum)//如果當前出隊節點爲最低層節點
{
continue;
}
if (nMaxValue < curNode->nValue)//若此節點物品價值大於揹包中最大物品價值
{//將揹包中物品替換爲當前節點所表示物品
nMaxValue = curNode->nValue;
preNode = curNode;
while (nullptr != preNode)
{
bestResult[preNode->id] = preNode->leftChild;
preNode = preNode->parent;
}
}
nCurrentValue = curNode->nValue;
nCurrentWeight = curNode->nWeight;
curId = curNode->id;
if (nMaxValue >= bound(curId + 1))//剪枝
{
continue;
}
//判斷下個物品是否可加入隊列
if (nCurrentWeight + goods[curId + 1].weight <= capacity)
{
activeNodes.push_back(new Node(curNode, true, nCurrentWeight + goods[curId + 1].weight, nCurrentValue + goods[curId + 1].value, curId + 1));
}
activeNodes.push_back(new Node(curNode, false, nCurrentWeight,nCurrentValue,curId + 1));
}
while (!diedNodes.empty())
{
curNode = diedNodes.front();
delete curNode;
diedNodes.pop_front();
}
}
四、算法複雜性分析
4.1 空間複雜性:限界函數爲O(1),最壞情況下需搜索2^(n +1) –2個節點,需O(2^n )個空間存儲節點,則算法空間複雜性爲O(2^n )。
4.2 時間複雜性:限界函數時間複雜度爲O(n),而最壞情況有2^(n +1) – 2個節點,若對每個節點用限界函數判斷,則其時間複雜度爲O(n2^n).而算法中時間複雜度主要依賴限界函數,則算法的時間複雜度爲O(n2^n)。
五、算法實現與測試
5.1 代碼實現
#include <iostream>
#include <vector>
#include <stack>
#include <algorithm>
#include <list>
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 Node
{
public:
Node *parent;
int nWeight;
int nValue;
int id;
bool leftChild;
Node(Node *_parent, bool _left, int weight, int value, int _id) :parent(_parent), leftChild(_left), nWeight(weight), nValue(value), id(_id)
{}
~Node()
{
}
};
class Knapsack
{
private:
int capacity;//揹包容量
int nGoodsNum;//物品數
vector<Goods> goods;
int nMaxValue;
int nCurrentWeight;
int nCurrentValue;
vector<bool> bestResult;
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);
}
}
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 knapsack_0_1_branchAndBound()
{
list<Node*> activeNodes;
list<Node*> diedNodes;
sortByUintValue();
//判斷第一個物品是否可放入揹包
if (goods[0].weight < capacity)
{
activeNodes.push_back(new Node(nullptr, true, goods[0].weight, goods[0].value,0));
}
activeNodes.push_back(new Node(nullptr, false, 0,0,0));
Node *curNode = nullptr;
Node *preNode = nullptr;
int curId;
while (!activeNodes.empty())
{
//取出隊列中最靠前的節點
curNode = activeNodes.front();
activeNodes.pop_front();
diedNodes.push_back(curNode);//放入死節點隊列
if (curNode->id + 1 == nGoodsNum)//如果當前出隊節點爲最低層節點
{
continue;
}
if (nMaxValue < curNode->nValue)//若此節點物品價值大於揹包中最大物品價值
{//將揹包中物品替換爲當前節點所表示物品
nMaxValue = curNode->nValue;
preNode = curNode;
while (nullptr != preNode)
{
bestResult[preNode->id] = preNode->leftChild;
preNode = preNode->parent;
}
}
nCurrentValue = curNode->nValue;
nCurrentWeight = curNode->nWeight;
curId = curNode->id;
if (nMaxValue >= bound(curId + 1))//剪枝
{
continue;
}
//判斷下個物品是否可加入隊列
if (nCurrentWeight + goods[curId + 1].weight <= capacity)
{
activeNodes.push_back(new Node(curNode, true, nCurrentWeight + goods[curId + 1].weight, nCurrentValue + goods[curId + 1].value, curId + 1));
}
activeNodes.push_back(new Node(curNode, false, nCurrentWeight,nCurrentValue,curId + 1));
}
while (!diedNodes.empty())
{
curNode = diedNodes.front();
delete curNode;
diedNodes.pop_front();
}
}
};
//獲取物品信息,此處只是將書上例子輸入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 = 4;
goods.value = 6;
allGoods.push_back(goods);
goods.weight = 5;
goods.value = 4;
allGoods.push_back(goods);
}
int main()
{
AllGoods allGoods;
GetAllGoods(allGoods); //要求按照單位物品價值由大到小排序
Knapsack knap(allGoods,NKNAPSACKCAP);
knap.printGoods();
knap.knapsack_0_1_branchAndBound();
knap.printResult();
return 0;
}
5.2 運行結果
注意:代碼中可能會用到C++11新特性,請在支持C++11標準的環境下編譯運行
(如VS2013)