回溯法(深度优先法)速解

算法解题部件:

  1. 根据问题定义解空间,这可以类似于概率论中的样本空间S,空间中包含所有的可能结果,多以n叉树实现;
  2. 约束条件,对解空间中的解做出约束,以缩减搜索解空间成本,避免不必要的搜索;
  3. 界限条件,根据约束条件,我们可以找到问题的所有可行解,当需要求得最优解是,我们需要设置界限条件,即当满足该条件时,我们可能找到最优解。

      当我们需要找到所有可行解的时候,则只需要设定约束函数即可,若要求最优解,就需要两条件同时上阵了!

下面我们以对简单的0-1背包问题为例,学习回溯法:

     题目:海盗先生驾着海盗船发现了一处宝藏之地,每件宝物i对应价值为vi,重量为wi,但是海盗们不太高兴,因为他们的船只能容纳重W的宝物了,而且宝物要是打碎了,就不值钱了,请你设计一个算法,帮助海盗计算如何装载,可以获得最大价值?

问题分析&算法设计

很明显,在n个宝物中选择几个宝物带走,即从集合S中选择一个子集,要求子集的总重量不超过W,而且价值最大

定义解空间:                                                                                                                                                                                                            针对于S中的每一个宝物,只会存在“拿”(1)与“不拿”(0)的问题,由此,我们可以得出解空间为{x1,x2,x3.....xn},其中xi=0/1;不难看出,解空间中一共有2^n种可能解,我们可以将解空间定义为深度为n的二叉树)(取左为1,取右为0)。

约束条件:                                                                                                                                                                                                             判断装上船的宝物总重量是否超出船容量,如果超出,就不在向下衍生。

界限条件:                                                                                                                                                                                                           在解空间中,对于任意一个中间节点来说,从根节点到此节点所代表的宝物状态(是否装入)已经确定,而此节点往后的宝物是不确定的,我们用cp表示已经确定的宝物总价值,用rp表示未确定的宝物的总价值,不难看出cp+rp即为经过此节点的可行解的最大价值!                                                                                                                                                                              如果最大价值小于等于当前记录的最优解,说明从此节点继续向下搜索不可能得到更优解,无须继续搜索;反之则应继续向下搜索。

  • 搜索过程中,沿着拓展节点的左分支拓展,表示装入宝物。由此可知,我们在拓展时应该判断约束条件是否成立,成立则左,不成立则右。
  • 在每个节点判断界限条件,若不成立,则该节点为死结点,无须向下搜索。

代码示例:

#include <iostream>
#include <string>
#include <algorithm>
#define M 105
using namespace std;

int i, j, n, w;  //n表示n个物品,w表示船容量
double W[M], V[M];
bool x[M];     
double cw, cp, bestp;  //cw,cp为当前船重量与价值,bestp为当前为止最优的价值记录
bool bestx[M];  //当前最优问题解

//计算已确定的物品和剩余的物品总价值
double Bound(int i)
{
	int rp = 0;
	while (i <= n)
	{
		rp += V[i];
		i++;
	}
	return cp+rp;
}

//用于
void Backtrack(int t)
{
	if (t > n)//到达树末的叶子
	{
		for (j = 1; j <= n; j++)
		{
			bestx[j] = x[j];
			//保存当前最优解,为什么不用判断就直接保存呢?因为有Bound的关系,能走到叶子,必然比之前的最优解更优
		}
		bestp = cp;
		return;
	}
	if (cw + W[t] <= w)
	{
		x[t] = 1;
		cw += W[t];
		cp += V[t];
		Backtrack(t + 1);
		cw -= W[t];
		cp -= V[t];
	}
	if (Bound(t+1) > bestp)   //判断是否搜索右子树
	{
		x[t] = 0;
		Backtrack(t + 1);
	}
}

void Knapsack(double w, int n)
{
	//初始化
	cw = 0; cp = 0;
	bestp = 0;
	double sumw = 0.0;
	double sumv = 0.0;  //用于记录所有物品的总重量和总价值
	for (i = 1; i <= n; i++)
	{
		sumw += W[i];
		sumv += V[i];
	}
	if (sumw <= w)
	{
		bestp = sumv;
		cout << "所有物品均放入背包,最大价值为: " << bestp << endl;
		return;
	}

	//开始搜索
	Backtrack(1);
	cout << "放入船的最大价值为: " << bestp << endl;
	cout << "放入的物品的序号为: ";
	for (i = 0; i <= n; i++)
	{
		if (bestx[i] == 1)
		{
			cout << i << " ";
		}
	}
	cout << endl;
}

int main()
{
	cout << "请输入物品的个数n: ";
	cin >> n;
	cout << "请输入船的容量w: ";
	cin >> w;
	cout << "请依次输入每个物品的重量和价值,以空格分开: ";
	for (i = 1; i <= n; i++)
	{
		cin >> W[i] >> V[i];
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/wangtao990503/article/details/88590817