第
5
章
回溯法
..5.1概述
在现实世界中,很多问题没有(至少目前没有)有效的算法,这些问题的求解
只能通过蛮力穷举搜索来实现。但蛮力法需要生成并评估所有的解,执行效率
低下。

回溯法(BackTrackingMethod)是一种深度优先搜索算法,该方法试图穷举
所有可能的解,因此,回溯法能够确保获得最优解。在搜索过程中根据问题约束
等规则对搜索树进行剪枝操作,跳过部分搜索区域,提高算法性能。回溯法按选
优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达
不到目标,就退回一步重新选择,这种走不通就退回再选择另外一个方向试探的
技术称为回溯法,满足回溯条件的某个状态的点称为“回溯点”。回溯法把问题的
解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍
历的过程中记录和寻找所有可行解或者最优解。

..5.2 
回溯法设计思路

复杂问题常常有很多可能解,这些可能解构成问题的解空间。确定了问题的
解空间结构后,回溯法将从开始结点(根结点)出发,以深度优先的方式搜索整个
解空间。搜索过程从开始结点开始搜索,将开始结点进行扩展。在当前的扩展结
点处,向纵深方向搜索并移至一个新结点,这个新结点就成为一个新的活结点,并
成为当前的扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前
的扩展结点就成为死结点。此时应回溯至最近的一个活结点处,并使其成为当前
的扩展结点。回溯法以上述工作方式递归地在解空间中搜索,直至找到所要求的
解或解空间中已无活结点时为止。

运用回溯法解题的关键要素有以下3点。

(1)针对给定的问题,定义问题的解空间。
(2)确定易于搜索的解空间结构。
搜索
(
。
3)以深度优先方式搜索解空间,并且在搜索过程中用剪枝函数避免无效

第5章 回溯法 81 
回溯法的算法框架如下。 
void back_trace(int t) { 
if(t ≥ n && x is better than optimal_x) //当前解更优 
optimal_x=x; //保留当前解 
update_bound(x); //更新剪枝函数,回溯至上一级 
else 
for(int i=s; i<=e; i++) { 
x[t]=node(i); 
if(constraint(t) && bound(t)) 
back_trace(t+1); 
} 
} 
其中,t表示递归深度,即当前扩展结点在解空间树中的深度;n是解空间树的高度。当t≥n 
时,算法已搜索到一个叶子结点,即当前解,此时对当前解进行判断,如果比已搜索到的最优
解更好,则更新最优解,同时,更新剪枝函数。已搜索到的最优解越接近问题的最优解,则后
续剪枝效果越好。s和e表示在当前扩展结点处子树的起始编号和终止编号;node(i)表示在
当前扩展结点处的第i个结点;函数constraint(t)和bound(t)分别表示当前扩展结点处的
约束函数和剪枝函数。深度优先搜索继续进行的条件是满足问题约束且该结点处不剪枝; 
否则,剪去相应的结点。
.. 5.3 回溯法示例与过程分析
5.3.1 n 皇后问题 
例5.1 在n×n 格的国际象棋棋盘上摆放n 个皇后(见图5.1,此时n=8),使其不能互
图5.1 八皇后问题示意图
相攻击,即任意两个皇后都不能处于同一行、同一列或
同一斜线上,有多少种摆法? 
n 皇后是由八皇后问题演变而来的。该问题由国
际象棋棋手马克斯·贝瑟尔于1848年提出:在8×8 
格的国际象棋棋盘上摆放8个皇后,使其不能互相攻
击,即任意两个皇后都不能处于同一行、同一列或同一
斜线上,求有多少种摆法。高斯认为有76 种方案。
1854年,在柏林的象棋杂志上不同的作者发表了40 
种不同的解,后来有人用图论的方法解出92种结果。
下面用数组模拟棋盘,从第一行开始,依次选择位
置,如果当前行中皇后位置满足条件,则选择下一行皇
后的位置;如果不满足条件,那么当前位置后移一位继续搜索。
下面以四皇后为例说明利用回溯法求解n 皇后问题的过程。
步骤1:在第1行放第一个皇后不受约束,可以在4个位置上随意放置。为了对整个解

