回溯法
(写于20200520)
回溯法
1、基本概念与关键理解
(1)回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
(2)回溯法的基本做法是搜索:或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。
搜索是算法设计的一大核心,搜索最简单的做法就是穷举。分治法、动态规划、回溯法、分支限界法等等都可以看作对穷举的“优化”,这些算法本质上都是在考虑如何有“规律”的组织每一次“随机”。
(3)问题的解向量:回溯法希望一个问题的解能够表示成一个n元式(x1,X2,…,xm)的形式。
显约束:对分量x;的取值限定。
隐约束:为满足问题的解而对不同分量之间施加的约束。
解空间:对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该实例的一个解空间。
(4)回溯法的重点与难点在于剪枝函数的设计。
常用剪枝函数有约束函数、限界函数。用约束函数在扩展结点处剪去不满足约束的子树; 用限界函数剪去得不到最优解的子树
2、基于递归的回溯法伪代码模板
void backtrack(int t){ //t为递归深度 if(t > n) output(x); //已经搜索到叶节点 else{ for(int i=f(n,t); i<=g(n,t); i++){ //f(n,t)、g(n,t)分别表示在当前扩展结点处未搜索过的子树的起始编号和终止编号 x[t] = h(i);//h(i)表示当前扩展结点处x[t]的第i个可选值 if(constraint(t) && bound(t)) backtrack(t+1); } } }
3、子集树、排列树
子集树:当所给的问题是从n个元素的集合S中找出S满足某种性质的子树时,相应的解空间树称为子集树。
例如:0-1背包问题所对应的解空间是一颗子集树,这类子集树通常有2n个叶子结点,其结点总数为2n+1-1,遍历子集树 需要Ω(2n)
排列树:当所给问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。排列树通常有n!个叶子结点,因此遍历排列树的需要2(n!)。
例如:旅行商问题的解空间是一颗排列树。
排列树的伪代码:
void backtrack (int t){ if (t>n) output(x); else{ for (int i=t;i<=n;i++){ swap(x[t],x[i]); if(legal(t)) backtrack(t+1); swap(x[t],x[i]); } } }
4、装载问题
问题描述:有一批共n个集装箱要装上2艘载重量分别为c1和c2的轮船,其 中集装箱i的重量为,且: n个集装箱重量之和小于c1+c2.装载问题要求确定是否有一个合理的装载方案可将这n个集装 箱装上这2艘轮船。如果有,找出一种装载方案。
代码及解释如下:
#include<stdio.h> const int MAXN = 100; int n,c,cw,r,bestw,w[MAXN],x[MAXN]; /*--------- n为集装箱数量 c为第一艘船的最大容量 cw为当前已装入第一艘船的重量 r为剩余未装入第一艘船的重量 bestw为目前为止最好的装载方案其对应的重量 w[i]集装箱i的重量 x[i]集装箱i是否装入第一艘船 */ void backtrack(int i){ if(i > n){ //reach the leaves if(cw > bestw) bestw = cw; return ; } r -= w[i]; if(cw + w[i] <= c){//constraint function for searching left x[i] = 1; cw += w[i]; backtrack(i+1); cw -= w[i]; } /*-------- 关键在于理解下面这个限界函数,目的是剪去得不到最优解的子树。 这个时候我们正在处理树的第i层,即正在决定第i个集装箱是否装入 如果说上面的约束函数是对左子树(装入i)的情况剪枝,那么该函数就是对右子树剪枝。 对该函数的理解:如果我把集装箱i抛弃掉,这个时候 如果已经装入的重量+未装入的重量比目前的最优解bestw还要差, 那么说明这种情况不可能得到最优解 也就是说如果把不装入集装箱i,肯定的不到最优解,后面的情况不用考虑了 而如果当前载重量cw + 剩余集装箱的重量r>当前最优载重量 best, 那么我们就要去尝试不装入它,因为有可能得到最优解 */ if(cw + r > bestw){ //bounding function for searching right x[i] = 0; backtrack(i+1); } r += w[i]; } int main(){ scanf("%d %d",&n,&c); for(int i=1;i<=n;i++){ scanf("%d",&w[i]); r += w[i]; //r初始化为所有集装箱重量之和 } backtrack(1); printf("%d\n",bestw); for(int i=1;i<=n;i++) if(x[i] == 1) printf("%d ",i); return 0; } /*---TEST----- 5 16 2 1 8 4 2 */

浙公网安备 33010602011771号