Skip to content

Commit 2ae06ed

Browse files
committed
update solution with pure dfs(geeksforgeeks)
1 parent 9a1a38c commit 2ae06ed

File tree

1 file changed

+141
-18
lines changed

1 file changed

+141
-18
lines changed

zh-hans/graph/topological_sorting.md

+141-18
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ The topological order can be:
5454

5555
Can you do it in both BFS and DFS?
5656

57-
## 题解1 - DFS
57+
## 题解1 - DFS(统计节点入度数)
5858

59-
图搜索相关的问题较为常见的解法是用 DFS 或者 BFS,这里我们先分析一下拓扑排序的核心要求:对于有向边 `A -> B`, A 需要出现在 B 之前。用过 Linux/MAC 的人对包管理工具肯定不陌生,如 apt-get, yum, pacman, brew 等,安装一项软件时往往要先将其所有依赖的软件包安装完。这个需求实现起来大概可以分为如下几个步骤:
59+
图搜索相关的问题较为常见的解法是用 DFS 或者 BFS,这里我们先分析一下拓扑排序的核心要求:对于有向边 `A -> B`, A 需要出现在 B 之前。用过 Linux/MAC 的人对包管理工具肯定不陌生,如 apt-get, yum, pacman, brew 等,安装一项软件时往往要先将其所有依赖的软件包安装完,拓扑排序解决的就是此类问题。这个需求实现起来大概可以分为如下几个步骤:
6060

