在这里插入图片描述

👋 大家好,欢迎来到我的技术博客!
💻 作为一名热爱 Java 与软件开发的程序员,我始终相信:清晰的逻辑 + 持续的积累 = 稳健的成长
📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。
🎯 本文将围绕数据结构与算法这个话题展开,希望能为你带来一些启发或实用的参考。
🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获!


数据结构与算法 - 队列在BFS中的作用:层序遍历与最短路径的基础 🧠

在计算机科学中,广度优先搜索(Breadth-First Search, BFS)是一种基础且强大的图遍历算法。它不仅用于图结构,也广泛应用于树结构、网格、状态空间等问题中。而支撑BFS高效运行的核心数据结构,正是队列(Queue)。

本文将深入探讨队列在BFS中的核心作用,从理论到实践,涵盖以下内容:

  • 队列的基本原理与Java实现
  • BFS算法的逻辑与队列的天然契合
  • 层序遍历(Level-order Traversal)的实现细节
  • BFS在无权图中最短路径问题中的应用
  • 多个真实场景案例(如迷宫、社交网络、单词接龙等)
  • 性能分析与常见陷阱
  • 可视化与Mermaid图表示例

无论你是初学者还是有一定经验的开发者,相信本文都能为你带来新的理解与启发。让我们开始吧!🚀


1. 队列:FIFO的优雅实现 📥📤

队列是一种先进先出(First-In-First-Out, FIFO)的数据结构。你可以把它想象成排队买票:先来的人先买票,后来的人排在队尾。

在Java中,Queue 是一个接口,常用实现类包括:

  • LinkedList:基于双向链表,支持高效的头尾操作
  • ArrayDeque:基于循环数组,性能通常优于 LinkedList
  • PriorityQueue:虽然也实现了 Queue,但它是堆结构,不适用于BFS(因为不保证FIFO)

1.1 Java队列的基本操作

import java.util.*;

public class QueueDemo {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        
        // 入队
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        
        // 出队
        System.out.println(queue.poll()); // 1
        System.out.println(queue.poll()); // 2
        
        // 查看队首(不移除)
        System.out.println(queue.peek()); // 3
        
        // 判断是否为空
        System.out.println(queue.isEmpty()); // false
    }
}

💡 提示:在BFS中,我们通常使用 offer()poll() 而不是 add()remove(),因为前者在失败时返回 falsenull,不会抛出异常,更安全。


2. BFS:从队列中诞生的遍历策略 🌐

BFS的核心思想是:从起点出发,逐层向外扩展。每一“层”代表距离起点相同步数的所有节点。

2.1 为什么BFS需要队列?

想象一下:你站在一个迷宫入口,想找到出口。你先探索所有一步能到的位置,再探索两步能到的位置……这正是BFS的思路。

队列天然支持这种“先处理近的,再处理远的”顺序:

  1. 将起点入队
  2. 出队一个节点,访问它
  3. 将它的所有未访问邻居入队
  4. 重复直到队列为空

这个过程保证了先入队的节点先被处理,从而实现“按层扩展”。

2.2 BFS伪代码

BFS(start):
    queue ← new Queue()
    visited ← new Set()
    queue.offer(start)
    visited.add(start)
    
    while queue is not empty:
        node ← queue.poll()
        process(node)
        for each neighbor of node:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.offer(neighbor)

3. 层序遍历:树的BFS应用 🌳

在二叉树中,BFS表现为层序遍历(Level-order Traversal):从上到下、从左到右逐层访问节点。

3.1 二叉树节点定义

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    
    TreeNode(int x) {
        val = x;
    }
}

3.2 层序遍历实现(按层分组)

import java.util.*;

public class LevelOrderTraversal {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) return result;
        
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        
        while (!queue.isEmpty()) {
            int levelSize = queue.size(); // 当前层的节点数
            List<Integer> currentLevel = new ArrayList<>();
            
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                currentLevel.add(node.val);
                
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            
            result.add(currentLevel);
        }
        
        return result;
    }
}

🔍 关键点levelSize = queue.size() 必须在循环前记录!因为队列在循环中会不断加入下一层节点。

3.3 示例与可视化

假设二叉树如下:

        3
       / \
      9  20
        /  \
       15   7

层序遍历结果为:[[3], [9,20], [15,7]]

用Mermaid绘制该树:

3
9
20
15
7

4. BFS求最短路径:无权图的黄金标准 🛤️

无权图(或边权相等的图)中,BFS是求单源最短路径的最优算法。为什么?

