用优先队列式分支限界法解决0-1背包问题

用优先队列式分支限界法解决0-1背包问题的算法思想:

1.分支限界法常以广度优先或最小耗费优先(最大效益优先)方式搜索问题的解空间树, 对于0-1背包问题的解空间树是一个颗子集树。

2.在分支限界法中有一个活结点表,活结点表中的每个活结点只有一次机会成为扩展结点,一旦成为  扩展结点就一次性产生所有儿子结点,在这些儿子结点中,导致不可行解或导致非最优解的儿子 结点被舍弃,其余儿子结点被加入到活结点表中。对于0-1背包问题中的每个活结点只有两个儿子 结点,分别表示对物品i的选取和对物品i的舍去;在判断儿子结点是否能加入到活结点表中,有两个 函数需要满足,第一个称为约束函数,判断能否满足背包容量约束,第二个称为限界函数,判断 是否可能有最优解。

3.为了尽快找到0-1背包问题的解,每次选取下一个活结点成为扩展结点的判断依据是当前情况下 最有可能找到最优解的下一个结点。因此,每次选择扩展结点的方法:当前情况下,在活结点表中 选择活结点的上界uprofit(通过限界函数Bound求出)最大的活结点成为当前的扩展结点。 这一过程一直持续到找到所需的解或活结点表为空时为止。这个过程体现出分支限界法以“最大 效益优先”方式进行。

4.为了在活结点表中选择拥有最大的上界uprofit的活结点,在活结点表上实现优先队列。

5.通过上述第3点,可以求出0-1背包问题的最优值。为了求出0-1背包问题的最优解,对于每一个在 活结点表中的活结点创建一个树结点,树节点需要反映该结点的父节点和是否有左孩子(有左孩子 表示物品i选取了,没有左孩子表示物品i舍去了)。因此,可以构造一颗子集树,最优解就是从树根 到叶子结点的路径,子集树的第i层的所有结点就是在不同情况下对物品i的取舍结点。构造最优解的 顺序是从叶子结点到根结点的过程。

从上述算法思想中,得出必须解决的问题:

1.优先队列式的活结点表

2.活结点表对应的子集树

算法涉及的函数功能:

1.建立一个最大堆、初始化最大堆、在最大堆中插入一个元素和在最大堆中取出最大元素

2.求解0-1背包问题的主函数Knapsack

3.向子集树和最大堆中插入结点函数AddLiveNode

4.计算结点价值上界函数Bound,为了方便,需要对物品以单位价值量排序

5.负责求解0-1背包问题的最优值和最优解函数MaxKnapsack

算法涉及的类:

1.树结点类,用于构造子集树,以便计算最优解

2.堆结点类,用于定义堆元素类型,便于MaxKnapsack函数使用

3.最大堆类,用于实现优先队列

4.物品类,用于保存物品编号和物品的单位重量价值

5.解决0-1背包问题的主类

以下是具体的代码:

#include "stdafx.h"
#include <iostream>
using namespace std;   

typedef int Typew;
typedef int Typep;

//物品类
class Object{
	friend Typep Knapsack(Typew *, Typep *, Typew, int, int *);
public:
	int operator <= (Object a) const{
		return (d >= a.d);
	}
private:
	int ID; //物品编号
	float d; //单位重量价值
};

//树结点类
class bbnode{
	friend class Knap;
	friend Typep Knapsack(Typew *, Typep *, Typew, int, int *);
private:
	bbnode *parent; //指向父节点的指针
	int LChild; //如果是左儿子结点取1,也即说明该物品已装进背包
};

//堆结点类
class HeapNode{
	friend class Knap;
	friend class MaxHeap;
public:
	operator Typep()const{return uprofit;};
private:
	Typep uprofit, //结点的价值上界
		  profit; //结点所相应的价值
	Typew weight; //结点所相应的重量
	int level; //活结点在子集树中所处的层序号
	bbnode *elemPtr; //指向该活结点在子集树中相应结点的指针
};

//最大堆类
class MaxHeap{
public:
	MaxHeap(int maxElem)
	{
		HeapElem = new HeapNode* [maxElem+1]; //下标为0的保留
		capacity = maxElem;
		size = 0;
	}
	void InsertMax(HeapNode *newNode);
	HeapNode DeleteMax(HeapNode* &N);

private:
	int capacity;
    int size;
    HeapNode **HeapElem; 

};