82算法设计与问题求解(第2版·微课版) 
空间进行遍历,每一个位置都不能漏掉。因此,将第1行的皇后放在第1行第1列的位置开
始搜索。
步骤2:当第一个皇后放在第1列后,根据问题约束,第2行的皇后只能放在第3列或
者第4列,如图5.2(a)所示。选择在第2行第3列放置第二个皇后,如图5.2(b)所示。
图5.2回溯法求解四皇后问题
步骤3:如图5.b) 按照以上两步放置皇后将导致第三个皇后没有合适的位置
进行放置,此时, 
2(所示, 
需要回溯重新决策第二个皇后的位置, 2(所示。

搜索将无法继续, 如图5.c)
步骤4:根据前面的决策,第三个皇后有一个合适的位置进行放置,即第2列。将第3 
行第2列放置第三个皇后,则第四个皇后没有合适的位置放置, 2(所示。

如图5.d)

步骤5:由于第四个皇后无法放置,则回溯重新决策第三个皇后的位置,选择下一个合
适的位置放置第三个皇后,由于第三行中无新的位置可选,因此,需要回溯重新决策第二个
皇后的位置,但第二个皇后可能的位置都已被搜索,所以,最终回溯重新决策第一个皇后的
位置,如图5.e)

2(所示。
步骤6:第一个皇后放在第2列,第二个皇后可选位置为第4列。根据问题约束,当将
第二个皇后放置在第4列,第三个皇后的可能位置只有第1列, 2(所示。

如图5.f)
步骤7:将第三个皇后放置于第1列,第四个皇后的可选位置为第3列。当将第四个皇
后放置于第3列,此时,已搜索至解的叶子结点,得到第一个可行解。

步骤8:由于第四个皇后无新的位置可以放置,回溯重新决策第三个皇后的位置;由于
第三个皇后无新的位置可以放置,回溯重新决策第二个皇后的位置;同理,由于第二个皇后
无新的位置放置,回溯重新决策第一个皇后的位置;第一个皇后还有第3列、第4列两个位
置可以搜索,此时,将第一个皇后放置于第3列,重新开始搜索,寻找新的可行解。按照上述
过程遍历解空间树,即可得到所有可行解。

值得注意的是,问题的求解并不需要用n×n 
的数组来表示结果,而只需要一个
n 
长度
的数组来表示每一行的皇后位置即可,即ai]k,表示第
i 
行的皇后位置为k。此时使
用一个ar[的数组就可以表示一个解,
r[=

n] 通过回溯可以得到所有可行解
。
n 
皇后问题的回溯法算法如下所示
。



第5章 回溯法 83 
//输入: n,表示皇后个数
//输出: n 皇后问题的所有可行解
步骤1: 初始化解向量arr[i]={-1}; 
步骤2: i=1; 
步骤3: while(i>=1) 
步骤3.1 把皇后i 摆放在下一列,即arr[i]++,如果arr[i]>n,则置arr[i]=-1,i--,转步
骤3; 
步骤3.2 考查arr[i]位置,如果不发生冲突,则转步骤3.3;否则转步骤3 回溯; 
步骤3.3 若n 个皇后已全部摆放,输出可行解,转步骤3 继续搜索下一个可行解,否则,i+ +, 
转步骤3; 
步骤4: 退出循环,搜索结束。
0-1背包
问题
5.3.2 0-1背包问题
例5.2 有一个背包,最多能承载10kg,现在有3个物品,质量分别为[4,8,5]、价值分
别为[24,40,20],详情如表5.1所示。那么应该如何选择才能使得你的背包背走最多价值
的物品? 
表5.1 0-1背包问题示例
物 品质量(w)/kg 价值(v)/元价值/质量(v/w) 
1 4 24 6 
2 8 40 5 
3 5 20 4 
解题思路:对于每一个物品i,该物品只有选与不选两种选择,总共有n 个物品,可以顺
序依次考虑每个物品,这样就形成了一棵解空间树。求解的基本思想就是遍历这棵树,以枚
举所有情况,最后进行判断,如果质量不超过背包容量,且价值最大,该方案就是问题的解。
定义:cr 为背包剩余质量、w [i]为第i 个物品的质量、C 为背包总质量、cv 为当前背包
价值、r 为剩余物品总价值、bestv为当前最优解的值。
在决策第i 个物品是否选择时,当前状态的左子树表示选择装入,右子树表示选择不
装入。剪
枝策略如下。
(1)约束函数:如果第i 个物品加入背包使得约束条件不成立,即cr<w [i],则无法装
入第i 个物品,剪去左子树。
(2)限界函数:如果剩余所有物品价值与当前背包价值之和小于当前的最优值,即
cv+r≤bestv,说明在当前状态无法搜索到更优的物品组合,放弃在右子树上进一步搜索。
表5.1所示问题搜索过程如下。
(1)从根结点A 开始按深度优先搜索。首先决策第一个物品是否装入背包。此时, 
C=10,w [1]=4,r=84,bestv=0,cv=0。显然,C>w [1],可以在左子树上进行搜索。
(2)左子树根结点为B,由于cr=10-4=6<8,所以第二个物品不能放入背包,因此, 
剪枝左子树;由于此时bestv=0,可以在B的右子树E上进行搜索。

84 算法设计与问题求解(第2版·微课版) 
(3)在右子树E上决策第三个物品是否放入,由于cr=10-4=6>5,因此,可以将第三
个物品放入背包,进入左子树J搜索。由于J为叶子结点,说明搜索到了一个可行解,该可
行解为(1,0,1),所对应的背包价值total_val=44>bestv(初值为0),保留当前最优解: 
bestv=44。
(4)搜索到可行解后回溯至J的父结点E,判断是否进入E的右子树搜索。此时cv= 
24,r=0,显然cv+r≤bestv,所以右子树剪枝。至此,E的左右子树搜索完毕,回溯至父结
点B;B的左右子树已搜索完毕,回溯至父结点A。
(5)在结点A 处cv=0,r=60>bestv,所以可以进入右子树C搜索。此时,cr>w [2], 
可以进入左子树F搜索。
(6)在左子树F处,cr=10-8=2<5,所以第三个物品不能放入背包,剪枝左子树L, 
进入右子树M 搜索。由于M 为叶子结点,所以搜得一个新的可行解(0,1,0)。该解对应的
背包价值total_val=40<bestv,不更新bestv。
(7)M 搜索完毕回溯至父结点F,此时,F左右子树已搜索完毕,回溯至父结点C。在结
点C处,cv+20<bestv,所以右子树没有必要搜索,直接剪枝。
(8)C的左右子树搜索完毕,回溯至父结点A。此时,A 的左右子树搜索完毕,解空间
中的每一个解都得到了遍历,所搜索到的bestv即为问题的最优解。
0-1背包问题的回溯法求解过程如图5.3所示。
图5.3 0-1背包问题的回溯法求解过程
0-1背包问题的回溯法算法如下所示。 
//输入: 背包总质量C,物品个数n,第i 个物品的质量w[i],价值v[i] 
//输出: 问题的最优解选择的物品x0[](x0[i]为1 代表选择第i 件物品,x0[i]为0 代表不选
择),bestv 为最优解的总价值
//x[],x[i]为1 代表选择第i 件物品,为0 代表不选择
cr ← C //背包剩余质量

