Skip to content

Commit

Permalink
广度优先搜索算法内容优化
Browse files Browse the repository at this point in the history
  • Loading branch information
itcharge committed Sep 5, 2023
1 parent 89568a8 commit 10116e7
Showing 1 changed file with 102 additions and 66 deletions.
168 changes: 102 additions & 66 deletions Contents/08.Graph/02.Graph-Traversal/03.Graph-BFS.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,95 @@
## 1. 广度优先搜索简介

> **广度优先搜索算法**(Breadth First Search):简称为 BFS,又译作宽度优先搜索 / 横向优先搜索。是一种用于遍历或搜索树或图的算法。该算法从根节点开始,沿着树的宽度遍历树或图的节点。如果所有节点均被访问,则算法中止
> **广度优先搜索算法(Breadth First Search)**:英文缩写为 BFS,又译作宽度优先搜索 / 横向优先搜索,是一种用于搜索树或图结构的算法。广度优先搜索算法从起始节点开始,逐层扩展,先访问离起始节点最近的节点,后访问离起始节点稍远的节点。以此类推,直到完成整个搜索过程
广度优先遍历类似于树的层次遍历过程。呈现出一层一层向外扩张的特点。先看到的节点先访问,后看到的节点后访问。遍历到的节点顺序符合「先进先出」的特点,所以广度优先搜索可以通过「队列」来实现。
因为遍历到的节点顺序符合「先进先出」的特点,所以广度优先搜索可以通过「队列」来实现。

## 2. 广度优先搜索过程演示
## 2. 广度优先搜索算法步骤

接下来我们以一个无向图为例,演示一下广度优先搜索的过程
接下来我们以一个无向图为例,介绍一下广度优先搜索的算法步骤

我们用邻接字典的方式存储无向图结构,对应结构代码如下:
1. 将起始节点 $u$ 放入队列中,并标记为已访问。
2. 从队列中取出一个节点,访问它并将其所有的未访问邻接节点 $v$ 放入队列中。
3. 标记已访问的节点 $v$,以避免重复访问。
4. 重复步骤 $2 \sim 3$,直到队列为空或找到目标节点。

```python
# 定义无向图结构
graph = {
"A": ["B", "C"],
"B": ["A", "C", "D"],
"C": ["A", "B", "D", "E"],
"D": ["B", "C", "E", "F"],
"E": ["C", "D"],
"F": ["D"]
}
```
::: tabs#BFS

@tab <1>

