回溯法(深度优先法)速解
作者:互联网
算法解题部件:
- 根据问题定义解空间,这可以类似于概率论中的样本空间S,空间中包含所有的可能结果,多以n叉树实现;
- 约束条件,对解空间中的解做出约束,以缩减搜索解空间成本,避免不必要的搜索;
- 界限条件,根据约束条件,我们可以找到问题的所有可行解,当需要求得最优解是,我们需要设置界限条件,即当满足该条件时,我们可能找到最优解。
当我们需要找到所有可行解的时候,则只需要设定约束函数即可,若要求最优解,就需要两条件同时上阵了!
下面我们以对简单的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;
}
标签:优先,double,速解,搜索,回溯,最优,cp,宝物,节点 来源: https://blog.csdn.net/wangtao990503/article/details/88590817