0-1揹包問題---回溯法

一、問題描述


             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 , X1X2X3…..Xn,Xi的值爲truefalse

     (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)



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章