用回溯法解决0-1背包问题

用回溯法解决0-1背包问题需要解决一下问题:

1.如何动态生成子集树

2.如何设计子集树中的结点类型

3.如何设计两个剪枝函数:约束函数和限界函数

4.如何保存一个或多个最优解,同时保存最优值

解决方法:

1.子集树通过动态的方式生成,子集树中的结点类型共用物品类型,其中结点之间的父子关系通过递归调用的方式关联,这种关系并不在类中设置变量显示表示。

2.为了方便限界函数的计算和程序中的使用,先对物品预处理,以物品的单位价值重量进行降序排序。

3.设置程序运行时一个构造最优解变量,该变量对应的最优值与当前最优解对于的最优值对比,如果优于当前最优解,则将覆盖当前最优解;如果与当前最优解对应的值相等,则同时保存两个最优解。

以下是具体的源代码:

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

typedef int Typew;
typedef int Typep;

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

//0-1背包问题的主类
class Knap{
public:
	Knap(Typew *w, Typep *p, Typew c, int n);
       Typep Knapsack();//回溯法解决0-1背包问题的主函数
	//回溯法求解01背包问题
	void BackTrack(int floor);
	//负责打印最优值和最优解,以物品编号的顺序打印结果
	void print();
private:
    //计算结点价值上界
	Typep Bound(int i);
	Typew c; //背包容量
	int n; //物品总数
	Object *Q; //在Q数组中存放的物品以单位重量价值降序排序
	Typew cw; //当前装包重量
	Typep cp; //当前装包价值
	int *cbestx; //当前最优解
	int count; //最优解的个数
	int *bestx[10]; //最优解,最优解的个数不超过10个。	
	Typep bestp; //最优值
	Typep oldbestp; //用于回溯法边界处理,保存上一次最优值
};

Knap::Knap(Typew *w, Typep *p, Typew c, int n)
{
	//初始化
	Typew W = 0;
	Typep P = 0;
	count = 0;
	this->c = c;
	oldbestp = 0;
	this->n = n;
	cw = 0;
	cp = 0;
	Q = new Object[n];
	for(int i =0; i<n; i++)
	{
		Q[i].ID = i+1;
		Q[i].d = 1.0*p[i]/w[i];
 		Q[i].w = w[i];
		Q[i].p = p[i];
		P += p[i];
		W += w[i];
	}
	//所有物品的总重量小于等于背包容量c
	if (W <= c) 
	{
		bestp = P;
		int *newbestx = new int[n];
		for(int i =0; i<n; i++)
		{
			newbestx[i] = 1;
		}
		bestx[count++] = newbestx;
		
	}
	//所有物品的总重量大于背包容量c,存在最佳装包方案
	//采用简单冒泡排序
	for(int i = 0; i<n-1; i++)
		for(int j = 0; j<n-i-1; j++)
		{
			if(Q[j].d < Q[j+1].d)
			{
				Object temp = Q[j];
				Q[j] = Q[j+1];
				Q[j+1] = temp;
			}
		}

}

Typep Knap::Knapsack()
{
	if(count > 0) //背包容量足够大,在初始化时已经将所有物品装入背包
	{
		print();
		return bestp;
	}
	else  //背包容量小于物品所有重量,存在最优装包方案
	{
		cbestx = new int[n];
		BackTrack(0); //从数组Q下标0,首结点开始回溯法求解
	}
		
}

void Knap::BackTrack(int floor)
{
	if(floor > n-1) //已经到了子集树中的叶子结点
	{
		if( cp == oldbestp ) //说明可能有多个最优解
		{
			int *newbe = new int[n];
			for (int i = 0; i < n; i++)
			{
				newbe[i] = cbestx[i];
			}
			bestx[count++] = newbe;
		}
		if( cp > oldbestp) //说明最优解需要更新同时只有一个
		{
			count = 0;
			int *newbe = new int[n];
			for (int i = 0; i < n; i++)
			{
				newbe[i] = cbestx[i];
			}
			bestx[count++] = newbe;
			oldbestp = cp;
		}
	}
	else
	{
		//选取数组Q下标为floor的物品,满足背包容量约束
		if (c >= cw + Q[floor].w)
		{
			cw += Q[floor].w;
			cp += Q[floor].p;
			if(cp >= bestp)
				bestp = cp;
			cbestx[floor] = 1;
			BackTrack(floor + 1);
			cw -= Q[floor].w;
			cp -= Q[floor].p;
		}
		//舍去数组Q下标为floor的物品,满足限界函数
		if(cp + Bound(floor + 1) >= bestp) 
		{
			cbestx[floor] = 0;
			BackTrack(floor + 1);
		}
		
	}
}

void Knap::print()
{
	Typep *original = new int[n+1];
	cout<<"以下每行为一种解法:"<<endl;
	for (int i = count-1; i >= 0; i--)
	{
		for (int j = 0; j < n; j++)
		{
			original[Q[j].ID] = bestx[i][j];
		}
		for (int k = 1; k <= n; k++)
		{
			cout<< original[k] <<" ";
		}
		cout<<endl;
	}
	cout<<"最优解的个数:"<<count<<endl;
	cout<<"最优值:"<<bestp<<endl;

}

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

int _tmain(int argc, _TCHAR* argv[])
{
	const int N = 4;
	Typew c = 7;
	Typew w[N] = {2,3,5,2};
	Typep p[N] = {6,4,8,4};
	cout<<"背包容量:"<<c <<" ,物品总数:"<< N<<endl;
	cout<<"物品重量数组:";
	for (int i = 0; i < N; i++)
	{
		cout<<w[i]<<" ";
	}
	cout<<endl;
	cout<<"物品价值数组:";
	for (int i = 0; i < N; i++)
	{
		cout<<p[i]<<" ";
	}
	cout<<endl;
	Knap K(w, p, c, N);
	K.Knapsack();
	K.print();
	system("pause");
	return 0;
}

运行结果如下图:

《用回溯法解决0-1背包问题》

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