回溯法
- 有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最优解时,往往使用回溯法。
- 回溯法的基本做法就是搜索,或者是一种能避免不必要搜索的暴力搜索法,这种方法适用于一些组合数相当大的问题。
回溯法在问题的解空间中,按深度优先策略,从根节点出发搜索解空间树,算法搜索到解空间中的任意一点时,首先进行判断该节点是不是包含问题的解,如果是不包含的,那么我们进行剪枝过程(就是跳过对该节点为根的子树的搜索,逐层向根节点进行回溯)否则,进行该子树,继续采用深度优先策略进行搜索。
问题的解空间
问题的解向量:回溯法希望一个问题的解能够表示成一个n元式(x1,x2,…xn)
显约束:对分量xi的取值进行限定
隐约束:为满足问题的解对不同分量之间施加的约束。
解空间:对于一个问题的实例,解向量满足显约束条件的所有多元组,构成了该实例的一个解空间。
PS:同一问题可以有多种比奥斯,有些比表示方法更简单,所需表示的状态空间更小(存储量少,搜索方法简单)
下面结合一个例子,来分清楚这些概念:
n=3时的0-1背包问题用完全二叉树表示的解空间:
生成问题状态的基本方法
扩展节点:一个正在产生儿子的节点称为扩展节点
活节点:一个自身已生成但其儿子还没有全部生成的节点称为活节点
死节点:一个所有儿子已经产生的节点称做死节点
深度优先的问题状态生成法:如果对一个扩展节点R,一旦产生了它的一个儿子C,就把C当做新的扩展节点,在完成对子树C(以C为根的子树)的穷尽搜索之后,将R重新变成扩展节点,继续生成R的下一个儿子(如果存在)
广度优先的问题状态生成法:在一个扩展节点变成死节点之前,它一直是扩展节点
回溯法:为了避免生成那些不可能产生最佳解的问题状态,要不断地利用限界函数(bounding function)来剪枝那些实际上不可能产生所需解的活节点,以减少问题的计算量。具有限界函数的深度优先法称为回溯法。(回溯法 = 穷举 +剪枝)
回溯法的基本思想
- 针对所给问题,定义问题的解空间
- 确定易于搜索的解空间结构
以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索
两个常用的剪枝函数:
1. 约束函数: 在扩展节点处减去不满足约束的子树
2. 限界函数: 减去得不到最优解子树
用回溯法解题的一个显著特征就是在搜索过程中动态产生问题的解空间。在任何死后,算法只保存了从根节点到当前扩展节点的路径。如果解空间树中从根节点到叶节点的最长路径的长度为h(n),则回溯法所需的计算空间通常为O(h(n))。而显式的存储整个解空间则需要O(2^h(n))或O(h(n)!)内存空间。
递归回溯
回溯法对解空间做深度优先搜索,因此,在一般情况下用递归方法实现回溯法。
针对N叉树的递归回溯方法
void backtrack(int t)
{
if(t > n)
{
//到达叶子节点,将结果输出
output(x);
}
else
{
//遍历节点t所有子节点
for(int i = f(n, t), i <= g(n, t); i ++)
{
x[t] = h[i];
//如果不满足剪枝条件,则继续遍历
if(constraint(t) && bound(t))
backtrack(t+