//0-1背包问题的主类
class Knap{
	//Knapsack主函数功能:解决初始化、求解最优值和最优解、回收内存
	friend Typep Knapsack(Typew *, Typep *, Typew, int, int *);
public:
	Typep MaxKnapsack();
private:
	MaxHeap *H;
	//Bound辅助Maxknapsack函数:计算结点价值上界
	Typep Bound(int i);
	//AddLiveNode辅助Maxknapsack函数:将活结点插入子集树和优先队列中
	void AddLiveNode(Typep up, Typep cp, Typew cw, int ch, int level);
	bbnode *E; //指向扩展结点的指针
	Typew c; //背包容量
	int n; //物品总数
	Typew *w; //物品重量数组(以单位重量价值降序)
	Typep *p; //物品价值数组(以单位重量价值降序)
	Typew cw; //当前装包重量
	Typep cp; //当前装包价值
	int *bestx; //最优解
};

void MaxHeap::InsertMax(HeapNode *newNode)
{
	//极端情况下暂未考虑,比如堆容量已满等等
	int i = 1;
		for (i = ++size; i/2 > 0 && HeapElem[i/2]->uprofit < newNode->uprofit; i /= 2)
		{
			HeapElem[i] = HeapElem[i/2];
		}
		HeapElem[i] = newNode;
}

HeapNode MaxHeap::DeleteMax(HeapNode *&N)
{
	//极端情况下暂未考虑
		if(size >0 )
		{
			N = HeapElem[1];
			//从堆顶开始调整
			int i = 1;
			while(i < size)
			{
				if(((i*2 +1) <= size) && HeapElem[i*2]->uprofit > HeapElem[i*2 +1]->uprofit)
				{
					HeapElem[i] = HeapElem[i*2];
					i = i*2;
				}
				else
				{
					if(i*2 <= size)
					{
							HeapElem[i] = HeapElem[i*2];
							i = i*2;
					}
					else
						break;
				}
			}
			if(i < size)
				HeapElem[i] = HeapElem[size];
		}
		size--;
		return *N;
}

Typep Knap::MaxKnapsack()
{
	H = new MaxHeap(1000);
	bestx = new int [n+1];
	//初始化,为处理子集树中的第一层做准备,物品i处于子集树中的第i层
	int i = 1; //生成子集树中的第一层的结点
	E = 0; //将首个扩展点设置为null,也就是物品1的父节点
	cw = 0;
	cp = 0;
	Typep bestp = 0; //当前最优值
	Typep up = Bound(1); // 选取物品1之后的价值上界
	//当选择左儿子结点时,上界约束up不用关心,重量约束wt需要考虑。因为上界约束跟父节点相同。
	//当选择右儿子结点时,上界约束up需要考虑,重量约束不需要考虑。因为父节点和该结点重量相同。
	while (i != n+1)
	{
		//检查当前扩展结点的左儿子结点
		Typew wt = cw + w[i]; //当前选择物品i之后的总重量wt
		if(wt <= c) //背包能将物品i装下,也即当前扩展结点的左儿子结点可行
		{
			if(cp + p[i] > bestp)
				bestp = cp + p[i];
			AddLiveNode(up, cp + p[i], cw + w[i], 1, i);
		}
		//检查当前扩展结点的右儿子结点
		up = Bound(i + 1); //未选择物品i之后的价值上界
		if(up >= bestp)
			AddLiveNode(up, cp, cw, 0, i);
		//从优先队列中选择价值上界最大的结点成为扩展结点
		HeapNode* N;
		H->DeleteMax(N);
		E = N->elemPtr;
		cw = N->weight;
		cp = N->profit;
		up = N->uprofit;
		i = N->level + 1; //准备生成N.level+1层的子集树结点
	}
	//从子集树中的某叶子结点开始构造当前最优解
	for (int i = n; i > 0; i--)
	{
		bestx[i] = E->LChild;
		E = E->parent;
	}
	return cp;

}

Typep Knap::Bound(int i)
{
	Typew cleft = c - cw;
	Typep b = cp;
	while (i<=n && w[i] <= cleft)
	{
		cleft -= w[i];
		b += p[i];
		i++;
	}
	if(i<=n) b += p[i]/w[i] * cleft;
	return b;
}

void Knap::AddLiveNode(Typep up, Typep cp, Typew cw, int ch, int level)
{
	bbnode *b=new bbnode;
    b->parent=E;
    b->LChild=ch;
	HeapNode *N = new HeapNode;
    N->uprofit=up;
    N->profit=cp;
    N->weight=cw;
    N->level=level;
	N->elemPtr=b;
	H->InsertMax(N);
}

