第6章 回溯算法
G
H
I
J Cr<W3 不可行解
K Cr=14,V=45 x=(1,0,0)
L
M Cr=15,V=25 不是最优解
N
O
W3=15,V3=25 Cr=0, V=50 x=(0,1,1)
6
例6.2 旅行商问题 • TSP问题(Traveling Salesman Problem)通常称为旅行商问题,也称为旅 行售货员问题、货担郎问题等,是组合优化中的著名难题,也是计算 复杂性理论、图论、运筹学、最优化理论等领域中的一个经典问题, 具有广泛的应用背景。TSP问题最早在20世纪20年代由数学家兼经济 学家Karl Menger提出来的。 • 问题的一般描述为:旅行商从n个城市中的某一城市出发,经过每个 城市仅有一次,最后回到原出发点,在所有可能的路径中求出路径长 度最短的一条。
9
• 在回溯法搜索解空间树时,通常采用两种策略(剪枝函数) 避免无效搜索以提高回溯法的搜索效率: 1. 用约束函数在扩展结点处减去不满足约束条件的子树; 2. 用限界函数减去不能得到最优解的子树。
– 解0—1背包问题的回溯法用剪枝函数剪去导致不可行解的子树。 – 解旅行商问题的回溯算法中,如果从根结点到当前扩展结点的部 分周游路线的费用已超过当前找到的最好周游路线费用,则以该 结点为根的子树中不包括最优解,就可以剪枝。
i 1
1 20 5 6 4 10 2
n1
(1,3,2,4,1)的总权值 为25,为最优回路。
3
15
4
8
• 回溯法找最小费用周游路线的主要过程
A 1 B 2 3 4
1
20 5
2 3 10 F 4 4 L
C 4 G 3 M 4 N 2 H
D
4 I 2 O 3 P 2 J
E 3 K 2 Q
6 4 3 15
• 在主函数中,需要对相关变量初始化:
memset(bestx, 0, sizeof(bestx)); memset(f2, 0, sizeof(f2)); bestf = infinite; f1 = 0; f = 0; for (i=0; i<=n; i++) x[i] = i;
• 因为这是一棵排列树,因此算法Backtrack(int t)的计算时 间复杂度为O(n!)。
for (int i=t; i<=n; i++) { f1 += job[x[i]][1]; f2[t] = ((f2[t-1]>f1) ? f2[t-1] : f1)+job[x[i]][2]; f += f2[t]; if (f<bestf) //剪枝 { swap(x[t], x[i]); Backtrack(t+1); swap(x[t], x[i]); } f1 -= job[x[i]][1]; f -= f2[t]; } }
– 例如,对于有n种可选择物品的0—1背包问题,其解空间由长度为 n的0—1向量组成,该解空间包含了对变量的所有可能的0—1赋值。
A
1 B 1 D 0 E 0 I 1 J 0 K 1 L F 0 M 1 N 1 0 C 0 G 0 O
1
H
3
6.1.2 回溯法的基本思想
• 在生成解空间树时,定义以下几个相关概念:
– 活结点:如果已生成一个结点而它的所有儿子结点还没有 全部生成,则这个结点叫做活结点。 – 扩展结点:当前正在生成其儿子结点的活结点叫扩展结点 (正扩展的结点)。 – 死结点:不再进一步扩展或者其儿子结点已全部生成的结 点就是死结点。
4
• 在确定了解空间的组织结构后,回溯从开始结点(根结 点)出发,以深度优先的方式搜索整个解空间。
6.2 流水作业调度问题
• 给定n个作业的集合j={j1,j2,…,jn}。 • 每一个作业ji都有两道工序,分别在两台机器上完成。一台机器一次 只能处理一道工序,并且工序一旦开始,就必须进行下去直到完成。 • 每一个作业必须先由机器1处理,然后由机器2处理。 • 作业ji需要机器j的处理时间为t[i][j],其中i= 1, 2, …, n ,j=1, 2。对于 一个确定的作业调度,设f[i][j]是作业i在机器j上的完成处理的时间, 所有作业在机器2上完成处理的时间之和定义如下:
– 可以系统地搜索一个问题的所有解或任意解,既有系统性又有 跳跃性。 – 回溯法的基本做法是搜索,或是一种组织得井井有条的,能避 免不必要搜索的穷举式搜索法。 – 这种以深度优先的方式系统地搜索问题的解的方法称为回溯法。
2
6.1 回溯算法的理论基础
6.1.1 问题的解空间 • 应用回溯法求解时,需要明确定义问题的解空间。问题的 解空间应至少包含问题的一个(最优)解。
7
• 设G=(V, E)是一个带权图,其每一条边(u, v)∈E的费用(权) 为正数w(u, v)。目的是要找出G的一条经过每个顶点一次 且仅经过一次的回路,即汉密尔顿(Hamilton)回路v1, v2 ,…,vn ,使回路的总权值最小:
min{ w(vi,vi1 ) w(vn,v1 )}
第6章 回溯算法
6.1 回溯算法的理论基础
6.1.1 问题的解空间 6.1.2 回溯法的基本思想 6.1.3 子集树与排列树
6.2 流水作业调度问题
1
• 回溯法是一种组织搜索的一般技术,有“通用的解题法” 之称,用它可以系统的搜索一个问题的所有解或任一解。 • 有许多问题,当需要找出它的解集或者要求回答什么解 是满足某些约束条件的最佳解时,往往要使用回溯法。
3
3,1,2 19
3,2,1 19
算法6.8(1) 流水作业调度问题回溯算法的数据结构
#define NUM 20 #define infinite 10000 int n; int job[NUM][3]; int x[NUM]; int bestx[NUM]; int f1; int f2[NUM]; int f; int bestf;
10
6.1.3 子集树与排列树
• 有时问题是要从一个集合的所有子集中搜索一个集合,作 为问题的解。方便地遍历一个集合的所有子集或者所有排列。
• 当问题是要计算n个元素的子集,以便达到某种优化目标 时,可以把这个解空间组织成一棵子集树。
– 例如,n个物品的0-1背包问题相应的解空间树就是一棵子集树。 – 这类子集树通常有2n个叶结点,结点总数为2n +1-1。
• 遍历子集树的任何算法,其计算时间复杂度都是Ω(2n)。
11
算法6.1 回溯算法搜索子集树的伪代码
//形参t为树的深度,根为1 void backtrack (int t) { if (t>n) update(x); else for (int i=0; i<=1; i++) //每个结点只有两个子树 { x[t]=i; //即0/1 if (constraint(t) && bound(t)) backtrack(t+1); } } • 约束函数constraint(t)和限界函数bound(t),称为剪枝函数。 • 函数update(x)是更新解向量x的。 • 约束函数constraint(t),一般可以从问题描述中找到。
12
• 当所给的问题是确定n个元素满足某种性质的排列时,可 以把这个解空间组织成一棵排列树。 • 排列树通常有n!个叶子结点。因此遍历排列树时,其计 算时间复杂度是Ω(n!) 。
– 例如,旅行商问题就是一棵排列树。
13
算法6.2 回溯算法搜索排列树的伪代码
//形参t为树的深度,根为1 void backtrack (int t) { if (t>n) update(x); else for (int i=t; i<=n; i++) { //为了保证排列中每个元素不同,通过交换来实现 swap(x[t], x[i]); if (constraint(t) && bound(t)) backtrack(t+1); swap(x[t], x[i]); //恢复状态 } }
– 这个开始结点成为一个活结点,同时成为当前的扩展结点。在 当前的扩展结点,搜索向深度方向进入一个新的结点。这个新 结点成为一个新的活结点,并成为当前的扩展结点。 – 若在当前扩展结点处不能再向深度方向移动,则当前的扩展结 点成为死结点,即该活结点成为死结点。此时回溯到最近的一 个活结点处,并使得这个活结点成为当前的扩展结点。 – 回溯法以这样的方式递归搜索整个解空间(树),直至满足中 止条件。
f f [i ][2]
i 1 n
• 称为该作业调度的完成时间之和。 • 由于只有两台机器,作业的处理顺序极大地影响结束时间f。流水作 业调度问题要求对于给定的n个作业,制定最佳作业调度方案,使其 完成时间和达到最小。
• 由于每个作业都要处理,只是在机器上的处理顺序不同,因此流水作 业调度问题的一个候选解是n个作业的一个排列。 • 设解向量为X ( x1, x2, …, xn) ,就是确定最优解是n个作业( 1, 2, …, n ) 的哪一个排列,因此解空间是一棵排列树。
5
• 例6.1 0—1背包问题 假设背包容量C=30,w={16,15,15},v={45,25,25}
Cr=C=30,V=0 A W1=16,V1=45 Cr=14,V=45 Cr<W2 不可行解 D B Cr=14 V=45 E W2=15,V2=25 Cr=15,V=25 F Cr=30,V=0 C
//∞ //作业的数量 //各作业所需处理时间 //当前作业调度方案 //当前最优作业调度方案 //机器1完成处理时间之和 //每个作业在机器2完成处理时间 //机器2完成时间之和 //当前最优值
算法6.8(2) 流水作业调度问题回溯算法的实现