【分支限界法】求解0/1背包问题

问题描述

0/1背包问题。假设有4个物品,其重量分别为(4, 7, 5, 3),价值分别为(40, 42, 25,
12),背包容量W=10,计算背包所装入物品的最大价值。

求解思路

首先,将给定物品按单位重量价值从大到小排序,结果如下:
【分支限界法】求解0/1背包问题_第1张图片
  应用贪心法求得近似解为(1, 0, 1, 0),获得的价值为65,这可以作为0/1背包问题的下界。
  如何求得0/1背包问题的一个合理的上界呢?考虑最好情况,背包中装入的全部是第1个物品且可以将背包装满,则可以得到一个非常简单的上界的计算方法:ub=W×(v1/w1)=10×10=100。于是,得到了目标函数的界[65, 100]。
  限界函数为:
在这里插入图片描述
目标函数的界[65, 100]
【分支限界法】求解0/1背包问题_第2张图片
  分支限界法求解0/1背包问题,其搜索空间如图所示,具体的搜索过程如下:
(1)在根结点1,没有将任何物品装入背包,因此,背包的重量和获得的价值均为0,根据限界函数计算结点1的目标函数值为10×10=100;
(2)在结点2,将物品1装入背包,因此,背包的重量为4,获得的价值为40,目标函数值为40 + (10-4)×6=76,将结点2加入待处理结点表PT中;在结点3,没有将物品1装入背包,因此,背包的重量和获得的价值仍为0,目标函数值为10×6=60,将结点3加入表PT中;
【分支限界法】求解0/1背包问题_第3张图片
(3)在表PT中选取目标函数值取得极大的结点2优先进行搜索;
(4)在结点4,将物品2装入背包,因此,背包的重量为11,不满足约束条件,将结点4丢弃;在结点5,没有将物品2装入背包,因此,背包的重量和获得的价值与结点2相同,目标函数值为40 + (10-4)×5=70,将结点5加入表PT中;
【分支限界法】求解0/1背包问题_第4张图片
(5)在表PT中选取目标函数值取得极大的结点5优先进行搜索;
(6)在结点6,将物品3装入背包,因此,背包的重量为9,获得的价值为65,目标函数值为65 + (10-9)×4=69,将结点6加入表PT中;在结点7,没有将物品3装入背包,因此,背包的重量和获得的价值与结点5相同,目标函数值为40 + (10-4)×4=64,将结点6加入表PT中;
【分支限界法】求解0/1背包问题_第5张图片
(7)在表PT中选取目标函数值取得极大的结点6优先进行搜索;
(8)在结点8,将物品4装入背包,因此,背包的重量为12,不满足约束条件,将结点8丢弃;在结点9,没有将物品4装入背包,因此,背包的重量和获得的价值与结点6相同,目标函数值为65;
(9)由于结点9是叶子结点,同时结点9的目标函数值是表PT中的极大值,所以,结点9对应的解即是问题的最优解,搜索结束。
【分支限界法】求解0/1背包问题_第6张图片
假设求解最大化问题,解向量为X=(x1, x2, …, xn),其中,xi的取值范围为某个有穷集合Si,|Si|=ri(1≤i≤n)。在使用分支限界法搜索问题的解空间树时,首先根据限界函数估算目标函数的界[down, up],然后从根结点出发,扩展根结点的r1个孩子结点,从而构成分量x1的r1种可能的取值方式。对这r1个孩子结点分别估算可能取得的目标函数值bound(x1),其含义是以该孩子结点为根的子树所可能取得的目标函数值不大于bound(x1),也就是部分解应满足:
bound(x1)≥bound(x1, x2)≥ … ≥bound(x1, x2, …, xk)≥ … ≥bound(x1, x2, …, xn)
若某孩子结点的目标函数值超出目标函数的界,则将该孩子结点丢弃;否则,将该孩子结点保存在待处理结点表PT中。从表PT中选取使目标函数取得极大值的结点作为下一次扩展的根结点,重复上述过程,当到达一个叶子结点时,就得到了一个可行解X=(x1, x2, …, xn)及其目标函数值bound(x1, x2, …, xn)。
如果bound(x1, x2, …, xn)是表PT中目标函数值最大的结点,则bound(x1, x2, …, xn)就是所求问题的最大值,(x1, x2, …, xn)就是问题的最优解;
如果bound(x1, x2, …, xn)不是表PT中目标函数值最大的结点,说明还存在某个部分解对应的结点,其上界大于bound(x1, x2, …, xn)。于是,用bound(x1, x2, …, xn)调整目标函数的下界,即令down=bound(x1, x2, …, xn),并将表PT中超出目标函数下界down的结点删除,然后选取目标函数值取得极大值的结点作为下一次扩展的根结点,继续搜索,直到某个叶子结点的目标函数值在表PT中最大。 【分支限界法】求解0/1背包问题_第7张图片

代码实现

}*/
#include
#include
#include
using namespace std;
//物品个数
int n;
//背包容量
int c;
//定义物品结构体
struct Item
{
	//物品编号
	int ItemID;
	//物品价值
	int value;
	//物品重量
	int weight;
	//质量/重量
	int ratio;
};

