预计阅读 15min
溯沿曲处疑山尽,行转通时觉水遥。
------黄廷用
“溯者,水欲下、違之而上也 。”回溯算法在搜索中寻找问题的解,如果发现走不通就返回之前的某个状态,因此而得名 。回溯算法使用满足一定限制的递归结构,通常用来解决多阶段的决策问题。本文选取了两个经典的回溯算法应用案例:N-皇后问题
游戏树
(一)N皇后问题 有一个NxN的棋盘,你要在棋盘上放置N个女王,你需要找出所有的满足下列条件的摆棋方案:任意两个女王不在同一行或者同一列, 也不能在任意一条对角线上, 对角线不只是两条主副对角线。 思路: 每放置一个皇后,就将其能够攻击的区域进行标记,然后放置下一个皇后,依次类推……; 此问题难点在于如何把控递归函数的返回条件,一种条件是N个皇后放置完成后,返回成功,一种条件是该行中已经没有可以放置的位置,此时返回失败,需要重新放置。 所谓的“重新放置”指的并不是将所有皇后清除重新来过,而是只返回上一层,将上一个导致本次放置失败的皇后进行清除,然后重新更新其位置,通过逐级放置、或逐级回溯可以达到遍历所有情况找到所有解。 我们用一个数组Q【0...N-1】来储存第一行到第N行女王的放置位置。 Q数组的初始值均为-1,count是个全局变量,用来计数,初始为0。 伪代码如下,在主程序调用PlaceQueens(Q[0...n-1], 0,count)即可
PlaceQueens(Q[0...n-1], r,count): if r==n print Q[0...n-1] count=count+1 else for j legal for i 的 if(Q[i]==j or Q[i]==j+r-i or Q[i]==j-r+i) legal if legal Q[r] PlaceQueens(Q[0...n-1], r+1)注意Q数组在运行过程中只有当前行以前的行信息是有用的,因此不必在每次调用结束后复原Q【j】的值。在此指出, if(Q[i]==j or Q[i]==j+r-i or Q[i]==j-r+i) 是本题关键
Q[i]==j 对应了同一列
Q[i]==j+r-i 也可写为Q[i]+i==j+r 对应了在同一条右倾的对角线上
Q[i]==j-r+i 也可写为Q[i]-i==j-r 对应了在同一条左倾的对角线上
(二)游戏树(极大极小树) 概述:
在玩对抗游戏时,通常假设双方都是顶级高手。因此默认敌我双方都能审时度势,作出最有利于自己的决策。
在轮到我方做决策时,我们希望最大化自己的收益,因此称为极大层;
在轮到敌方做决策时,他们的利益最大化就是我方利益最小化,因此称为极小层。
极大极小层交替出现,因此称为极大极小树(游戏树)。
每个决策都有一个收益值,我们会在所有可选的方案中选择收益值最大的方案。相反地,对方会选择让我方收益值最小的方案。
有一个NxN的棋盘,有甲乙两人对决,两人均有N个棋子,甲的棋子位于棋盘左侧边缘,乙的棋子位于棋盘上侧边缘。甲的棋子每次只能向右移动一格,或者在右侧挨着一个乙的棋子时可以越过乙的棋子向右移动两个。乙的 棋子每次只能向下移动一格,或者在下方挨着一个甲的棋子时可以越过甲的棋子向下移动两格。 所有的棋子不能重叠。先把所有棋子移到另一侧棋盘边缘者胜。游戏过程如下图: 存在一个简单的回溯算法来教你玩这个游戏---或者任意的不存在随机和隐藏信息的且可以在有限步内结束的双人游戏。 游戏的状态由每个棋子的位置以及当前要下棋的玩家身份组成。这些状态可以转换成一个游戏树,状态x若可以转换成转态y ,则其对应的节点之间存在一条边。游戏树的根节点是游戏的初始状态。每一个从根节点到叶子节点的路径就是一个完整的游戏. 我们遍历这个游戏树,并且按照下面递归地定义一个游戏状态是“好”还是坏 (好 和 坏 即为上文提到的收益值,也可用1,-1 ):
如果当前的玩家已经赢了, 或者他可以把游戏转态移动到了对对手玩家来说是坏的状态,那么当前游戏转态就是好的。
如果当前的玩家已经输了,或者他任何的操作都会把游戏状态移动到对对手玩家来说是好的状态,则当前游戏状态就是坏的。
如果非叶子结点至少有一个坏的孩子节点,它就是好的节点;
如果非叶子结点所有的孩子节点都是好的,他就是坏的节点;
最后小声说一下,公众号菜单里加了资料领取这一栏。今天也有更新C++语言学习资料呦。可以来戳~~