Skip to content

Commit 6763734

Browse files
committed
Update 03.Graph-BFS.md
1 parent 7bb6f0b commit 6763734

File tree

1 file changed

+111
-66
lines changed

1 file changed

+111
-66
lines changed

Contents/08.Graph/02.Graph-Traversal/03.Graph-BFS.md

Lines changed: 111 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
2-
31
## 1. 广度优先搜索简介
42

53
> **广度优先搜索算法**(Breadth First Search):简称为 BFS,又译作宽度优先搜索 / 横向优先搜索。是一种用于遍历或搜索树或图的算法。该算法从根节点开始,沿着树的宽度遍历树或图的节点。如果所有节点均被访问,则算法中止。
@@ -24,110 +22,137 @@ graph = {
2422
}
2523
```
2624

27-
该无向图的结构如图左所示,图右为宽度优先搜索的遍历路径。
25+
该无向图对应的邻接字典表示:无向图中有 `A``B``C``D``E``F``6` 个节点,其中与 `A` 节点相连的有 `B``C` 两个节点,与 `B` 节点相连的有 `A``C``D` 三个节点,等等。
26+
27+
该无向图的结构如图左所示,其宽度优先搜索的遍历路径如图右所示。
2828

2929
![](https://qcdn.itcharge.cn/images/20220105135253.png)
3030

31-
其对应的动态演示图如下所示
31+
其广度优先搜索的遍历过程如下动图所示
3232

3333
![](https://qcdn.itcharge.cn/images/20220105175535.gif)
3434

3535
## 3. 基于队列实现的广度优先搜索
3636

3737
### 3.1 基于队列实现的广度优先搜索实现步骤
3838

39-
1. `graph` 为存储无向图的字典变量,`start` 为开始节点。
40-
1. 然后定义 `visited` 为标记访问节点的 set 集合变量。定义 `q` 为存放节点的队列。
41-
1. 首先将起始节点放入队列 `q`,即 `q.put(start)`并将其标记为访问,即 `visited.add(start)`
39+
1. 定义 `graph` 为存储无向图的字典变量,`start` 为开始节点`def bfs(graph, start):` 为队列实现的广度优先搜索方法
40+
2. 定义 `visited` 为标记访问节点的 set 集合变量`queue` 为存放节点的队列。
41+
3. 首先将起始节点标记为访问,即 `visited.add(start)`并将其放入队列 `queue`,即 `queue.append(start)`
4242
4. 从队列中取出第一个节点 `node_u`。访问节点 `node_u`,并对节点进行相关操作(看具体题目要求)。
43-
2. 遍历与节点 `node_u` 相连并构成边的节点 `node_v`
44-
- 如果 `node_v` 没有被访问过则将 `node_v` 节点放入队列中,并标记访问,即 `q.append(node_v)``visited.add(node_v)`
45-
6. 重复步骤 4 ~ 5,直到 `q` 为空。
43+
5. 遍历与节点 `node_u` 相连并构成边的节点 `node_v`
44+
- 如果 `node_v` 没有被访问过(即 `node_v` 不在 `visited` 中):则将 `node_v` 节点放入队列中,并标记访问,即 `q.append(node_v)``visited.add(node_v)`
45+
6. 重复步骤 4 ~ 5,直到队列 `queue` 为空。
4646

4747
### 3.2 基于队列实现的广度优先搜索实现代码
4848

4949
```Python
5050
import collections
5151

5252
def bfs(graph, start):
53-
visited = set(start)
54-
q = collections.deque([start])
53+
visited = set()
54+
queue = collections.deque([])
55+
56+
visited.add(start)
57+
queue.append(start)
5558

