Skip to content

Commit 4b6f2f0

Browse files
committed
修复和优化语句表述
1 parent d517b53 commit 4b6f2f0

File tree

7 files changed

+959
-988
lines changed

7 files changed

+959
-988
lines changed

docs/06_graph/06_02_graph_structure.md

Lines changed: 386 additions & 383 deletions
Large diffs are not rendered by default.

docs/06_graph/06_03_graph_dfs.md

Lines changed: 112 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
## 1. 深度优先搜索简介
22

3-
> **深度优先搜索算法(Depth First Search)**英文缩写为 DFS,是一种用于搜索树或图结构的算法。深度优先搜索算法采用了回溯思想,从起始节点开始,沿着一条路径尽可能深入地访问节点,直到无法继续前进时为止,然后回溯到上一个未访问的节点,继续深入搜索,直到完成整个搜索过程
3+
> **深度优先搜索算法(Depth First Search,简称 DFS**是一种用于遍历或搜索树、图等结构的经典算法。其核心思想是「沿一条路径尽可能深入」,遇到无法继续的节点时再回溯到上一个分叉点,继续探索其他路径,直到遍历完整个结构或找到目标为止
44
5-
深度优先搜索算法中所谓的深度优先,就是说优先沿着一条路径走到底,直到无法继续深入时再回头
5+
深度优先的含义,就是每次优先选择一条路径一直走到底,只有在无法继续时才回退,尝试其他分支
66

7-
在深度优先遍历的过程中,我们需要将当前遍历节点 $u$ 的相邻节点暂时存储起来,以便于在回退的时候可以继续访问它们。遍历到的节点顺序符合「后进先出」的特点,这正是「递归」和「堆栈」所遵循的规律,所以深度优先搜索可以通过「递归」或者「堆栈」来实现
7+
在 DFS 的遍历过程中,我们通常需要暂存当前节点 $u$ 的相邻节点,以便回溯时继续访问未遍历的节点。由于遍历顺序具有「后进先出」的特性,DFS 可以通过递归(系统栈)或显式使用栈结构来实现
88

99
## 2. 深度优先搜索算法步骤
1010

11-
接下来我们以一个无向图为例,介绍一下深度优先搜索的算法步骤。
11+
下面以无向图为例,简要梳理深度优先搜索的基本流程:
1212

13-
1. 选择起始节点 $u$,并将其标记为已访问
14-
2. 检查当前节点是否为目标节点(看具体题目要求)。
15-
3. 如果当前节点 $u$ 是目标节点,则直接返回结果
16-
4. 如果当前节点 $u$ 不是目标节点,则遍历当前节点 $u$ 的所有未访问邻接节点
17-
5. 对每个未访问的邻接节点 $v$,从节点 $v$ 出发继续进行深度优先搜索(递归)
18-
6. 如果节点 $u$ 没有未访问的相邻节点,回溯到上一个节点,继续搜索其他路径
19-
7. 重复 $2 \sim 6$ 步骤,直到遍历完整个图或找到目标节点为止。
13+
1. 选定起始节点 $u$,将其标记为已访问
14+
2. 判断当前节点 $u$ 是否为目标节点(具体依据题目要求)。
15+
3. 如果 $u$ 为目标节点,直接返回结果
16+
4. 如果 $u$ 不是目标节点,则遍历 $u$ 的所有未被访问的邻接节点
17+
5. 对每一个未访问的邻接节点 $v$,递归地从 $v$ 继续进行深度优先搜索
18+
6. 如果 $u$ 没有未访问的邻接节点,则回溯到上一个节点,继续探索其他分支
19+
7. 重复步骤 $2 \sim 6$,直到遍历完整个图或找到目标节点为止。
2020

2121
::: tabs#DFS
2222

@@ -46,105 +46,126 @@
4646

4747
:::
4848

49-
## 3. 基于递归实现的深度优先搜索
49+
## 3. 基于递归的深度优先搜索
5050

51-
### 3.1 基于递归实现的深度优先搜索算法步骤
51+
### 3.1 基于递归的深度优先搜索算法步骤
5252

53-
深度优先搜索算法可以通过递归来实现,以下是基于递归实现的深度优先搜索算法步骤
53+
深度优先搜索(DFS)可以通过递归方式实现。其基本递归流程如下
5454

55-
1. 定义 $graph$ 为存储无向图的嵌套数组变量,$visited$ 为标记访问节点的集合变量。$u$ 为当前遍历边的开始节点。定义 `def dfs_recursive(graph, u, visited):` 为递归实现的深度优先搜索方法
56-
2. 选择起始节点 $u$,并将其标记为已访问,即将节点 $u$ 放入 $visited$ 中`visited.add(u)`)。
57-
3. 检查当前节点 $u$ 是否为目标节点(看具体题目要求)。
58-
4. 如果当前节点 $u$ 是目标节点,则直接返回结果
59-
5. 如果当前节点 $u$ 不是目标节点,则遍历当前节点 $u$ 的所有未访问邻接节点
60-
6. 对每个未访问的邻接节点 $v$,从节点 $v$ 出发继续进行深度优先搜索(递归),即调用 `dfs_recursive(graph, v, visited)`
61-
7. 如果节点 $u$ 没有未访问的相邻节点,则回溯到最近访问的节点,继续搜索其他路径
62-
8. 重复 $3 \sim 7$ 步骤,直到遍历完整个图或找到目标节点为止。
55+
1. $graph$ 为无向图的邻接表,$visited$ 为已访问节点的集合,$u$ 为当前节点。定义递归函数 `def dfs_recursive(graph, u, visited):`
56+
2. 将起始节点 $u$ 标记为已访问`visited.add(u)`)。
57+
3. 判断当前节点 $u$ 是否为目标节点(具体依据题目要求)。
58+
4. 如果 $u$ 为目标节点,直接返回结果
59+
5. 如果 $u$ 不是目标节点,则遍历 $u$ 的所有未访问的邻接节点 $v$
60+
6. 对每个未访问的邻接节点 $v$,递归调用 `dfs_recursive(graph, v, visited)` 继续搜索
61+
7. 如果 $u$ 没有未访问的邻接节点,则自动回溯到上一个节点,继续探索其他分支
62+
8. 重复步骤 $3 \sim 7$,直到遍历完整个图或找到目标节点为止。
6363

