Bootstrap

每日一题:解决N皇后问题的深度优先搜索(DFS)算法详解

每日一题:解决N皇后问题的深度优先搜索(DFS)算法详解

引言

N皇后问题是经典的算法问题之一,要求在N×N的棋盘上放置N个皇后,使得它们互不攻击。具体来说,任何两个皇后不能在同一行、同一列或同一对角线上。本文将详细介绍如何使用深度优先搜索(DFS)算法来解决N皇后问题,并提供一个完整的C++实现。

1、问题描述

给定一个正整数n,表示一个n×n的棋盘,要求放置n个皇后,使得每行、每列和每条对角线上都只有一个皇后。返回所有可能的摆法数量。
在这里插入图片描述

例如,当n=4时,有两种摆法:

4
2

2、算法思路

2.1 深度优先搜索(DFS)

DFS是一种用于遍历或搜索树或图的算法。在N皇后问题中,我们可以将棋盘的状态看作一个树,每个节点代表棋盘的一个状态,边代表放置一个皇后的操作。

2.2 剪枝

为了减少搜索空间,我们需要在DFS过程中进行剪枝。具体来说,当我们放置一个皇后时,需要检查当前列、主对角线和副对角线是否已经被其他皇后占据。如果已经被占据,则跳过该位置。

2.3 回溯

当我们在某一行放置皇后后,继续递归到下一行。如果某一行无法放置皇后,则回溯到上一行,尝试其他位置。

3、代码实现

以下是使用C++实现的N皇后问题的DFS算法:

class Queens {
public:
    int cnt = 0, N;
    bool col[15], dpos[30], dneg[30];

    int nQueens(int n) {
        N = n;
        dfs(0);
        return cnt;
    }

    void dfs(int i) {
        if (i == N && i != 0) { // 到达最后一行,注意0是特殊数据
            cnt++; // 计数
            return;
        }
        for (int j = 0; j < N; j++) { // 在这一行的每一列放皇后
            if (col[j] || dpos[i + j] || dneg[i - j + N - 1]) continue; // 有别的皇后能攻击到就忽略这个位置
            col[j] = dpos[i + j] = dneg[i - j + N - 1] = 1; // 占领这些相应的列和左下对角线和右下对角线
            dfs(i + 1); // 到下一行
            col[j] = dpos[i + j] = dneg[i - j + N - 1] = 0; // 拿掉这行的皇后,取消相应的占领
        }
    } // 运行到这说明该行放置皇后失败,不计数返回
};

代码解析

  1. 变量定义

    • cnt:记录合法的摆法数量。
    • N:棋盘的大小。
    • col[15]:标记每一列是否被占据。
    • dpos[30]:标记主对角线是否被占据。
    • dneg[30]:标记副对角线是否被占据。
  2. nQueens函数

    • 初始化棋盘大小N,并调用DFS函数开始搜索。
  3. dfs函数

    • 如果当前行i等于N,说明已经成功放置了N个皇后,增加计数器cnt
    • 遍历当前行的每一列,尝试放置皇后。
    • 如果当前位置可以放置皇后(即列、主对角线和副对角线都没有被占据),则标记这些位置为被占据,并递归到下一行。
    • 如果递归返回,说明当前行的皇后放置失败,取消标记,尝试下一个位置。

4、深入解析:

深入解析N皇后问题中的对角线表示:dpos[i + j]dneg[i - j + N - 1]

在解决N皇后问题时,除了需要确保每行和每列只有一个皇后外,还需要确保每条对角线上也只有一个皇后。为了高效地检查对角线上是否已经存在皇后,我们通常使用两个数组 dposdneg 来分别表示主对角线副对角线的状态。本文将详细解释为什么 dpos[i + j]dneg[i - j + N - 1] 可以表示棋盘上的对角线。


4.1 棋盘的对角线

在一个N×N的棋盘上,每个位置 (i, j) 属于两条对角线:

  • 主对角线:从左上到右下的对角线。
  • 副对角线:从右上到左下的对角线。

我们需要为每条对角线分配一个唯一的标识符,以便快速检查是否有皇后占据了该对角线。


4.2 主对角线的表示:dpos[i + j]

