下面将使用回溯思想解决若干经典问题并通过它们来说明使用回溯的基本思路 什么叫回溯法 回溯是一种比较简单、比较常用的搜索策略。 它的基本思想是假设某问题的解决步骤可能有N步,且每一步的解决方法又可能有M种,那么就按照某种顺序依次试探每一步中的各种方法,一旦某一步的所有方法都失效,那么就返回上一步继续试探上一步骤的其他M−1种方法。简而言之就是从一条路往前走,能进则进,不能进则退回来,换一条路再试。 通常用回溯法解决问题的一般步骤为:首先,定义一个解空间,它包含问题的解,也就是每一步所采用的各种方法。然后利用适于搜索的方法组织解空间,再利用深度优先法搜索解空间。并利用限界函数避免移动到不可能产生解的子空间。这里问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯法的一个重要特性。而其中全面访问所有可能的情况又可以分为两种:一是不考虑给定问题的特有性质,按事先定好的顺序,依次运用规则,即盲目搜索的方法;另一种则考虑问题给定的特有性质,选用合适的规则,提高搜索的效率,即启发式的搜索。 下面简单举几个例子来阐释回溯法 ---- 迷 宫 问 题 ---- 迷宫问题是应用回溯法解决的典型问题。迷宫早出现在古希腊神话中。传说在远古时代,麦诺安帝国国王弥诺斯在克里特岛建造了一座有无数宫殿的迷宫,迷宫中道路曲折纵横,谁进去都别想出来,而且在迷宫的纵深处还有一个牛头怪。弥诺斯要求雅典每隔 9 年送 7 对童男童女到克里特岛。这些童男童女就是供奉给牛头怪吃的。 来自雅典的王子忒修斯立志要为民除害,于是他与当年选献的童男童女一起来到克里特岛,忒修斯上岸后爱上了美丽的阿里阿德涅公主。当公主知道忒修斯的使命后,就送给他一把魔剑和一个线球,以免忒修斯受到牛头怪的伤害。 勇敢的忒修斯一进入迷宫,就将线球的一端拴在迷宫的入口处,然后放开线团,沿着曲折复杂的通道,向迷宫深处走去。后,他终于找到了牛头怪,并用阿里阿德涅公主给的剑,奋力杀死了牛头怪。然后,他带着童男童女,顺着线路走出了迷宫。在阿里阿德涅公主的帮助下,忒修斯等人一起逃出了克里特岛。 现假设迷宫由一系列交叉路口组成,从一个入口进入后可以沿着 3 个方向走:向左、向前或者向右。迷宫的路径由一系列标识路口的序号组成。为了求解迷宫问题,需要用到回溯的方法,当沿着某一路径一步步走向出口却发现进入死胡同之时,就回溯一步或多步,寻找其他可走的路径。 如下图所示为一个迷宫。从入口 1 的位置出发,沿东走到节点 2 时发现有两条路。标记节点 2,然后继续向东走,直到走到尽头无路可走时回溯到节点 2 尝试另外一种可能的走法,即向右转,沿南走到节点4,这时有3个选择,可以向右、向前或者向左。结果发现向左或向右都走不通,则再次退回到节点 4 向前,沿南走到节点 5。如此继续下去,则可以终到达出口 10 的位置。下面给出了求解迷宫问题的示例程序。 迷宫
由此例可以简单总结出应用回溯法的一般思路。即首先必须明确定义问题的解空间。而问题的解空间应当至少包含该问题的一个解。对于迷宫问题,各路口的集合就是问题的解空间。对于每个节点遍历尝试的可能性多不超过 3 种,即左转、右转和向前,仅当这 3 种可能性都被否定时才回溯至前一状态。在定义了问题的解空间之后还应当考虑如何将解空间进行有效的组织,以使得回溯法能够方便地搜索这些子空间中的节点。在必要的时候还应当注意优化搜索的策略以提高算法的实时性。 当以上准备完成之后就从出发点开始,以深度优先的方式对整个解空间进行搜索。该出发点随即被更新为当前的扩展节点。也就是从一个可能的路径进行深入以产生下一个新的节点,并将新的节点更新为扩展节点。一旦当前的扩展节点既不能得出整个问题的一个解,也不能再继续向更深的方向进行搜索,那么就返回上一个节点并将上一个节点重新更新为新的扩展节点,再尝试另一种可能性。回溯法正是采用这种工作方式以递归为基础在解空间内开展系统的搜索工作,直到求出问题的解或者表明问题无解为止。需要说明的是因为回溯法是对解空间的深度优先搜索,所以可以考虑使用树结构的递归遍历方式完成搜索工作。当然这并非是唯一的途径,也可以考虑使用树结构的非递归遍历方法,那样整个回溯过程将以迭代的形式完成。 ---- 八皇后问题 ---- 八皇后问题是一个古老而著名的问题,它是回溯法的典型例题。该问题早是由德国棋手马克斯•贝瑟尔(Max Bezzel)于 1848 年提出。之后陆续有数学家对其进行研究,其中包括德国数学家卡尔•弗里德里希•高斯(Karl Friedrich Gauss)和格奥尔格•康托(Georg Cantor),该文是这样描述的:在 8 行 8 列的国际象棋棋盘上摆放着 8 个皇后。若 2 个皇后位于同一行、同一列或同一对角线上,则称它们为互相攻击。在国际象棋中皇后是强大的棋子,因为它的攻击范围大,下图显示了一个皇后的攻击范围。
现在要求使这 8 个皇后不能相互攻击,即任意 2 个皇后都不能处于同一行、同一列或同一对角线上,问有多少种摆法。高斯认为有 76 种方案。1854 年在柏林的象棋杂志上不同的作者发表了 40 种不同的解,后来有人用图论的方法解出 92 种结果。现代教学中,把八皇后问题当成一个经典递归算法例题。下图显示了两种 8 个皇后不相互攻击的情况。
现在来看如何使用回溯法解决八皇后问题。这个算法将在棋盘上一列一列地摆放皇后直到 8 个皇后在不相互攻击的情况下都被摆放在棋盘上,算法便终止。当一个新加入的皇后因为与已经存在的皇后之间相互攻击而不能被摆在棋盘上时,算法便发生回溯。一旦发生这种情况,就试图把后放在棋盘上的皇后移动到其他地方。这样做是为了让新加入的皇后能够在不与其他皇后相互攻击的情况下被摆放在棋盘的适当位置上。 如图下图的情况(需要发生回溯的情况),尽管第 7 个皇后不会与已经放在棋盘上的任何一个皇后发生攻击,但仍然需要将它移除并发生回溯,因为无法为第 8 个皇后在棋盘上找到合适的位置。 算法的回溯部分将尝试移动第 7 个皇后到第 7 列的另外一点来为第 8 个皇后在第 8 列寻找一个合适的位置。如果第7个皇后由于在第7列找不到合适的位置而无法被移动,那么算法就必须去掉它并回溯到第 6 列的皇后。终算法不断重复着摆放皇后和回溯的过程直到找到问题的解为止。
由于回溯法也是在试图搜索整个解空间中的所有可能的选择,于是有人会误认为回溯法与穷举法差不多,但事实上回溯法要较穷举法在效率上高出很多。这里就以一种简单的估算方法来对八皇后问题进行一下分析。首先采用穷举法,那么可以容易得出该问题解空间的节点总数为 109601。然后在使用回溯法的前提下随机选取 20 条路径,分别估计它们的节点个数并求得总数的平均值。因为已知八皇后问题共有 92 种解,所以选择 20 种随机路径进行估计所得出的结果已经可以较为接近实际数值。经过计算得出回溯法产生的节点数的平均值约为 1740,这相对于 109601 不足 2%。可见回溯法作为一种跳跃性和系统性相结合的搜索方法是具有较高效率的。 下面给出了求解八皇后问题的示例程序。
相关图书
《算法之美:隐匿在数据 结构背后的原理(C++版)》 45个常用算法、22个经典问题 点拨面试热点,生活化实例详解 通俗易懂,系统全面 左飞 著
2016年3月出版
√ 探秘算法世界、求索数据结构之道 √ 汇集经典问题、畅享编程技法之趣 √ 点拨求职热点、敲开业界名企之门