因为BFS按“距离”逐层扩展,第一次访问到目标节点时,路径一定是最短的

4.1 图的表示:邻接表

import java.util.*;

public class Graph {
    private Map<Integer, List<Integer>> adjList;
    
    public Graph() {
        adjList = new HashMap<>();
    }
    
    public void addEdge(int u, int v) {
        adjList.computeIfAbsent(u, k -> new ArrayList<>()).add(v);
        adjList.computeIfAbsent(v, k -> new ArrayList<>()).add(u); // 无向图
    }
    
    public List<Integer> getNeighbors(int node) {
        return adjList.getOrDefault(node, new ArrayList<>());
    }
}

4.2 BFS求最短路径长度

public int shortestPathLength(Graph graph, int start, int end) {
    if (start == end) return 0;
    
    Queue<Integer> queue = new LinkedList<>();
    Map<Integer, Integer> dist = new HashMap<>(); // 距离记录
    
    queue.offer(start);
    dist.put(start, 0);
    
    while (!queue.isEmpty()) {
        int node = queue.poll();
        int currentDist = dist.get(node);
        
        for (int neighbor : graph.getNeighbors(node)) {
            if (!dist.containsKey(neighbor)) {
                dist.put(neighbor, currentDist + 1);
                if (neighbor == end) {
                    return currentDist + 1;
                }
                queue.offer(neighbor);
            }
        }
    }
    
    return -1; // 不可达
}

4.3 实际案例:社交网络中的“六度空间”

在社交网络中,BFS可以快速找到两个人之间的最短朋友链。

例如:A认识B,B认识C,C认识D → A到D的距离是3。

🌐 想了解更多?可以阅读 Wikipedia: Six Degrees of Separation(可正常访问 ✅)


5. 经典问题实战 💪

5.1 迷宫最短路径(网格BFS)

给定一个二维网格,0表示可通行,1表示障碍,求从左上角到右下角的最短路径长度。

public int shortestPathInMaze(int[][] maze) {
    if (maze == null || maze.length == 0 || maze[0].length == 0) return -1;
    int m = maze.length, n = maze[0].length;
    if (maze[0][0] == 1 || maze[m-1][n-1] == 1) return -1;
    
    Queue<int[]> queue = new LinkedList<>();
    boolean[][] visited = new boolean[m][n];
    int[][] directions = {{0,1}, {1,0}, {0,-1}, {-1,0}}; // 右、下、左、上
    
    queue.offer(new int[]{0, 0});
    visited[0][0] = true;
    int steps = 0;
    
    while (!queue.isEmpty()) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            int[] curr = queue.poll();
            int x = curr[0], y = curr[1];
            
            if (x == m-1 && y == n-1) return steps;
            
            for (int[] dir : directions) {
                int nx = x + dir[0], ny = y + dir[1];
                if (nx >= 0 && nx < m && ny >= 0 && ny < n 
                    && !visited[nx][ny] && maze[nx][ny] == 0) {
                    visited[nx][ny] = true;
                    queue.offer(new int[]{nx, ny});
                }
            }
        }
        steps++;
    }
    
    return -1;
}

🧩 技巧:使用 steps 变量记录层数,每处理完一层就 steps++

5.2 单词接龙(Word Ladder)

给定两个单词 beginWordendWord,以及一个字典 wordList,每次只能改变一个字母,求最少变换次数。

这是LeetCode经典题(LeetCode 127. Word Ladder ✅ 可访问)。

public int ladderLength(String beginWord, String endWord, List<String> wordList) {
    Set<String> dict = new HashSet<>(wordList);
    if (!dict.contains(endWord)) return 0;
    
    Queue<String> queue = new LinkedList<>();
    Set<String> visited = new HashSet<>();
    
    queue.offer(beginWord);
    visited.add(beginWord);
    int level = 1;
    
    while (!queue.isEmpty()) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            String word = queue.poll();
            if (word.equals(endWord)) return level;
            
            // 尝试每个位置的26个字母
            char[] chars = word.toCharArray();
            for (int j = 0; j < chars.length; j++) {
                char original = chars[j];
                for (char c = 'a'; c <= 'z'; c++) {
                    if (c == original) continue;
                    chars[j] = c;
                    String newWord = new String(chars);
                    if (dict.contains(newWord) && !visited.contains(newWord)) {
                        visited.add(newWord);
                        queue.offer(newWord);
                    }
                }
                chars[j] = original; // 恢复
            }
        }
        level++;
    }
    
    return 0;
}

⚠️ 注意:此解法时间复杂度较高(O(N×M×26)),但清晰展示了BFS思想。实际可优化为预处理邻接关系。


6. 队列与BFS的性能分析 ⏱️

6.1 时间复杂度

  • 树的层序遍历:O(N),每个节点访问一次
  • 图的BFS:O(V + E),V为顶点数,E为边数
  • 网格BFS:O(M×N),每个格子最多入队一次

6.2 空间复杂度

  • 队列最大可能存储一层的所有节点
    • 完全二叉树最后一层:O(N/2) ≈ O(N)
    • 网格最坏情况(如空旷区域):O(M×N)

6.3 常见陷阱

陷阱 说明 解决方案
忘记标记已访问 导致无限循环或重复处理 入队时立即标记 visited
在循环中动态改变 queue.size() 层序遍历错乱 提前保存 size = queue.size()
使用栈代替队列 变成DFS 确保使用FIFO结构

7. 高级变体:双向BFS 🔄

当起点和终点都已知时,可以同时从两端进行BFS,大幅减少搜索空间

例如:单词接龙中,从 beginWordendWord 同时扩展,当两个搜索集相交时停止。

public int ladderLengthBidirectional(String beginWord, String endWord, List<String> wordList) {
    Set<String> dict = new HashSet<>(wordList);
    if (!dict.contains(endWord)) return 0;
    
    Set<String> beginSet = new HashSet<>();
    Set<String> endSet = new HashSet<>();
    Set<String> visited = new HashSet<>();
    
    beginSet.add(beginWord);
    endSet.add(endWord);
    int level = 1;
    
    while (!beginSet.isEmpty() && !endSet.isEmpty()) {
        // 总是从较小的集合开始扩展(优化)
        if (beginSet.size() > endSet.size()) {
            Set<String> temp = beginSet;
            beginSet = endSet;
            endSet = temp;
        }
        
        Set<String> nextLevel = new HashSet<>();
        for (String word : beginSet) {
            char[] chars = word.toCharArray();
            for (int i = 0; i < chars.length; i++) {
                char original = chars[i];
                for (char c = 'a'; c <= 'z'; c++) {
                    if (c == original) continue;
                    chars[i] = c;
                    String newWord = new String(chars);
                    if (endSet.contains(newWord)) return level + 1;
                    if (!visited.contains(newWord) && dict.contains(newWord)) {
                        nextLevel.add(newWord);
                        visited.add(newWord);
                    }
                }
                chars[i] = original;
            }
        }
        beginSet = nextLevel;
        level++;
    }
    
    return 0;
}

📈 效果:搜索空间从 O(b^d) 降到 O(b^(d/2)),其中 b 是分支因子,d 是深度。


8. 可视化:BFS过程演示 🎥

下面用Mermaid展示一个简单图的BFS过程:

A
B
C
D
E
F
G

BFS顺序:A → B → C → D → E → F → G

队列变化过程:

步骤 队列状态(队首→队尾) 出队节点
初始 [A] -
1 [B, C] A
2 [C, D, E] B
3 [D, E, F, G] C
4 [E, F, G] D
5 [F, G] E
6 [G] F
7 [] G

9. 队列的替代方案?为什么不用其他结构? ❌

  • (Stack):LIFO,会导致DFS行为,无法保证最短路径
  • 优先队列(PriorityQueue):用于Dijkstra算法(有权图),BFS中不必要且降低效率
  • 数组/列表:手动维护“指针”模拟队列可行,但代码复杂且易错

结论:队列是BFS的唯一自然选择


10. 扩展阅读与资源 📚


结语:队列虽小,作用巨大 🔚

队列作为BFS的“引擎”,虽简单却不可或缺。它确保了算法按层推进,从而在层序遍历、最短路径、状态搜索等问题中发挥关键作用。

掌握队列与BFS的配合,是你迈向算法高手的重要一步。下次当你面对一个“最少步数”或“逐层扩展”的问题时,不妨问问自己:能否用BFS + 队列解决?

Happy coding! 💻✨


🙌 感谢你读到这里!
🔍 技术之路没有捷径,但每一次阅读、思考和实践,都在悄悄拉近你与目标的距离。
💡 如果本文对你有帮助,不妨 👍 点赞、📌 收藏、📤 分享 给更多需要的朋友!
💬 欢迎在评论区留下你的想法、疑问或建议,我会一一回复,我们一起交流、共同成长 🌿
🔔 关注我,不错过下一篇干货!我们下期再见!✨

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