1、第5章 回溯法,有许多问题,当需要找出它的解集或者要求回答什么 解是满足某些约束条件的最佳解时,往往要使用回溯法。回溯法的基本做法是搜索,或是一种组织得井井有条 的,能避免不必要搜索的穷举式搜索法。这种方法适用于 解一些组合数相当大的问题。回溯法在问题的解空间树中,按深度优先策略,从根 结点出发搜索解空间树。算法搜索至解空间树的任意一点 时,先判断该结点是否包含问题的解。如果肯定不包含, 则跳过对该结点为根的子树的搜索,逐层向其祖先结点回 溯;否则,进入该子树,继续按深度优先策略搜索。,本章主要知识点: 5.1 回朔法的算法框架 5.2 装载问题 5.3 批处理作业调度 5.4 符号三角形问题
2、 5.5 n后问题 5.6 0-1背包问题 5.7 最大团问题 5.8 图的m着色问题 5.9 旅行售货员问题 5.10 圆排列问题 5.11 电路板排列问题 5.12 连续邮资问题 5.13 回朔法效率分析,5.1 回朔法的算法框架,5.1.1 问题的解空间,问题的解向量:回溯法希望一个问题的解能够表示成一个n元式(x1,x2,xn)的形式。 显约束:对分量xi的取值限定。 隐约束:为满足问题的解而对不同分量之间施加的约束。解空间:对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该实例的一个解空间。,注意:同一个问题可以有多种表示,有些表示方法更 简单,所需表示的状态空间更小(
3、存储量少,搜索方法简 单)。,对于n=3的0-1背包问题,其解空间是一个深度为4的 满二叉数。,当w=16,15,15,p=45,25,25,c=30时,0-1 背包问题的解向量是:(0,1,1),5.1.2 回朔法的基本思想,扩展结点: 一个正在产生儿 子的结点称为扩展结点。,活结点: 一个自身已生成但其 儿子还没有全部生成的节点称 做活结点。,死结点:一个所有儿子已经产生的结点称做死结点。,深度优先的问题状态生成法: 如果对一个扩展结点R,一旦 产生了它的一个儿子C,就把 C当做新的扩展结点。在完成 对子树C(以C为根的子树) 的穷尽搜索之后,将R重新变 成扩展结点,继续生成R的下 一个儿
4、子(如果存在)。,宽度优先的问题状态生成法:在一个扩展结点变成死结点 之前,它一直是扩展结点。,回溯法:为了避免生成那些 不可能产生最佳解的问题状 态,要不断地利用限界函数 (bounding function)来处死 那些实际上不可能产生所需 解的活结点,以减少问题的 计算量。具有限界函数的深 度优先生成法称为回溯法。,(1) 针对所给问题,定义问题的解空间; (2) 确定易于搜索的解空间结构; (3) 以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。,常用剪枝函数:用约束函数在扩展结点处剪去不满足约束的子树;用限界函数剪去得不到最优解的子树。,用回溯法解题的一个显著特征是在
5、搜索过程中动态产 生问题的解空间。在任何时刻,算法只保存从根结点到当 前扩展结点的路径。如果解空间树中从根结点到叶结点的 最长路径的长度为h(n),则回溯法所需的计算空间通常为 O(h(n)。而显式地存储整个解空间则需要O(2h(n)或 O(h(n)!)内存空间。,例如: 对于n=3的0-1背包问题,当w=16,15,15, p=45,25,25,c=30时,该0-1背包问题的最优解向量是 x=(0,1,1)。其解空间是一个深度为4的满二叉数。,各叶子所代表的解向量是,结点H:x=(1,1,1),结点I: x=(1,1,0),结点J: x=(1,0,1),结点K:x=(1,0,0),结点L:
6、x=(0,1,1),结点M: x=(0,1,0),结点N: x=(0,0,1),结点O: x=(0,0,0),当扩展结点是E时,活结点有A, B,E; 死结点有D,H,I。,当扩展结点是F时,活结点有A, C,F; 死结点有B,D,H,I,E,J,K。,例如: 对于带权图G,在 图G中求费用最小的周游 路线,其解空间是如右图 所示的树。它的每个叶子 代表了一个解向量。,含有n个顶点的无向完全图有n(n-1)/2条边。由于是完 全图,所以从指定的顶点出发的不同周游路线有(n-1)!条。,回溯法就是在解空间数上一个一个地搜索出每一个解 向量,并在一定的意义下,从这些解向量中通过计算,求 出最优解向
7、量。,对于特定的问题,其解空间树往往特别大,包含的叶 子非常多,因此,剪枝函数对回溯算法的优化非常重要。,5.1.3 递归回朔,回溯法对解空间作深度优先搜索,因此,在一般情况 下用递归方法实现回溯法。,void backtrack (int t) /对于第t层的一个扩展结点 if (tn) output(x); /到达一个叶子,输出该叶子所表示的解向量else /没有到达一个叶子for (int i=f(n,t);i=g(n,t);i+) /对于当前扩展结点的第i个孩子xt=h(i); /求该孩子所表示的部分解向量if (constraint(t) /生成该孩子,并将其作为新的当前结点 ,5.
8、1.4 迭代回朔,采用树的非递归深度优先遍历算法, 可将回溯法表示为一个非递归迭代过程。,void iterativeBacktrack () int t=1; /对于第t层的一个扩展结点while (t0) /当该扩展结点存在时 if (f(n,t)=g(n,t) /如果扩展结点有孩子for (int i=f(n,t);i=g(n,t);i+) /考察其第i个孩子 xt=h(i); /求该孩子所表示的部分解向量if (constraint(t) /回溯 ,5.1.5 子集树和排列树,遍历子集树需O(2n)计算时间,遍历排列树需要O(n!)计算时间,void backtrack (int t)
9、 if (tn) output(x);elsefor (int i=0;i=1;i+) xt=i;if (legal(t) backtrack(t+1); ,void backtrack (int t) if (tn) output(x);else for (int i=t;i=n;i+) swap(xt, xi);if (legal(t) backtrack(t+1);swap(xt, xi); ,5.2 装载问题,1. 问题描述,有一批共n个集装箱要装上2艘载 重量分别为c1和c2的轮船,其中集装 箱i的重量为wi,且,装载问题要求确定是否有一个合理的装载方案 可将这个集装箱装上这2艘轮船
10、。如果有,找出一 种装载方案。容易证明,如果一个给定装载问题有解,则采 用下面的策略可得到最优装载方案。(1) 首先将第一艘轮船尽可能装满;(2) 将剩余的集装箱装上第二艘轮船。将第一艘轮船尽可能装满等价于选取全体集装 箱的一个子集,使该子集中集装箱重量和最接近。 于是,装载问题等价于以下特殊的0-1背包问题。,用回溯法设计解装载问题的O(2n)计算时间算法。在某些情况下该算法优于动态规划算法。,public class Loading static int n; /集装箱个数static int w; /各个集装箱的重量static int c; /第1艘船的载重量static int cw
11、; /当前扩展结点表示的装上船的集装箱重量总和static int bestw; /当前所求到的所有解中的最优值public static int maxLoading(int ww, int cc) n=ww.length-1; /初始化w=ww; c=cc; cw=0; bestw=0;backtrack(1); /从第1个集装箱开始考虑return bestw; /返回最优值,2. 算法设计,cw始终是当前扩展结点的所表示的上第一艘船的集装箱的重量和,本算法所求的bestw是上第一艘船的集装箱的重量和的最优值,private static void backtrack(int i) /对
12、于第i个集装箱 if(in) /如果到达叶子 if(cwbestw) /如果该叶子代表的解优于当前最优解bestw=cw; /把该叶子代表的解作为当前最优解return; if(cw+wi=c) /如果第i个集装箱可以上船 cw+=wi; backtrack(i+1); /则生成扩展结点的左孩子cw-=wi; /子树搜索完毕,回溯backtrack(i+1); /否则生成扩展结点的右孩子 ,public class Loading static int n; /集装箱个数static int w; /各个集装箱的重量static int c; /第1艘船的载重量static int cw; /
13、当前扩展结点表示的装上船的集装箱重量总和static int bestw; /当前所求到的所有解中的最优值static int r; /r是当前码头上剩余集装箱重量和public static int maxLoading(int ww, int cc) n=ww.length-1; / 初始化w=ww;c=cc;cw=0;bestw=0;for(int i=1; i=n; i+) /计算当前还没有考虑的集装箱的重量和r+=wi;backtrack(1); /从第1个集装箱开始考虑return bestw; /返回最优值,3. 上界函数,private static void backtrac
14、k(int i) if(in) /如果到达叶子 if(cwbestw) /如果该叶子代表的解优于当前最优解bestw=cw; /把该叶子代表的解作为当前最优解return; r=-wi; /第i个集装箱已经被考虑if(cw+wibestw) /如果扩展结点的右子树中可能有更优的解backtrack(i+1); /则生成扩展结点的右孩子r+=wi; /修复r ,public class Loading static int n; static int w; static int c; static int cw;static int bestw; static int r; static int
15、 x;static int bestx;public static int maxLoading(int ww, int cc, int xx) n=ww.length-1;w=ww; c=cc; cw=0;bestw=0;x=new intn+1; bestx=xx;for(int i=1; i=n; i+)r+=wi;backtrack(1); return bestw;,4. 构造最优解,private static void backtrack(int i) if(in) if(cwbestw) for(int j=1; jbestw) xi=0; backtrack(i+1); r+
16、=wi; ,public static int maxLoading(int w, int c, int bestx) int i=1;n=ww.length-1;int x=new intn+1; int bestx=0;int cw=0;int r=0;for(int j=1; j=n; j+)r+=wj;while(1) while(i=n,5. 迭代回溯,if(in) for(int j=1; j0 ,1. 问题描述,5.3 批处理作业调度,给定n个作业的集合J1,J2,Jn。每个作业必须先由机器1处 理,然后由机器2处理。作业Ji需要机器j的处理时间为tji。对于一 个确定的作业调度
17、,设Fji是作业i在机器j上完成处理的时间。所有 作业在机器2上完成处理的时间和称为该作业调度的完成时间和。 批处理作业调度问题要求对于给定的n个作业,制定最佳作业调度 方案,使其完成时间和达到最小。,这3个作业的6种可能的调度方案是 1,2,3;1,3,2;2,1,3;2,3,1;3,1,2;3,2,1; 它们所相应的完成时间和分别是19, 18, 20, 21, 19, 19。 易见,最佳调度方案是1,3,2,其完成时 间和为18。,public class FlowShop static int n, /作业个数f1, /扩展结点所表示的作业在机器1上的完成处理时间f, /所有作业在机器
18、2上完成处理时间和,即完成时间和bestf; /当前最优值static int m; /作业i在机器j上所需要的处理时间是mijstatic int x; /当前作业调度序列static int bestx; /当前最优作业调度序列static int f2; /作业i在机器2上的完成处理时间是f2iprivate static void backtrack(int i) /i的初始调用值是1 if(in) /如果到达叶子 for(int j=1; j=n; j+) bestxj=xj; /则将该叶子所代表的调度序列作为当前最优bestf=f;,2. 算法设计,else /否则在i到n选择一个
19、孩子进入下一层for(int j=i; jf1)? f2i-1:f1)+mxj2;f+=f2i;if(fbestf) MyMath.swap(x,i,j);backtrack(i+1);MyMath.swap(x,i,j);f1-=mxj1;f-=f2i; ,右图是由14个“+” 和14个“-”组成的符号 三角形。2个同号下面 都是“+”,2个异号下 面都是“-”。,5.4 符号三角形问题,+ + - + - + + + - - - - + - + + + - + + - + - -+,在一般情况下,符号三角形的第一行有n个符号。符号 三角形问题要求对于给定的n,计算有多少个不同的符号 三角形
20、,使其所含的“+”和“-”的个数相同。,解向量:用n元组x1:n表示符号三角形的第一行。 可行性约束函数:当前符号三角形所包含的“+”个数与“-”个数均不超过n*(n+1)/4 无解的判断:n*(n+1)/2为奇数,public class Triangles static int n, /符号三角形第1行的符号个数half, / n(n+1)/4count; /当前”+”号的个数static int p; /符号三角形矩阵static long sum; /已找到的符号三角形个数private static long compute (int nn) n=nn; /初始化 count=0;
21、sum=0;falf=n*(n+1)/2;if(half%2=1) return 0; /无解half=half/2;p=new int n+1n+1; for(int i=0; i=n; i+)for(int j=0; j=n; j+)pij=0; /初始化符号三角形为全”-”backtrack(1); /从第1行有一个符号开始return sum; ,private static void backtrack(int t) if(counthalf)|(t*(t-1)/2-counthalf)return;if(tn) sum+;elsefor(int i=0; i2; i+) p1t=i
22、;count=count+i;for(int j=2; j=t; j+) pjt-j+1=pj-1t-j+1pj-1t-j+2;conut+=pjt-j+1; backtrack(t+1);for(int j=2; j=t; j+)count-=pjt-j+1;count-=i; ,t,t-j+1,j,1. 问题描述,5.5 n后问题,在nn格的棋盘上放置彼此不受攻击的n个皇后。按照国际象 棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的 棋子。n后问题等价于在nn格的棋盘上放置n个皇后,任何2个皇 后不放在同一行或同一列或同一斜线上。,2. 算法设计,解向量:(x1, x2, ,
23、xn) 显约束:xi=1,2, ,n 隐约束:1)不同列:xixj2)不处于同一正、反对角线:|i-j|xi-xj|,public class Nqueen1 static n; /皇后个数static int x; /当前解向量static long sum; /当前已经找到的可行方案数public static long nQueen(int nn) n=nn; /初始化sum=0;x=new intn+1;for(int i=0; i=n; i+)xi=0;backtrack(1); /return sum;,private static boolean place (int k) fo
24、r (int j=1; jn) sum+;elsefor (int i=1;i=n;i+) xt=i; /第t行第i列放皇后if (place(t) /如果没有后之间的相互冲突backtrack(t+1); /则深入 ,3. 迭代回溯,public class Nqueen2 static n; static int x; static long sum; public static long nQueen(int nn) n=nn; sum=0;x=new intn+1;for(int i=0; i=n; i+)xi=0;backtrack(); return sum;,private st
25、atic boolean place (int k) for (int j=1; j0) /当行数k大于0时 xk+=1; /第k行当前列的下一列放皇后while(xk=n)/ 当前行的所有列都不能放皇后,回溯 ,解空间:子集树 可行性约束函数: 上界函数:cp+r=bestp,5.6 0-1背包问题,cp当前扩展结点所获得的价值,r在扩展结点处尚未考虑的剩余 物品的价值总和,bestp当前最优价值。,public class Knapsack private class Element implements Comparable int id;double d;private Element
26、 (int idd, double dd) id=idd;d=dd; ,public int comparable(Object x) double xd=(Element) x).d;if(dxd) return -1;if(d=xd) return 0return 1;public boolean equals(Object x) return d=(Element) x).d; static double c;static int n;static double w;static double p;static double cw;static double cp;static doub
27、le bestp;,public static double knapsack(double pp, double ww, double cc) c=cc; n=pp.length-1; cw=0.0; cp=0.0; best=0.0;Element q=new Elementn;for(int i=1; in) bestp=cp; return; if(cw+wibestp)backtrack(i+1);,private static double bound(int i)/ 计算上界double cleft = c - cw; / 剩余容量double bound = cp;/ 以物品单
28、位重量价值递减序装入物品while (i = n ,1. 问题描述,5.7 最大团问题,给定无向图G=(V,E)。如果UV,且对任意u,vU有(u,v) E,则称U是G的完全子图。G的完全子图U是G的团当且仅当U不 包含在G的更大的完全子图中。G的最大团是指G中所含顶点数最多 的团。如果UV且对任意u,vU有(u,v)E,则称U是G的空子图。 G的空子图U是G的独立集当且仅当U不包含在G的更大的空子图中。 G的最大独立集是G中所含顶点数最多的独立集。对于任一无向图 G=(V,E)其补图G=(V1,E1)定义为:V1=V,且(u,v)E1当且仅 当(u,v)E。,团是极大完全子图,最大团是包含顶
29、点数最多的团;独立集是 极大空子图,最大独立集是包含顶点数最多的独立集。 U是G的最 大团当且仅当U是G的最大独立集。,2. 算法描述,解空间:子集树 可行性约束函数:顶点i到已选入的顶点集中每一个顶点都有边相连。 上界函数:有足够多的可选择顶点使得算法有可能在右子树中找到 更大的团。,public class MaxClique static int x; / 当前解static int n; / 图中顶点个数static int cn; / 当前扩展结点所表示的完全子图中的顶点个数static int bestn; / 当前当前已经求得的团中最大团的顶点个数static int bestx
30、; /当前已经求得的团中的最大团static booleana; / 图的邻接矩阵public static maxClique(int v) x=new intn+1;cn=0;bestn=0;bestx=v; /最优解初始化为图的全集backtrack(1);return bestn;,private static void backtrack(int i) if (i n) / 如果到达叶结点 for (int j = 1; j bestn) /n-i是剩余顶点个数。进入右子树 xi = 0; backtrack(i + 1); ,进一步改进算法的建议,选择合适的搜索顺序,可以使得上界函
31、数更有效的发 挥作用。例如在搜索之前可以将顶点按度从小到大排序。 这在某种意义上相当于给回溯法加入了启发性。定义Si=vi,vi+1,.,vn,依次求出Sn,Sn-1,.,S1的解。 从而得到一个更精确的上界函数,若cn+Si=max则剪枝。 同时注意到:从Si+1到Si,如果找到一个更大的团,那么 vi必然属于找到的团,此时有Si=Si+1+1,否则Si=Si+1。 因此只要max的值被更新过,就可以确定已经找到最大值, 不必再往下搜索了。,1. 问题描述,5.8 图的m着色问题,给定无向连通图G和m种不同的颜色。用这些颜色为图G的各顶 点着色,每个顶点着一种颜色。是否有一种着色法使G中每条
32、边的2 个顶点着不同颜色。这个问题是图的m可着色判定问题。若一个图 最少需要m种颜色才能使图中每条边连接的2个顶点着不同颜色,则 称这个数m为该图的色数。求一个图的色数m的问题称为图的m可着 色优化问题。,平面图:所有顶点和边 都存在画在平面上且任何两 条边都不相交的方法。对于平面图有著名的四 色定理。,假设G是一个包含n个顶点的平面图,给定m种颜色。现要设计 算法判断图G是否可m着色;若是则找出所有可能的着色方法。,2. 算法描述,解空间树:该问题的解空间树是一棵深度为n+1的满m叉树。,解向量:x=(x1, x2, , xn)表示顶点i所着颜色为xi。 可行性约束函数:顶点i与已着色的相邻
33、顶点颜色不重复。,第二顶点的m种 颜色选择,public class Coloring static int n, m; /顶点数和颜色数static boolean a; /图的存储结构static int x; /当前解向量static long sum; /可行解的总数public static long mColoring(int mm) m=mm; sum=0; backtrack(1); return sum; private static void backtrack(int t) if(tn) sum+; for(int i=1; i=n; i+) System.out.pri
34、nt(xi+” ”);System.out.println();else for(int i=1; i=m; i+) xt=i; if(ok(t) backtrack(t+1); ,private static boolean ok(int k) for(int j=1; j=n; j+)if(akj ,思考:图的m着色问题与图的最大团问题有何关系,你能否利用这个关系改进最大团问题的上界?,假设G是一个包含n个顶点的带权图,从给定顶点1出发,寻找 一条回路,最终到达顶点1,使得回路中各个边的权值之和最小。,5.9 旅行售货员问题,解空间:排列树,public class Bttsp stati
35、c int n;static int x;static int bestx;static float bestc;static float cc;static float a;public static float tsp(int v) x=new intn+1;for(int i=1; i=n; i+) xi=i;bestc=Float.MAX-VALUE;bestx=v;,backtrack(2);return bestc;,private static void backtrack(int i) if (i = n) if (axn - 1xnFloat.MAX_VALUE ,1. 问题
36、描述,5.10 圆排列问题,给定n个大小不等的圆c1,c2,cn,现要将这n个圆排进一个矩 形框中,且要求各圆与矩形框的底边相切。圆排列问题要求从n个 圆的所有排列中找出有最小长度的圆排列。例如,当n=3,且所给 的3个圆的半径分别为1,1,2时,这3个圆的最小长度的圆排列如 图所示。其最小长度为,2. 算法设计,public class Circles static int n;static float min;static float x;static float r;public static float circlePerm(int nn, float rr) n=nn;min=100
37、000;x=new floatn+1;r=rr;backtrack(1);return min;,private static void backtrack(int t) if (tn) compute();elsefor(int j = t; j = n; j+) MyMath.swap(r, t, j);float centerx=center(t);if (centerx+rt+r1min) /下界约束 xt=centerx;backtrack(t+1);MyMath.swap(r, t, j);,private static float center(int t) / 计算当前所选择圆
38、的圆心横坐标 float temp=0;for (int j=1;jtemp) temp=valuex;return temp;private static void compute() / 计算当前圆排列的长度 float low=0, high=0;for (int i=1;ihigh) high=xi+ri;if (high-lowmin) min=high-low; ,5.11 电路板排列问题,1. 问题描述,设B=1,2,n是n块电路板的集合。集合L=N1,N2,Nm是n 块电路板的m个连接块。其中每个连接块Ni是B的一个子集,且Ni中 的电路板用同一根导线连接在一起。对于给定的集合
39、B和L,试求出 n块电路板的一种最佳排列x,即电路板xi插在第i个插槽,使得x所 确定的电路板排列密度density(x),即跨越相邻电路板插槽的最大连 线数,达到最小。,n=8,m=5 B=1,2,3,4,5,6,7,8 L=N1,N2,N3,N4,N5 N1=4,5,6 N2=2,3 N3=1,3 N4=3,6 N5=7,8,插槽1,插槽2,插槽3,插槽4,插槽5,插槽6,插槽7,电路 板2,插槽8,电路 板1,电路 板3,电路 板4,电路 板5,电路 板6,电路 板7,电路 板8,x=2,1,3,4,5,6,7,8,density(x)=2,N1,N2,N3,N4,N5,2. 算法设计,
40、public class Board static int n;static int m;static int x;static int bestx;static int total;static int now;static int bestd;static int b;public static int arrange(int bb, int mm, int xx) n=bb.length-1;m=mm;x=new intn+1;bestx=xx;total=new intm+1;now=new intm+1;bestd=m+1;,b=bb;for(int i=1; i=n; i+) xi
41、=i;for(int j=1; j=m; j+)totalj+=bij;backtrack(1, 0);return bestd;,private static void backtrack(int i, int dd) if(i=n) for(int j=1; j0,mymath.swap(x, i, j ); for(int k=1; k=m; k+)nowk-=bxjk; ,5.12 连续邮资问题,1. 问题描述,假设国家发行了n种不同面值的邮票,并且规定每张信封上最多 只允许贴m张邮票。连续邮资问题要求对于给定的n和m的值,给出 邮票面值的最佳设计,在1张信封上可贴出从邮资1开始,增量
42、为1的 最大连续邮资区间。,例如,当n=5和m=4时,面值为(1,3,11,15,32)的5种邮票可以贴 出邮资的最大连续邮资区间是1到70。,private static void backtrack(int t) if(counthalf)|(t*(t-1)/2-counthalf)return;if(tn) sum+;else,public class Triangles static int n,half,count; static int p;static long sum;private static long compute (int nn) n=nn; count=0; sum=0;falf=n*(n+1)/2;if(half%2=1) return 0;half=half/2;p=new int n+1n+1; for(int i=0; i=n; i+)for(int j=0; j=n; j+)pij=0;backtrack(1);return sum;,5.13 回溯法的效率分析,private static void backtrack(int t) if(counthalf)|(t*(t-1)/2-counthalf)return;if(tn) sum+;else,