56-
while q:
57-
node_u = q.popleft()
59+
while queue:
60+
node_u = queue.popleft()
5861
print(node_u)
5962
for node_v in graph[node_u]:
6063
if node_v not in visited:
6164
visited.add(node_v)
62-
q.append(node_v)
65+
queue.append(node_v)
6366
```
6467

6568
## 4. 广度优先搜索应用
6669

67-
### 4.1 无向图中连通分量的数目
70+
### 4.1 克隆图
6871

6972
#### 4.1.1 题目链接
7073

71-
- [323. 无向图中连通分量的数目 - 力扣(LeetCode)](https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/)
74+
- [133. 克隆图 - 力扣(LeetCode)](https://leetcode.cn/problems/clone-graph/)
7275

7376
#### 4.1.2 题目大意
7477

75-
给定 `n` 个节点(编号从 `0``n - 1`)的图的无向边列表 `edges`,其中 `edges[i] = [u, v]` 表示节点 `u` 和节点 `v` 之间有一条无向边
78+
**描述**:以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 `adjList[i]` 表示值为 `i + 1`的节点的邻接列表,`adjList[i][j]` 表示值为 `i + 1` 的节点与值为 `adjList[i][j]` 的节点有一条边
7679

77-
要求:计算该无向图中连通分量的数量
80+
**要求**:返回该图的深拷贝
7881

79-
#### 4.1.3 解题思路
82+
**说明**
8083

81-
先来看一下图论中相关的名次解释。
84+
- 节点数不超过 `100`
85+
- 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。
86+
- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
87+
- 由于图是无向的,如果节点 `p` 是节点 `q` 的邻居,那么节点 `q` 也必须是节点 `p` 的邻居。
88+
- 图是连通图,你可以从给定节点访问到所有节点。
8289

83-
- 连通图:在无向图中,如果可以从顶点 $v_i$ 到达 $v_j$,则称 $v_i$ 和 $v_j$ 连通。如果图中任意两个顶点之间都连通,则称该图为连通图。
84-
- 无向图的连通分量:如果该图为连通图,则连通分量为本身;否则将无向图中的极大连通子图称为连通分量,每个连通分量都是一个连通图。
85-
- 无向图的连通分量个数:无向图的极大连通子图的个数。
90+
**示例**
8691

87-
接下来我们来解决这道题。
92+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/133_clone_graph_question.png)
8893

89-
由于题目给定的是无向边列表。我们先将其转变为邻接表的形式。然后使用广度优先搜索计算联通分量的个数。具体做法如下:
94+
```Python
95+
输入:adjList = [[2,4],[1,3],[2,4],[1,3]]
96+
输出:[[2,4],[1,3],[2,4],[1,3]]
97+
解释:
98+
图中有 4 个节点。
99+
节点 1 的值是 1,它有两个邻居:节点 24
100+
节点 2 的值是 2,它有两个邻居:节点 13
101+
节点 3 的值是 3,它有两个邻居:节点 24
102+
节点 4 的值是 4,它有两个邻居:节点 13
103+
```
90104

91-
- 使用变量 `count` 记录连通分量个数。使用集合变量 `visited` 记录访问过的节点,使用邻接表 `graph` 记录图结构。
92-
-`0` 开始,依次遍历 `n` 个节点。
93-
- 如果第 `i` 个节点未访问过:
94-
- 将其添加到 `visited` 中。
95-
- 并且连通分量个数累加,即 `count += 1`
96-
- 定义一个队列 `q`,将第 `i` 个节点加入到队列中。
97-
- 从队列中取出第一个节点,遍历与其链接的节点,并将未遍历过的节点加入到队列 `q``visited` 中。
98-
- 直到队列为空,则继续向后遍历。
105+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/graph-1.png)
99106

100-
- 最后输出连通分量数目 `count`
107+
```Python
108+
输入:adjList = [[2],[1]]
109+
输出:[[2],[1]]
110+
```
111+
112+
#### 4.1.3 解题思路
101113

102-
#### 4.1.4 代码
114+
##### 思路 1:广度优先搜索
103115

104-
```Python
105-
import collections
116+
1. 使用哈希表 `visited` 来存储原图中被访问过的节点和克隆图中对应节点,键值对为 原图被访问过的节点:克隆图中对应节点。使用队列`queue` 存放节点。
117+
2. 根据起始节点,创建一个新的节点,并将其添加到哈希表 `visited` 中,即 `visited[node] = Node(node.val, [])`。然后将起始节点放入队列 `queue`中,即 `queue.append(node)`
118+
3. 从队列中取出第一个节点 `node_u`。访问节点 `node_u`
119+
4. 遍历与节点 `node_u` 相连并构成边的节点 `node_v`
120+
1. 如果 `node_v` 没有被访问过(即 `node_v` 不在 `visited` 中):
121+
1. 则根据 `node_v` 创建一个新的节点,并将其添加到哈希表 `visited` 中,即 `visited[node_v] = Node(node_v.val, [])`
122+
2. 然后将 `node_v` 节点放入队列 `queue` 中,即 `queue.append(node_v)`
123+
5. 重复步骤 3 ~ 4,直到队列 `queue` 为空。
124+
6. 广度优先搜索结束,返回起始节点的克隆节点(即 `visited[node]`)。
106125

