递归回溯
测试
回溯可以被认为是选择性的<一个target="_blank" rel="nofollow" href="//www.parkandroid.com/wiki/trees-basic/">树/图一个>遍历方法。树是表示某个初始起始位置(父节点)和最终目标状态(其中一个叶子)的一种方式。回溯使我们能够处理这样的情况:一种原始的蛮力方法可能会爆炸成无数可供考虑的选择。回溯是一种精细的蛮力。在每个节点上,我们排除明显不可能的选项,然后递归地只检查那些有可能的选项。这样,在树的每个深度,我们都减少了将来需要考虑的选择数量。
假设你碰到了一片坏叶子。通过撤销最近的选择,并尝试该选项集中的下一个选项,您可以回溯以继续搜索一个好的叶子。如果您没有选项了,就撤销让您到这里的选项,并在该节点尝试另一个选项。如果您在根节点上没有任何选择,那么就没有好的叶子可以找到。
回溯是解决约束满足问题的关键,如填字游戏、口头算术、数独和许多其他谜题。它还可用于求解背包问题、文本解析等组合优化问题。
回溯的有趣之处在于,我们只回溯到需要到达前一个决策点的地方,而这个决策点是一个尚未探索的替代方案。一般来说,这将是在最近的决策点。最终,越来越多的这些决策点将被充分探索,我们将不得不越来越远地回溯。如果我们一路回溯到我们的初始状态,并从那里探索了所有的替代方案,我们可以得出结论,特定的问题是不可解决的。在这种情况下,我们已经完成了穷举递归的所有工作,并且知道不存在可行的解。
排列
给定一组项的排列是元素的某种重排。它可以显示一个数组 一个的长度 N有 n!排列。例如数组['J','O','N']有以下排列:
1 2 3 4 5 6 |
|
这里应用的回溯算法相当直接,因为调用不受任何约束。我们不是从一个不需要的结果返回,我们只是返回到以前的状态,而不过滤掉不需要的输出。下面的图片和代码对此进行了更详细的阐述:
如图所示,算法是基于交换的。当实现时,回溯部分将在打印排列后将项目交换回它们以前的位置。
下面的python代码展示了如何做到这一点:
12 3 4 5 6 7 8 9 10 11 12 13 |
|
它输出
1 2 3 4 5 6 7 |
|
迷你数独
数独是一种逻辑谜题,其目标是用数字填充网格,以便组成网格的每一列、每一行和每一个子网格包含来自网格的所有数字 1来 n.同一单个整数不能在同一行、同一列或同一子网格中出现两次。
让我们看一个简化的 3.×3.迷你版的原始数独拼图。在这里,每个单元格都包含一个子网格 1元素与是平凡的不同。这意味着我们只需要检查行和列是否包含整数 1, 2和 3.没有重复。
下面是一个迷你数独谜题的例子(左)和它的解决方案(右)
现在应该很明显,递归回溯这个谜题已经成熟。现在让我们列出将帮助我们解决它的伪代码。
12 3 4 5 6 7 8 9 10 11 12 13 |
|
上面的代码是回溯的一个经典示例。如果给定的板董事会
应该在函数之外。
实现一个实际的迷你 3.×3.解算器,并使用它打印解\s到下面的难题
python中的示例解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17日18 19 20 21日22日23日24日25日26日27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64从itertools进口*从复制进口复制defis_distinct(列表):辅助函数is_solved检查列表中的所有元素是否不同(忽略了0)“‘使用=[]为我在列表:如果我==0:继续如果我在使用:返回假使用.附加(我)返回真正的defis_valid(brd):" '检查3x3迷你数独是否有效' "为我在范围(3.):行=[brd[我] [0],brd[我] [1],brd[我] [2]]如果不is_distinct(行):返回假上校=[brd[0] [我],brd[1] [我],brd[2] [我]]如果不is_distinct(上校):返回假返回真正的def解决(brd,清空=9):“‘解决了mini-Sudoku复兴开发银行是董事会Empty是空单元格的数量“‘如果清空==0:#基本情况返回is_valid(brd)为行,上校在产品(范围(3.),重复=2):#遍历每个单元格细胞=brd[行] [上校]如果细胞! =0:#如果不是空跳转继续brd2=复制(brd)为测试在[1,2,3.]:brd2[行] [上校]=测试如果is_valid(brd2)和解决(brd2,清空-1):返回真正的#回溯brd2[行] [上校]=0返回假董事会=[[0,0,0],[1,0,0],[0,3.,1]]解决(董事会,9-3.)为行在董事会:#打印解决方案打印行
它输出
1 2 3 4>>> [3,1,2] [1,2,3] [2,3,1]
路径找到
回溯的一个更实用和更广为人知的例子是寻径。例如,一个机器人可以在迷宫中规划自己的路径,通过重复走这些路径,并从那些没有通向任何地方的路径返回。当然,这需要我们以算法兼容的方式来表示迷宫。常用的方法是使用 2−d矩阵和其中的值来表示障碍或路径。下面是一个简化版的迷宫解决问题,应该有助于澄清回溯算法。
简化的寻径问题
给定一个 N×N矩阵的块与一个源的左上块,我们想要找到一个路径从源到目的地(右下块)。我们只能向下和向左移动。路径是 1墙是由 0.
下面是一个迷宫的示例(黑色单元格不可访问)
1 2 3 4 |
|
解决方案如下:
现在我们可以概述一个回溯算法,它以坐标形式返回包含路径的数组。例如,对于上面的图片,解决方案是 (0,0)→(1,0)→(1,1)→(2,1)→(3.,1)→(3.,2)→(3.,3.)
1 2 3 4 5 6 |
|
python中的实现如下所示
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
八皇后
与排列问题相反,这里我们将看到一个回溯的例子,它涉及到检查许多约束条件。这听起来不太好,但有大量的限制实际上允许我们大大减少搜索空间,当我们回溯。这也意味着在运行时和性能方面有了实质性的改进。
一个解决方案的例子
图像信用(维基百科):
在计算机科学中回溯的一个非常常见的例子是放置问题 N在棋盘上的皇后,没有两个皇后互相攻击的方式。棋盘由 8×8细胞。皇后可以垂直、水平和对角线移动。问题是计算解的数量,而不是列举每个单独的解。
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
由于国际象棋的性质,当我们覆盖国际象棋棋盘时,当我们找到一个正方形,在给定的配置下,我们不能放置另一个皇后时,我们就会削减搜索空间。回溯搜索在这里是最有效的,因为它消除了周围 95%搜索空间。上面的伪代码显示了如何做到这一点的细节。
当然,在实际编写实现时,我们担心的是数据结构和实际表示问题的有效方法。下面的python代码展示了如何处理回溯搜索的实现。
8-Queen的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17日18 19 20 21日22日23日24日25日26日27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65从itertools进口*进口复制类董事会:"一个代表跳棋棋盘的类"def__init__(自我,n):#初始化类自我.董事会=[[没有一个为我在范围(8)]为我在范围(8)]自我.块=集()def__str__(自我):"允许我们打印电路板" "年代=''为我在自我.董事会:年代+ =str(我)+'\ n'返回年代defPlaceQueen(自我,行,列):"将皇后放置在row,column "自我.块.添加((行,列))自我.董事会[行] [列]=“问”defRemoveQueen(自我,行,列):“‘从给定的‘行’和‘列’删除一个‘皇后’' '''自我.董事会[行] [列]=没有一个自我.块.删除((行,列))defisAttacking(自我,piece1,piece2):" '检查piec1是否攻击piec2 ' "如果piece1[0]==piece2[0]或piece1[1]==piece2[1]:#检查它们是否在同一行或col返回真正的“‘是时候检查他们是否在对角线进攻了这可以通过简单的代数运算有效地完成如果它们在同一对角线上满足包含两点的直线方程x1,日元,x2,y2=piece1[1],piece1[0],piece2[1],piece2[0]米=浮动(y2-日元)/(x2-x1)如果腹肌(米)! =1.0:返回假其他的:b=y2-米*x2返回日元==米*x1+bdefisAttackingAny(自我,一块):“‘检查是否被攻击棋盘上的其他棋子"为piece1在自我.块:如果自我.isAttacking(一块,piece1):返回真正的返回假defNQueens(董事会,n):如果n==0:返回[董事会]解决方案=[]我=0为piece1在产品(范围(8),重复=2):如果piece1在董事会.块:继续如果不董事会.isAttackingAny(piece1):我+ =1新鲜的=复制.deepcopy(董事会)新鲜的.PlaceQueen(piece1[0],piece1[1])解决方案+ =NQueens(新鲜的,n-1)返回解决方案
8-Queen的问题
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30“‘作者:Bipin Oli“‘defisValid(行,pos):如果pos>=N或行>=N:返回假如果pos在索尔:返回假为我在范围(len(索尔)):如果pos==索尔[我]+行-我或pos==索尔[我]-(行-我):返回假返回真正的def解决(pos,质量控制):质量控制-=1索尔.附加(pos)如果质量控制==0:返回真正的#试为我在范围(N):如果(isValid(N-质量控制,我)):如果(解决(我,质量控制)):返回真正的索尔.流行()质量控制+ =1返回假皇后区=int(输入().带())N=皇后区索尔=[]为我在范围(皇后区):如果(解决(我,N)):打印(索尔)索尔=[]