十九、搜索与图论——DFS与BFS

本文介绍了DFS和BFS两种搜索算法的基本思路和特点,包括DFS的执着探索和BFS的稳重求解最短路径。通过n皇后问题、走迷宫和八数码问题的实例,展示了DFS如何通过递归实现全排列和解决冲突,以及BFS如何利用队列实现按层遍历。同时,文章提到了剪枝和回溯策略在DFS中的重要作用。
摘要由CSDN通过智能技术生成

一、基本思路

1、DFS深度搜索——执着

  • 数据结构:
    • stack
  • 空间复杂度:
    • O(n)—> 只需要记录下路径上的点
  • 特点:
      1. 不具有最短性
      1. 每一个节点会一条路走到黑,到达叶节点之后,然后进行回溯处理
  • 重要概念:
    • 剪枝:通过一定的判断条件,实现尚未到达叶节点的时候就返回上一层进行处理。
    • 回溯:走到叶节点或者经过剪枝判断,返回上一层重新进行判断与分支,与此同质,最重的是需要恢复现场,即将当前节点dfs之前的状态恢复,重新进行下一步处理。
    • DFS本质上就是递归。
      DFS搜索过程

2、BFS——稳重

  • 数据结构:
    • queue
  • 空间复杂度:
  • O(2^n) —> 需要把整个一层的节点存下来
  • 特点:
      1. 最短路问题求解,因为是按层来进行搜索的(前提是路径上的权值为1)
      1. 每层距离根节点的距离相等,按照层数进行遍历处理,即为不同距离
  • 模板:
quene<> // 初始化
while(queue 不为空){
	temp = queue[hh++]; //首节点处理
	拓展t的操作
}

在这里插入图片描述

二、例题题解

排列数字(DFS)

在这里插入图片描述

// java题解实现DFS
import java.util.*;

public class Main{
    static int N = 10;                      // 此处是确定开多少空间
    static int n;                           // 此处代表的是有多少个数1~n
    static int[] path = new int[N];         
    // 此处存储的是遍历中使用到的数,也就是在当前路径中,我们用到了哪些了数
    // 每一条路径在输出之后都被更新了
    static boolean[] str = new boolean[N];  // 此处代表的是1~n的数有没有被用过,str[i] = ture就代表第i个数已经被用过了
    
    static void dfs(int u){                         // u 表示要当前在第几步
        if(u == n){                                 // 已经到达根节点了,也就是最后一层了
            for(int i = 0;i < n; i++){
                System.out.print(path[i] + " ");    // 输出当前遍历路径上的存储值
            }
            System.out.println();
        }else{
            for(int i = 1; i <= n;i++){             // 此处之所以从1开始,是因为其中的str【i】代表的就是数字i是否被使用
                if(str[i] == false){
                    path[u] = i;                    // 存下本次遍历的值
                    str[i] = true;                  // 已经用过了
                    dfs(u + 1);                     // 递归进行下一层的遍历
                    str[i] = false;                 // 在从最后一层回溯过程中,需要恢复现场,重新进行选择
                }
            }
        }
    }
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        
        dfs(0);         // 因为一开始一个数也没有存,所以从第0步开始进行搜索
        
    }
}
n皇后问题(DFS)

在这里插入图片描述

//方法一:全排列的办法,按行进行处理
import java.util.*;

public class Main{
    static int N = 20;
    static int n;                           // 行列数
    static char[][] g = new char[N][N];     // 存储棋盘内容
    static boolean[] col = new boolean[N];  // 列标志位,因为在遍历行
    static boolean[] dg = new boolean[N];   // 正对角线(左到右,y = -x)
    static boolean[] udg = new boolean[N];  // 反对角线