第5章 回溯法 85 
cv ← 0 //背包当前价值
r ← v[1]+v[2]+…+v[n] //剩余物品总价值
bestv ← 0 //最优解的总价值
knapwack(i,r,cv,cr)//i 为第i 件物品,r 为剩余物品总价值,cv 为背包当前价值,cr 为背包
//剩余质量
{ 
if cr>=0 and cv>bestv then //找到并更新最优解 
bestv ← cv; 
for j ← 1 to i 
x0[j]← x[j] 
end for 
for j ← i+1 to n 
x0[j]← 0 
end for 
end if 
if cv+r<=bestv then //限界函数剪枝 
return 
end if 
if cr>=w[i]then //只有cr>=w[i]才需要搜索左子树 
x[i]← 1 
knapwack(i+1,r-v[i],cv+v[i],cr-w[i]) //搜索左子树 
x[i]← 0 
end if 
knapwack(i+1,r-v[i],cv,cr); //搜索右子树
} 
5.3.3 图的m 着色问题
图的m 
着色问题 
例5.3 给定无向连通图G=(V,E)和正整数m ,求最小的整数m ,使得用m 种颜色对
G 中的顶点着色后任意两个相邻顶点着色不同。
解题思路:问题要求任意相邻顶点颜色不同。如图5.4(a)所示,有A、B、C、D、E共5 
个顶点组成的无向图G,如果采用图5.4(b)的着色方法,可以看到相邻顶点B、C着色相同, 
因此该解为错误解;如果采用图5.4(c)的着色方法,则满足题目条件,因此该解为可行解。
给定足够多的颜色,即m 足够大,问题一定存在解。图的m 着色问题的目标是找到最
小的m 使得图的着色方案可行。
对于有n 个顶点的图,每个顶点有m 种着色方案,整个问题解空间构成一棵深度为n 
的m 叉树,可以利用回溯法在解空间树种遍历所有路径,搜索到最小的m 。由于问题的解
空间树规模大,需要有效的方案进行剪枝操作,避免不必要的操作。
剪枝策略如下。
(1)约束函数:图的相邻顶点颜色不能相同,如果给某个顶点着色导致相邻顶点颜色
相同,则剪去该扩展分支。

