DFS 深度优先搜索

DFS 深度优先搜索

基本概述

DFS (Depth First Search) 深度优先遍历 俗称暴搜

在这里插入图片描述

数据结构 stack 空间 O ( h ) O(h) O(h)

不具有最短性

DFS本质上就是递归,要想学会DFS就要理解好递归。

递归就是"自己"调用"自己"的过程,但是一定要设置递归的终止条件,调用函数的过程就是将函数进行压栈执行,如果不设置终止条件,函数就会不断的调用自己,也就是一直压栈下去,最终爆栈。

重要的点有三个:

  1. 递归终止条件如何设置
  2. 返回值应该是什么,该传递给上一层什么信息
  3. 这一层的递归中应该做什么工作

经典例题

排列数字

给定一个整数 n n n,将数字 1 ∼ n 1∼n 1n 排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

思路:

题目要求就是要求出 n n n个数字的全排列。

假设 n = 3 n=3 n=3时,那么作为开头的数字就有三个,所以要找出每一种数字开头都有哪些组合,如下图所示,是一个DFS的搜索过程。在搜索每个分支的时候都要进行判断,当前组合的数字是否没有重复?如果没有重复,就可以走到叶子结点的部分,每个叶子结点都是一种数字的排列组合。

n n n个数字,那么以每个数字开头的情况就有 n n n个,所以需要重复 n n n次搜索过程,搜索出每一个数字作为开头情况时的排列组合。搜索的过程就像是一棵树,不但要考虑好树的分支个数,还要考虑树的深度,每个叶子结点都是排列出来的结果,这个结果中包含了 n n n个数字,那它的上一层就是 n − 1 n-1 n1个数字,以此类推一直到达第0层也就是根结点的位置,一共有n + 1个数字,所以需要控制好搜索的深度,从第0层开始,搜索到第 n n n层。

在排列数字的过程中需要判断数字是否重复出现,如果没有重复出现就可以将该数字纳入到组合中去,否则就判除该数字。为了判断数字是否重复出现我们可以开辟一个数组,用于标记每一个数字是否重复出现,但是要注意,DFS是一个递归的过程,所以在每一层递归都要记得”恢复现场“。

在这里插入图片描述

代码:

import java.util.*;

public class Main {
    static final int N = 20;
    static int[] p = new int[N]; // 用于存放排列好的数字
    static boolean[] st = new boolean[N]; // 判断数字是否出现过的数组
    static int n;
    
    public static void dfs(int u) {
        // 当搜索层数等于树的深度时,就可以输出结果
        if (u == n) {
            for (int i = 0; i < n; i++) {
                System.out.print(p[i] + " ");
            }
            System.out.println();
            return; // 要注意回溯
        }
        // 以每个数字开头的情况有n中,根节点上就有n个分支,所以需要循环n次,每次都搜索到最深处找到答案
        for (int i = 1; i <= n; i ++) {
            if (!st[i]) {
                p[u] = i; // 将第n层的不重复数字放到数组中
                st[i] = true; // 将这个数字标记为出现过
                dfs(u + 1);
                st[i] = false; // 恢复现场,将此数字标记为未出现过
            }
        }      
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        dfs(0); // 从第0层,也就是根节点进行搜索
    }
}
n-皇后问题

n n n−皇后问题是指将 n n n 个皇后放在 n × n n×n n×n 的国际象棋棋盘上,使得皇后不能相互攻击到,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

在这里插入图片描述

输入样例:

4

输出样例:

.Q..
...Q
Q...
..Q.

..Q.
Q...
...Q
.Q..

思路:

与排列数字的题有相似之处,皇后棋子是否放在棋盘格子上的,棋盘的格子 n ∗ n n * n nn,那么从第一行开始,依次查找每个格子,判断是否可以将棋子放到格子上;

在这里插入图片描述
也就是说需要进行n次DFS,搜索每一行的棋子位置,同时与排列数字的题一样,需要考虑树的深度的问题;一共是n列,那么树深就是n + 1(因为树根从0开始,一直到第n行就是树的最深处)。

皇后棋子不能存在于同一行同一列还有同一个对角线,因此需要设置三个数组用于标记是否有棋子出现在对角线的位置上。

还要注意用于记录对角线的数组的下标换算问题,可以将我们的这个棋盘看成是一个平面直角坐标系,有 x x x轴和 y y y轴。因此对角线就是形如 y = x + b y = x + b y=x+b y = − x + b y = -x + b y=x+b的直线。直线方程中的 b b b就是我们要计算出的下标;因此需要设置两个数组来记录两种方向的对角线上的棋子出现。

在这里插入图片描述

代码:

import java.util.*;

public class Main {
    static final int N = 20; // 将数组长度设置为20防止下标越界问题
    static int n;
    static char[][] g = new char[N][N];
    static boolean[] col = new boolean[N], dg = new boolean[N], udg = new boolean[N];
    
    public static void dfs(int row) {
        if (row == n) { // 判断是否每一行都填上了棋子(是否到了树的最大深度)
            for (int i = 0 ;i < n; i++) {
                for (int j = 0; j < n ; j++) 
                    System.out.print(g[i][j]);
                System.out.println();
            }
            System.out.println();
            return; // 回溯
        }
        // 用有n列,就需要dfs处理n次
        for (int i = 0; i < n; i++) {
            if (!col[i] && !dg[n - row + i] && !udg[row + i]) { // 判断列上,两个对角线上是否出现过
                g[row][i] = 'Q'; // 填入棋子
                col[i] = dg[n - row + i] = udg[row + i] = true; // 标记为已经填入棋子
                dfs(row + 1); // 递归处理下一行
                col[i] = dg[n - row + i] = udg[row + i] = false; // 结束递归后恢复现场 
                g[row][i] = '.'; // 同上
            }
        }
    }
    
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        
        // 在dfs前需要初始化棋盘
        for (int i = 0 ;i < n; i++)
            for (int j = 0; j < n ; j++) 
                g[i][j] = '.';
        // 也可以使用Arrays.fill()方法来初始化
        // for (char[] c : g) {
        //     Arrays.fill(c, '.');
        // }
        dfs(0); // 从0开始的dfs
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神烦狗闯入了你的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值