//Knapsack返回最大价值,最优值保存在bestx
Typep Knapsack(Typew *w, Typep *p, Typew c, int n, int *bestx)
{//数组w、p和bestx中下标为0的元素保留不用
	//初始化
	Typew W = 0;
	Typep P = 0;
	Object *Q = new Object[n];
	for(int i =1; i<=n; i++)
	{
		Q[i-1].ID = i;
		Q[i-1].d = 1.0*p[i]/w[i];
		P += p[i];
		W += w[i];
	}
	//所有物品的总重量小于等于背包容量c
	if (W <= c)
	{
		for(int i =1; i<=n; i++)
		{
			bestx[i] = p[i];
		}
		return P;
	}
	//所有物品的总重量大于背包容量c,存在最佳装包方案
	//sort(Q,n);对物品以单位重量价值降序排序
	//采用简单冒泡排序
	for(int i = 1; i<n; i++)
		for(int j = 1; j<= n-i; j++)
		{
			if(Q[j-1].d < Q[j].d)
			{
				Object temp = Q[j-1];
				Q[j-1] = Q[j];
				Q[j] = temp;
			}
		}

	Knap K;
	K.p = new Typep [n+1];
	K.w = new Typew [n+1];
	for(int i = 1; i<=n; i++)
	{
		K.p[i] = p[Q[i-1].ID];//(以单位重量价值降序排序)
		K.w[i] = w[Q[i-1].ID];//(以单位重量价值降序排序)
	}
	K.cp = 0;
	K.cw = 0;
	K.c = c;
	K.n = n;
	Typep bestp = K.MaxKnapsack();
	for(int i = 1; i<=n; i++)
	{
		bestx[Q[i-1].ID] = K.bestx[i];
	}
	delete [] Q;
	delete [] K.w;
	delete [] K.p;
	delete [] K.bestx;
	delete [] K.H;
	return bestp;
}

int _tmain(int argc, _TCHAR* argv[])
{
	const int N = 4;
        Typew c=8; //背包容量
	int bestx[N+1]; //最优解
	int bestp; //最优值
	//需要说明的是,这里的数组p[]和w[]的第一个元素是-1,这是因为我在操作过程中
	//都是从数组元素的1开始的,而我们知道数组中第一个元素是0号元素,所以我这里用-1填上
	Typep p[]={-1,6,4,7,4};//物品价值
	Typew w[]={-1,2,3,5,2};//物品重量
	bestp = Knapsack(w, p, c, N, bestx);
	cout<<"物品总数N = "<< N<<",背包容量C = "<< c<<endl;
	for (int i = 1; i <= N; i++)
	{
		if(i ==1 ) cout<<"重量数组:";
		cout<<w[i];
		if(i != N) cout<<",";
		else
			cout<<endl;
	}
	for (int i = 1; i <= N; i++)
	{
		if(i ==1 ) cout<<"价值数组:";
		cout<<p[i];
		if(i != N) cout<<",";
		else
			cout<<endl;
	}
	for (int i = 1; i <= N; i++)
	{
		if(i ==1 ) cout<<"是否选取:";
		cout<<bestx[i];
		if(i != N) cout<<",";
		else
			cout<<endl;
	}
	cout<<"背包最优价值:"<<bestp<<endl;
	system("pause");
	return 0;
}

运行结果如下图:

时间: 2024-08-30 02:12:19

用优先队列式分支限界法解决0-1背包问题的相关文章

优先队列式分支限界法-最小重量机器设计问题

问题描述: 设某一机器由n个部件组成,每一种部件都可以从m个不同的供应商处购得.设是从供应商j处购得的部件i的重量,是相应的价格.试设计一个优先队列式分支限界法,给出总价格不超过d的最小重量机器设计. [之所以想记录这个问题,是因为我觉得自己"用各个部件的最小重量作为未来最理想重量"的这个设计还挺特别.其他都是实验报告中的内容] 算法描述: 算法实现: #include<stdio.h> #include<stdlib.h> struct NodeType {

动态规划算法实现部分——0/1背包问题

