算法——回溯法

简介:

回溯法

回溯法有“通用的解题法”之称。用它可以系统地搜索一个问题的所有解或任一解。回溯法是一种即带有系统性又带有跳跃性的搜索算法。它在问题的解空间树中,按深度优先策略,从根节点出发搜索解空间树。算法搜索至解空间树的任一结点时,先判断该节点是否包含问题的解。如果不包含,则跳过对以该节点为根的子树的搜索,逐层向其它祖先节点回溯。否则,进入该子树,继续按照深度优先策略搜索。回溯法求问题的所有解时,要回溯到根,且根节点的所有子树都已被搜索遍才结束。回溯法求问题的一个解时,只要搜索到问题的一个解就可结束。这种以深度优先方式系统搜索问题的算法称为回溯法,它是用于解组合数大的问题。


 

问题的解空间

用回溯法解问题时,应明确定义问题的解空间。问题的解空间至少包含问题的一个(最优)解。例如对于有n种可选择物品的0-1背包问题,其解空间由长度为n的0-1向量组成。该解空间包含对变量的所有可能的0-1赋值。例如n=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背包问题,可用一颗完全的二叉树表示其解空间,如下图。

解空间树的第i层到第i+1层边上的标号给出了变量的值。从树根到叶子的任一路径表示解空间中的一个元素。例如,从根节点到节点H的路径相当与解空间中的元素(1,1,1)。


 

回溯法的基本思想

确定了解空间的组织结构后,回溯法从根节点出发,以深度优先搜索方式搜索整个解空间。回溯法以这种工作方式递归地在解空间中搜索,直到找到所要求的解或解空间所有解都被遍历过为止。
回溯法搜索解空间树时,通常采用两种策略避免无效搜索,提高回溯法的搜索效率。其一是用约束函数在当前节点(扩展节点)处剪去不满足约束的子树;其二是用限界函数剪去得不到最优解的子树。这两类函数统称为剪枝函数。

回溯法解题通常包含以下三个步骤:

1.针对所给问题,定义问题的解空间;
2.确定易于搜索的解空间结构;
3.以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。


 

递归回溯

回溯法对解空间作深度优先搜索,因此在一般情况下可用递归函数来实现回溯法。一般函数结构如下:

复制代码
 1 void Bcktrack(int t) //参数t表示当前递归深度
 2 {
 3     if(t>n)Output(x); //遍历到解,则将解输出或其他处理
 4     else
 5     {
 6         //f(n,t)和g(n,t)表示当前节点(扩展节点)处未搜索过的子树的起始编号和中指编号
 7         for(int i=f(n,t);i<=g(n,t);i++)    
 8         {
 9             x[t]=h(i);    //h(i)表示当前节点(扩展节点)处x[i]的第i个可选值
10             if(Constarint(t)&&Bound(t)) //剪枝函数:约束函数,限界函数
11                 Bcktrack(t+1);
12         }
13     }
14 }
复制代码

 


迭代回溯

采用树的非递归深度优先算法遍历算法,也可以将回溯法表示为一个非递归的迭代过程。一般函数形式如下:

复制代码
 1 void IterativeBacktrack(void)
 2 {
 3     int t=1;    //t表示当前递归深度
 4     while(t>0)
 5     {
 6         if(f(n,t)<=g(n,t))
 7         {
 8             //f(n,t)和g(n,t)表示当前节点(扩展节点)处未搜索过的子树的起始编号和中指编号
 9             for(int i=f(n,t);i<=g(n,t);i++)
10             {
11                 x[t]=h(i);
12                 if(Constraint(t)&&Bound(t))    //剪枝函数:约束函数,限界函数
13                 {
14                     if(Solution(t)) Output(x);    //判断当前节点是否已经得到问题的可行解
15                     else t++
16                 }
17             }
18         }
19         else t--;
20     }
21 }
复制代码

 


 

算法复杂度

用回溯法解体的一个显著特征是在搜索过程中动态产生问题的解空间。在任何时刻,算法只保存从根节点到当前节点(扩展节点)的路径。如果解空间树从根节点到叶节点的最长路径的长度为h(n),则回溯法所需的计算空间通常为O(h(n))。而显式地存储整个解空间则需要O(2^h(n))或O(h(n)!)内存空间。


 

经典例子:八皇后问题

八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当 n = 1 或 n ≥ 4 时问题有解。

以下是n后问题的例子代码:

n后问题

 

  测试结果:带入参数8,得到92种解,这个符合答案。

 

参考资料

维基百科:八皇后问题 http://zh.wikipedia.org/wiki/%E5%85%AB%E7%9A%87%E5%90%8E%E9%97%AE%E9%A2%98

计算机算法设计与分析/王晓东编著。-3版。-北京:电子工业出版社,2007.5

本文转自 Ron Ngai 博客园博客,原文链接:http://www.cnblogs.com/rond/archive/2012/07/10/2584044.html  ,如需转载请自行联系原作者

相关文章
|
6月前
|
算法 定位技术 C++
基本算法-回溯法(迷宫问题)
基本算法-回溯法(迷宫问题)
167 0
|
6月前
|
存储 算法 容器
精选算法题(1)——枚举符合要求的算术表达式(DFS、回溯法)
精选算法题(1)——枚举符合要求的算术表达式(DFS、回溯法)
|
10月前
|
存储 算法 索引
从小白开始刷算法 回溯法篇 leetcode.78
从小白开始刷算法 回溯法篇 leetcode.78
|
10月前
|
存储 机器学习/深度学习 算法
从小白开始刷算法 回溯法篇 leetcode.22
从小白开始刷算法 回溯法篇 leetcode.22
|
12月前
|
算法 JavaScript 搜索推荐
JS算法之回溯法
何为回溯法 集合的组合、排列 利用回溯算法解决其他问题
|
算法
算法笔记之回溯法(3)
旅行商问题 问题描述 假设有5个点,这五个点之间是用无向边来连接的,但是每一个边是有权重的,这实际上是一个无向带权图。我们希望在最小权重的情况下走过这5个点,且不重复,那应该怎样来实现呢? 算法设计 定义问题的解空间:问题解的形式为n元组{x1,x2,...,xi,...,xn},分量xi表示第i个要去的旅游景点编号,景点的集合为S={1,2,...,N}。
1023 0
|
算法 定位技术
算法笔记之回溯法(2)
着色问题 问题分析 假设地图共有7个区域,分别是A/B/C/D/E/F/G,对上面顺序进行编号,每个区域用一个结点表示,相邻的区域有连线,那么地图就转化成一个无向连接图。 算法设计 定义问题的解空间。
932 0
|
算法
算法笔记之回溯法(1)
回溯法 回溯法的思想是:能进则进,进不了换,换不了退。隐约束指对能否得到问题的可行解和最优解做出的约束。隐约束包括约束函数和限界函数。 关键步骤是: 定义解空间; 确定解空间的组织结构(子集树、排列数、m叉树等); 搜索解空间。
1412 0