![广度优先搜索 1](https://qcdn.itcharge.cn/images/20230905152316.png)

@tab <2>

![广度优先搜索 2](https://qcdn.itcharge.cn/images/20230905152327.png)

@tab <3>

![](https://qcdn.itcharge.cn/images/20230905152350.png)

@tab <4>

![广度优先搜索 4](https://qcdn.itcharge.cn/images/20230905152401.png)

@tab <5>

该无向图对应的邻接字典表示:无向图中有 `A``B``C``D``E``F``6` 个节点,其中与 `A` 节点相连的有 `B``C` 两个节点,与 `B` 节点相连的有 `A``C``D` 三个节点,等等。
![广度优先搜索 5](https://qcdn.itcharge.cn/images/20230905152420.png)

该无向图的结构如图左所示,其宽度优先搜索的遍历路径如图右所示。
@tab <6>

![](https://qcdn.itcharge.cn/images/20220105135253.png)
![广度优先搜索 6](https://qcdn.itcharge.cn/images/20230905152433.png)

其广度优先搜索的遍历过程如下动图所示。
@tab <7>

![](https://qcdn.itcharge.cn/images/20220105175535.gif)
![广度优先搜索 7](https://qcdn.itcharge.cn/images/20230905152445.png)

:::

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

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

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

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

```python
import collections

def bfs(graph, start):
visited = set()
queue = collections.deque([])

visited.add(start)
queue.append(start)

while queue:
node_u = queue.popleft()
print(node_u)
for node_v in graph[node_u]:
if node_v not in visited:
visited.add(node_v)
queue.append(node_v)
class Solution:
def bfs(self, graph, u):
visited = set() # 使用 visited 标记访问过的节点
queue = collections.deque([]) # 使用 queue 存放临时节点

visited.add(u) # 将起始节点 u 标记为已访问
queue.append(u) # 将起始节点 u 加入队列中

while queue: # 队列不为空
u = queue.popleft() # 取出队头节点 u
print(u) # 访问节点 u
for v in graph[u]: # 遍历节点 u 的所有未访问邻接节点 v
if v not in visited: # 节点 v 未被访问
visited.add(v) # 将节点 v 标记为已访问
queue.append(v) # 将节点 v 加入队列中


graph = {
"0": ["1", "2"],
"1": ["0", "2", "3"],
"2": ["0", "1", "3", "4"],
"3": ["1", "2", "4", "5"],
"4": ["2", "3"],
"5": ["3", "6"],
"6": []
}

# 基于队列实现的广度优先搜索
Solution().bfs(graph, "0")
```

## 4. 广度优先搜索应用
Expand All @@ -75,20 +102,22 @@ def bfs(graph, start):

#### 4.1.2 题目大意

**描述**:以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 `adjList[i]` 表示值为 `i + 1`的节点的邻接列表,`adjList[i][j]` 表示值为 `i + 1` 的节点与值为 `adjList[i][j]` 的节点有一条边。
**描述**:以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 $adjList[i]$ 表示值为 $i + 1$ 的节点的邻接列表,$adjList[i][j]$ 表示值为 $i + 1$ 的节点与值为 $adjList[i][j]$ 的节点有一条边。

**要求**:返回该图的深拷贝。

**说明**

- 节点数不超过 `100`
- 节点数不超过 $100$
- 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。
- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
- 由于图是无向的,如果节点 `p` 是节点 `q` 的邻居,那么节点 `q` 也必须是节点 `p` 的邻居。
- 由于图是无向的,如果节点 $p$ 是节点 $q$ 的邻居,那么节点 $q$ 也必须是节点 $p$ 的邻居。
- 图是连通图,你可以从给定节点访问到所有节点。

**示例**

- 示例 1:

![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/133_clone_graph_question.png)

```python
Expand All @@ -102,6 +131,8 @@ def bfs(graph, start):
节点 4 的值是 4,它有两个邻居:节点 13
```

- 示例 2:

![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/graph-1.png)

```python
Expand All @@ -113,15 +144,14 @@ def bfs(graph, start):

##### 思路 1:广度优先搜索

1. 使用哈希表 `visited` 来存储原图中被访问过的节点和克隆图中对应节点,键值对为 原图被访问过的节点:克隆图中对应节点。使用队列`queue` 存放节点。
2. 根据起始节点,创建一个新的节点,并将其添加到哈希表 `visited` 中,即 `visited[node] = Node(node.val, [])`。然后将起始节点放入队列 `queue`中,即 `queue.append(node)`
3. 从队列中取出第一个节点 `node_u`。访问节点 `node_u`
4. 遍历与节点 `node_u` 相连并构成边的节点 `node_v`
1. 如果 `node_v` 没有被访问过(即 `node_v` 不在 `visited` 中):
1. 则根据 `node_v` 创建一个新的节点,并将其添加到哈希表 `visited` 中,即 `visited[node_v] = Node(node_v.val, [])`
2. 然后将 `node_v` 节点放入队列 `queue` 中,即 `queue.append(node_v)`
5. 重复步骤 3 ~ 4,直到队列 `queue` 为空。
6. 广度优先搜索结束,返回起始节点的克隆节点(即 `visited[node]`)。
1. 使用哈希表 $visited$ 来存储原图中被访问过的节点和克隆图中对应节点,键值对为「原图被访问过的节点:克隆图中对应节点」。使用队列 $queue$ 存放节点。
2. 根据起始节点 $node$,创建一个新的节点,并将其添加到哈希表 $visited$ 中,即 `visited[node] = Node(node.val, [])`。然后将起始节点放入队列中,即 `queue.append(node)`
3. 从队列中取出第一个节点 $node\underline{}u$。访问节点 $node\underline{}u$。
4. 遍历节点 $node\underline{}u$ 的所有未访问邻接节点 $node\underline{}v$(节点 $node\underline{}v$ 不在 $visited$ 中)。
5. 根据节点 $node\underline{}v$ 创建一个新的节点,并将其添加到哈希表 $visited$ 中,即 `visited[node_v] = Node(node_v.val, [])`
6. 然后将节点 $node\underline{}v$ 放入队列 $queue$ 中,即 `queue.append(node_v)`
7. 重复步骤 $3 \sim 6$,直到队列 $queue$ 为空。
8. 广度优先搜索结束,返回起始节点的克隆节点(即 $visited[node]$)。

##### 思路 1:代码

Expand Down Expand Up @@ -161,7 +191,7 @@ class Solution:

#### 4.2.2 题目大意

**描述**:给定一个只包含 `0``1` 元素的二维数组,`1` 代表岛屿,`0` 代表水。一座岛的面积就是上下左右相邻的 `1` 所组成的连通块的数目。
**描述**:给定一个只包含 $0$、$1$ 元素的二维数组,$1$ 代表岛屿,$0$ 代表水。一座岛的面积就是上下左右相邻的 $1$ 所组成的连通块的数目。

**要求**:计算出最大的岛屿面积。

Expand All @@ -170,18 +200,23 @@ class Solution:
- $m == grid.length$。
- $n == grid[i].length$。
- $1 \le m, n \le 50$。
- $grid[i][j]$ 为 `0``1`
- $grid[i][j]$ 为 $0$$1$

**示例**

- 示例 1:

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

```python
输入: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]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1
```

- 示例 2:

```python
输入:grid = [[0,0,0,0,0,0,0,0]]
输出:0
```
Expand All @@ -190,12 +225,13 @@ class Solution:

##### 思路 1:广度优先搜索

1. 使用 `ans` 记录最大岛屿面积。
2. 遍历二维数组的每一个元素,对于每个值为 `1` 的元素:
1. 将该元素置为 `0`。并使用队列 `q` 存储该节点位置。使用 `temp_ans` 记录当前岛屿面积。
2. 然后从队列 `q` 中取出第一个节点位置 `(i, j)`。遍历该节点位置上、下、左、右四个方向上的相邻节点。并将其置为 `0`(避免重复搜索)。并将其加入到队列中。并累加当前岛屿面积,即 `temp_ans += 1`
3. 不断重复上一步骤,直到队列 `q` 为空。
1. 使用 $ans$ 记录最大岛屿面积。
2. 遍历二维数组的每一个元素,对于每个值为 $1$ 的元素:
1. 将该元素置为 $0$。并使用队列 $queue$ 存储该节点位置。使用 $temp\underline{}ans$ 记录当前岛屿面积。
2. 然后从队列 $queue$ 中取出第一个节点位置 $(i, j)$。遍历该节点位置上、下、左、右四个方向上的相邻节点。并将其置为 $0$(避免重复搜索)。并将其加入到队列中。并累加当前岛屿面积,即 `temp_ans += 1`
3. 不断重复上一步骤,直到队列 $queue$ 为空。
4. 更新当前最大岛屿面积,即 `ans = max(ans, temp_ans)`
3. 将 $ans$ 作为答案返回。

##### 思路 1:代码

Expand All @@ -212,16 +248,16 @@ class Solution:
if grid[i][j] == 1:
grid[i][j] = 0
temp_ans = 1
q = collections.deque([(i, j)])
while q:
i, j = q.popleft()
queue = collections.deque([(i, j)])
while queue:
i, j = queue.popleft()
for direct in directs:
new_i = i + direct[0]
new_j = j + direct[1]
if new_i < 0 or new_i >= rows or new_j < 0 or new_j >= cols or grid[new_i][new_j] == 0:
continue
grid[new_i][new_j] = 0
q.append((new_i, new_j))
queue.append((new_i, new_j))
temp_ans += 1

ans = max(ans, temp_ans)
Expand Down

0 comments on commit 10116e7

Please sign in to comment.