    static void dfs(int u){
        if(u == 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();
        }else{
            for(int i = 0; i < n; i++){
                if(col[i] == false && dg[i + u] == false && udg[n + i - u] == false){       
                    // 标志位没有用过,则表示可以插入
                    // 其中的对角线 i + u 代表的是i为y,u为x,代表偏移量b = y + x,即为标志斜线的编号
                    // 其中的反对角线 n - u + i 代表的是i为y,u为x,代表偏移量编号b = y - x,
                    // 但是会存在负值,加一个偏移n
                    g[u][i]  = 'Q';
                    col[i] = true;
                    dg[i + u] = true;
                    udg[n - u + i] = true;

                    dfs(u + 1);

                    // 恢复现场
                    col[i] = false;
                    dg[i + u] = false;
                    udg[n - u + i] = false;
                    g[u][i]  = '.';
                }
            }

        }




    }
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        n = in.nextInt();

        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; j++){
                g[i][j] = '.';
            }
        }
        dfs(0);
    }
}


// 方法二:按棋盘点进行深搜,逐点进行判断处理
import java.util.*;

public class Main{
    static int N = 20;
    static int n;
    static char[][] g = new char[N][N];
    static boolean[] row = new boolean[N];
    static boolean[] col = new boolean[N];
    static boolean[] dg = new boolean[N];
    static boolean[] udg = new boolean[N];

    static void dfs(int x, int y, int u){       // x,y代表点的位置,u代表当前操作到每行最后了嘛
        if(y == n){     // 这一行遍历完了,回到开始,开始下一行
            y = 0;
            x++;
        }

        if(x == n){
            if(u == 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;
        }


        // 不放皇后,因此u不变
        dfs(x, y + 1, u);

        // 放皇后
        if(row[x] == false && col[y] == false && dg[x + y] == false && udg[x - y + n] == false){
            g[x][y] = 'Q';
            row[x] = true;
            col[y] = true;
            dg[x + y] = true;
            udg[x - y + n] = true;

            dfs(x, y + 1, u + 1);

            // 恢复现场
            row[x] = false;
            col[y] = false;
            dg[x + y] = false;
            udg[x - y + n] = false;
            g[x][y] = '.';
        }


    }


    public static void main(String[] args){
        Scanner in  = new Scanner(System.in);
        n = in.nextInt();

        for (int i = 0; i < n; i ++){
            for(int j = 0; j < n; j ++){
                    g[i][j] = '.';
            }
        }

        dfs(0, 0, 0);

    }
}

走迷宫(BFS)

在这里插入图片描述

// 走迷宫问题
import java.util.*;
import java.io.*;

public class Main{
    static int N = 110;
    static int[][] g = new int[N][N];           // 地图存储
    static int[][] d = new int[N][N];           // 可以走的路径上,搜索的每一层的距离大小,初始化为-1,则表示没走过
    static Pair[] quene = new Pair[N * N];      
    // 存储需要搜索的节点或位置,如果是搜索完毕则出队列,搜索到的新的节点加在队列后面
    // 用队列的原因,是为了一次存储的都是同层结构,也都是每个节点搜索到的同一层加在后面
    // 节点存储用的Pair自定义,N*N是因为最多的情况就是N*M都添加一遍
    static int hh = 0;                          // 队列首指针
    static int tt = -1;                         // 队列尾指针,
    static int n;                               // 行
    static int m;                               // 列
    
    // 宽度优先搜索算法,返回值是距离最小
    // 保证距离最小的关键在于[][]的使用,作用有两个
    // 一个是存储距离长度,一个是不走已经添加过的路,也就不会转圈圈
    static int bfs(){

        d[0][0] = 0;                                // 定义左上角加入路径,起始点的距离为0
        quene[++tt] = new Pair(0,0);                // 将第一个点加入搜索初始节点,为后面进行搜索
        int[] dx = {-1, 0, 1, 0};                   // 使用向量的形式进行方向的表示
        int[] dy = {0, 1, 0, -1};
        // 分别是 上,右,下,左
        
        // 下面是对队列中的节点进行搜索处理
        while(hh <= tt){                            // 队列不为空
            Pair temp = quene[hh++];                // 取出队首节点,作为搜索节点
            
            for(int i = 0; i < 4; i++){             // 进行四个方向的确定,看是否能走下去
                int nex = temp.first + dx[i];       // 节点搜索变化
                int ney = temp.second + dy[i];
                
                // 判断xy是否在规定范围内,注意xy的范围不一致,并不一定是方阵
                // g = 0 表示可以走
                // d = -1表示没有走过,那么这个点就算是可以加入队列的新的搜索节点
                if(nex >= 0 && nex < n && ney >= 0 && ney < m && d[nex][ney] == -1 && g[nex][ney] == 0){
                    d[nex][ney] = d[temp.first][temp.second] + 1;   // 记录下新的搜索节点的路径距离值,也就是所在层数
                    quene[++tt] = new Pair(nex,ney);        // 将新搜索到的节点,加入队列,后面还会继续拓展
                }
            }
            // 因为肯定有一条路径会到达终点,所以说不用关心最终节点,队列空了就表示搜索完了
        }
        return d[n - 1][m - 1];         // 返回右下角节点的距离值,就是最短路径
    }
    
