用回溯法解决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;
}
运行结果如下图:


  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在Matlab中使用回溯法解决0-1背包问题的步骤如下: 1. 定义问题的输入参数:物品总数n,每个物品的体积w[i]和价v[i],以及背包的总容量W。 2. 创建一个递归函数,该函数用于搜索所有可能的解决方案。函数的输入参数包括当前考虑的物品索引t,当前已放入背包的物品总体积currentWeight,当前已放入背包的物品总价currentValue,以及一个布尔数组x,用于记录哪些物品已经放入背包中。 3. 在递归函数中,首先判断是否已经考虑完所有物品(即t是否等于n)。如果是,则返回当前已放入背包的物品总价currentValue。 4. 如果还有物品未考虑,则分别考虑两种情况:将第t个物品放入背包或不放入背包。 5. 如果将第t个物品放入背包,需要判断是否超出背包的总容量。如果超出,则直接返回当前已放入背包的物品总价currentValue。 6. 如果没有超出背包的总容量,则更新当前已放入背包的物品总体积currentWeight和总价currentValue,并将x[t]设置为1,表示第t个物品已放入背包。 7. 继续递归调用函数,考虑下一个物品(即t+1),将递归调用的结果加到当前已放入背包的物品总价currentValue中。 8. 将x[t]重新设置为0,表示第t个物品不放入背包。 9. 继续递归调用函数,考虑下一个物品(即t+1),并将递归调用的结果加到当前已放入背包的物品总价currentValue中。 10. 返回两种情况中的较大作为当前已放入背包的物品总价。 11. 在主程序中调用递归函数,并传入初始参数。 以下是一个使用回溯法解决0-1背包问题的Matlab代码示例: ```matlab function max_value = backtrack_knapsack(n, w, v, W) x = zeros(1, n); % 初始化布尔数组x max_value = backtrack(1, 0, 0, x); % 调用递归函数 function value = backtrack(t, currentWeight, currentValue, x) if t > n value = currentValue; else % 将第t个物品放入背包 if currentWeight + w(t) <= W x(t) = 1; value1 = backtrack(t+1, currentWeight + w(t), currentValue + v(t), x); else value1 = -inf; % 超出背包容量,设置为负无穷 end % 不将第t个物品放入背包 x(t) = 0; value2 = backtrack(t+1, currentWeight, currentValue, x); % 返回两种情况中的较大 value = max(value1, value2); end end end % 示例用法 n = 4; % 物品总数 w = [2, 3, 4, 5]; % 物品体积 v = [3, 4, 5, 6]; % 物品价 W = 8; % 背包总容量 max_value = backtrack_knapsack(n, w, v, W); disp(max_value); % 输出最大价 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值