算法-回溯算法
完整可执行代码见 sun2ot/algorithm-learning
n 后问题
问题定义
在 格的棋盘上放置彼此不受攻击的 个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 后问题等价于在 格的棋盘上放置 个皇后,任何 2 个皇后不放在同一行或同一列或同一斜线上。
算法思想
用 元组 表示 后问题的解。其中, 表示皇后 放在第 行的第 列,则存在以下要求:
- 不在同一行: 不同就可以保证
- 不在同一列: 表示第 列,因此 也互不相同
[!info] 对于不在同一斜线的情况,做出以下论述。
假设 格的棋盘的行号从左到右、列号从上到下分别为 ,则:
-
斜率为 的直线(有若干条,且互相平行)所对应的格子,例如下图中黄色方格的坐标分别为 ,其横纵坐标的差值都为 。同理,红色、黄色对应方格的坐标差值 和 ,也是分别相同的。
因此,假设两个皇后放置的位置分别为 ,若 ,则说明两者同处于斜率为 的斜线上。
-
斜率为 的直线所对应的格子,例如下图中宇宙超级无敌绝绝魅惑紫色方格的坐标分别为 ,其横纵坐标的和都为 。同理,青色与褐色对应的方格的坐标和也是分别相同的。
因此,假设两个皇后放置的位置分别为 ,若 ,则说明两者同处于斜率为 的斜线上。
综上所述,我们可以给出 N 后问题的所有约束。
代码实现
[!info] 关于 N 后问题,有一个未按照上述思路优化的原始版本,见仓库代码
NQueen.c
1 | /** |
[!question] 为什么
isSafe()
中检查冲突时,只需要检查前 row 行?
在 N 后问题中,我们通常会按行顺序,每一次放置一个皇后。也就是说,当我们在检查是否可以在第 row
行和第 col
列放置皇后时,我们知道所有在第 row
行之前的行已经放置了皇后,而所有在第 row
行之后的行还没有放置皇后。
01 背包问题
问题定义
01 背包问题是背包问题的一个特例,即装入物品时,不能装入物品的一部分,要么全部装入、要么不装入,因为这种非此即彼的选择方式,得名 01
背包问题。
算法思想
在背包问题的解决过程中,我们需要尝试所有可能的物品组合,以找到最优解。为了实现这一点,我们对每个物品都有两种选择:选择它或者不选择它,分别对应解空间的左右子树。当我们选择了一个物品(即 selected[index] = true;
)并递归地处理了所有后续的物品后,我们需要撤销对当前物品的选择,以便我们可以在不选择当前物品的情况下,尝试所有可能的后续选择。
但是,当不选择当前物品时,我们可以借助剪枝函数提前回溯:若不选择当前物品 index
,则假设尽力装入从下一物品 index+1
开始的剩余物品(按背包问题的方式,即可以装入部分),可以求出一个价值上界。那么:
- 如果这个价值上界小于当前的最高价值,则无需继续考虑从
index+1
开始的该分枝。原因很简单,你 TM 把剩下的破玩意都塞进去也不能达到新的最大价值,那我还装你干什么。 - 反之,如果这个价值上界大于当前的最高价值,那才有考虑该分枝的价值。
代码实现
1 | // 定义物品结构体 |