    public static void main(String[] args) throws IOException{
        StreamTokenizer in = new StreamTokenizer(new InputStreamReader(System.in));
        in.nextToken();
        n = (int) in.nval;
        in.nextToken();
        m = (int) in.nval;

        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                in.nextToken();
                g[i][j] = (int) in.nval;
                d[i][j] = -1;               //初始化距离矩阵d
            }
        }
        
        System.out.print(bfs());

    }
}

class Pair{
    int first;
    int second;
    Pair(int x, int y){
        this.first = x;
        this.second = y;
    }
}
八数码问题(BFS)

在这里插入图片描述

import java.util.*;

public class Main{
    
    // 进行数组交换
    static void swap(char[] arr, int x, int y){     
        // 此处是进行字符数组内部的交换,也就相当于完成了其中移动操作
        // 值传递,但是因为才字符数组传递的是地址,故此,实际值也发生了改变
        char temp = arr[x];
        arr[x] = arr[y];
        arr[y] = temp;
    }
    
    static int bfs(String start,String end){
        Map<String,Integer> dis = new HashMap<>();      // 存储距离,key是其中的队列内容
        Queue<String> q = new LinkedList<>();           // 存储后面进行搜索的空间
        
        q.offer(start);     // 将初始值插入队列
        dis.put(start,0);   // 初始化距离,代表这是第一步
        
        // 方向搜索
        int[] dx = {-1, 0, 1, 0};
        int[] dy = {0, 1, 0, -1};
        
        // 判断队列是否为空,也就是还能不能继续搜索
        while(!q.isEmpty()){
            // 返回队首值,然后进行删除
            String temp = q.poll();
            
            // 找到x在当前维护string中的索引
            int k = temp.indexOf('x');
            int x = k / 3;          // 坐标转换,转换为三行三列,记住这个技巧
            int y = k % 3;
            
            // 如果已经存入了最后结果,说明已经成功了,map中存了其中的距离值
            if(temp.equals(end)){
                return dis.get(temp);       // 距离存储值
            }
            
            // 四个方向搜索
            for(int i = 0; i < 4; i++){
                int nex = x + dx[i] ;       // 需要搜索的下一个位置在3*3中的位置
                int ney = y + dy[i];
                
                // 判断下一个点是否在我们要移动的数组内3*3
                if(nex >= 0 && nex < 3 && ney >= 0 && ney < 3){
                    char[] arr = temp.toCharArray();
                    swap(arr,k, nex * 3 + ney);         // x的移动
                    // 此处很重要
                    // nex * 3 + ney是为了将交换节点————映射回将在原来string中的位置索引
                    String process = new String(arr);
                    
                    if(dis.get(process) == null){
                        dis.put(process, dis.get(temp) + 1);    // 存储并更新新的值,记录这是第几步到达这个样子
                        q.offer(process);                       // 将新的图加入搜索队列
                    }
                }
            }
        }
        
        return -1;
        
    }
    
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        String start = "";
        for(int i = 0;i < 9; i++){
            String s = in.next();
            start += s;
        }
        
        String end = "12345678x";
        System.out.print(bfs(start,end));
    }  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

牙否

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

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

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

打赏作者

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

抵扣说明:

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

余额充值