1.概述
(1)广度优先遍历 (Breadth First Search),又称宽度优先遍历,是最简便的图的搜索算法之一。
(2)已知图 G = (V, E) 和一个源顶点 start,宽度优先搜索以一种系统的方式探寻 G 的边,从而“发现” start 所能到达的所有顶点,并计算 start 到所有这些顶点的距离(最少边数),该算法同时能生成一棵根为 start 且包括所有可达顶点的广度优先树。对从 start 可达的任意顶点 v,广度优先树中从 start 到 v 的路径对应于图 G 中从 start 到 v 的最短路径,即包含最小边数的路径。该算法对有向图和无向图同样适用。
(3)之所以称之为广度优先遍历,是因为算法自始至终一直通过已找到和未找到顶点之间的边界向外扩展,就是说,算法首先搜索和 start 距离为 k 的所有顶点,然后再去搜索和 start 距离为 k + 1 的其他顶点。
2.代码实现
(1)当使用邻接矩阵来表示图时,其代码实现如下:
class Solution {
public static void bfs(int[][] adjMatrix, int start) {
int n = adjMatrix.length;
boolean[] visited = new boolean[n];
Queue<Integer> queue &#61; new LinkedList<>();
if (start < 0 || start > n - 1) {
System.out.println("起点编号应为 [0, " &#43; (n - 1) &#43; "] 之间的整数&#xff01;");
return;
}
queue.offer(start);
visited[start] &#61; true;
System.out.print(start &#43; " ");
while (!queue.isEmpty()) {
int node &#61; queue.poll();
for (int i &#61; 0; i < n; i&#43;&#43;) {
if (adjMatrix[node][i] !&#61; 0 && !visited[i]) {
System.out.print(i &#43; " ");
visited[i] &#61; true;
queue.offer(i);
}
}
}
}
}
&#xff08;2&#xff09;当使用邻接表来表示图时&#xff0c;其代码实现如下&#xff1a;
class Solution {
public static void bfs(List<Integer>[] adjList, int start) {
int n &#61; adjList.length;
boolean[] visited &#61; new boolean[n];
Queue<Integer> queue &#61; new LinkedList<>();
if (start < 0 || start > n - 1) {
System.out.println("起点编号应为 [0, " &#43; (n - 1) &#43; "] 之间的整数&#xff01;");
return;
}
queue.offer(start);
visited[start] &#61; true;
System.out.print(start &#43; " ");
while (!queue.isEmpty()) {
int node &#61; queue.poll();
for (int nextNode : adjList[node]) {
while (!visited[nextNode]) {
System.out.print(nextNode &#43; " ");
visited[nextNode] &#61; true;
queue.offer(nextNode);
}
}
}
}
}
&#xff08;3&#xff09;下面以图 G 为例来说明&#xff1a;
① 构造邻接矩阵&#xff1a;
int[][] adjMatrix &#61; {
{0, 1, 1, 0, 1},
{1, 0, 0, 1, 1},
{1, 0, 0, 0, 1},
{0, 1, 0, 0, 1},
{1, 1, 1, 1, 0}
};
② 构造邻接表&#xff1a;
int n &#61; 5;
List<Integer>[] adjList &#61; new ArrayList[n];
for (int i &#61; 0; i < n; i&#43;&#43;) {
adjList[i] &#61; new ArrayList<>();
}
adjList[0].add(1);
adjList[0].add(2);
adjList[0].add(4);
adjList[1].add(0);
adjList[1].add(3);
adjList[1].add(4);
adjList[2].add(0);
adjList[2].add(4);
adjList[3].add(1);
adjList[3].add(4);
adjList[4].add(0);
adjList[4].add(1);
如果 start &#61; 2&#xff0c;那么遍历的节点依次为&#xff1a;
2 0 4 1 3
遍历过程如下图所示&#xff1a;
&#xff08;4&#xff09;无论是邻接表还是邻接矩阵的存储方式&#xff0c;BFS 算法都需要借助一个辅助队列 queue&#xff0c;n 个顶点均需入队一次&#xff0c;在最坏的情况下&#xff0c;空间复杂度为 O(|V|)&#xff0c;而时间复杂度与图的存储方式有关&#xff1a;
- 采用邻接矩阵存储方式时&#xff0c;查找每个顶点的邻接点所需的时间为O(|V|)&#xff0c;故算法总的时间复杂度为 O(|V|2)&#xff1b;
- 采用邻接表存储方式时&#xff0c;每个顶点均需搜索一次(或入队一次)&#xff0c;故时间复杂度为 O(|V|)&#xff0c;在搜索任一顶点的邻接点时&#xff0c;每条边至少访问一次&#xff0c;故时间复杂度为 O(|E|)&#xff0c;算法总的时间复杂度为 O(|V| &#43; |E|)&#xff1b;
3.应用
&#xff08;1&#xff09;除了对图进行遍历以外&#xff0c;BFS 在求解最短路径或者最短步数上有很多的应用。
&#xff08;2&#xff09;LeetCode 中的934.最短的桥这题便是对 BFS 的应用&#xff1a;
思路如下&#xff1a;
① 通过遍历找到数组 grid 中的 1 后进行广度优先搜索&#xff0c;此时可以得到第一座岛的位置集合&#xff0c;记为 island&#xff0c;并将其位置全部标记为 −1。
② 从 island 中的所有位置开始进行 BFS&#xff0c;当它们到达了任意的 1 时&#xff0c;即表示搜索到了第二个岛&#xff0c;搜索的层数就是答案。
代码实现如下&#xff1a;
class Solution {
public int shortestBridge(int[][] grid) {
int n &#61; grid.length;
int[][] dirs &#61; {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};
List<int[]> island &#61; new ArrayList<>();
Queue<int[]> queue &#61; new ArrayDeque<>();
for (int i &#61; 0; i < n; i&#43;&#43;) {
for (int j &#61; 0; j < n; j&#43;&#43;) {
if (grid[i][j] &#61;&#61; 1) {
queue.offer(new int[]{i, j});
grid[i][j] &#61; -1;
while (!queue.isEmpty()) {
int[] land &#61; queue.poll();
int x &#61; land[0];
int y &#61; land[1];
island.add(land);
for (int k &#61; 0; k < 4; k&#43;&#43;) {
int nextX &#61; x &#43; dirs[k][0];
int nextY &#61; y &#43; dirs[k][1];
if (nextX >&#61; 0 && nextY >&#61; 0 && nextX < n && nextY < n && grid[nextX][nextY] &#61;&#61; 1) {
queue.offer(new int[]{nextX, nextY});
grid[nextX][nextY] &#61; -1;
}
}
}
for (int[] land : island) {
queue.offer(land);
}
int step &#61; 0;
while (!queue.isEmpty()) {
int size &#61; queue.size();
for (int k &#61; 0; k < size; k&#43;&#43;) {
int[] land &#61; queue.poll();
int x &#61; land[0];
int y &#61; land[1];
for (int d &#61; 0; d < 4; d&#43;&#43;) {
int nextX &#61; x &#43; dirs[d][0];
int nextY &#61; y &#43; dirs[d][1];
if (nextX >&#61; 0 && nextY >&#61; 0 && nextX < n && nextY < n) {
if (grid[nextX][nextY] &#61;&#61; 0) {
queue.offer(new int[]{nextX, nextY});
grid[nextX][nextY] &#61; -1;
} else if (grid[nextX][nextY] &#61;&#61; 1) {
return step;
}
}
}
}
step&#43;&#43;;
}
}
}
}
return 0;
}
}
&#xff08;3&#xff09;大家可以去 LeetCode 上找相关的 BFS 的题目来练习&#xff0c;或者也可以直接查看LeetCode算法刷题目录&#xff08;Java&#xff09;这篇文章中的 BFS 章节。如果大家发现文章中的错误之处&#xff0c;可在评论区中指出。