回溯法简介
2012-05-20 15:53 youxin 阅读(1719) 评论(0) 编辑 收藏 举报回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
- 有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。
- 回溯法的基本做法是搜索,或是一种组织得井井有条的、能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。
- 回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解:如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
问题的解空间
- 应用回溯法解问题时,首先应明确定义问题的解空间。问题的解空间应至少包含问题的一个(最优)解。
- 问题的解向量:回溯法希望一个问题的解能够表示成一个n元式(x1,x2,…,xn)的形式
- 显约束:对分量xi的取值限定
- 隐约束:为满足问题的解而对不同分量之间施加的约束
- 解空间:对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该实例的一个解空间
- 注意:同一个问题可以有多种表示,有些表示方法更简单,所需表示的状态空间更小(存储量少,搜索方法简单)
- 例如,对于有n种可选物品的0-1背包问题,其解空间由长度为n的0-1向量组成
状态空间树的动态搜索
可能解----》可行解---》最优解
可能解:解空间的一个子集。
可行解:满足约束条件的解
最优解:使目标函数取极值的最优解。
在背包问题中,有2^n中可能解,其中有些是可行解,有些不是可行解。在可行解中,也只有一个或几个是最优解。有些问题不需要寻找最优解,例如后面的八后问题和图的着色问题,只要找出满足约束条件的可行解即可。
- 回溯法的基本步骤:
- (1)针对所给问题,定义问题的解空间;
- (2)确定易于搜索的解空间结构;
- (3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
- 常用剪枝函数:
- 用约束函数在扩展结点处剪去不满足约束的子树;
- 用限界函数剪去得不到最优解的子树。
- 关于复杂性:
- 用回溯法解题的一个显著特征是在搜索过程中动态产生问题的解空间。在任何时刻,算法只保存从根结点到当前扩展结点的路径。如果解空间树中从根结点到叶结点的最长路径的长度为h(n),则回溯法所需的计算空间通常为O(h(n))。而显式地存储整个解空间则需要O(2h(n))或O(h(n)!)内存空间。
- 剪枝函数
-
其一是使用约束函数,在扩展节点处剪去不满足约束的子树;
其二是用限界函数, “剪去”不能达到最优解的子树。
这两种函数统称为剪枝函数。
- 扩展结点:一个正在产生儿子的结点称为扩展结点 E_节点
- 活结点:一个自身已生成但其儿子还没有全部生成的节点称做活结点 L_节点
- 死结点:一个所有儿子已经产生的结点或不满足约束或目标函数的节点称做死结点 d_节点
- e节点必然是L节点
以d_节点作为根的子树,可以在搜索过程汇总删除。
- 深度优先的问题状态生成法:如果对一个扩展结点R,一旦产生了它的一个儿子C,就把C当做新的扩展结点。在完成对子树C(以C为根的子树)的穷尽搜索之后,将R重新变成扩展结点,继续生成R的下一个儿子(如果存在)
- 宽度优先的问题状态生成法:在一个扩展结点变成死结点前,它一直是扩展结点
- 回溯法:为了避免生成那些不可能产生最佳解的问题状态,要不断地利用限界函数(bounding function)来处死(剪枝)那些实际上不可能产生所需解的活结点,以减少问题的计算量。具有限界函数的深度优先生成法称为回溯法。(回溯法 = 穷举 + 剪枝)。
回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法。
回溯法基本思想:走不通,则回头。
void backtracking (int t) { if (t > n) { // 到达叶子结点,将结果输出 output (x); } else { // 遍历结点t的所有子结点,即枚举t所有可能的路径 // f(n,t)=下界;g(n,t)=上界; for (int i = f(n,t); i <= g(n,t); i ++ ) {// x[t] = h[i];//满足界限函数和约束函数 // 如果不满足剪枝条件,则继续遍历,进入下一层 if (constraint (t) && bound (t)) backtrack (t + 1); } } }
采用树的非递归深度优先遍历算法,可将回溯法表示为一个非递归迭代过程
// 针对N叉树的迭代回溯方法 void iterativeBacktrack () { int t = 1; while (t > 0) { //有路可走 if (f(n,t) <= g(n,t)) { // 遍历结点t的所有子结点 for (int i = f(n,t); i <= g(n,t); i ++) { x[t] = h(i); // 剪枝 if (constraint(t) && bound(t)) { // 找到问题的解,输出结果 if (solution(t)) { output(x); } else // 未找到,向更深层次遍历 t ++; } } } else { t--; } } }
回溯法通常在解空间树上进行搜索,一般依赖的两种数据结构:子集树和排列树
子集树(遍历子集树需O(2^n)计算时间):
一般有装载问题、符号三角形问题、0-1背包问题、最大团问题
void backtrack (int t) { if (t > n) // 到达叶子结点 output (x); else for (int i = 0;i <= 1;i ++) { x[t] = i; // 约束函数 if ( legal(t) ) backtrack( t+1 ); } }
排列树(遍历排列树需要O(n!)计算时间):
一般有批处理作业调度、n后问题、旅行售货员问题、圆排列问题、电路板排列问题
void backtrack (int t) { if (t > n) output(x); else for (int i = t;i <= n;i++) { // 完成全排列 swap(x[t], x[i]); if (legal(t)) backtrack(t+1); swap(x[t], x[i]); } }
其中f(n,t),g(n,t)表示当前扩展结点处未搜索过的子树的起始标号和终止标号, h(i)表示当前扩展节点处,x[t]第i个可选值constraint(t)和bound(t)是当前 扩展结点处的约束函数和限界函数。constraint(t)返回true时,在当前扩展结点 x[1:t]取值满足约束条件,否则不满足约束条件,可减去相应的子树。bound(t)返 回的值为true时,在当前扩展结点x[1:x]处取值未使目标函数越界,还需要由backtrack(t+1) 对其相应的子树进一步搜索。
参考: http://www.xinx.sdnu.edu.cn/sfzx/jpsykc/xlcad/xu05.html
http://blog.csdn.net/hguisu/article/details/7709276
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通