Skip to content

Commit 374531f

Browse files
committed
Update 01.Graph-DFS.md
1 parent ebbacf5 commit 374531f

File tree

1 file changed

+122
-54
lines changed

1 file changed

+122
-54
lines changed

Contents/08.Graph/02.Graph-Traversal/01.Graph-DFS.md

Lines changed: 122 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,17 @@
22

33
## 1. 深度优先搜索简介
44

5-
> **深度优先搜索算法**(Depth First Search):英文缩写为 DFS。是一种用于遍历或搜索树或图的算法。该算法沿着树的深度遍历树的节点,会尽可能深的搜索树的分支。当节点 `v` 的所在边都己被探寻过,搜索将回溯到发现节点 `v` 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止
5+
> **深度优先搜索算法**(Depth First Search):英文缩写为 DFS。是一种用于搜索树或图的算法。所谓深度优先,就是说每次都尝试向更深的节点走
66
7-
深度优先搜索使用的是回溯思想,这种思想很适合使用「递归」来实现。而递归对问题的处理顺序,遵循了「后进先出」的规律。所以递归问题的处理,需要借助「堆栈」来实现
7+
深度优先搜索采用了回溯思想,该算法沿着树的深度遍历树的节点,会尽可能深的搜索树的分支。当节点 `v` 的所在边都己被探寻过,搜索将回溯到发现节点 `v` 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止
88

9-
在深度优先遍历的过程中,我们需要将当前遍历节点 `v` 的相邻节点暂时存储起来,以便于在回退的时候可以继续访问它们。遍历到的节点顺序符合「后进先出」的特点,所以深度优先搜索可以通过「递归」或者「堆栈」来实现。
9+
在深度优先遍历的过程中,我们需要将当前遍历节点 `v` 的相邻节点暂时存储起来,以便于在回退的时候可以继续访问它们。遍历到的节点顺序符合「后进先出」的特点,这正是「递归」和「堆栈」所遵循的规律,所以深度优先搜索可以通过「递归」或者「堆栈」来实现。
1010

1111
## 2. 深度优先搜索过程演示
1212

1313
接下来我们以一个无向图为例,演示一下深度优先搜索的过程。
1414

15-
我们用邻接字典的方式存储无向图结构,对应结构代码如下
15+
我们用邻接字典的方式存储无向图结构,对应结构如下
1616

1717
```Python
1818
# 定义无向图结构
@@ -26,23 +26,25 @@ graph = {
2626
}
2727
```
2828

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

3133
![](https://qcdn.itcharge.cn/images/20211214180351.png)
3234

33-
其对应的动态演示图如下所示
35+
其深度优先搜索的遍历过程如下动态图所示
3436

3537
![](https://qcdn.itcharge.cn/images/20211214175901.gif)
3638

3739
## 3. 基于递归实现的深度优先搜索
3840

3941
### 3.1 基于递归实现的深度优先搜索实现步骤
4042

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)`
4648

4749
### 3.2 基于递归实现的深度优先搜索实现代码
4850

@@ -112,31 +114,55 @@ def dfs_stack(graph, start):
112114

113115
#### 5.1.2 题目大意
114116

115-
给定一个由 `1`(陆地)`0`(水)组成的的二维网格 `grid`
117+
**描述**:给定一个由字符 `'1'`(陆地)和字符 `'0'`(水)组成的的二维网格 `grid`
116118

117-
要求:计算网格中岛屿的数量。
119+
**要求**:计算网格中岛屿的数量。
118120

119-
注意
121+
**说明**
120122

121-
- 岛屿总是被水包围,并且每座岛屿只能由水平方向 / 竖直方向上相邻的陆地连接形成
123+
- 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成
122124
- 此外,你可以假设该网格的四条边均被水包围。
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+
```
123150

124151
#### 5.1.3 解题思路
125152

126-
如果把上下左右相邻的字符 `1` 看做是 `1` 个连通块,这道题的目的就是求解一共有多少个连通块。我们可以使用深度优先搜索来做。具体做法如下:
153+
如果把上下左右相邻的字符 `'1'` 看做是 `1` 个连通块,这道题的目的就是求解一共有多少个连通块。
154+
155+
使用深度优先搜索或者广度优先搜索都可以。
127156

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:深度优先搜索
136158

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. 最终统计出深度优先搜索的次数就是我们要求的岛屿数量。
138164

139-
#### 5.1.4 代码
165+
##### 思路 1:代码
140166

141167
```Python
142168
class Solution:
@@ -146,10 +172,10 @@ class Solution:
146172
if i < 0 or i >= n or j < 0 or j >= m or grid[i][j] == '0':
147173
return 0
148174
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)
153179

154180
def numIslands(self, grid: List[List[str]]) -> int:
155181
count = 0
@@ -161,6 +187,11 @@ class Solution:
161187
return count
162188
```
163189

190+
##### 思路 1:复杂度分析
191+
192+
- **时间复杂度**:$O(m \times n)$。其中 $m$ 和 $n$ 分别为行数和列数。
193+
- **空间复杂度**:$O(m \times n)$。
194+
164195
### 5.2 克隆图
165196

166197
#### 5.2.1 题目链接
@@ -169,45 +200,82 @@ class Solution:
169200

170201
#### 5.2.2 题目大意
171202

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

174-
要求:返回该图的深拷贝。
205+
**要求**:返回该图的深拷贝。
175206

176-
#### 5.2.3 解题思路
207+
**说明**
177208

178-
深拷贝的意思就是构建一张与原图结构、值均一样的图,但是所用的节点不再是原图节点的引用,即每个节点都要新建。
209+
- 节点数不超过 `100`
210+
- 每个节点值 $Node.val$ 都是唯一的,$1 \le Node.val \le 100$。
211+
- 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
212+
- 由于图是无向的,如果节点 `p` 是节点 `q` 的邻居,那么节点 `q` 也必须是节点 `p` 的邻居。
213+
- 图是连通图,你可以从给定节点访问到所有节点。
179214

180-
可以使用深度优先搜索遍历图的所有节点,并在遍历图的同时新建节点,并构建新图。具体做法如下
215+
**示例**
181216

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)
189218

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,它有两个邻居:节点 24
225+
节点 2 的值是 2,它有两个邻居:节点 13
226+
节点 3 的值是 3,它有两个邻居:节点 24
227+
节点 4 的值是 4,它有两个邻居:节点 13
228+
```
229+
230+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/02/01/graph-1.png)
191231

192232
```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+
```
197236

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:深度优先搜索
203244

245+
1. 使用哈希表 `visitedDict` 来存储原图中被访问过的节点和克隆图中对应节点,键值对为 原图被访问过的节点:克隆图中对应节点。
246+
2. 从给定节点开始,以深度优先搜索的方式遍历原图。
247+
1. 如果当前节点被访问过,则返回隆图中对应节点。
248+
2. 如果当前节点没有被访问过,则创建一个新的节点,并保存在哈希表中。
249+
3. 遍历当前节点的邻接节点列表,递归调用当前节点的邻接节点,并将其放入克隆图中对应节点。
250+
3. 递归结束,返回克隆节点。
251+
252+
##### 思路 1:代码
253+
254+
```Python
255+
class Solution:
204256
def cloneGraph(self, node: 'Node') -> 'Node':
205257
if not node:
206258
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)
209272
```
210273

274+
##### 思路 1:复杂度分析
275+
276+
- **时间复杂度**:$O(n)$。其中 $n$ 为图中节点数量。
277+
- **空间复杂度**:$O(n)$。
278+
211279
## 参考资料
212280

213281
- 【文章】[深度优先搜索 - LeetBook - 力扣(LeetCode)](https://leetcode.cn/leetbook/read/dfs/egx6xc/)

0 commit comments

Comments
 (0)