6161
1. 找出不依赖其他顶点的顶点,即入度为0,这一定是符合要求的某一个拓扑排序的第一个顶点。
6262
2. 在图中去掉入度为 0 的顶点,并重新计算一次各顶点的入度,递归调用邻居节点,迭代第一步。
@@ -164,8 +164,7 @@ public class Solution {
164164
for (DirectedGraphNode node : graph) {
165165
inDegreeMap.putIfAbsent(node, 0);
166166
for (DirectedGraphNode neighbor : node.neighbors) {
167-
inDegreeMap.putIfAbsent(neighbor, 0);
168-
inDegreeMap.put(neighbor, inDegreeMap.get(neighbor) + 1);
167+
inDegreeMap.put(neighbor, inDegreeMap.getOrDefault(neighbor, 0) + 1);
169168
}
170169
}
171170
@@ -188,25 +187,22 @@ public class Solution {
188187

189188
### 源码分析
190189

191-
C++中使用 unordered_map 可获得更高的性能,私有方法中使用引用传值。在 `dfs` 递归的过程中,将节点加入到最终结果后需要对其入度减一,否则在上层循环邻居节点时会有重复。这里的 `dfs` 是伪 DFS, 因为这里只处理入度为 0 的节点。
190+
C++中使用 unordered_map 可获得更高的性能,私有方法中使用引用传值。在 `dfs` 递归的过程中,将节点加入到最终结果后需要对其入度减一,否则在上层循环邻居节点时会有重复。这里的 `dfs` 是剪枝过后的 DFS, 因为这里只处理入度为 0 的节点。另外在第一次求各节点的入度数时,需要先初始化为0,否则会漏掉部分入度为0的节点
192191

193192
### 复杂度分析
194193

195194
以 V 表示顶点数,E 表示有向图中边的条数。
196195

197196
首先获得节点的入度数,时间复杂度为 $$O(V+E)$$, 使用了哈希表存储,空间复杂度为 $$O(V)$$. 遍历图求得入度为0的节点,时间复杂度为 $$O(V)$$. 仅在入度为0时调用 DFS,故时间复杂度为 $$O(V+E)$$.
198-
199-
需要注意的是这里的 DFS 不是纯 DFS,使用了 BFS 的思想进行了优化,否则一个节点将被遍历多次,时间复杂度可能恶化为指数级别。
200-
201197
综上,时间复杂度近似为 $$O(V+E)$$, 空间复杂度为 $$O(V)$$.
202198

203199
## 题解2 - BFS
204200

205-
拓扑排序除了可用 DFS 求解外,也可使用 BFS, 相比题解1使用递归获取入度为 0 的节点,我们还可以通过队列获取非邻居节点的其他入度为 0 的节点。具体方法为:
201+
拓扑排序除了可用 DFS 求解外,也可使用 BFS, 相比题解1使用递归顺腾摸瓜获取入度为 0 的节点,我们还可以通过队列获取非邻居节点的其他入度为 0 的节点,即 BFS。具体方法为:
206202

207203
1. 获得图中各节点的入度。
208-
2. BFS 首先遍历求得入度数为0的节点,入队,便于下一次 BFS。
209-
3. 队列不为空时,弹出队顶元素并对其邻接节点进行 BFS,将入度为0的节点加入到最终结果和队列中,重复此过程直至队列为空。
204+
2. BFS 首先遍历求得所有入度数为0的节点,入队,便于下一次 BFS。
205+
3. 队列不为空时,弹出队顶元素并对其邻接节点入度数减一,将入度为0的节点加入到队列中,重复此过程直至队列为空。
210206

211207
### C++
212208

@@ -228,24 +224,24 @@ public:
228224
vector<DirectedGraphNode*> topSort(vector<DirectedGraphNode*> graph) {
229225
vector<DirectedGraphNode*> result;
230226
if (graph.size() == 0) return result;
231-
227+
232228
map<DirectedGraphNode*, int> indegree;
233229
// get indegree of all DirectedGraphNode
234230
indeg(graph, indegree);
235231
queue<DirectedGraphNode*> q;
236232
// bfs
237233
bfs(graph, indegree, q, result);
238-
234+
239235
return result;
240236
}
241-
237+
242238
private:
243239
/** get indegree of all DirectedGraphNode
244240
*
245241
*/
246242
void indeg(vector<DirectedGraphNode*> &graph,
247243
map<DirectedGraphNode*, int> &indegree) {
248-
244+
249245
for (int i = 0; i < graph.size(); ++i) {
250246
for (int j = 0; j < graph[i]->neighbors.size(); j++) {
251247
if (indegree.find(graph[i]->neighbors[j]) == indegree.end()) {
@@ -256,7 +252,7 @@ private:
256252
}
257253
}
258254
}
259-
255+
260256
void bfs(vector<DirectedGraphNode*> &graph, map<DirectedGraphNode*, int> &indegree,
261257
queue<DirectedGraphNode *> &q, vector<DirectedGraphNode*> &ret) {
262258
@@ -266,7 +262,7 @@ private:
266262
q.push(graph[i]);
267263
}
268264
}
269-
265+
270266
while (!q.empty()) {
271267
DirectedGraphNode *cur = q.front();
272268
q.pop();
@@ -282,14 +278,141 @@ private:
282278
};
283279
```
284280
281+
### Java
282+
283+
```java
284+
/**
285+
* Definition for Directed graph.
286+
* class DirectedGraphNode {
287+
* int label;
288+
* ArrayList<DirectedGraphNode> neighbors;
289+
* DirectedGraphNode(int x) { label = x; neighbors = new ArrayList<DirectedGraphNode>(); }
290+
* };
291+
*/
292+
293+
public class Solution {
294+
/*
295+
* @param graph: A list of Directed graph node
296+
* @return: Any topological order for the given graph.
297+
*/
298+
public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
299+
ArrayList<DirectedGraphNode> result = new ArrayList<DirectedGraphNode>();
300+
301+
if (graph == null || graph.size() == 0) return result;
302+
303+
Queue<DirectedGraphNode> queue = new LinkedList<>();
304+
Map<DirectedGraphNode, Integer> map = getIndegreeMap(graph);
305+
for (Map.Entry<DirectedGraphNode, Integer> entry : map.entrySet()) {
306+
if (entry.getValue() == 0) {
307+
queue.offer(entry.getKey());
308+
}
309+
}
310+
311+
bfs(queue, map, result);
312+
313+
return result;
314+
}
315+
316+
private Map<DirectedGraphNode, Integer> getIndegreeMap(ArrayList<DirectedGraphNode> graph) {
317+
Map<DirectedGraphNode, Integer> map = new HashMap<>();
318+
319+
for (DirectedGraphNode node : graph) {
320+
map.putIfAbsent(node, 0);
321+
for (DirectedGraphNode neighbor : node.neighbors) {
322+
map.put(neighbor, map.getOrDefault(neighbor, 0) + 1);
323+
}
324+
}
325+
326+
return map;
327+
}
328+
329+
private void bfs(Queue<DirectedGraphNode> queue, Map<DirectedGraphNode, Integer> map, ArrayList<DirectedGraphNode> result) {
330+
331+
while (!queue.isEmpty()) {
332+
DirectedGraphNode node = queue.poll();
333+
result.add(node);
334+
for (DirectedGraphNode neighbor : node.neighbors) {
335+
map.put(neighbor, map.get(neighbor) - 1);
336+
if (map.get(neighbor) == 0) {
337+
queue.offer(neighbor);
338+
}
339+
}
340+
}
341+
}
342+
}
343+
```
344+
285345
### 源码分析
286346

287-
C++中在判断入度是否为0时将对 map 产生副作用,在求入度数时只有入度数大于等于1才会出现在 map 中,故不在 map 中时直接调用 indegree 方法将产生新的键值对,初始值为0,恰好满足此题需求。
347+
C++中在判断入度是否为0时将对 map 产生副作用,在求入度数时只有入度数大于等于1才会出现在 map 中,故不在 map 中时直接调用 indegree 方法将产生新的键值对,初始值为0,恰好满足此题需求。与 DFS 的解法不同,在 bfs 的实现中可以不对已加入到最终结果的节点入度数减一,因为没有上一层循环了。
288348

289349
### 复杂度分析
290350

291351
同题解1 的分析,时间复杂度为 $$O(V+E)$$, 空间复杂度为 $$O(V)$$.
292352

353+
## 题解3 - DFS(递归保证局部拓扑排序)
354+
355+
与题解1和题解2中依赖先计算入度数不同,这种解法可以不必事先计算入度数,从图中任意一个节点递归,直至将其所有邻接节点入栈后再对自己入栈,这样可保证有依赖的节点一定在其父节点后面出栈,遍历完图中所有节点即可得最终有效结果。这里需要借助辅助标记 Set 或者数组。具体过程可参考 GeeksforGeeks 中的视频讲解。
356+
357+
### Java
358+
359+
```java
360+
/**
361+
* Definition for Directed graph.
362+
* class DirectedGraphNode {
363+
* int label;
364+
* ArrayList<DirectedGraphNode> neighbors;
365+
* DirectedGraphNode(int x) { label = x; neighbors = new ArrayList<DirectedGraphNode>(); }
366+
* };
367+
*/
368+
369+
public class Solution {
370+
/*
371+
* @param graph: A list of Directed graph node
372+
* @return: Any topological order for the given graph.
373+
*/
374+
public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
375+
ArrayList<DirectedGraphNode> result = new ArrayList<>();
376+
377+
if (graph == null || graph.size() == 0) return result;
378+
379+
Set<DirectedGraphNode> visited = new HashSet<>();
380+
Deque<DirectedGraphNode> stack = new ArrayDeque<>();
381+
382+
for (DirectedGraphNode node : graph) {
383+
dfs(node, visited, stack);
384+
}
385+
386+
while (!stack.isEmpty()) {
387+
result.add(stack.pop());
388+
}
389+
390+
return result;
391+
}
392+
393+
private void dfs(DirectedGraphNode root,
394+
Set<DirectedGraphNode> visited,
395+
Deque<DirectedGraphNode> stack) {
396+
397+
if (!visited.contains(root)) {
398+
visited.add(root);
399+
for (DirectedGraphNode neighbor : root.neighbors) {
400+
dfs(neighbor, visited, stack);
401+
}
402+
stack.offerFirst(root);
403+
}
404+
}
405+
}
406+
```
407+
408+
### 源码分析
409+
410+
注意 Java 中栈 Stack 的使用即可,基础数据结构小节中有解析。
411+
412+
### 复杂度分析
413+
414+
同题解1 的分析,遍历所有节点所有边,时间复杂度为 $$O(V+E)$$, 空间复杂度为 $$O(V)$$.
415+
293416
## Reference
294417

295418
- [Topological Sorting 参考程序 Java/C++/Python](http://www.jiuzhang.com/solutions/topological-sorting/)

0 commit comments

Comments
 (0)