86算法设计与问题求解(第2版·微课版) 
图5.4图的着色问题示例
(2)限界函数:用变量m记录当前最优解(最小值), 在搜索的过程中记录部分解中已
经使用颜色数量clr,如果clr≥m,则终止当前解的搜索,回溯到合适的位置再行搜索其
他解。例
5.3所示问题解空间树搜索过程如图5.5所示。
图5.图的
m 
着色问题解空间树搜索过程

5 

对于n=5的图5.求最小颜色数
m 
的过程如下。

4所示问题,

(1)搜索树从状态1开始,为顶点A赋1号颜色,此时没有颜色冲突,满足问题约束,转
入状态2。
(2)为顶点B赋1号色,与A=1冲突,所以该分支被剪枝;接着为B赋2号色,没有颜

第5章 回溯法 87 
色冲突,满足问题约束,转入状态4。
(3)为顶点C赋1、2号色,分别与A=1、B=2冲突,所以只能为C赋3号色,没有颜色
冲突,满足问题约束,转入状态7。
(4)为顶点D赋1号色,没有颜色冲突,满足问题约束,转入状态8。
(5)为顶点E赋1、2、3号色,分别与B=2、C=3、D=1冲突,所以只能为E赋4号色。
此时,搜索已到达解空间树的叶子结点,着色方案(1,2,3,1,4)是一个可行方案,总共用了
m =4种颜色。
(6)为了遍历解空间树,从状态12回溯至状态8,状态8下的4种选择已遍历完毕,继
续回溯至状态7,为顶点D赋2号色,与B=2的方案冲突,所以该分支被剪切,继续为顶点
D赋3号色,满足问题约束,转入状态14。
(7)在状态14结点为顶点E赋1号色,满足问题约束,此时搜索到达解空间树的叶子
结点,着色方案(1,2,3,3,1)是一个可行解,总共用了m =3种颜色。
(8)从状态15回溯至状态14,此时部分解为(1,2,3,3),已用了clr=3种颜色,继续在
状态14下探索E的着色方案并不能优化m 的值,所以这些虚线内所有分支都被剪枝,以减
少搜索空间。
(9)从状态14回溯至状态7,此时部分解为(1,2,3),已用了clr=3种颜色,所以状态7 
下其他分支被剪枝;继续回溯至状态4,此时部分解为(1,2),使用颜色数量为clr=2,m 存
在优化空间,但此时状态4已遍历了m =3种颜色,所以只能回溯至状态2。
(10)在状态2下可以为顶点B赋3号色转入状态18,在状态18下为顶点C赋1或者
3号色均不满足问题约束;因此,只能为顶点C赋2号色,但此时部分解已使用了clr=3种
颜色,无法优化m ,所以这些分支都被剪枝。
(11)此时,A=1的分支全部搜索完毕,回溯至状态1并赋A=2转入状态21继续进行
搜索,由于状态21下的分支不能满足问题约束或者无法优化m 都被剪枝,回溯至状态1并
赋A=3转入状态25继续进行搜索,具体过程与A=2类似,其扩展结点先后被剪枝。
经过搜索,可得m 的最优解为3,即至少需要3种颜色才能为图5.4所示的无向图进行
着色且相邻顶点颜色不同。
搜索图的m 着色问题的最小m 值伪代码如下。 
//color[]表示可行解,color[i]表示为第i 个顶点赋的颜色号
m←∞ 
search_min_clr(i){ 
clr ← cnt_clr(color[1..i -1]) //cnt_clr 用于统计部分解中颜色数量 
if clr>=m then //部分解使用颜色数量大于或等于最优解m 
return //剪枝回溯至上一个状态 
end if 
if i>n and clr<m then //涂色完成且使用颜色小于最优解,记录最优解至m 中 
m ← clr 
return //回溯至上一个状态 
end if 
for j ← 1 to m

88 算法设计与问题求解(第2版·微课版) 
color[i]← j 
if judge(i) then //judge 用于判断任何相邻顶点颜色是否不同,否则剪枝 
search_min_clr(i+1) //搜索下一个顶点颜色 
end if 
color[i]←0 
end for 
} 
5.3.4 批处理作业调度问题
例5.4 设有n 个作业{1,2,…,n}要在两台机器上处理,每个作业要先在机器1上处
理,再在机器2上处理,请给出一种作业调度方案,使得所有任务完成的总时间最短。
解题思路:问题的目标是求批处理作业调度完成的最短时间,基本策略是尽量保证每
台机器的空闲时间最小。显然,第一台机器在调度作业时不受加工顺序制约,所以,第一台
机器不能空闲,否则会推迟作业在第二台机器上的开始加工时间。
以表5.2所示问题说明不同的作业调度顺序会影响完成的时间。
表5.2 批处理作业调度问题示例
作 业机器1加工时间机器2加工时间
J1 2 1 
J2 4 2 
J3 3 3 
1.调度方案一:J1→J2→J3 
从图5.6可知,完成所有任务所需时间为12。
图5.6 调度方案一完成任务所需时间
2.调度方案二:J3→J1→J2 
从图5.7可知完成所有任务所需时间为11。
显然,调度顺序决定最终完成工作的时间。为得到最优调度顺序,需要搜索所有可能的
调度方案。设共有n 个作业,未调度任何作业为状态1,在状态1下可以调度任一作业,当
确定状态1所调度的作业后,下一级结点只能从剩下的n-1个作业中选择,因此,解空间树
是一棵排列树,如图5.8所示。