64-
### 3.2 基于递归实现的深度优先搜索实现代码
64+
### 3.2 基于递归的深度优先搜索实现代码
6565

6666
```python
6767
class Solution:
6868
def dfs_recursive(self, graph, u, visited):
69-
print(u) # 访问节点
70-
visited.add(u) # 节点 u 标记其已访问
71-
69+
"""
70+
递归实现深度优先搜索(DFS)
71+
:param graph: 字典表示的邻接表,key为节点,value为邻接节点列表
72+
:param u: 当前访问的节点
73+
:param visited: 已访问节点的集合
74+
"""
75+
print(u) # 访问当前节点 u
76+
visited.add(u) # 标记节点 u 已访问
77+
78+
# 遍历所有邻接节点
7279
for v in graph[u]:
73-
if v not in visited: # 节点 v 未访问过
74-
# 深度优先搜索遍历节点
80+
if v not in visited: # 如果邻接节点 v 未被访问
81+
# 递归访问邻接节点 v
7582
self.dfs_recursive(graph, v, visited)
7683

7784

85+
# 示例图(邻接表形式,节点为字符串,边为无向边)
7886
graph = {
79-
"A": ["B", "C"],
80-
"B": ["A", "C", "D"],
81-
"C": ["A", "B", "D", "E"],
82-
"D": ["B", "C", "E", "F"],
83-
"E": ["C", "D"],
84-
"F": ["D", "G"],
85-
"G": []
87+
"1": ["2", "3"],
88+
"2": ["1", "3", "4"],
89+
"3": ["1", "2", "4", "5"],
90+
"4": ["2", "3", "5", "6"],
91+
"5": ["3", "4"],
92+
"6": ["4", "7"],
93+
"7": []
8694
}
8795

88-
# 基于递归实现的深度优先搜索
96+
# 初始化已访问节点集合
8997
visited = set()
90-
Solution().dfs_recursive(graph, "A", visited)
98+
# 从节点 "1" 开始进行深度优先搜索
99+
Solution().dfs_recursive(graph, "1", visited)
91100
```
92101

93-
## 4. 基于堆栈实现的深度优先搜索
102+
## 4. 基于堆栈的深度优先搜索
94103

95-
### 4.1 基于堆栈实现的深度优先搜索算法步骤
104+
### 4.1 基于堆栈的深度优先搜索算法步骤
96105

97-
深度优先搜索算法除了基于递归实现之外,还可以基于堆栈来实现。同时,为了防止多次遍历同一节点,在使用栈存放节点访问记录时,我们将「当前节点」以及「下一个将要访问的邻接节点下标」一同存入栈中,从而在出栈时,可以通过下标直接找到下一个邻接节点,而不用遍历所有邻接节点
106+
除了递归方式,深度优先搜索(DFS)还可以用显式的栈结构实现。为避免重复访问同一节点,栈中不仅存储当前节点,还记录下一个待访问的邻接节点下标。这样每次出栈时,可以直接定位下一个邻接节点,无需遍历全部邻接点
98107

99-
以下是基于堆栈实现的深度优先搜索的算法步骤
108+
基于堆栈的 DFS 步骤如下
100109