126+
##### 思路 1:代码
127+
128+
```Python
107129
class Solution:
108-
def countComponents(self, n: int, edges: List[List[int]]) -> int:
109-
count = 0
110-
visited = set()
111-
graph = [[] for _ in range(n)]
112-
113-
for x, y in edges:
114-
graph[x].append(y)
115-
graph[y].append(x)
116-
117-
for i in range(n):
118-
if i not in visited:
119-
visited.add(i)
120-
count += 1
121-
q = collections.deque([i])
122-
while q:
123-
node_u = q.popleft()
124-
for node_v in graph[node_u]:
125-
if node_v not in visited:
126-
visited.add(node_v)
127-
q.append(node_v)
128-
return count
130+
def cloneGraph(self, node: 'Node') -> 'Node':
131+
if not node:
132+
return node
133+
134+
visited = dict()
135+
queue = collections.deque()
136+
137+
visited[node] = Node(node.val, [])
138+
queue.append(node)
139+
140+
while queue:
141+
node_u = queue.popleft()
142+
for node_v in node_u.neighbors:
143+
if node_v not in visited:
144+
visited[node_v] = Node(node_v.val, [])
145+
queue.append(node_v)
146+
visited[node_u].neighbors.append(visited[node_v])
147+
148+
return visited[node]
129149
```
130150

151+
##### 思路 1:复杂度分析
152+
153+
- **时间复杂度**:$O(n)$。其中 $n$ 为图中节点数量。
154+
- **空间复杂度**:$O(n)$。
155+
131156
### 4.2 岛屿的最大面积
132157

133158
#### 4.2.1 题目链接
@@ -136,19 +161,34 @@ class Solution:
136161

137162
#### 4.2.2 题目大意
138163

139-
给定一个只包含 `0``1` 元素的二维数组 `grid`,其中 `1` 代表岛屿,`0` 代表水。一座岛的面积就是上下左右相邻的 `1` 所组成的连通块的数目。
164+
**描述**:给定一个只包含 `0``1` 元素的二维数组,`1` 代表岛屿,`0` 代表水。一座岛的面积就是上下左右相邻的 `1` 所组成的连通块的数目。
165+
166+
**要求**:计算出最大的岛屿面积。
167+
168+
**说明**
140169

141-
要求:计算出最大的岛屿面积。
170+
- $m == grid.length$。
171+
- $n == grid[i].length$。
172+
- $1 \le m, n \le 50$。
173+
- $grid[i][j]$ 为 `0``1`
142174

143-
示例
175+
**示例**
144176

145-
![img](https://assets.leetcode.com/uploads/2021/05/01/maxarea1-grid.jpg)
177+
![](https://assets.leetcode.com/uploads/2021/05/01/maxarea1-grid.jpg)
146178

147-
图中最大的岛屿面积为 6。
179+
```Python
180+
输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
181+
输出:6
182+
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1
183+
184+
185+
输入:grid = [[0,0,0,0,0,0,0,0]]
186+
输出:0
187+
```
148188

149189
#### 4.2.3 解题思路
150190

151-
使用广度优先搜索方法。具体做法如下:
191+
##### 思路 1:广度优先搜索
152192

153193
1. 使用 `ans` 记录最大岛屿面积。
154194
2. 遍历二维数组的每一个元素,对于每个值为 `1` 的元素:
@@ -157,7 +197,7 @@ class Solution:
157197
3. 不断重复上一步骤,直到队列 `q` 为空。
158198
4. 更新当前最大岛屿面积,即 `ans = max(ans, temp_ans)`
159199

160-
#### 4.2.4 代码
200+
##### 思路 1:代码
161201

162202
```Python
163203
import collections
@@ -188,6 +228,11 @@ class Solution:
188228
return ans
189229
```
190230

231+
##### 思路 1:复杂度分析
232+
233+
- **时间复杂度**:$O(n \times m)$,其中 $m$ 和 $n$ 分别为行数和列数。
234+
- **空间复杂度**:$O(n \times m)$。
235+
191236
## 参考资料
192237

193238
- 【文章】[广度优先搜索 - LeetBook - 力扣(LeetCode)](https://leetcode.cn/leetbook/read/bfs/e69rh1/)

0 commit comments

Comments
 (0)