//定义搜索节点
struct Node
{
	Node(){
		value = 0;
		weight = 0;
		level = 0;
		parent = 0;
		bound = 0;
	}
	//搜索到该节点的价值
	int value;
	//搜索到该节点的总重量
	int weight;
	//搜索以该节点韦根的子树能达到的价值上界
	int bound;
	//层次
	int level;
	//父节点
	struct Node *parent;
};

//按照大顶堆的形式存放
struct cmp
{
	bool operator()(Node *a, Node *b){
		return a->bound < b->bound;
	}
};

//比较函数
bool Itemcmp(Item item1, Item item2);
//分支限界法
int branchAndBound(Item items[], int c);
int searchCount = 0;
int maxSize = 0;
//限界函数
float maxBound(Node *node, Item items[], int c);

int main(){
	int maxValue;
	cout << "请输入物品的个数:";
	cin >> n;
	cout << "请输入背包容量:";
	cin >> c;
	int *w = new int[n];
	int *v = new int[n];
	cout << "请输入" << n << "个物品的质量:" << endl;
	for (int i = 0; i < n; i++)
	{
		cin >> w[i];
	}
	cout << "请输入" << n << "个物品的价值:" << endl;
	for (int i = 0; i < n; i++)
	{
		cin >> v[i];
	}

	//定义物品结构体
	Item *items = new Item[n];
	//初始化结构体数组
	for (int i = 0; i < n; i++)
	{
		items[i].ItemID = i;
		items[i].value = v[i];
		items[i].weight = w[i];
		items[i].ratio = 1.0*v[i] / w[i];
	}
	//按价值率排序
	sort(items, items + n, Itemcmp);
	cout << "商品的价值依次为:";
	for (int i = 0; i < n; i++)
	{
		cout << v[i] << "  ";
	}
	cout << endl;
	cout << "商品的质量分别为:";
	for (int i = 0; i < n; i++)
	{
		cout << w[i] << " ";
	}
	cout << endl;
	cout << "选取方案为:" << endl;
	maxValue = branchAndBound(items, c);
	cout << "最大价值为:" << maxValue;
	getchar();
	getchar();
	getchar();
	delete []w;
	delete []v;
}

//比较函数
bool Itemcmp(Item item1, Item item2){
	return item1.ratio > item2.ratio;
}

//分支限界函数
int branchAndBound(Item items[], int c){
	int *x = new int[n];
	for (int i = 0; i < n; i++)
	{
		x[i] = 0;
	}
	//保存最大价值
	int maxValue;
	//保存当前最大价值节点
	Node *maxNode = new Node();
	//最大价值优先队列
	priority_queue<Node *, vector<Node *>, cmp> maxQueue;
	Node *firstNode, *curNode;
	
	searchCount = 1;
	firstNode = new Node();
	firstNode->bound = maxBound(firstNode, items, c);
	firstNode->parent = NULL;
	maxQueue.push(firstNode);
	maxValue = 0;
	maxNode = firstNode;
	while (!maxQueue.empty()){
		curNode = maxQueue.top();
		maxQueue.pop();
		//扩展左孩子
		if (curNode->weight + items[curNode->level].weight <= c){
			Node *leftNode = new Node();
			leftNode->value = curNode->value + items[curNode->level].value;
			leftNode->weight = curNode->weight + items[curNode->level].weight;
			leftNode->level = curNode->level + 1;
			leftNode->parent = curNode;
			leftNode->bound = curNode->bound;
			if (leftNode->level<n)
			{
				maxQueue.push(leftNode);
				searchCount++;
			}
			if (maxValue<leftNode->value)
			{
				maxValue = leftNode->value;
				maxNode = leftNode;
			}
		}

		//扩展右孩子节点
		if (maxBound(curNode, items, c)>maxValue){
			Node *rightNode = new Node();
			rightNode->value = curNode->value;
			rightNode->weight = curNode->weight;
			rightNode->level = curNode->level + 1;
			rightNode->parent = curNode;
			rightNode->bound = maxBound(rightNode, items, c);
			if (rightNode->level<n)
			{
				maxQueue.push(rightNode);
				searchCount++;
			}
		}
		if (maxQueue.size()>maxSize)
		{
			maxSize = maxQueue.size();
		}
	}
	curNode = maxNode;
	while (curNode)
	{
		int tempValue = curNode->value;
		curNode = curNode->parent;
		if (curNode&&curNode->value != tempValue){
			x[items[curNode->level].ItemID] = 1;
		}
	}
	for (int i = 0; i < n; i++)
	{
		cout << x[i] << "  ";
	}
	cout << endl;
	return maxValue;
}

//限界函数
float maxBound(Node *node, Item items[], int c){
	float maxValue;
	//背包剩余容量
	int restCapacity;
	int i;

	maxValue = node->value;
	restCapacity = c - node->weight;
	i = node->level;

	while (i<n&&restCapacity>items[i].weight)
	{
		maxValue += items[i].value;
		restCapacity -= items[i].weight;
		i++;
	}
	if (restCapacity!=0)
	{
		maxValue += restCapacity*items[i].ratio;
	}
	return maxValue;
}

运行结果

【分支限界法】求解0/1背包问题_第8张图片

你可能感兴趣的:(算法设计与分析,C语言,C++程序设计)