以下内容摘抄自《计算机算法设计与分析 王晓东著 配套的PPT》
有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最优解时,往往要使用回溯法。
回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。回溯法有“通用解题法”之称。
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意结点时,先判断该结点是否包含问题的解。若肯定不包含,则跳过对以该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先搜索策略搜索。
回溯法求问题的所有解时,要回溯到根,且根结点的所有子树都被搜索遍才结束。在它求问题的一个解时,只要搜索到问题的一个解就结束。
回溯法:以深度优先方式系统搜索问题解的算法,它适用于求解组合数较大的问题。
回溯法既有系统性又有跳跃性
它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。——系统性
算法搜索至解空间树的任一结点时,判断该结点为根的子树是否包含问题的解,如果肯定不包含,则跳过以该结点为根的子树的搜索,逐层向其祖先结点回溯。否则,进入该子树,继续深度优先的策略进行搜索。——跳跃性
复杂问题常常有很多的可能解,这些可能解构成了问题的解空间。解空间应该包括所有的可能解,也就是进行穷举的搜索空间。
确定正确的解空间很重要。如果没有确定正确的解空间就开始搜索,可能会增加很多重复解,或者根本就搜索不到正确的解。
为了用回溯法求解一个具有n个输入的问题,一般情况下,将其可能解表示为满足某个约束条件的等长向量X=(x1, x2, …, xn),其中分量xi (1≤i≤n)的取值范围是某个有限集合Si={ai1, ai2, …, airi}。
所有可能的解向量构成了问题的解空间。
问题的解空间一般用解空间树(Solution Space Trees,也称状态空间树)的方式组织。树的根结点位于第1层,表示搜索的初始状态,第2层的结点表示对解向量的第一个分量做出选择后到达的状态,第1层到第2层的边上标出对第一个分量选择的结果。依此类推,从树的根结点到叶子结点的路径就构成了解空间的一个可能解
生成问题状态的基本方法
扩展结点:一个正在产生儿子的结点称为扩展结点
活结点:一个自身已生成但其儿子还没有全部生成的节点称做活结点
死结点:一个所有儿子已经产生的结点称做死结点
深度优先的问题状态生成法:如果对一个扩展结点R,一旦产生了它的一个儿子C,就把C当做新的扩展结点。在完成对子树C(以C为根的子树)的穷尽搜索之后,将R重新变成扩展结点,继续生成R的下一个儿子(如果存在)。
宽度优先的问题状态生成法:在一个扩展结点变成死结点之前,它一直是扩展结点。
回溯法:为了避免生成那些不可能产生最佳解的问题状态,要不断地利用限界函数(bounding function)来处死那些实际上不可能产生所需解的活结点,以减少问题的计算量。具有限界函数的深度优先生成法称为回溯法。
回溯法的基本思想
从开始结点(根结点)出发,按照深度优先策略遍历解空间树,搜索满足约束条件的解。
这个开始结点成为活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为新的活结点,并成为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向扩展,则当前扩展结点就成为死结点。
此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点;直到找到一个解或全部解。
在搜索至树中任一结点时,先判断该结点对应的部分解是否满足约束条件,或者是否超出目标函数的界,也就是判断该结点是否包含问题的(最优)解,如果肯定不包含,则跳过对以该结点为根的子树的搜索,即所谓剪枝(Pruning);否则,进入以该结点为根的子树,继续按照深度优先策略搜索。
回溯法的基本步骤
(1)针对所给问题,定义问题的解空间;
(2)确定易于搜索的解空间结构;
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
回溯法的搜索过程涉及的结点(称为搜索空间)只是整个解空间树的一部分,在搜索过程中,通常采用两种策略避免无效搜索:
(1)用约束条件剪去得不到可行解的子树;
(2)用目标函数剪去得不到最优解的子树。
这两类函数统称为剪枝函数(Pruning Function)。需要注意的是,问题的解空间树是虚拟的,并不需要在算法运行时构造一棵真正的树结构,只需要存储从根结点到当前结点的路径。
回溯法解题的一个显著特征是在搜索过程中动态产生问题的解空间。在任何时刻,算法只保存从根结点到当前扩展结点的路径。如果解空间树中从根结点到叶结点的最长路径的长度为h(n),则回溯法所需的计算空间通常为O(h(n))。而显式地存储整个解空间则需要O(2h(n))或O(h(n)!)内存空间。
回溯法对解空间做深度优先搜索。在一般情况下用递归方法实现回溯法:
void backtrack (int t) //搜索到树的第t层
{//由第t层向第t + 1层扩展,确定x[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);
}
}
例如全排列的实现:
void backtrack (int t)
{
if (t>n) output(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]);
}
}