101-
1. 定义 $graph$ 为存储无向图的嵌套数组变量,$visited$ 为标记访问节点的集合变量。$start$ 为当前遍历边的开始节点。定义 $stack$ 用于存放节点访问记录的栈结构
110+
1. $graph$ 为无向图的邻接表,$visited$ 为已访问节点集合,$stack$ 为辅助栈
102111
2. 选择起始节点 $u$,检查当前节点 $u$ 是否为目标节点(看具体题目要求)。
103-
3. 如果当前节点 $u$ 是目标节点,则直接返回结果。
104-
4. 如果当前节点 $u$ 不是目标节点,则将节点 $u$ 以及节点 $u$ 下一个将要访问的邻接节点下标 $0$ 放入栈中,并标记为已访问,即 `stack.append([u, 0])``visited.add(u)`
105-
5. 如果栈不为空,取出 $stack$ 栈顶元素节点 $u$,以及节点 $u$ 下一个将要访问的邻接节点下标 $i$。
106-
6. 根据节点 $u$ 和下标 $i$,取出将要遍历的未访问过的邻接节点 $v$。
107-
7. 将节点 $u$ 以及节点 u 的下一个邻接节点下标 $i + 1$ 放入栈中。
108-
8. 访问节点 $v$,并对节点进行相关操作(看具体题目要求)。
109-
9. 将节点 $v$ 以及节点 $v$ 下一个邻接节点下标 $0$ 放入栈中,并标记为已访问,即 `stack.append([v, 0])``visited.add(v)`
110-
10. 重复步骤 $5 \sim 9$,直到 $stack$ 栈为空或找到目标节点为止。
112+
3. 如果 $u$ 是目标节点,直接返回结果。
113+
4. 如果 $u$ 不是目标节点,将 $[u, 0]$(节点 $u$ 及其下一个邻接节点下标 $0$)压入栈,并将 $u$ 标记为已访问:`stack.append([u, 0])``visited.add(u)`
114+
5. 当栈非空时,弹出栈顶元素 $[u, i]$,其中 $i$ 表示下一个待访问的邻接节点下标。
115+
6. 如果 $i$ 小于 $graph[u]$ 的邻接节点数,则取出 $v = graph[u][i]$。
116+
7. 将 $[u, i + 1]$ 压回栈中,表示下次将访问 $u$ 的下一个邻接节点。
117+
8. 如果 $v$ 未被访问,则对 $v$ 进行相关操作(如打印、判定等),并将 $[v, 0]$ 压入栈,同时标记 $v$ 已访问。
118+
9. 重复步骤 $5 \sim 8$,直到栈为空或找到目标节点为止。
119+
120+
这种实现方式可以模拟递归调用过程,且便于控制递归深度,适合递归层数较深或需手动管理回溯的场景。
111121

112122
### 4.2 基于堆栈实现的深度优先搜索实现代码
113123

114124
```python
115125
class Solution:
116126
def dfs_stack(self, graph, u):
117-
print(u) # 访问节点 u
118-
visited, stack = set(), [] # 使用 visited 标记访问过的节点, 使用栈 stack 存放临时节点
119-
120-
stack.append([u, 0]) # 将节点 u,节点 u 的下一个邻接节点下标放入栈中,下次将遍历 graph[u][0]
121-
visited.add(u) # 将起始节点 u 标记为已访问
122-
123-
127+
"""
128+
基于显式栈的深度优先搜索(DFS),适用于无向图/有向图的邻接表表示。
129+
:param graph: dict,邻接表,key为节点,value为邻接节点列表
130+
:param u: 起始节点
131+
"""
132+
visited = set() # 记录已访问节点,防止重复遍历
133+
stack = [] # 显式栈,模拟递归过程
134+
135+
stack.append([u, 0]) # 入栈:节点u及其下一个待访问邻接节点的下标 0
136+
visited.add(u) # 标记起始节点已访问
137+
print(u) # 访问起始节点
138+
124139
while stack:
125-
u, i = stack.pop() # 取出节点 u,以及节点 u 下一个将要访问的邻接节点下标 i
126-
127-
if i < len(graph[u]):
128-
v = graph[u][i] # 取出邻接节点 v
129-
stack.append([u, i + 1]) # 下一次将遍历 graph[u][i + 1]
130-
if v not in visited: # 节点 v 未访问过
131-
print(v) # 访问节点 v
132-
stack.append([v, 0]) # 下一次将遍历 graph[v][0]
133-
visited.add(v) # 将节点 v 标记为已访问
134-
140+
cur, idx = stack.pop() # 取出当前节点及其下一个邻接节点下标
141+
neighbors = graph[cur] # 当前节点的所有邻接节点
142+
143+
# 如果还有未遍历的邻接节点
144+
if idx < len(neighbors):
145+
v = neighbors[idx] # 取出下一个邻接节点
146+
stack.append([cur, idx + 1]) # 当前节点下标 + 1,回溯时继续遍历下一个邻接点
147+
148+
if v not in visited:
149+
print(v) # 访问新节点
150+
visited.add(v) # 标记为已访问
151+
stack.append([v, 0]) # 新节点入栈,准备遍历其邻接节点
152+
153+
# 也可以返回 visited 集合,便于后续处理
154+
# return visited
135155

156+
# 示例图(邻接表形式,节点为字符串,边为无向边)
136157
graph = {
137-
"A": ["B", "C"],
138-
"B": ["A", "C", "D"],
139-
"C": ["A", "B", "D", "E"],
140-
"D": ["B", "C", "E", "F"],
141-
"E": ["C", "D"],
142-
"F": ["D", "G"],
143-
"G": []
158+
"1": ["2", "3"],
159+
"2": ["1", "3", "4"],
160+
"3": ["1", "2", "4", "5"],
161+
"4": ["2", "3", "5", "6"],
162+
"5": ["3", "4"],
163+
"6": ["4", "7"],
164+
"7": []
144165
}
145166

146-
# 基于堆栈实现的深度优先搜索
147-
Solution().dfs_stack(graph, "A")
167+
# 从节点 "1" 开始进行深度优先搜索
168+
Solution().dfs_stack(graph, "1")
148169
```
149170

