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 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 +) –2个节点,需O(2^n )个空间存储节点,则算法空间复杂性为O(2^n )。

   4.2 时间复杂性:限界函数时间复杂度为O(n),而最坏情况有2^(n +) – 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 运行结果


 《0-1背包问题---分支限界法》


   注意:代码中可能会用到C++11新特性,请在支持C++11标准的环境下编译运行

         (如VS2013)


 

    原文作者:分支限界法
    原文地址: https://blog.csdn.net/u013507368/article/details/40861981
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