Skip to content

DFS

深度优先搜索 Depth First Search,DFS。每次会尽量往深处搜索,直到叶节点处进行回溯(回溯指的是返回上一层节点,再查看当前节点有无 没有搜索过的子节点

img

如图所示,从 1 开始的 DFS 结果为:

cpp
1 2 5 6 3 4 7 8

DFS 使用栈(递归函数栈)实现,由于 DFS 需要记录路径上的点(方便回溯),因此 DFS 需要使用的空间与树的高度成正比 O(h)

排列数字

AC842. 排列数字

DFS 的搜索流程是一棵树。

如何思考 排列数字?正常来说人类都是划出 3 个空格,然后挨个填入数字,注意,下一个填入的数字要求前面从来没有出现过。

  • path[N]:结果数组,占位空格
  • st[N]:当我们 for 循环遍历数字的时候,用来表示前面是否用过该数字
  • 回溯需要 恢复现场

核心代码:

cpp
// ...
    for (int i = 1; i <= n; i ++) {
        if (!st[i]) {
            path[u] = i;
            st[i] = true;
            dfs(u + 1); // 这一步结束就是回溯到上一层节点
            st[i] = false;
        }
    }

注意到,这里的搜索顺序就是 空格

八皇后

1_597ec77c49-8-queens.png

首先思考搜索顺序。

  • 64 格子,一个格子一个格子进行搜索
  • 8 行,由于皇后可以攻击一整行,所以一行一行搜索,看这一行的皇后放在哪一列
  • 这么一看,如果对角线不能攻击的话,似乎就是一个全排列问题

因此这里还需要边做边判断是否相互攻击,攻击了就剪枝掉(有点类似 st[N])。

image-20231130080603933

  • 对角线是 2n1,因此开 dg[20]
  • 正对角线为红色,截距为 x + y
  • 反对角线为绿色,截距为 x - y,将它们集体下移就不会出现负数了

这里可以考虑 y=x+b,y=x+b 这两条直线的截距,一共 15 条对角线和反对角线。

按行遍历的核心代码:

cpp
// ...
    for (int i = 0; i < n; i ++) {
        if (!col[i] && !dg[u + i] && !udg[n - u + i]) {
            g[u][i] = 'Q';  // u row i col
            col[i] = true;
            dg[u + i] = true;
            udg[n - u + i] = true;
            
            dfs(u + 1);

            g[u][i] = '.';
            col[i] = false;
            dg[u + i] = false;
            udg[n - u + i] = false;
        }
    }
  • 最优性剪枝(当前一定不如最优解)
  • 可行性剪枝(当前一定不可行)

另一种写法,每一格每一格搜索:

首先,按照坐标枚举的时候,防止坐标越界:

cpp
// ...
    if (y == n) {
        y = 0;
        x ++;
    }

然后在内部有放或者不放皇后两种做法:

  • 不放皇后,y 坐标自然增长,待放置皇后个数不变
  • 放皇后,y 坐标自然增长,待放置皇后个数少一个
cpp
// ...
    dfs(x, y + 1, s); // not put queen

    if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]) {
        g[x][y] = 'Q';
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;

        dfs(x, y + 1, s + 1); // put queen
        
        g[x][y] = '.';
        row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;
    }

Released under the MIT License.