150171
## 5. 深度优先搜索应用
@@ -198,17 +219,19 @@ Solution().dfs_stack(graph, "A")
198219

199220
#### 5.1.3 解题思路
200221

201-
如果把上下左右相邻的字符 `'1'` 看做是 `1` 个连通块,这道题的目的就是求解一共有多少个连通块
222+
本题实质是统计二维网格中「上下左右」相连的 `'1'` 组成的连通块数量,即岛屿的个数
202223

203-
使用深度优先搜索或者广度优先搜索都可以
224+
可以采用深度优先搜索(DFS)或广度优先搜索(BFS)来实现
204225

205226
##### 思路 1:深度优先搜索
206227

207-
1. 遍历 $grid$。
208-
2. 对于每一个字符为 `'1'` 的元素,遍历其上下左右四个方向,并将该字符置为 `'0'`,保证下次不会被重复遍历。
209-
3. 如果超出边界,则返回 $0$。
210-
4. 对于 $(i, j)$ 位置的元素来说,递归遍历的位置就是 $(i - 1, j)$、$(i, j - 1)$、$(i + 1, j)$、$(i, j + 1)$ 四个方向。每次遍历到底,统计数记录一次。
211-
5. 最终统计出深度优先搜索的次数就是我们要求的岛屿数量。
228+
1. 遍历整个 $grid$ 网格。
229+
2. 当遇到字符为 `'1'` 的格子时,说明发现了新的岛屿,执行深度优先搜索(DFS):
230+
- 将当前格子标记为 `'0'`,避免重复访问。
231+
- 递归访问其上下左右四个相邻格子(即 $(i - 1, j)$、$(i + 1, j)$、$(i, j - 1)$、$(i, j + 1)$)。
232+
- 如果递归过程中遇到越界或当前格子为 `'0'`,则直接返回。
233+
3. 每当一次完整的 DFS 结束,岛屿计数加一。
234+
4. 最终 DFS 执行的次数即为岛屿的总数。
212235

213236
##### 思路 1:代码
214237

@@ -290,12 +313,12 @@ class Solution:
290313

291314
##### 思路 1:深度优先搜索
292315

293-
1. 使用哈希表 $visitedDict$ 来存储原图中被访问过的节点和克隆图中对应节点,键值对为「原图被访问过的节点:克隆图中对应节点」
294-
2. 从给定节点开始,以深度优先搜索的方式遍历原图。
295-
1. 如果当前节点被访问过,则返回隆图中对应节点
296-
2. 如果当前节点没有被访问过,则创建一个新的节点,并保存在哈希表中
297-
3. 遍历当前节点的邻接节点列表,递归调用当前节点的邻接节点,并将其放入克隆图中对应节点
298-
3. 递归结束,返回克隆节点
316+
1. 使用哈希表 $visitedDict$ 记录原图节点与其对应的克隆节点,键为原图节点,值为克隆节点
317+
2. 从给定节点出发,采用深度优先搜索遍历原图:
318+
1. 如果当前节点已在哈希表中,直接返回对应的克隆节点,避免重复克隆
319+
2. 如果当前节点未被访问,则新建一个克隆节点,并将其加入哈希表
320+
3. 遍历当前节点的所有邻居,对每个邻居递归调用克隆函数,并将返回的克隆节点加入当前克隆节点的邻居列表
321+
3. 递归返回当前节点的克隆节点
299322

300323
##### 思路 1:代码
301324

0 commit comments

Comments
 (0)