算法思想之5回溯法
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
作业2
作业3
3
2
1
3
if (t>n) output(x);
else for (int i=t;i<=n;i++) { swap(x[t], x[i]); if (legal(t)) backtrack(t+1);
这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。
用约束函数在扩展结点处剪去不满足
个扩展结点 R,一旦产生了一个儿子 C,就 将 C 当做新的扩展结点。在完成对子树 C 的穷尽搜索后,将 R 再次变成扩展结点, 继续生成 R 的下一个儿子。
R C
约束条件的子树。
用界限函数剪去得不到最优解的子树。
这两类函数统称为剪枝函数。
7
8
1
6
30 5
采用回溯算法解决问题的3个步骤
5.5 地图着色问题
5.6 货郎担问题
1
2
5.1 回溯算法框架
问题的解空间:回溯算法将问题的解表示成一 个n元式(x1,x2,…,xn) 。 xi的取值范围为某个有穷集 S。 xi的所有取值范围的组合被称为解空间。 例如: 0/1背包问题的 S={0,1} 解空间 : { (0,0,0),(0,0,1),(0,1,0),(0,1,1), (1,0,0),(1,0,1),(1,1,0),(1,1,1) }
约束函数
界限函数
void backtrack (int i) { // 搜索第 i层结点 if (i > n) { // 到达叶结点 if (cw>bestw) { 将 x数组的内容拷贝到bestx数组中; bestw = cw; } return ; } r -= w[i]; if (cw + w[i] <= c) { // 搜索左子树 x[i] = 1; cw += w[i]; 增加两个数组: backtrack(i + 1); int[ ] x:记录搜索路径 cw -= w[i]; int[ ] bestx:记录最优路径 } if (cw+r>bestw) { x[i] = 0; backtrack(i + 1); } r += w[i]; }
21
构造最优解
左分支根据约束函数剪枝,右分支根据界限函数剪枝
22
5.3 批处理作业调度
给定n个作业的集合 {J 1,J2,…,Jn}。每个作业必 须由两台机器处理,且先由 机器1处理,再由机器 2处理。 作业Ji需要机器j的处理时间 为tji。Fji是作业i在机器j上 完成处理的时间。
23
所有作业在机器 2上完成时间和 :
tji 作业1 作业2 作业3
机器1 2 3 2 2 3 2
机器2 1 1 3 1 1 3
m
x
1
2
3
bestx
f2
27
28
5.4 n 后问题
在n× n格的棋盘上放置彼此 不受攻击的n个皇后。按照国 际象棋的规则,皇后可以攻 击与之处在同行或同列或同 对角线上的棋子。n 后问题等 价于在 n×n格的棋盘上放置n 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 1 2 3 4 5 6 7 8
cw = 0
1
1
int bestw; //当前最优载重量
cw =10 2
1
cw = 50
3
0
4
4
17ቤተ መጻሕፍቲ ባይዱ
18
3
void backtrack (int i) { // 搜索第i层结点 if (i > n) { // 到达叶结点 if (cw>bestw) bestw = cw; return ; } if (cw + w[i] <= c) { // 搜索左子树 cw += w[i]; 约束函数:cw + w[i] <= c backtrack(i + 1); cw -= w[i]; } backtrack(i + 1); // 搜索右子树,cw 没有变化 } 注意:此算法只计算出 bestw,没有记录最优值。
f=3+7+8=18
swap(x[t], x[i]); } }
M1
M2
f=4+6+10=20
25
int n, f1, f, bestf; int [][] m; int [] x; int [] bestx; int [] f2;
// 作业数 // 机器 1完成处理时间 // 完成时间和 // 当前最优值 // 各作业所需的处理时间 // 当前作业调度 // 当前最优作业调度 // 机器 2完成处理时间
2
10
4
(1)针对所给问题,定义问题的解空间; (2)确定易于搜索的解空间结构; (3)以深度优先方式搜索解空间,并在搜索过程 中利用剪枝函数避免无效地搜索。
3
20
4
例如:①->④->②->③ -> ① 总费用为25 ①->④->③->② 此刻费用为29, 利用界限函数剪掉
9
10
回溯算法解题特征
用回溯法解题的显著特征是在搜索过程 中动态产生问题的解空间。在任何时刻, 算法只保存从根结点到当前扩展结点的 路径。如果解空间树中从根结点到叶结 点的最长路径的长度为h(n),则回溯法 所需的计算空间约为O(h(n))。
void backtrack(int i) { if (i > n) { for (int j = 1; j <= n; j++) bestx[j] = x[j]; bestf = f; } else for (int j = i; j <= n; j++) { f1+=m[x[j]][1]; f2[i]=((f2[i-1]>f1)?f2[i-1]:f1)+m[x[j]][2]; f+=f2[i]; if (f < bestf) { swap(x,i,j); backtrack(i+1); swap(x,i,j); } f1-=m[x[j]][1]; f-=f2[i]; } }
递归回溯 算法框架
t:表示当前的递归深度; n:控制递归深度 constraint(t):约束函数; bound(t):界限函数 h(i):当前状态第i个可选值 f(n,t):在当前扩展结点处未搜索的子树的起始编号 g(n,t):在当前扩展结点处未搜索的子树的终止编号
12
2
子集树
当给定的问题是从 n个元素的集合中找出满足某 种性质的子集时,相应的解空间树为子集树。 遍历子集树需要的时间复杂性为 O(2n)
3
解空间可以采用树结构表示
{ (0,0,0), (0,0,1), (0,1,0), (0,1,1), (1,0,0), (1,0,1), (1,1,0), (1,1,1) }
例如: n = 3 时的 0-1背包问题可以用一棵完全 二叉树表示其解空间。
4
货郎担问题。某售货员要到若干城市推销 商品。已知各城市之间的路程,试为售货 员选定一条从驻地出发,经过每个城市一 次且仅一次,最后回到驻地的最短路径。
第 5 章 回溯算法
5.1 回溯算法框架 5.2 装载问题 5.3 批处理作业问题 5.4 n后问题
回溯算法
回溯算法是一种搜索思路,基本做法是在问题 的解空间树中,采用深度优先策略,从根结点
出发搜索解空间树。算法搜索至解空间树的任
意结点时,先判断该结点是否包含问题的解。 如果肯定不包含,则跳过对该结点为根的子树 的搜索,并向其祖先结点回溯;否则,进入该 子树,继续按深度优先策略搜索。
26
void backtrack(int i) { if (i > n) { for (int j = 1; j <= n; j++) bestx[j] = x[j]; void backtrack (int t) { bestf = f; if (t>n) output(x); } else for (int j = i; j <= n; j++) { else f1+=m[x[j]][1]; for (int i=t;i<=n;i++) { f2[i]=((f2[i-1]>f1)?f2[i-1]:f1)+m[x[j]][2]; swap(x[t], x[i]); f += f2[i]; if (legal(t)) backtrack(t+1); if (f < bestf) { swap(x,i,j); swap(x[t], x[i]); backtrack(i+1); } swap(x,i,j); } } f1 -= m[x[j]][1]; f -= f2[i]; } }
void backtrack (int t) { if (t>n) output(x); else for (int i=1;i>=0;i--) { //取值范围 x[t]=i; if (legal(t)) // 合法 backtrack(t+1); } }
13
排列树
当给定的问题是确定n个元素满足某种性质 的排列时,相应的解空间树为排列树。 遍历排列树需要的时间复杂性为 O(n!)
有 n 个集装箱要装上 2 艘载重分别为 c1和
c2 的轮船,其中集装箱 i 的重量为 wi,且
w
i 1
n
i
c1 c2
可以证明,如果一个给定装载问题有解,则采 用下面的策略可得到最优装载方案。 (1)首先将第一艘轮船尽可能装满; (2)将剩余的集装箱装上第二艘轮船。 将第一艘轮船尽可能装满等价于选取全体集装 箱的一个子集,使该子集中集装箱重量之和最 接近c1。由此可知,装载问题等价于以下特殊 的0-1背包问题。
19
增加界限函数优化算法
增加一个记录当前剩余集装箱重量的变量 r。
r
j i 1
w
n
j
当cw + r ≦bestw 剪掉右枝。 r的初始值为:
r wj
j 1 n
20
void backtrack (int i) { // 搜索第 i层结点 if (i > n) { // 到达叶结点 if (cw > bestw) bestw = cw; return ; } r -= w[i]; if (cw + w[i] <= c) { // 搜索左子树 cw += w[i]; backtrack(i + 1); cw -= w[i]; } if (cw+r>bestw) backtrack(i + 1); r += w[i]; }
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]); } }
14
5.2 装载问题
起始点 1
6 4 30 5 10
生成问题状态的基本方法
活结点 :已生成且其儿子没有全部生成的点 扩展结点 :正在产生儿子的结点 死结点 :所有儿子已经全部产生的结点
活结点 扩展结点 死结点
2
3
20
4
5
6
1
生成问题状态的基本方法
采用深度优先法生成解空间树:如果对一
用回溯法搜索解空间时,可以采用两种 策略避免无效搜索:
f F2 i
i 1 n
t1i
t2 i
作业1 作业2 作业3
2 3 2
1 1 3
批处理作业调度问题要求对于给定的 n 个 作业,制定最佳作业调度方案,使其完成 时间总和达到最短。
24
4
t1i 作业1 2
t2i 1
M1 M2 f=3+6+10=19 M1 M2
void backtrack (int t) {
i 1 n
n=3; c1 = c2 = 50; w = [10,40,40]
int n; int[] w; int c; int cw;
//集装箱数 //集装箱重量数组 //第 1艘轮船的载重量 //当前载重量
界限函数 (不选择当前元素 ):
当前载重量cw+剩余集装箱的重量 r当前最优载重量bestw
max wi xi
i 1 n
装载问题要求确定是否有一个合理的装载方案 可将这些集装箱装上这 2 艘轮船。如果有, 找出一种装载方案。
w x
i 1
n
i i
c1
xi {0, 1}, 1 i n
15
用回溯法解决此问题 的计算时间度O(2n) 。
16
解空间:子集树
可行性约束函数 (选择当前元素 ): wi xi c1
11
void backtrack (int t) { if (t>n) output(x); else { for (int i=f(n,t); i<=g(n,t); i++) { x[t]=h(i); if (constraint(t)&&bound(t)) backtrack(t+1); } } }