BFS

BFS通常用于解决最短路问题。一旦题目提到“最短”,就要考虑是否可以用BFS。

BFS适用于边权为0或1的问题。如果所有边权都是1,则可以直接用BFS。如果有边权为0,则需用双端BFS,也可用Dijkstra算法。

在BFS中,分为单源BFS和多源BFS。单源BFS从一个起点开始,逐层扩展搜索。多源BFS允许同时从多个起点扩展搜索,保证了算法的正确性。

 

LeetCode 111. 二叉树的最小深度

BFS模板,题目要求最小深度,可以使用BFS遍历到第一个叶子节点。

class Solution {
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
    int res = 1;
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        int qSize = queue.size();
        for (int i = 0; i < qSize; i++) {
            TreeNode node = queue.poll();
            if (node.left == null && node.right == null) {
                return res;
            }
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
        res++;
    }

    return 0;
}

}

 

baidu2023091902

给定一个有向图,一共有 ( n ) 个节点以及 ( n ) 条边,问:从 1 号节点出发,( k ) 步之内能够到达哪些节点?

输入描述

  • 第一行两个整数 ( n ) 和 ( k ),表示节点的数量,以及最多走的步数。
  • 第二行 ( n ) 个整数 ( a_1, a_2, ..., a_n ),表示从节点 ( i ) 到节点 ( a_i ) 有一条有向边。

约束条件

  • ( 1 ≤ n ≤ 10^5 )
  • ( 1 ≤q k ≤ 10^{18} )
  • ( 1 ≤ a_i ≤ n )

输出描述

能到达的节点的编号,按从小到大的顺序输出。

样例

输入:

5 100
5 4 5 2 3

输出:

1 2 3 4 5

Solution

import java.util.*;

/**
 * @description: Solve the problem from https://codefun2000.com/p/P1586
 *                Use BFS algorithm to traverse a graph.
 * @author: wenLiu
 * @create: 2024/5/25 12:45
 */
public class Main {
    static final int N = 100010;
    static List<Integer>[] g = new ArrayList[N]; // Adjacency list representation of the graph
    static long n, k; // Number of nodes and maximum depth
    static long[] w = new long[N]; // Values of nodes
    static boolean[] st = new boolean[N]; // Array to mark visited nodes

    /**
     * Perform breadth-first search (BFS) on the graph.
     * Start from node 1 and mark nodes visited within the given depth limit.
     */
    static void bfs() {
        st[1] = true; // Mark node 1 as visited
        Queue<Integer> q = new LinkedList<>(); // Queue for BFS
        q.add(1); // Add node 1 to the queue
        long depth = 0; // Initialize depth
        while (!q.isEmpty()) {
            int sz = q.size(); // Get the size of the current level
            depth++; // Increment depth for the next level
            for (int i = 0; i < sz; i++) {
                int t = q.poll(); // Dequeue the node
                for (int x : g[t]) { // Traverse neighbors of the dequeued node
                    if (!st[x] && depth < k) { // If neighbor is not visited and within depth limit
                        st[x] = true; // Mark neighbor as visited
                        q.add(x); // Add neighbor to the queue
                    }
                }
            }
        }
    }

    /**
     * Perform depth-first search (DFS) on the graph.
     * Start from node 1 and mark nodes visited recursively.
     */
    private static void dfs(int u) {
        if (st[u]) return; // If node is visited, return
        st[u] = true; // Mark node as visited
        for (Integer x : g[u]) { // Traverse neighbors of the current node
            dfs(x); // Recursively visit unvisited neighbors
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextLong(); // Read the number of nodes
        k = sc.nextLong(); // Read the maximum depth
        for (int i = 1; i <= n; i++) {
            w[i] = sc.nextLong(); // Read the value of each node
        }
        for (int i = 1; i <= n; i++) {
            g[i] = new ArrayList<>(); // Initialize adjacency list for each node
            g[i].add((int) w[i]); // Add the value of the node as a neighbor
        }
        bfs(); // Perform BFS traversal
//        dfs(1); // Optionally perform DFS traversal from node 1
        for (int i = 1; i <= n; i++) {
            if (st[i]) { // If node is visited
                System.out.print(i + " "); // Print the visited node
            }
        }
    }
}

huawei2023041902

题目内容

给出一个城市的路口数量以及道路连接情况,判断是否能从一个正常的路口顺利出城,并找到最省油的路径。

输入描述

第一行是路口数量 ( n )(0固定为根节点),接下来 ( m ) 行描述边的连接情况,最后一行是堵车的路口数量 ( d ) 及具体路口编号。

输出描述

如果能顺利出城,输出任意一个最短路径(注意如果存在多条最短路径,请按照节点序号排序输出,比如 0->1 和 0-> 3两条路径,第一个节点0一样,则比较第二个节点1和3,1比3小,因此输出0->1这条路径。再如0->5->2->3和 0->5->1->4,则输出 0->5->1->4。);否则输出"NULL"。

示例

样例1

输入
4
3
0 1
0 2
0 3
2
2
3



输出
0->1

样例2

输入
7
6
0 1
0 3
1 2
3 4
1 5
5 6
1
4

输出
0->1->2

Solution

import java.util.*;

/**
 * @description:城市道路
 * @create:2024/5/25
 */
public class Main {
    // 定义常量
    static final int N = 10010;

    static int n, m, q; // 城市路口数量,道路数量,堵车路口数量

    // 标记数组,标记节点是否已经被访问过
    static boolean[] st = new boolean[N];

    // 图的邻接表表示
    static ArrayList<ArrayList<Integer>> g = new ArrayList<>();

    // 距离数组,记录每个节点距离起点的最短距离
    static int[] dist = new int[N], father = new int[N];

    // BFS遍历
    static int bfs() {
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(0);
        if (st[0]) return -1;
        st[0] = true;
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[0] = 0;
        while (!queue.isEmpty()) {
            int t = queue.poll();
//判断当前节点是否为叶子节点
            if (g.get(t).size() == 1 && father[t] == g.get(t).get(0)) return t;
            for (int x : g.get(t)) {
                if (st[x]) continue;
                father[x] = t;
                queue.offer(x);
                dist[x] = dist[t] + 1;
                st[x] = true;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 读取数据
        n = sc.nextInt(); // 路口数量
        m = sc.nextInt(); // 道路数量
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        while (m-- > 0) {
            int a = sc.nextInt(), b = sc.nextInt();
            g.get(a).add(b);
            g.get(b).add(a);
        }
        q = sc.nextInt(); // 堵车路口数量
        while (q-- > 0) {
            int a = sc.nextInt(); // 堵车路口编号
            st[a] = true; // 标记该路口已堵车
        }
        // 对图进行排序
        for (ArrayList<Integer> t : g) Collections.sort(t);
        int t = bfs();
        if (t == -1) System.out.println("NULL");
        else {
            // 输出路径
            ArrayList<Integer> path = new ArrayList<>();
            while (t != 0) {
                path.add(t);
                t = father[t];
            }
            path.add(0);
            Collections.reverse(path);
            for (int i = 0; i < path.size(); i++) {
                if (i > 0) System.out.print("->");
                System.out.print(path.get(i));
            }
        }
    }
}

华为2023050603

在一个 n×n 大小的迷宫中,存在 k 个陷阱,每个位置的墙壁状态循环以 3 个单位时间为一个循环(0 表示没有墙壁,1 表示有墙壁)。每个单位时间可以向上、下、左、右移动一个地图单位,或原地踏步。

限制:

  • 移动方向上有陷阱,或移动目的地在下一个单位时间有墙壁,不可移动。
  • 当前所在位置在下一个单位时间有墙壁,不能停在原地。

计算找到宝藏的最短时间,若无法到达输出-1。

输入描述

  • 整数 n 表示迷宫的大小 (2 <= n <= 100)
  • 整数 k 表示陷阱的数量 (0 < k <= n×n−2)
  • 接下来 2×k 个整数,每两个表示一个陷阱的位置 (row, col)
  • 两对整数 (row1, col1) 和 (row2, col2) 表示宝藏和起始位置
  • n 行,每行 n 个字符串(0 或 1),表示墙壁状态循环

输出描述

输出一个整数,表示找到宝藏的最短时间。

样例
输入

3
2
1 0 1 2
2 1 2 0
100 100 100
100 000 100
000 000 001

输出

1

思路:BFS求最短路(三维)

  • 定义三维数组 dist[i][j][k] 表示到达 (i, j) 且状态为 k 时的最短时间。
import java.util.*;

// 节点类,用于表示迷宫中的位置及其时间状态
class Node {
    int x, y, time; // x 和 y 是节点的坐标,time 是当前时间
    Node(int x, int y, int time) {
        this.x = x;
        this.y = y;
        this.time = time;
    }
}

public class Main {
    static final int N = 110; // 最大迷宫尺寸
    static int[] dx = {-1, 1, 0, 0, 0}, dy = {0, 0, 1, -1, 0}; // 方向数组,包括上下左右和原地踏步
    static int[][][] dist = new int[N][N][3]; // 三维数组,存储到达每个点的最短时间,第三维为状态(0、1、2)
    static boolean[][] g = new boolean[N][N]; // 记录陷阱位置
    static String[][] state = new String[N][N]; // 记录每个位置的墙壁状态循环
    static int n, k, sx, sy, ex, ey; // 迷宫大小、陷阱数量、起点和终点坐标

    // BFS 求最短路径
    static int bfs() {
        // 初始化距离数组,设置为无穷大
        for (int[][] row : dist) 
            for (int[] col : row) 
                Arrays.fill(col, Integer.MAX_VALUE);

        // 起点位置的初始状态
        dist[sx][sy][0] = 0;
        Queue<Node> queue = new LinkedList<>();
        queue.offer(new Node(sx, sy, 0));

        while (!queue.isEmpty()) {
            Node t = queue.poll();
            if (t.x == ex && t.y == ey) return t.time; // 如果到达终点,返回时间

            int x = t.x, y = t.y, time = t.time;
            for (int i = 0; i < 5; i++) { // 包括四个方向和原地踏步
                int a = dx[i] + x, b = dy[i] + y, next_time = time + 1;
                // 检查新的位置是否合法,并且没有陷阱和障碍物
                if (a < 0 || a >= n || b < 0 || b >= n || g[a][b] || state[a][b].charAt(next_time % 3) == '1') 
                    continue;
                // 如果新的状态时间比之前的时间短,更新距离并加入队列
                if (dist[a][b][next_time % 3] > dist[x][y][time % 3] + 1) {
                    dist[a][b][next_time % 3] = dist[x][y][time % 3] + 1;
                    queue.offer(new Node(a, b, next_time));
                }
            }
        }
        return -1; // 如果无法到达终点,返回 -1
    }

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

        // 读取陷阱位置
        for (int i = 0; i < k; i++) {
            int a = sc.nextInt(), b = sc.nextInt();
            g[a][b] = true;
        }

        // 读取起点和终点位置
        ex = sc.nextInt();
        ey = sc.nextInt();
        sx = sc.nextInt();
        sy = sc.nextInt();

        // 读取每个位置的墙壁状态循环
        for (int i = 0; i < n; i++) 
            for (int j = 0; j < n; j++) 
                state[i][j] = sc.next();

        // 输出最短时间
        System.out.println(bfs());
    }
}

拼多多20230222

题目内容

华光林共有 (N) 个广场(分别编号 1~(N)),以及有 (N-1) 条道路,保证广场两两之间相互连通,且只有唯一一条通路。已知第 (i) 个广场有 (Ai) 只马。

路线选择规则如下:

  1. 任意一个广场不能两次访问。
  2. 背包只能装下最多 (M) 份马粮。
  3. 1 份马粮只能投喂 1 只马。
  4. 若要经过某个广场则要投喂其中所有的马。

求应当如何选择路线使得能投喂到最多数量的广场,且需要的马粮数量最少。

输入描述

第一行,两个整数 (N) 和 (M),分别表示广场的数量,和准备带出门的马粮的份数(1 ≤ (N) ≤ 50,000,0 ≤ (M) ≤ 5,000,000)。

第二行,共 (N) 个整数,其中第 (i) 个整数 (Ai) 表示第 (i) 个广场的马的数量。(0 ≤ (Ai) ≤ 100)

接下来 (N-1) 行,每行两个整数 (X) 和 (Y),表示编号 (X) 和 (Y) 的广场之间存在一条道路。(1 ≤ (X, Y) ≤ (N))

输出描述

共一行,两个整数,分别表示:能投喂最多数量的广场,以及在此情况下需要最少多少马粮。

样例

样例一

输入:

2 1
2 1
1 2

输出:
0 0

样例二:

输入

3 4
1 2 3
1 2
2 3

输出
2 3

Solution

BFS求最大深度。

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    // 最大广场数量的常数定义
    static final int N = 500010;
    
    // 定义全局变量
    static int n, m, res, minv;// n 实际的广场数量,m 可用的马粮数量,res 投喂到的广场数量,minv 消耗的最少马粮.
    static int[] w = new int[N]; // 每个广场上的马数量
    static boolean[] st = new boolean[N]; // 记录广场是否被访问过
    static ArrayList<ArrayList<Integer>> g = new ArrayList<>(N); // 图的邻接表表示
    
    // 广度优先搜索(BFS)函数
    public static void bfs() {
        // 初始化队列
        Queue<int[]> queue = new ArrayDeque<>();
        // 起点加入队列,起点编号为1,初始深度为1,剩余马粮为m减去起点的马粮消耗
        queue.add(new int[] {1, 1, m - w[1]});
        st[1] = true; // 标记起点已访问
        
        // BFS遍历
        while (!queue.isEmpty()) {
            int[] curr = queue.poll(); // 取出队列头部元素
            int d = curr[0]; // 当前深度
            int u = curr[1]; // 当前广场编号
            int val = curr[2]; // 剩余马粮
            
            // 遍历当前广场的所有相邻广场
            for (int x : g.get(u)) {
                // 如果相邻广场已访问过或者剩余马粮不足以投喂该广场的马,则跳过
                if (st[x] || val < w[x]) continue;
                
                // 更新结果,若找到更优的路径则更新res和minv
                if (res < d + 1) {
                    res = d + 1;
                    minv = m - (val - w[x]);
                } else if (res == d + 1) {
                    minv = Math.min(minv, m - (val - w[x]));
                }
                
                // 将相邻广场加入队列,更新剩余马粮并标记已访问
                queue.add(new int[] {d + 1, x, val - w[x]});
                st[x] = true;
            }
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt(); // 读取广场数量
        m = scanner.nextInt(); // 读取马粮数量
        
        // 初始化邻接表
        for (int i = 0; i < N; i++) {
            g.add(new ArrayList<>());
        }
        
        // 读取每个广场的马数量
        for (int i = 1; i <= n; i++) {
            w[i] = scanner.nextInt();
        }
        
        // 读取广场之间的道路信息,构建邻接表
        for (int i = 0; i < n - 1; i++) {
            int a = scanner.nextInt();
            int b = scanner.nextInt();
            g.get(a).add(b);
            g.get(b).add(a);
        }
        
        // 如果初始马粮不足以投喂起点的马,直接输出结果0 0
        if (m < w[1]) {
            System.out.println("0 0");
        } else {
            // 否则执行广度优先搜索
            bfs();
            // 输出结果
            System.out.println(res + " " + minv);
        }
    }
}

2023华为2023051703

题目描述

给定一个 m∗n 大小的场地,由二维数组 h 记录高度值,二维数组 o 记录减速值。初始速度为 1

移动时速度变化值为 h1 - h2 - o2(大于 0 为加速,小于 0 为减速)。速度不会为 0 或负值。

求到达哪些点时速度依旧为 1,并求出这些位置的个数。

输入描述

  1. 第一行:m, n(1 ≤ m ≤ 100, 1 ≤ n ≤ 100)
  2. 第二行:初始位置
  3. 第三行:场地每个点位的高度值 h[i][j](0 ≤ h ≤ 100)
  4. 第四行:每个点位的减速值 o[i][j](0 ≤ o ≤ 100)

输出描述

输出速度为 1 的位置的个数。

样例1

输入

2,2
1,1
5,0 0,6
0,6 7,0

输出

1

样例2

输入

2,2
0,0
0,0 0,0
0,0 0,0

输出

3

题目思路:BFS+一维哈希记录

这道题不是问能否到达终点,而是需要记录 BFS 每个点所有可能的速度,并统计速度为 1 的点的数量。我们可以将二维数组的每个点 (i, j) 转化为一维表示,即:i * m + j,然后通过 set 记录所有可能的速度值。

与传统 BFS 不同,传统 BFS 使用 map 存储 (x, y) 以判断点 (x, y) 是否被经过,只需判断是否经过一次,因为多次到达 (x, y) 不影响其对相邻点的影响。但在这道题中,不同速度会导致相邻点产生不同速度,因此我们需要使用 map 存储 (x, y, v),其中 x, y 为位置,v 为速度。这样当位置和速度都相同时,对相邻点的影响才会相同。

因此可以用hashMap<Integer,Set>结构进行存储.

Solution

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
      
        String[] s = sc.nextLine().trim().split(",");
        int n = Integer.parseInt(s[0]), m = Integer.parseInt(s[1]);
        s = sc.nextLine().trim().split(",");
        int sx = Integer.parseInt(s[0]), sy = Integer.parseInt(s[1]);
        int[][] h = new int[n][m], o = new int[n][m];
        s = sc.nextLine().trim().replace(" ", ",").split(",");
        for (int i = 0, idx = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                h[i][j] = Integer.parseInt(s[idx++]);
            }
        }
        s = sc.nextLine().trim().replace(" ", ",").split(",");
        for (int i = 0, idx = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                o[i][j] = Integer.parseInt(s[idx++]);
            }
        }//输入

        // 状态记录
        Map<Integer, Set<Integer>> map = new HashMap<>();

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                map.put(i * m + j, new HashSet<>());//对于每一个点开辟一个set记录出现过的速度
            }
        }

        Queue<int[]> q = new LinkedList<>();//开辟队列
        map.get(sx * m + sy).add(1);
        q.offer(new int[]{sx, sy, 1});//初始点入队列 (x,y,v)
        int cnt = 0;
        int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
        while (!q.isEmpty()) {
            int[] t = q.poll();
            int x = t[0], y = t[1], v = t[2];
            for (int i = 0; i < 4; i++) {
                int a = x + dx[i], b = y + dy[i];
                if (a < 0 || a >= n || b < 0 || b >= m) continue;//上面几句BFS经典操作
                int nv = v + h[x][y] - h[a][b] - o[a][b];//求得速度
                if (nv <= 0 || map.get(a * m + b).contains(nv)) continue;//一旦发现(x,y,v)已经被记录或者v<=0时就跳过
                
                if (nv == 1) cnt++;//如果(x,y,1)第一次发生,答案就加1
                map.get(a * m + b).add(nv);//更新map,存储每一个(x,y,v)
                q.offer(new int[]{a, b, nv});//BFS经典操作
            }
        }
        System.out.println(cnt);
    }
}
posted @ 2024-05-29 10:39  菠萝包与冰美式  阅读(18)  评论(0编辑  收藏  举报