第5章 回溯法 89 
图5.7 调度方案二完成任务所需时间
图5.8 解空间树
图5.8所示的排列树中包含了3种作业的全排列,只要搜索整棵解空间树,一定可以搜
索到最优调度方案。显然,全排列树随着问题规模增加解空间急剧膨胀,是一个典型的NP 
难问题。因此,回溯法只适用于求解小规模的问题,而且需要对解空间树进行剪枝以提高搜
索速度。
定义a[i]为第i 个作业在第一台机器上的加工时间,b[i]为第i 个作业在第二台机器
上的加工时间,m1[1..n]为机器1上已调度作业的结束时间,m2[1..n]为机器2上已调度作
业结束的结束时间,x[1..n]表示n 个作业的调度方案。根据图5.9可得作业在两台机器上
的结束时间。
根据图5.9可知第k 个作业在机器1、机器2上的结束时间分别为
m1[k]= m1[k -1]+a[x[k]] 
m2[k]= max(m1[k],m2[k -1])+b[x[k]] 
设当前搜索到的最优调度所需时间为tbest,当前搜索调度第k 个作业,剩余作业在机器
2上加工时间为sum,则可按如下规则剪枝。
剪枝规则:如果m2[k]+sum≥tbest,则剪枝,如图5.10所示。
批处理作业调度问题的回溯算法可表示如下。 
//输入: 第i 个作业在机器1 上的处理时间a[i],在机器2 上的处理时间b[i] 
//输出: 最优调度序列x0[1..n] 
//x[1..n],当前调度序列
//f[1..n],f[i]为1 代表第i 个作业已被处理,为0 代表未被处理