Go语言实战案例-广度优先遍历BFS

简介: 广度优先遍历(BFS)是一种层级展开的搜索策略,常用于树与图的遍历、最短路径查找、二维数组中的感染扩散等问题。它借助队列实现,优先访问当前层所有节点,再进入下一层,适用于寻找最短路径、层序遍历、岛屿问题等场景。

 

一、什么是广度优先遍历(BFS)?

广度优先遍历(Breadth-First Search,简称 BFS)是一种层级展开的搜索策略。

其核心思想是:先访问当前层的所有节点,再访问下一层的节点。常借助队列(Queue)实现,保证先入先出,按层推进。


二、BFS 的应用场景

应用场景 描述
树的层序遍历 从上到下、从左到右访问所有节点
图的最短路径 在无权图中找起点到终点的最短路径
二维数组搜索(如迷宫) 最少步数、感染扩散、岛屿问题
多源 BFS 同时从多个起点扩展,如火焰蔓延

三、BFS vs DFS 的区别

特性 BFS(广度优先) DFS(深度优先)
访问方式 一层一层遍历 一条路走到底
实现结构 队列(FIFO) 栈或递归(LIFO)
找最短路径 是(无权图/网格) 否(不能保证最短)
空间复杂度 O(w),w为最宽层节点数 O(h),h为最大深度

四、BFS 在树结构中的应用:层序遍历

示例:层序遍历二叉树

type TreeNode struct {
    Val   int
    Left  *TreeNode
    Right *TreeNode
}

Go 实现(按层收集每层元素)

func levelOrder(root *TreeNode) [][]int {
    var result [][]int
    if root == nil {
        return result
    }
    queue := []*TreeNode{root}
    for len(queue) > 0 {
        size := len(queue)
        level := []int{}
        for i := 0; i < size; i++ {
            node := queue[0]
            queue = queue[1:]
            level = append(level, node.Val)
            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
        result = append(result, level)
    }
    return result
}

示例输入:

// 构建如下树:
//      1
//     / \
//    2   3
//   / \   \
//  4   5   6
root := &TreeNode{Val: 1}
root.Left = &TreeNode{Val: 2}
root.Right = &TreeNode{Val: 3}
root.Left.Left = &TreeNode{Val: 4}
root.Left.Right = &TreeNode{Val: 5}
root.Right.Right = &TreeNode{Val: 6}
fmt.Println(levelOrder(root))
// 输出:[[1], [2,3], [4,5,6]]

五、BFS 在图结构中的应用:最短路径

我们以无向图为例,从起点出发,寻找所有节点的最短路径长度。

图结构定义:

type Graph struct {
    adj map[int][]int
}
func NewGraph() *Graph {
    return &Graph{adj: make(map[int][]int)}
}
func (g *Graph) AddEdge(u, v int) {
    g.adj[u] = append(g.adj[u], v)
    g.adj[v] = append(g.adj[v], u) // 无向图
}

BFS 实现最短路径(起点到所有点)

func BFSShortestPaths(g *Graph, start int) map[int]int {
    dist := make(map[int]int)
    visited := make(map[int]bool)
    queue := []int{start}
    dist[start] = 0
    visited[start] = true
    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]
        for _, neighbor := range g.adj[node] {
            if !visited[neighbor] {
                dist[neighbor] = dist[node] + 1
                visited[neighbor] = true
                queue = append(queue, neighbor)
            }
        }
    }
    return dist
}

示例调用:

g := NewGraph()
g.AddEdge(1, 2)
g.AddEdge(1, 3)
g.AddEdge(2, 4)
g.AddEdge(3, 5)
g.AddEdge(4, 5)
dist := BFSShortestPaths(g, 1)
fmt.Println(dist) // 输出每个节点到1的最短路径长度

六、BFS 在二维数组上的应用:最短路径 or 感染扩散

经典问题:迷宫最短路径