代码: import java.util.*; import java.util.Scanner; /* *动态规划思想解决0/1背包问题 */ public class Main{ public static void main(String[] args){ Scanner in=new Scanner(System.in); System.out.println("输入背包的容量"); int bagCap=in.nextInt(); //背包的容量 System.out.pri

0/1背包问题的动态规划法求解 —— Java 实现

0/1背包问题的动态规划法求解,前人之述备矣,这里所做的工作,不过是自己根据理解实现了一遍,主要目的还是锻炼思维和编程能力,同时,也是为了增进对动态规划法机制的理解和掌握. 值得提及的一个问题是,在用 JAVA 实现时, 是按算法模型建模,还是用对象模型建模呢? 如果用算法模型,那么 背包的值.重量就直接存入二个数组里:如果用对象模型,则要对背包以及背包问题进行对象建模.思来想去,还是采用了对象模型,尽管心里感觉算法模型似乎更好一些.有时确实就是这样,对象模型虽然现在很主流,但也不是万能的,采用

Android应用插件式开发解决方法[转]

一.现实需求描述 一般的,一个Android应用在开发到了一定阶段以后,功能模块将会越来越多,APK安装包也越来越大,用户在使用过程中也没有办法选择性的加载自己需要的功能模块.此时可能就需要考虑如何分拆整个应用了. 二.解决方案提出 一般有两种方式,一种是将应用按照功能分拆成多个应用,用户需要哪个就下载哪个,都需要就都下载.应用之间,可以在代码层面做一定的关联,以共享部分信息.另一种方式,类似于其他平台插件的方式,用户可以在主应用中可以选择性的下载需要的插件,不需要该功能,则不需要下载. 第一种

Android应用插件式开发解决方法

Android应用插件式开发解决方法 一.现实需求描述 一般的,一个Android应用在开发到了一定阶段以后,功能模块将会越来越多,APK安装包也越来越大,用户在使用过程中也没有办法选择性的加载自己需要的功能模块.此时可能就需要考虑如何分拆整个应用了. 二.解决方案提出 一般有两种方式,一种是将应用按照功能分拆成多个应用,用户需要哪个就下载哪个,都需要就都下载.应用之间,可以在代码层面做一定的关联,以共享部分信息.另一种方式,类似于其他平台插件的方式,用户可以在主应用中可以选择性的下载需要的插件

背包问题:0/1背包问题 普通背包问题(贪心算法只适用于普通背包问题)

//sj和vj分别为第j项物品的体积和价值,W是总体积限制. //V[i,j]表示从前i项{u1,u2,…,un}中取出来的装入体积为j的背包的物品的最大价值. 第一种:0/1背包问题 最大化 ,受限于  1)若i=0或j=0,  V[i,j] = 0 2)若j<si, V[i,j] = V[i-1,j] 3)若i>0且j>=si, V[i,j] = Max{V[i-1,j],V[i-1,j-si]+vi} 第二种:背包问题:在选择物品i装入背包时,可以选择物品i的一部分,而不一定要全部

0/1背包问题(回溯法)

回溯法是一个既带有系统性又带有跳跃性的搜索算法.它在包含问题的所有解的解空间树中,按深度优先策略,从根结点出发搜索解空间树.算法搜索至解空间树的任意一结点时,先判断该结点是否包含问题的解.如果肯定不包含,则跳过对该结点为根的子树搜索,逐层向其祖先结点回溯:否则 ,进入该子树,继续按深度优先策略搜索. 问题的解空间 用回溯法解问题时,应明确定义问题的解空间.问题的解空间至少包含问题的一个(最优)解.对于 n=3 时的 0/1 背包问题,可用一棵完全二叉树表示解空间,如图所示: 求解步骤 1)针对所

动态规划算法求解0,1背包问题

首先我们来看看动态规划的四个步骤: 1. 找出最优解的性质,并且刻画其结构特性: 2. 递归的定义最优解: 3. 以自底向上的方式刻画最优值: 4. 根据计算最优值时候得到的信息,构造最优解 其中改进的动态规划算法:备忘录法,是以自顶向下的方式刻画最优值,对于动态规划方法和备忘录方法,两者的使用情况如下: 一般来讲,当一个问题的所有子问题都至少要解一次时,使用动态规划算法比使用备忘录方法好.此时,动态规划算法没有任何多余的计算.同时,对于许多问题,常常可以利用其规则的表格存取方式,减少动态规划算

第十六章 贪心算法——0/1背包问题

1.问题描述: 给定n种物品和一背包.物品i的重量是wi,其价值为vi,背包的容量为C.问:应如何选择装入背包的物品,使得装入背包中物品的总价值最大? 形式化描述:给定c >0, wi >0, vi >0 , 1≤i≤n.要求找一n元向量(x1,x2,…,xn,), xi∈{0,1}, ∋ ∑ wi xi≤c,且∑ vi xi达最大.即一个特殊的整数规划问题. 2.最优性原理: 设(y1,y2,…,yn)是 (3.4.1)的一个最优解.则(y2,…,yn)是下面相应子问题的一个最优解: