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) 个广场有 (A
路线选择规则如下:
- 任意一个广场不能两次访问。
- 背包只能装下最多 (M) 份马粮。
- 1 份马粮只能投喂 1 只马。
- 若要经过某个广场则要投喂其中所有的马。
求应当如何选择路线使得能投喂到最多数量的广场,且需要的马粮数量最少。
输入描述
第一行,两个整数 (N) 和 (M),分别表示广场的数量,和准备带出门的马粮的份数(1 ≤ (N) ≤ 50,000,0 ≤ (M) ≤ 5,000,000)。
第二行,共 (N) 个整数,其中第 (i) 个整数 (A
接下来 (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
,并求出这些位置的个数。
输入描述
- 第一行:
m, n
(1 ≤ m ≤ 100, 1 ≤ n ≤ 100) - 第二行:初始位置
- 第三行:场地每个点位的高度值
h[i][j]
(0 ≤ h ≤ 100) - 第四行:每个点位的减速值
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);
}
}