2
2
3
3
## 1. 深度优先搜索简介
4
4
5
- > ** 深度优先搜索算法** (Depth First Search):英文缩写为 DFS。是一种用于遍历或搜索树或图的算法。该算法沿着树的深度遍历树的节点,会尽可能深的搜索树的分支。当节点 ` v ` 的所在边都己被探寻过,搜索将回溯到发现节点 ` v ` 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止 。
5
+ > ** 深度优先搜索算法** (Depth First Search):英文缩写为 DFS。是一种用于搜索树或图的算法。所谓深度优先,就是说每次都尝试向更深的节点走 。
6
6
7
- 深度优先搜索使用的是回溯思想,这种思想很适合使用「递归」来实现。而递归对问题的处理顺序,遵循了「后进先出」的规律。所以递归问题的处理,需要借助「堆栈」来实现 。
7
+ 深度优先搜索采用了回溯思想,该算法沿着树的深度遍历树的节点,会尽可能深的搜索树的分支。当节点 ` v ` 的所在边都己被探寻过,搜索将回溯到发现节点 ` v ` 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止 。
8
8
9
- 在深度优先遍历的过程中,我们需要将当前遍历节点 ` v ` 的相邻节点暂时存储起来,以便于在回退的时候可以继续访问它们。遍历到的节点顺序符合「后进先出」的特点,所以深度优先搜索可以通过「递归」或者「堆栈」来实现。
9
+ 在深度优先遍历的过程中,我们需要将当前遍历节点 ` v ` 的相邻节点暂时存储起来,以便于在回退的时候可以继续访问它们。遍历到的节点顺序符合「后进先出」的特点,这正是「递归」和「堆栈」所遵循的规律, 所以深度优先搜索可以通过「递归」或者「堆栈」来实现。
10
10
11
11
## 2. 深度优先搜索过程演示
12
12
13
13
接下来我们以一个无向图为例,演示一下深度优先搜索的过程。
14
14
15
- 我们用邻接字典的方式存储无向图结构,对应结构代码如下 :
15
+ 我们用邻接字典的方式存储无向图结构,对应结构如下 :
16
16
17
17
``` Python
18
18
# 定义无向图结构
@@ -26,23 +26,25 @@ graph = {
26
26
}
27
27
```
28
28
29
- 该无向图的结构如图左所示,图右为深度优先搜索的遍历路径。
29
+ 该无向图对应的邻接字典表示:无向图中有 ` A ` 、` B ` 、` C ` 、` D ` 、` E ` 、` F ` 共 ` 6 ` 个节点,其中与 ` A ` 节点相连的有 ` B ` 、` C ` 两个节点,与 ` B ` 节点相连的有 ` A ` 、` C ` 、` D ` 三个节点,等等。
30
+
31
+ 该无向图的结构如图左所示,其深度优先搜索的遍历路径如图右所示。
30
32
31
33
![ ] ( https://qcdn.itcharge.cn/images/20211214180351.png )
32
34
33
- 其对应的动态演示图如下所示 。
35
+ 其深度优先搜索的遍历过程如下动态图所示 。
34
36
35
37
![ ] ( https://qcdn.itcharge.cn/images/20211214175901.gif )
36
38
37
39
## 3. 基于递归实现的深度优先搜索
38
40
39
41
### 3.1 基于递归实现的深度优先搜索实现步骤
40
42
41
- - ` graph ` 为存储无向图的字典变量,` visited ` 为标记访问节点的 set 集合变量。` start ` 为当前遍历边的开始节点。` def dfs_recursive(graph, start, visited): ` 为递归实现的深度优先搜索方法。
42
- - 将 ` start ` 标记为已访问,即将 ` start ` 节点放入 ` visited ` 中(` visited.add(start) ` )。
43
- - 访问节点 ` start ` ,并对节点进行相关操作(看具体题目要求)。
44
- - 遍历与节点 ` start ` 相连并构成边的节点 ` end ` 。
45
- - 如果 ` end ` 没有被访问过,则从 ` end ` 节点调用递归实现的深度优先搜索方法,即 ` dfs_recursive(graph, end, visited) ` 。
43
+ 1 . 定义 ` graph ` 为存储无向图的字典变量,` visited ` 为标记访问节点的 set 集合变量。` start ` 为当前遍历边的开始节点。` def dfs_recursive(graph, start, visited): ` 为递归实现的深度优先搜索方法。
44
+ 2 . 将 ` start ` 标记为已访问,即将 ` start ` 节点放入 ` visited ` 中(` visited.add(start) ` )。
45
+ 3 . 访问节点 ` start ` ,并对节点进行相关操作(看具体题目要求)。
46
+ 4 . 遍历与节点 ` start ` 相连并构成边的节点 ` end ` 。
47
+ 1 . 如果 ` end ` 没有被访问过,则从 ` end ` 节点调用递归实现的深度优先搜索方法,即 ` dfs_recursive(graph, end, visited) ` 。
46
48
47
49
### 3.2 基于递归实现的深度优先搜索实现代码
48
50
@@ -112,31 +114,55 @@ def dfs_stack(graph, start):
112
114
113
115
#### 5.1.2 题目大意
114
116
115
- 给定一个由 ` 1 ` (陆地)和 ` 0 ` (水)组成的的二维网格 ` grid ` 。
117
+ ** 描述 ** :给定一个由字符 ` '1' ` (陆地)和字符 ` '0' ` (水)组成的的二维网格 ` grid ` 。
116
118
117
- 要求 :计算网格中岛屿的数量。
119
+ ** 要求 ** :计算网格中岛屿的数量。
118
120
119
- 注意 :
121
+ ** 说明 ** :
120
122
121
- - 岛屿总是被水包围,并且每座岛屿只能由水平方向 / 竖直方向上相邻的陆地连接形成 。
123
+ - 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成 。
122
124
- 此外,你可以假设该网格的四条边均被水包围。
125
+ - $m == grid.length$。
126
+ - $n == grid[ i] .length$。
127
+ - $1 \le m, n \le 300$。
128
+ - ` grid[i][j] ` 的值为 ` '0' ` 或 ` '1' ` 。
129
+
130
+ ** 示例** :
131
+
132
+ ``` Python
133
+ 输入:grid = [
134
+ [" 1" ," 1" ," 1" ," 1" ," 0" ],
135
+ [" 1" ," 1" ," 0" ," 1" ," 0" ],
136
+ [" 1" ," 1" ," 0" ," 0" ," 0" ],
137
+ [" 0" ," 0" ," 0" ," 0" ," 0" ]
138
+ ]
139
+ 输出:1
140
+
141
+
142
+ 输入:grid = [
143
+ [" 1" ," 1" ," 0" ," 0" ," 0" ],
144
+ [" 1" ," 1" ," 0" ," 0" ," 0" ],
145
+ [" 0" ," 0" ," 1" ," 0" ," 0" ],
146
+ [" 0" ," 0" ," 0" ," 1" ," 1" ]
147
+ ]
148
+ 输出:3
149
+ ```
123
150
124
151
#### 5.1.3 解题思路
125
152
126
- 如果把上下左右相邻的字符 ` 1 ` 看做是 ` 1 ` 个连通块,这道题的目的就是求解一共有多少个连通块。我们可以使用深度优先搜索来做。具体做法如下:
153
+ 如果把上下左右相邻的字符 ` '1' ` 看做是 ` 1 ` 个连通块,这道题的目的就是求解一共有多少个连通块。
154
+
155
+ 使用深度优先搜索或者广度优先搜索都可以。
127
156
128
- - 使用变量 ` count ` 统计连通块数目。然后遍历 ` grid ` 。
129
- - 对于 ` (i, j) ` 位置上的元素:
130
- - 如果 ` grid[i][j] == 1 ` ,调用深度优先搜索方法,令统计变量 + 1。
131
- - 深度优先搜索方法:初始位置 ` (i, j) ` 位置是一块岛屿,目的是找到该点的岛屿边界。
132
- - 将其置为 ` 0 ` (避免重复搜索)。然后从该点出发,递归遍历上、下、左、右四个方向,也就是递归遍历 ` (i - 1, j) ` 、` (i, j - 1) ` 、` (i + 1, j) ` 、` (i, j + 1) ` 四个方向。
133
- - 终止条件:
134
- - ` (i, j) ` 超出矩阵范围。
135
- - ` (i, j) ` 位置上是水,即 ` grid[i][j] == 0 ` 。
157
+ ##### 思路 1:深度优先搜索
136
158
137
- - 最终统计出来的连通块数 ` count ` 就是我们要求的岛屿数量。
159
+ 1 . 遍历 ` grid ` 。
160
+ 2 . 对于每一个字符为 ` '1' ` 的元素,遍历其上下左右四个方向,并将该字符置为 ` 0 ` ,保证下次不会被重复遍历。
161
+ 3 . 如果超出边界,则返回 ` 0 ` 。
162
+ 4 . 对于 ` (i, j) ` 位置的元素来说,递归遍历的位置就是 ` (i - 1, j) ` 、` (i, j - 1) ` 、` (i + 1, j) ` 、` (i, j + 1) ` 四个方向。每次遍历到底,统计数记录一次。
163
+ 5 . 最终统计出深度优先搜索的次数就是我们要求的岛屿数量。
138
164
139
- #### 5.1.4 代码
165
+ ##### 思路 1: 代码
140
166
141
167
``` Python
142
168
class Solution :
@@ -146,10 +172,10 @@ class Solution:
146
172
if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] == ' 0' :
147
173
return 0
148
174
grid[i][j] = ' 0'
149
- self .dfs(grid, i+ 1 , j)
150
- self .dfs(grid, i, j+ 1 )
151
- self .dfs(grid, i- 1 , j)
152
- self .dfs(grid, i, j- 1 )
175
+ self .dfs(grid, i + 1 , j)
176
+ self .dfs(grid, i, j + 1 )
177
+ self .dfs(grid, i - 1 , j)
178
+ self .dfs(grid, i, j - 1 )
153
179
154
180
def numIslands (self , grid : List[List[str ]]) -> int :
155
181
count = 0
@@ -161,6 +187,11 @@ class Solution:
161
187
return count
162
188
```
163
189
190
+ ##### 思路 1:复杂度分析
191
+
192
+ - ** 时间复杂度** :$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。
193
+ - ** 空间复杂度** :$O(m \times n)$。
194
+
164
195
### 5.2 克隆图
165
196
166
197
#### 5.2.1 题目链接
@@ -169,45 +200,82 @@ class Solution:
169
200
170
201
#### 5.2.2 题目大意
171
202
172
- 给定一个无向连通图中一个节点的引用 。
203
+ ** 描述 ** :以每个节点的邻接列表形式(二维列表)给定一个无向连通图,其中 ` adjList[i] ` 表示值为 ` i + 1 ` 的节点的邻接列表, ` adjList[i][j] ` 表示值为 ` i + 1 ` 的节点与值为 ` adjList[i][j] ` 的节点有一条边 。
173
204
174
- 要求 :返回该图的深拷贝。
205
+ ** 要求 ** :返回该图的深拷贝。
175
206
176
- #### 5.2.3 解题思路
207
+ ** 说明 ** :
177
208
178
- 深拷贝的意思就是构建一张与原图结构、值均一样的图,但是所用的节点不再是原图节点的引用,即每个节点都要新建。
209
+ - 节点数不超过 ` 100 ` 。
210
+ - 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。
211
+ - 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
212
+ - 由于图是无向的,如果节点 ` p ` 是节点 ` q ` 的邻居,那么节点 ` q ` 也必须是节点 ` p ` 的邻居。
213
+ - 图是连通图,你可以从给定节点访问到所有节点。
179
214
180
- 可以使用深度优先搜索遍历图的所有节点,并在遍历图的同时新建节点,并构建新图。具体做法如下 :
215
+ ** 示例 ** :
181
216
182
- - 使用字典变量 ` visited ` 存储访问过的节点,键值对为 ` 原节点 : 新节点 ` 。
183
- - 从 ` node ` 节点开始,调用深度优先搜索方法。
184
- - 如果 ` node ` 节点在 ` visited ` 中,则返回 ` visited ` 中存储的新节点,即 ` visited[node] ` 。
185
- - 新建复制节点 ` clone_node ` ,赋值为 ` node.val ` 。
186
- - 将其加入到 ` visited ` 中,即 ` visited[node] = clone_node ` 。
187
- - 遍历 ` node ` 节点的相邻节点,并从相邻节点开始,递归调用深度优先搜索方法。
188
- - 最后返回 ` clone_node ` 。
217
+ ![ ] ( https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/133_clone_graph_question.png )
189
218
190
- #### 5.2.4 代码
219
+ ``` Python
220
+ 输入:adjList = [[2 ,4 ],[1 ,3 ],[2 ,4 ],[1 ,3 ]]
221
+ 输出:[[2 ,4 ],[1 ,3 ],[2 ,4 ],[1 ,3 ]]
222
+ 解释:
223
+ 图中有 4 个节点。
224
+ 节点 1 的值是 1 ,它有两个邻居:节点 2 和 4 。
225
+ 节点 2 的值是 2 ,它有两个邻居:节点 1 和 3 。
226
+ 节点 3 的值是 3 ,它有两个邻居:节点 2 和 4 。
227
+ 节点 4 的值是 4 ,它有两个邻居:节点 1 和 3 。
228
+ ```
229
+
230
+ ![ ] ( https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/graph-1.png )
191
231
192
232
``` Python
193
- class Solution :
194
- def dfs (self , node : ' Node' , visited ) -> ' Node' :
195
- if node in visited:
196
- return visited[node]
233
+ 输入:adjList = [[2 ],[1 ]]
234
+ 输出:[[2 ],[1 ]]
235
+ ```
197
236
198
- clone_node = Node(node.val, [])
199
- visited[node] = clone_node
200
- for neighbor in node.neighbors:
201
- clone_node.neighbors.append(self .dfs(neighbor, visited))
202
- return clone_node
237
+ #### 5.2.3 解题思路
238
+
239
+ 所谓深拷贝,就是构建一张与原图结构、值均一样的图,但是所用的节点不再是原图节点的引用,即每个节点都要新建。
240
+
241
+ 可以用深度优先搜索或者广度优先搜索来做。
242
+
243
+ ##### 思路 1:深度优先搜索
203
244
245
+ 1 . 使用哈希表 ` visitedDict ` 来存储原图中被访问过的节点和克隆图中对应节点,键值对为 原图被访问过的节点:克隆图中对应节点。
246
+ 2 . 从给定节点开始,以深度优先搜索的方式遍历原图。
247
+ 1 . 如果当前节点被访问过,则返回隆图中对应节点。
248
+ 2 . 如果当前节点没有被访问过,则创建一个新的节点,并保存在哈希表中。
249
+ 3 . 遍历当前节点的邻接节点列表,递归调用当前节点的邻接节点,并将其放入克隆图中对应节点。
250
+ 3 . 递归结束,返回克隆节点。
251
+
252
+ ##### 思路 1:代码
253
+
254
+ ``` Python
255
+ class Solution :
204
256
def cloneGraph (self , node : ' Node' ) -> ' Node' :
205
257
if not node:
206
258
return node
207
- visited = dict ()
208
- return self .dfs(node, visited)
259
+ visitedDict = dict ()
260
+
261
+ def dfs (node : ' Node' ) -> ' Node' :
262
+ if node in visitedDict:
263
+ return visitedDict[node]
264
+
265
+ clone_node = Node(node.val, [])
266
+ visitedDict[node] = clone_node
267
+ for neighbor in node.neighbors:
268
+ clone_node.neighbors.append(dfs(neighbor))
269
+ return clone_node
270
+
271
+ return dfs(node)
209
272
```
210
273
274
+ ##### 思路 1:复杂度分析
275
+
276
+ - ** 时间复杂度** :$O(n)$。其中 $n$ 为图中节点数量。
277
+ - ** 空间复杂度** :$O(n)$。
278
+
211
279
## 参考资料
212
280
213
281
- 【文章】[ 深度优先搜索 - LeetBook - 力扣(LeetCode)] ( https://leetcode.cn/leetbook/read/dfs/egx6xc/ )
0 commit comments