type Point struct {
    X, Y int
}
func bfsMaze(grid [][]int, start, end Point) int {
    if grid[start.X][start.Y] == 1 {
        return -1
    }
    m, n := len(grid), len(grid[0])
    visited := make([][]bool, m)
    for i := range visited {
        visited[i] = make([]bool, n)
    }
    dirs := []Point{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
    queue := []Point{start}
    visited[start.X][start.Y] = true
    steps := 0
    for len(queue) > 0 {
        size := len(queue)
        for i := 0; i < size; i++ {
            curr := queue[0]
            queue = queue[1:]
            if curr == end {
                return steps
            }
            for _, d := range dirs {
                nx, ny := curr.X+d.X, curr.Y+d.Y
                if nx >= 0 && ny >= 0 && nx < m && ny < n && grid[nx][ny] == 0 && !visited[nx][ny] {
                    visited[nx][ny] = true
                    queue = append(queue, Point{nx, ny})
                }
            }
        }
        steps++
    }
    return -1
}

七、复杂度分析

  • 时间复杂度:O(n)
    n 为图中节点数(或矩阵中格子数)
  • 空间复杂度:O(n)
    用于存储访问状态、队列

八、典型题目举例(LeetCode)

题目 描述
LeetCode 102 层序遍历 二叉树的层级展开
LeetCode 200 岛屿数量 类似 BFS 求二维图中连通区域
LeetCode 542 01矩阵 多源 BFS 计算每个 1 到最近 0 的距离
LeetCode 752 打开转盘锁 最少步数解锁问题

九、常见问题与调试技巧

  • • ❗ 队列顺序不能乱,要始终保证 FIFO(先进先出)
  • • ❗ 图结构必须标记访问状态,避免重复访问
  • • ✔ BFS 是解决“最少步数”问题的首选
  • • ✔ 多源同时扩展,用多个起点一起入队即可

十、总结

项目 内容说明
算法类型 广度优先搜索
核心结构 队列(Queue)
最适用问题 最短路径、层序遍历、感染传播问题
数据结构支持 树、图、二维数组(网格)
空间复杂度 O(n)

 

相关文章
|
2月前
|
Linux Go iOS开发
Go语言100个实战案例-进阶与部署篇:使用Go打包生成可执行文件
本文详解Go语言打包与跨平台编译技巧,涵盖`go build`命令、多平台构建、二进制优化及资源嵌入(embed),助你将项目编译为无依赖的独立可执行文件,轻松实现高效分发与部署。
|
3月前
|
数据采集 数据挖掘 测试技术
Go与Python爬虫实战对比:从开发效率到性能瓶颈的深度解析
本文对比了Python与Go在爬虫开发中的特点。Python凭借Scrapy等框架在开发效率和易用性上占优,适合快速开发与中小型项目;而Go凭借高并发和高性能优势,适用于大规模、长期运行的爬虫服务。文章通过代码示例和性能测试,分析了两者在并发能力、错误处理、部署维护等方面的差异,并探讨了未来融合发展的趋势。
321 0
|
2月前
|
存储 前端开发 JavaScript
Go语言实战案例-项目实战篇:编写一个轻量级在线聊天室
本文介绍如何用Go语言从零实现一个轻量级在线聊天室,基于WebSocket实现实时通信,支持多人消息广播。涵盖前后端开发、技术选型与功能扩展,助你掌握Go高并发与实时通信核心技术。
|
3月前
|
负载均衡 监控 Java
微服务稳定性三板斧:熔断、限流与负载均衡全面解析(附 Hystrix-Go 实战代码)
在微服务架构中,高可用与稳定性至关重要。本文详解熔断、限流与负载均衡三大关键技术,结合API网关与Hystrix-Go实战,帮助构建健壮、弹性的微服务系统。
457 1
微服务稳定性三板斧:熔断、限流与负载均衡全面解析(附 Hystrix-Go 实战代码)
|
3月前
|
安全 Go 开发者
Go语言实战案例:使用sync.Mutex实现资源加锁
在Go语言并发编程中,数据共享可能导致竞态条件,使用 `sync.Mutex` 可以有效避免这一问题。本文详细介绍了互斥锁的基本概念、加锁原理及实战应用,通过构建并发安全的计数器演示了加锁与未加锁的区别,并封装了一个线程安全的计数器结构。同时对比了Go中常见的同步机制,帮助开发者理解何时应使用 `Mutex` 及其注意事项。掌握 `Mutex` 是实现高效、安全并发编程的重要基础。
|
3月前
|
数据采集 Go API
Go语言实战案例:使用context控制协程取消
本文详解 Go 语言中 `context` 包的使用,通过实际案例演示如何利用 `context` 控制协程的生命周期,实现任务取消、超时控制及优雅退出,提升并发程序的稳定性与资源管理能力。
|
3月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
3月前
|
Go 开发者
Go语言实战案例:使用select监听多个channel
本文为《Go语言100个实战案例 · 网络与并发篇》第5篇,详解Go并发核心工具`select`的使用。通过实际案例讲解如何监听多个Channel、实现多任务处理、超时控制和非阻塞通信,帮助开发者掌握Go并发编程中的多路异步事件处理技巧。
|
Go
Go实战(一)-概述
Go实战(一)-概述
165 0
Go实战(一)-概述
|
1月前
|
存储 安全 Java
【Golang】(4)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法
结构体可以存储一组不同类型的数据,是一种符合类型。Go抛弃了类与继承,同时也抛弃了构造方法,刻意弱化了面向对象的功能,Go并非是一个传统OOP的语言,但是Go依旧有着OOP的影子,通过结构体和方法也可以模拟出一个类。
156 1