回溯算法是找到一些计算问题的全部或者部分解的一种普适性的算法,其递增得增加候选者到解集合中,而当发现候选者c不能成功找到解决路径时就果断丢弃候选者
c。
最经典的回溯算法的例子是8个皇后的问题,该问题是在一个标准的国际象棋棋盘上放置8个皇后,并且使她们不能相互攻击。在普通的回溯方法中,部分候选者先在
开始的k行安置k个皇后,其都在不同的行和不同的列。任何包含可以互相攻击的皇后的解都可以被丢弃,因为它们不可能能组成一个合适的解。 回溯算法只能被应用
在满足特定条件的问题下。这些问题必须承认“部分候选者解”这一概念,以及以一个相对比较快的测试出其是否可能是一个合适的解。例如,想要在一个无序的表中定
位一个给定的值这种问题,回溯算法就是没有任何意义的。而当回溯算法可用的时候,通常其会比无理的穷举法要快得多,因为其能够在一次简单的测试中排除很多的候
选者。
回溯算法对解决约束满足问题的解决是非常有用的,例如纵横字谜、文字算法、Sudoku以及其他的一些算法。其通常也是最方便的(即使不是最有效的)技术来解析像
背包问题和其他组合最优化的问题。其也是一些所谓的逻辑编程语言的基础,例如Icon、Planner和Prolog。回溯算法也被用在Mediawiki软件的差分引擎上。
回溯算法依赖于用户给定“黑盒步骤”,该步骤定义了需要解决的问题、部分解的本质以及他们如何扩展到整个解。因此其是一个更加类似于元启发式的,而不是一个特定的算法。尽管,不像许多其他的元启发式,其承诺在一定的时间内找到有限的问题的全部解。
算法的描述
回溯算法列举了部分解的一个集合,原则上说,是可以通过多样的途径来计算出给定问题的全部可能的解。这个完成是递增得完成的,通过一系列的扩展的步骤。
概念上说,部分解是树结构的节点,可能搜索的树。每一个部分解是都通过一个简单的扩展后的候选者的父亲。而树的叶子则是那些不能在扩展的候选者。
回溯算法利用递归的方法遍历这样一棵树,从根节点往下,以深度优先的顺序。在每一个节点c,算法检查c是否能够完成一个合适的解。如果不是的话,那么以c为根节点的子树就不可能是可能的解,因而被跳过。否则的话,(1)算法检查c自己是不是一个合适的解,如果是的话就报告给用户。(2)递归的列举c的所有子树。测试及子树是根据用户给定的步骤定义的。
因此,实际遍历的树只是潜在可能的树的一个子集而已。算法的开销就是实际树遍历的节点数乘以获取以及处理每个节点的时间。
伪码
为了将回溯算法利用到特定的问题,必须为算法特定的问题实例提供数据P,以及6个步骤参数,root、reject、accept、first、next以及output。这些步骤应该将数据P作为参数并操作如下:
1、root('P'):返回在搜索树的根节点的所有的可能解
2、reject(P,c):只有当c不能完成解时,返回true
3、accept(P,c):只有当c是一个解是,返回true
4、first(P,c):产生候选者c的一个扩展
5、next(P,s):在扩展s之后,产生候选者的下一个扩展
6、output(P,c):利用P中的c
因此回溯算法就是bt( root(P) )
procedure bt(c)
if reject(P,c) then return
if accept(P,c) then output(P,c)
s <- first(P,c)
while s != 空集 do
bt(s)
s <- next(P,s)
c。
最经典的回溯算法的例子是8个皇后的问题,该问题是在一个标准的国际象棋棋盘上放置8个皇后,并且使她们不能相互攻击。在普通的回溯方法中,部分候选者先在
开始的k行安置k个皇后,其都在不同的行和不同的列。任何包含可以互相攻击的皇后的解都可以被丢弃,因为它们不可能能组成一个合适的解。 回溯算法只能被应用
在满足特定条件的问题下。这些问题必须承认“部分候选者解”这一概念,以及以一个相对比较快的测试出其是否可能是一个合适的解。例如,想要在一个无序的表中定
位一个给定的值这种问题,回溯算法就是没有任何意义的。而当回溯算法可用的时候,通常其会比无理的穷举法要快得多,因为其能够在一次简单的测试中排除很多的候
选者。
回溯算法对解决约束满足问题的解决是非常有用的,例如纵横字谜、文字算法、Sudoku以及其他的一些算法。其通常也是最方便的(即使不是最有效的)技术来解析像
背包问题和其他组合最优化的问题。其也是一些所谓的逻辑编程语言的基础,例如Icon、Planner和Prolog。回溯算法也被用在Mediawiki软件的差分引擎上。
回溯算法依赖于用户给定“黑盒步骤”,该步骤定义了需要解决的问题、部分解的本质以及他们如何扩展到整个解。因此其是一个更加类似于元启发式的,而不是一个特定的算法。尽管,不像许多其他的元启发式,其承诺在一定的时间内找到有限的问题的全部解。
算法的描述
回溯算法列举了部分解的一个集合,原则上说,是可以通过多样的途径来计算出给定问题的全部可能的解。这个完成是递增得完成的,通过一系列的扩展的步骤。
概念上说,部分解是树结构的节点,可能搜索的树。每一个部分解是都通过一个简单的扩展后的候选者的父亲。而树的叶子则是那些不能在扩展的候选者。
回溯算法利用递归的方法遍历这样一棵树,从根节点往下,以深度优先的顺序。在每一个节点c,算法检查c是否能够完成一个合适的解。如果不是的话,那么以c为根节点的子树就不可能是可能的解,因而被跳过。否则的话,(1)算法检查c自己是不是一个合适的解,如果是的话就报告给用户。(2)递归的列举c的所有子树。测试及子树是根据用户给定的步骤定义的。
因此,实际遍历的树只是潜在可能的树的一个子集而已。算法的开销就是实际树遍历的节点数乘以获取以及处理每个节点的时间。
伪码
为了将回溯算法利用到特定的问题,必须为算法特定的问题实例提供数据P,以及6个步骤参数,root、reject、accept、first、next以及output。这些步骤应该将数据P作为参数并操作如下:
1、root('P'):返回在搜索树的根节点的所有的可能解
2、reject(P,c):只有当c不能完成解时,返回true
3、accept(P,c):只有当c是一个解是,返回true
4、first(P,c):产生候选者c的一个扩展
5、next(P,s):在扩展s之后,产生候选者的下一个扩展
6、output(P,c):利用P中的c
因此回溯算法就是bt( root(P) )
procedure bt(c)
if reject(P,c) then return
if accept(P,c) then output(P,c)
s <- first(P,c)
while s != 空集 do
bt(s)
s <- next(P,s)
有人说,回溯实际上是递归的展开,但实际上。两者的指导思想并不一致。
打个比方吧,递归法好比是一个军队要通过一个迷宫,到了第一个分岔口,有3条路,将军命令3个小队分别去探哪条路能到出口,3个小队沿着3条路分别前进,各自到达了路上的下一个分岔口,于是小队长再分派人手各自去探路——只要人手足够(对照而言,就是计算机的堆栈足够),最后必将有人找到出口,从这人开始只要层层上报直属领导,最后,将军将得到一条通路。所不同的是,计算机的递归法是把这个并行过程串行化了。
而回溯法则是一个人走迷宫的思维模拟——他只能寄希望于自己的记忆力,如果他没有办法在分岔口留下标记(电视里一演到什么迷宫寻宝,总有恶人去改好人的标记)。