一、什么是广度优先搜索?
广度优先搜索(Breadth-First Search,简称 BFS)是一种经典的图和树的遍历算法,它的核心思想是从起点出发,逐层向外扩展,先访问离起点最近的节点,再访问更远的节点,就像水波从中心向四周扩散一样。
简单来说,广度优先搜索就像走迷宫:
- 从起点出发,先探索所有离起点一步的位置;
- 再探索离起点两步的位置;
- 重复这个过程,直到找到终点或者所有位置都探索完;
- 因为是逐层探索,所以第一次到达终点的路径一定是最短路径。
二、广度优先搜索的核心步骤
广度优先搜索的核心步骤可以分为以下几步:
- 初始化队列:将起点加入队列,标记为已访问;
- 取出队首元素:从队列中取出当前要探索的位置;
- 判断终点:如果当前位置是终点,返回成功;
- 探索四个方向:依次尝试上、下、左、右四个方向,判断是否越界、是否是墙、是否已经访问;
- 标记并加入队列:如果方向合法,标记为已访问,记录来源,加入队列;
- 重复:继续取出队首元素,直到队列为空。
三、广度优先搜索的代码实现
1. Python 版本(直观易懂)
from collections import deque # 方向数组:右、下、左、上 dr = [0, 1, 0, -1] dc = [1, 0, -1, 0] # 迷宫地图:0表示通路,1表示墙 maze = [ [0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1, 1, 0], [0, 1, 0, 0, 0, 0, 1, 0], [0, 1, 0, 1, 1, 0, 1, 0], [0, 1, 0, 1, 0, 0, 1, 0], [0, 1, 0, 1, 0, 1, 1, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 1, 1, 1, 0] ] # 访问标记数组 visited = [[False for _ in range(8)] for _ in range(8)] # 来源记录,用于回溯路径 prev = [[(-1, -1) for _ in range(8)] for _ in range(8)] def bfs(start_row, start_col, end_row, end_col): # 初始化队列 q = deque() q.append((start_row, start_col)) visited[start_row][start_col] = True while q: # 取出队首元素 r, c = q.popleft() # 判断是否到达终点 if r == end_row and c == end_col: return True # 探索四个方向 for i in range(4): nr = r + dr[i] nc = c + dc[i] # 边界检查:越界、撞墙、已访问 if 0 <= nr < 8 and 0 <= nc < 8 and maze[nr][nc] == 0 and not visited[nr][nc]: visited[nr][nc] = True prev[nr][nc] = (r, c) # 记录来源 q.append((nr, nc)) return False def get_path(end_row, end_col): # 从终点回溯到起点 path = [] cur = (end_row, end_col) while cur != (-1, -1): path.append(cur) cur = prev[cur[0]][cur[1]] # 反转路径,得到从起点到终点的顺序 path.reverse() return path # 测试:从左上角(0,0)到右下角(7,7) if bfs(0, 0, 7, 7): print("找到最短路径:") path = get_path(7, 7) for p in path: print(p, end=" -> ") else: print("没有找到路径")2. C 语言版本(更贴近底层)
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> #define ROWS 8 #define COLS 8 // 方向数组:右、下、左、上 int dr[4] = {0, 1, 0, -1}; int dc[4] = {1, 0, -1, 0}; // 迷宫地图:0表示通路,1表示墙 int maze[ROWS][COLS] = { {0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 1, 1, 1, 1, 1, 0}, {0, 1, 0, 0, 0, 0, 1, 0}, {0, 1, 0, 1, 1, 0, 1, 0}, {0, 1, 0, 1, 0, 0, 1, 0}, {0, 1, 0, 1, 0, 1, 1, 0}, {0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 1, 0} }; // 访问标记数组 bool visited[ROWS][COLS] = {false}; // 来源记录,用于回溯路径 typedef struct { int r; int c; } Pair; Pair prev[ROWS][COLS]; // 队列结构体 typedef struct { Pair data[ROWS * COLS]; int front; int rear; } Queue; // 初始化队列 void initQueue(Queue* q) { q->front = 0; q->rear = 0; } // 入队 void enqueue(Queue* q, Pair p) { q->data[q->rear++] = p; } // 出队 Pair dequeue(Queue* q) { return q->data[q->front++]; } // 判断队列是否为空 bool isQueueEmpty(Queue* q) { return q->front == q->rear; } bool bfs(int start_r, int start_c, int end_r, int end_c) { Queue q; initQueue(&q); Pair start = {start_r, start_c}; enqueue(&q, start); visited[start_r][start_c] = true; prev[start_r][start_c] = (Pair){-1, -1}; while (!isQueueEmpty(&q)) { Pair cur = dequeue(&q); int r = cur.r; int c = cur.c; // 判断是否到达终点 if (r == end_r && c == end_c) { return true; } // 探索四个方向 for (int i = 0; i < 4; i++) { int nr = r + dr[i]; int nc = c + dc[i]; // 边界检查:越界、撞墙、已访问 if (nr >= 0 && nr < ROWS && nc >= 0 && nc < COLS && maze[nr][nc] == 0 && !visited[nr][nc]) { visited[nr][nc] = true; prev[nr][nc] = (Pair){r, c}; // 记录来源 enqueue(&q, (Pair){nr, nc}); } } } return false; } void getPath(int end_r, int end_c) { Pair path[ROWS * COLS]; int pathLen = 0; Pair cur = {end_r, end_c}; // 从终点回溯到起点 while (cur.r != -1 || cur.c != -1) { path[pathLen++] = cur; cur = prev[cur.r][cur.c]; } // 反转路径,得到从起点到终点的顺序 for (int i = pathLen - 1; i >= 0; i--) { printf("(%d, %d)", path[i].r, path[i].c); if (i > 0) { printf(" -> "); } } printf("\n"); } int main() { if (bfs(0, 0, 7, 7)) { printf("找到最短路径:\n"); getPath(7, 7); } else { printf("没有找到路径\n"); } return 0; }四、广度优先搜索的特点
- 空间复杂度:O (宽度),因为使用队列存储当前层的所有节点;
- 时间复杂度:O (节点数 + 边数),因为每个节点和边最多被访问一次;
- 一定是最短路径:因为是逐层探索,第一次到达终点的路径一定是最短的;
- 实现方式:使用队列,先进先出,逐层扩展。
五、广度优先搜索的优化
为了提高广度优先搜索的效率,可以进行以下优化:
- 双向 BFS:从起点和终点同时开始 BFS,直到相遇,减少搜索范围;
- 剪枝:提前排除不可能到达终点的路径,减少不必要的探索;
- 使用优先队列:如果边有权重,可以使用优先队列实现 Dijkstra 算法,找到权重最小的路径。
六、广度优先搜索的实际应用场景
广度优先搜索是一种非常基础且重要的算法,常见场景包括:
- 图和树的遍历:遍历图或树的所有节点;
- 最短路径问题:在无权图中找到最短路径;
- 迷宫问题:找到迷宫的最短出路;
- 社交网络:找到两个人之间的最短社交距离;
- 网页爬虫:从一个网页开始,逐层爬取所有相关网页;
- 广播路由:在网络中广播消息,确保所有节点都能收到。
七、广度优先搜索 vs 深度优先搜索
广度优先搜索和深度优先搜索是两种最常用的图遍历算法,它们的区别如下:
八、总结
广度优先搜索是一种经典的图和树的遍历算法,它的核心思想是从起点出发,逐层向外扩展,先访问离起点最近的节点,再访问更远的节点,第一次到达终点的路径一定是最短路径。
广度优先搜索的时间复杂度为 O (节点数 + 边数),空间复杂度为 O (宽度),适合解决最短路径、社交网络距离等问题。
希望这篇文章能帮助你理解广度优先搜索的原理和实现!