为什么是 i + j

  • 在同一主对角线上的所有位置 (i, j) 满足 i + j 是一个常数。
    • 例如,在4×4的棋盘上:
      • 位置 (0, 0) 的主对角线标识符为 0 + 0 = 0
      • 位置 (1, 1) 的主对角线标识符为 1 + 1 = 2
      • 位置 (2, 2) 的主对角线标识符为 2 + 2 = 4
  • 因此,i + j 可以唯一标识一条主对角线。

示例

在4×4的棋盘上,主对角线的标识符如下:

0123
00123
11234
22345
33456
  • 可以看到,每条主对角线的 i + j 值都是唯一的。

4.3 副对角线的表示:dneg[i - j + N - 1]

定义

  • 副对角线是从右上到左下的对角线。
  • 对于棋盘上的任意一个位置 (i, j),其副对角线的标识符可以通过 i - j + N - 1 计算得到。

为什么是 i - j + N - 1

  • 在同一副对角线上的所有位置 (i, j) 满足 i - j 是一个常数。
    • 例如,在4×4的棋盘上:
      • 位置 (0, 3) 的副对角线标识符为 0 - 3 + 3 = 0
      • 位置 (1, 2) 的副对角线标识符为 1 - 2 + 3 = 2
      • 位置 (2, 1) 的副对角线标识符为 2 - 1 + 3 = 4
  • 由于 i - j 的值可能为负数,我们通过加上 N - 1 来确保标识符始终为非负数。

示例

在4×4的棋盘上,副对角线的标识符如下:

0123
03210
14321
25432
36543
  • 可以看到,每条副对角线的 i - j + N - 1 值都是唯一的。

4.4 为什么需要 dposdneg

在N皇后问题中,我们需要快速检查某个位置 (i, j) 是否可以被放置皇后。具体来说,需要满足以下条件:

  1. 当前列 j 没有被占据(通过 col[j] 检查)。
  2. 当前主对角线 i + j 没有被占据(通过 dpos[i + j] 检查)。
  3. 当前副对角线 i - j + N - 1 没有被占据(通过 dneg[i - j + N - 1] 检查)。

通过 dposdneg,我们可以高效地标记和检查对角线的状态,从而避免重复计算。


4.5 代码中的具体实现

在代码中,dposdneg 是两个布尔数组,分别用于标记主对角线和副对角线是否被占据。具体实现如下:

bool col[15], dpos[30], dneg[30]; // 列、主对角线、副对角线的标记数组

void dfs(int i) {
    if (i == N) { // 如果已经放置了N个皇后
        cnt++; // 增加计数
        return;
    }
    for (int j = 0; j < N; j++) { // 尝试在当前行的每一列放置皇后
        if (col[j] || dpos[i + j] || dneg[i - j + N - 1]) continue; // 检查是否被占据
        col[j] = dpos[i + j] = dneg[i - j + N - 1] = true; // 标记占据
        dfs(i + 1); // 递归到下一行
        col[j] = dpos[i + j] = dneg[i - j + N - 1] = false; // 回溯,取消标记
    }
}

关键点

  • dpos[i + j]:标记主对角线。
  • dneg[i - j + N - 1]:标记副对角线。
  • 如果某个位置 (i, j) 的主对角线或副对角线已经被占据,则跳过该位置。

5. 总结

  • dpos[i + j]dneg[i - j + N - 1] 是用于表示棋盘上主对角线和副对角线的唯一标识符。
  • 通过这两个数组,我们可以高效地检查某个位置是否可以被放置皇后,从而避免冲突。
  • 这种表示方法是N皇后问题中常用的优化技巧,能够显著减少计算量。

总结

通过深度优先搜索和剪枝策略,我们可以高效地解决N皇后问题。该算法的时间复杂度为O(N!),但由于剪枝的存在,实际运行时间会大大减少。希望本文能帮助你理解N皇后问题的解法,并掌握DFS算法的应用。

关于深度搜素更多内容,点击:C++二叉树深度探索:DFS函数详解与应用实例
关注专栏:每日一题:C++ LeetCode精讲,和我一起进步


希望本文能帮助你更好地理解N皇后问题中对角线的表示方法!如果有任何疑问,欢迎在评论区留言讨论!


版权声明:本文为博主原创文章,转载请注明出处。点个赞吧
图片名称

;