本文主要是介绍分支限界与回溯法对比,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
分支限界法类似于回溯法,也是一种在问题的解空间树 T 上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出 T 中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。 由于求解目标不同,导致分支限界法与回溯法在解空间树T上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树T。分支限界法的搜索策略是:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展对点。为了有效地选择下一扩展结点,以加速搜索的进程,在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一棵有序树,常见的有子集树和排列树。在搜索问题的解空间树时,分支限界法与回溯法对当前扩展结点所使用的扩展方式不同。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,那些导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被子加入活结点表中。此后,从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所求的解或活结点表为空时为止。
有一些问题其实无论用回溯法还是分支限界法都可以得到很好的解决,但是另外一些则不然。也许我们需要具体一些的分析——到底何时使用分支限界而何时使用回溯呢?下表列出了回溯法和分支限界法的一些区别:
方法 | 对解空间树的搜索方式 | 存储结点的常用数据结构 | 结点存储特性 | 常用应用 |
回溯法 | 深度优先搜索 | 堆栈 | 活结点的所有可行子结点被遍历后才被从栈中弹出 | 找出满足约束条件的所有解 |
分支限界法 | 广度优先或最小消耗优先搜索 | 队列、优先队列 | 每个结点只有一次成为活结点的机会 | 找出满足约束条件的一个解或特定意义下的最优解 |
A、一个比较适合采用回溯法解决的问题——n后问题
在n*n的国际象棋棋盘上摆下n个后,使所有的后都不能攻击到对方,找出所有符合要求的情况。
n后问题的解空间树是一棵排列树,一旦一种组合是一种解,则解与解之间不存在优劣的分别。直到搜索到叶节点时才能确定出一组解。这时我们用回溯法可以系统地搜索问题的全部解,而且由于解空间树是排列树的特性,代码的编写十分容易,在最坏的情况下,堆栈的深度不会超过n。如果我们采取分支限界法,在解空间树的第一层就会产生n个活结点,如果不考虑剪枝,将在第二层产生n*(n-1)个活节点,如此下去对队列空间的要求太高。n后问题不适合使用分支限界法处理的根源是它需要找处所有解的组合,而不是某种最优解(事实上也没有最优解可言)。形象一点来说,如果让我们自己人工解决n后问题,我们的思路应该是放一个后,看冲不冲突,不冲突的话放下一个,冲突的话改变位置,这样可以只用一个棋盘而且有规律地找到所有符合条件的情况,这正是回溯法的模拟过程。然而分支限界法则可以被这样表述。拿来一个棋盘,摆下一只后;再拿一个棋盘,再摆一只;待到每个棋盘都有一只后以后,每个棋盘又被分解成更多的盘面...,这样,棋盘越来越多,但是由于解和解(包括局部解)之间缺乏因果和限制的联系。棋盘之间并不能根据对方的信息获得什么,这显然是对资源的浪费。
B、一个既可以采用回溯法也可以采用分支限界法解决的问题——0-1背包问题
给定若干物品的重量和价值,以及一个背包的容量上限。求出一种方案使的背包中存放物品的价值最高。这个问题除了可以考虑回溯法和分支限界法之外,还可以用动态规划的方法解决,但在这里我们主要讨论前两种方法的对比。
0-1背包问题的解空间树是一棵子集树,所要求的解具有最优性质。
如果我们采用回溯法解决这个问题,我们采用如下的搜索策略:只要一个结点的左儿子结点是一个可行结点就搜索其左子树;而对于右子树,我们需要用贪心算法构造一个上界函数[[这个函数表明这个结点的子树所能达到的可能的最大容量(因为只有将0-1背包问题改变为背包问题才可能利用贪心算法,因此这个上界函数在绝大多数情况下不会是上确界函数)],只在这个上界函数的值超过当前最优解时才进入搜索。随着搜索进程的推进,最优解不断得到加强,对搜索的限制就越来越严格。
如果我们采用分支限界法解决这个问题,同样需要用到贪心算法构造的上界函数,所不同的是,这个上界函数的作用不在于判断是否进入一个结点的子树继续搜索,因为在搜索到达叶节点之前,我们也无法知道已经得到的最优解是什么。在这里,我们用一个最大堆来实现活结点的优先队列,上界函数的值将作为优先级,这样一旦有一个叶结点成为扩展结点,就表明已经找到了最优解。
可以看出,用两种方法处理0-1背包问题都有一定的可行性,相比之下回溯法的思路容易理解一些,但是这是一个寻找最优解的问题,由于采用了优先队列处理,不同的结点不再像n后问题那样没有相互之间的牵制和联系,用分支限界法处理效果一样很好。
C、一个比较适合采用分支限界法解决的问题——布线问题
印刷电路板将布线区域划分成n*m个方格阵列。精确的电路布线问题要求确定连接方格a的中点到方格b的中点的最短布线方案。在布线时,电路只能沿直线或直角布线。为了避免线路相交,已布了线的方格做了封锁标记,其他线路不允许穿过被封锁的方格。
布线问题的解空间是一个图,适合采用队列式分支限界法来解决。从起始位置a开始将它作为第一个扩展结点。与该结点相邻并且可达的方格被加入到活结点队列中,并且将这些方格标记为1,表示它们到a的距离为1。接着从活结点队列中取出队首作为下一个扩展结点,并将与当前扩展结点相邻且未标记过的方格标记为2,并存入活节点队列。这个过程一直继续到算法搜索到目标方格b或活结点队列为空时为止(表示没有通路)。
现在来看上面两张图。左图演示了分支限界法的过程。最开始,队列中的活结点为标1的格子,随后经过一个轮次,活结点变为标2的格子,以此类推,一旦b方格成为活节点便表示找到了最优方案。为什么这条路径一定就是最短的呢?这是由于我们这个搜索过程的特点所决定的,假设存在一条由a至b的更短的路径,b结点一定会更早地被加入到活结点队列中并得到处理。
右图表示了a和b之间的最短布线路径。值得一提的是,在搜索的过程中我们虽然可以知道结点距起点的路径长度,却无法直接获得具体的路径描述。为了构造出具体的路径,我们需要从目标方格开始向起始方格回溯,逐步构造出最优解,也就是每次向标记距离比当前方格距离少1的相邻方格移动,直至到达起始方格时为止。
现在我们来考虑,为什么这个问题用回溯法来处理就是相当低效的。回溯法的搜索是依据深度优先的原则进行的,如果我们把上下左右四个方向规定一个固定的优先顺序去进行搜索,搜索会沿着某个路径一直进行下去直到碰壁才换到另一个子路径,但是我们最开始根本无法判断正确的路径方向是什么,这就造成了搜索的盲目和浪费。更为致命的是,即使我们搜索到了一条由a至b的路径,我们根本无法保证它就是所有路径中最短的,这要求我们必须把整个区域的所有路径逐一搜索后才能得到最优解。正因为如此,布线问题不适合用回溯法解决。
还有许多类似于布线的问题可以为分支限界法提供良好的展示空间。在今年10月底刚刚结束的ACM竞赛北京分赛区的比赛中,就出现一一个和布线问题十分相似的问题,可以用分支限界法取得很好的效果。
我们简单地描述一下这个问题。如左图,B1,B2,B3,B4代表一条蛇的身躯,其中B1是蛇头(蛇的身躯大小、起始位置以及障碍物的位置由输入获得),同样是在一个范围内搜索两个固定点蛇头和点(1,1)之间的最短路径。题目的要求相比于布线问题有一些简化的地方,比如终点的位置是固定的,而且只要求求出最短路径的长度,不要求写出具体的路径,也就是可以省出回溯起点的步骤,但是它也有一个需要做一定处理才能解决的推广,就是蛇所要移动的方向上除了不能是障碍物之外,还不能是自己身躯的一部分,比如蛇从左图的位置移动,不能向上或向右移动而只能移动到下方B1的位置形成右图的局面。
要做如何的处理才能解决这个问题呢?可以采取布线问题的解决框架,但是在每个结点处所出现的障碍物需要变化。如果我们仅仅在每个结点处把蛇身标记为与障碍物相同的记号固然是一种容易想到的思路,但是这样处理不利于从一个格子的障碍物情形推广到相邻格子的障碍物情形。比较有效的做法是将蛇身描述成一个定长队列,队列首是蛇尾而队列尾是蛇头(如果为了思路的连贯性,也可以反过来处理,但是这样应该使用双向队列),这样一来从一个方格推广到相邻方格的时候,我们只要对这个队列做一步处理就可以得到新的描述蛇身的队列。然后,可以用这个队列和方格本身的位置组成一个结构或类,在求解过程中依然使用队列式分支限界法,只不过队列中的元素是我们所定义的那个结构或类的对象。利用STL等一些泛型技术,这个过程还是比较容易实现的。
struct Position //描述一个位置的结构 { int posx,posy; }; struct SnakeNode //描述分支限界法队列中一个结点的结构 { Position square ;//方格位置信息 list<Position> snakeBody(snakeLength); //描述蛇身的队列,每一个格的蛇仍用Position描述 } ; |
这样初始时我们用queue<SnakeNode> nodeList(0)建立这个队列,之后的过程和布线问题就基本一致了。
原文地址:http://blog.163.com/d_cjiang/blog/static/11866648220100810948486/
这篇关于分支限界与回溯法对比的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!