回溯法介绍

回溯法

有时会遇到这样一类题目,它的问题可以分解,但是又不能得出明确的动态规划或是递归解法,此时可以考虑用回溯法解决此类问题。回溯法的优点 在于其程序结构明确,可读性强,易于理解,而且通过对问题的分析可以大大提高运行效率。但是,对于可以得出明显的递推公式迭代求解的问题,还是不要用回溯 法,因为它花费的时间比较长。

回溯法的基本思想

对于用回溯法求解的问题,首先要将问题进行适当的转化,得出状态空间树。 这棵树的每条完整路径都代表了一种解的可能。通过深度优先搜索这棵树,枚举每种可能的解的情况;从而得出结果。但是,回溯法中通过构造约束函数,可以大大 提升程序效率,因为在深度优先搜索的过程中,不断的将每个解(并不一定是完整的,事实上这也就是构造约束函数的意义所在)与约束函数进行对照从而删除一些 不可能的解,这样就不必继续把解的剩余部分列出从而节省部分时间。

回溯法中,首先需要明确下面三个概念:

(一)约束函数:约束函数是根据题意定出的。通过描述合法解的一般特征用于去除不合法的解,从而避免继续搜索出这个不合法解的剩余部分。因此,约束函数是对于任何状态空间树上的节点都有效、等价的。

(二)状态空间树:刚刚已经提到,状态空间树是一个对所有解的图形描述。树上的每个子节点的解都只有一个部分与父节点不同。

(三)扩展节点、活结点、死结点:所谓扩展节点,就是当前正在求出它的子节点的节点,在DFS中,只允许有一个扩展节点。活结点就是通过与约束函数的对照,节点本身和其父节点均满足约束函数要求的节点;死结点反之。由此很容易知道死结点是不必求出其子节点的(没有意义)。

深度优先搜索(DFS)和广度优先搜索(FIFO)

在 分支界限法中,一般用的是FIFO或最小耗费搜索;其思想是一次性将一个节点的所有子节点求出并将其放入一个待求子节点的队列。通过遍历这个队列(队列在 遍历过程中不断增长)完成搜索。而DFS的作法则是将每一条合法路径求出后再转而向上求第二条合法路径。而在回溯法中,一般都用DFS。为什么呢?这是因 为可以通过约束函数杀死一些节点从而节省时间,由于DFS是将路径逐一求出的,通过在求路径的过程中杀死节点即可省去求所有子节点所花费的时间。FIFO 理论上也是可以做到这样的,但是通过对比不难发现,DFS在以这种方法解决问题时思路要清晰非常多。

因此,回溯法可以被认为是一个有过剪枝的DFS过程。

利用回溯法解题的具体步骤

首先,要通过读题完成下面三个步骤:

(1)描述解的形式,定义一个解空间,它包含问题的所有解。

(2)构造状态空间树。

(3)构造约束函数(用于杀死节点)。



然后就要通过DFS思想完成回溯,完整过程如下:

(1)设置初始化的方案(给变量赋初值,读入已知数据等)。

(2)变换方式去试探,若全部试完则转(7)。

(3)判断此法是否成功(通过约束函数),不成功则转(2)。

(4)试探成功则前进一步再试探。

(5)正确方案还未找到则转(2)。

(6)已找到一种方案则记录并打印。

(7)退回一步(回溯),若未退到头则转(2)。

(8)已退到头则结束或打印无解。

回溯法的数据结构

回溯法的状态空间树,在计算机上的数据结构有两种表示方法。当用k表示树的节点层数,n表示节点总数,m表示解的总数时:

(一)用m个k元组表示m种不同的解。其中,每组中的元素是[1,n]中的一个元素。在Pascal中,可以这样定义变量:

var a:array[1..k,1..m]of integer;(integer可以依n的范围决定)

(二)用m个n元组表示m种不同的解。因为所有的节点都包含在每个解的表示中,每组中的元素只有两种情况,被选用和不被选用。在Pascal中,可以这样定义变量:

var a:array[1..n,1..m]of boolean;

这两种数据结构的解空间最多都含有2n个不同的元组。
 
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
回溯法是一种经典的解决问题的算法,其基本思想是在搜索过程中,当发现当前搜索路径不能得到正确的解答时,就返回到上一步,重新选择其他的路径进行搜索,直到找到解答为止。在应用回溯法时,通常需要定义一个状态空间和一个状态转移函数,以便在搜索中对状态空间进行遍历。 下面以八皇后问题为例,介绍一下回溯法的应用过程: 八皇后问题是一个经典的问题,要求在一个8x8的棋盘上放置8个皇后,使得每个皇后都不在同一行、同一列或同一对角线上。回溯法可以通过枚举棋盘上每个位置的可能性,并检查该位置是否可行来解决这个问题。 首先,我们定义一个状态空间,表示棋盘上每个位置的可能状态,比如用一个二维数组board表示,board[i][j]表示棋盘上第i行第j列的位置是否可以放置皇后。 然后,我们定义一个状态转移函数,表示在当前状态下,如何进行搜索,比如可以从第一行开始,枚举第一行的每个位置,然后判断是否可行,如果可行,就向下一行搜索,否则就回溯到上一行继续搜索。 具体的实现过程可以参考下面的伪代码: ```python def backtrack(board, row): # 如果已经遍历完所有行,说明找到了一个解法 if row == len(board): return True for col in range(len(board)): # 检查该位置是否可行 if is_valid(board, row, col): # 标记该位置已被占用 board[row][col] = 1 # 继续向下一行搜索 if backtrack(board, row+1): return True # 如果下一行搜索失败,就回溯到当前行,重新选择其他位置 board[row][col] = 0 # 如果所有位置都尝试过了,还没有找到解法,就返回失败 return False def is_valid(board, row, col): for i in range(row): # 检查同一列是否有皇后 if board[i][col] == 1: return False # 检查左上角到右下角是否有皇后 if col-row+i >= 0 and board[i][col-row+i] == 1: return False # 检查右上角到左下角是否有皇后 if col+row-i < len(board) and board[i][col+row-i] == 1: return False return True ``` 在这个算法中,backtrack函数表示搜索的过程,is_valid函数表示如何检查当前位置是否可行。整个算法的时间复杂度是O(N!),其中N是棋盘的大小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值