@@ -54,9 +54,9 @@ The topological order can be:
54
54
55
55
Can you do it in both BFS and DFS?
56
56
57
- ## 题解1 - DFS
57
+ ## 题解1 - DFS(统计节点入度数)
58
58
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 等,安装一项软件时往往要先将其所有依赖的软件包安装完,拓扑排序解决的就是此类问题 。这个需求实现起来大概可以分为如下几个步骤:
60
60
61
61
1 . 找出不依赖其他顶点的顶点,即入度为0,这一定是符合要求的某一个拓扑排序的第一个顶点。
62
62
2 . 在图中去掉入度为 0 的顶点,并重新计算一次各顶点的入度,递归调用邻居节点,迭代第一步。
@@ -164,8 +164,7 @@ public class Solution {
164
164
for (DirectedGraphNode node : graph) {
165
165
inDegreeMap.putIfAbsent(node, 0);
166
166
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);
169
168
}
170
169
}
171
170
@@ -188,25 +187,22 @@ public class Solution {
188
187
189
188
### 源码分析
190
189
191
- C++中使用 unordered_map 可获得更高的性能,私有方法中使用引用传值。在 ` dfs ` 递归的过程中,将节点加入到最终结果后需要对其入度减一,否则在上层循环邻居节点时会有重复。这里的 ` dfs ` 是伪 DFS, 因为这里只处理入度为 0 的节点。
190
+ C++中使用 unordered_map 可获得更高的性能,私有方法中使用引用传值。在 ` dfs ` 递归的过程中,将节点加入到最终结果后需要对其入度减一,否则在上层循环邻居节点时会有重复。这里的 ` dfs ` 是剪枝过后的 DFS, 因为这里只处理入度为 0 的节点。另外在第一次求各节点的入度数时,需要先初始化为0,否则会漏掉部分入度为0的节点 。
192
191
193
192
### 复杂度分析
194
193
195
194
以 V 表示顶点数,E 表示有向图中边的条数。
196
195
197
196
首先获得节点的入度数,时间复杂度为 $$ O(V+E) $$ , 使用了哈希表存储,空间复杂度为 $$ O(V) $$ . 遍历图求得入度为0的节点,时间复杂度为 $$ O(V) $$ . 仅在入度为0时调用 DFS,故时间复杂度为 $$ O(V+E) $$ .
198
-
199
- 需要注意的是这里的 DFS 不是纯 DFS,使用了 BFS 的思想进行了优化,否则一个节点将被遍历多次,时间复杂度可能恶化为指数级别。
200
-
201
197
综上,时间复杂度近似为 $$ O(V+E) $$ , 空间复杂度为 $$ O(V) $$ .
202
198
203
199
## 题解2 - BFS
204
200
205
- 拓扑排序除了可用 DFS 求解外,也可使用 BFS, 相比题解1使用递归获取入度为 0 的节点,我们还可以通过队列获取非邻居节点的其他入度为 0 的节点。具体方法为:
201
+ 拓扑排序除了可用 DFS 求解外,也可使用 BFS, 相比题解1使用递归顺腾摸瓜获取入度为 0 的节点,我们还可以通过队列获取非邻居节点的其他入度为 0 的节点,即 BFS 。具体方法为:
206
202
207
203
1 . 获得图中各节点的入度。
208
- 2 . BFS 首先遍历求得入度数为0的节点 ,入队,便于下一次 BFS。
209
- 3 . 队列不为空时,弹出队顶元素并对其邻接节点进行 BFS,将入度为0的节点加入到最终结果和队列中 ,重复此过程直至队列为空。
204
+ 2 . BFS 首先遍历求得所有入度数为0的节点 ,入队,便于下一次 BFS。
205
+ 3 . 队列不为空时,弹出队顶元素并对其邻接节点入度数减一,将入度为0的节点加入到队列中 ,重复此过程直至队列为空。
210
206
211
207
### C++
212
208
@@ -228,24 +224,24 @@ public:
228
224
vector<DirectedGraphNode* > topSort(vector<DirectedGraphNode* > graph) {
229
225
vector<DirectedGraphNode* > result;
230
226
if (graph.size() == 0) return result;
231
-
227
+
232
228
map<DirectedGraphNode*, int> indegree;
233
229
// get indegree of all DirectedGraphNode
234
230
indeg(graph, indegree);
235
231
queue<DirectedGraphNode*> q;
236
232
// bfs
237
233
bfs(graph, indegree, q, result);
238
-
234
+
239
235
return result;
240
236
}
241
-
237
+
242
238
private:
243
239
/* * get indegree of all DirectedGraphNode
244
240
*
245
241
*/
246
242
void indeg (vector<DirectedGraphNode* > &graph,
247
243
map<DirectedGraphNode* , int> &indegree) {
248
-
244
+
249
245
for (int i = 0; i < graph.size(); ++i) {
250
246
for (int j = 0; j < graph[i]->neighbors.size(); j++) {
251
247
if (indegree.find(graph[i]->neighbors[j]) == indegree.end()) {
@@ -256,7 +252,7 @@ private:
256
252
}
257
253
}
258
254
}
259
-
255
+
260
256
void bfs(vector<DirectedGraphNode*> &graph, map<DirectedGraphNode*, int> &indegree,
261
257
queue<DirectedGraphNode *> &q, vector<DirectedGraphNode*> &ret) {
262
258
@@ -266,7 +262,7 @@ private:
266
262
q.push(graph[i]);
267
263
}
268
264
}
269
-
265
+
270
266
while (!q.empty()) {
271
267
DirectedGraphNode *cur = q.front();
272
268
q.pop();
@@ -282,14 +278,141 @@ private:
282
278
};
283
279
```
284
280
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
+
285
345
### 源码分析
286
346
287
- C++中在判断入度是否为0时将对 map 产生副作用,在求入度数时只有入度数大于等于1才会出现在 map 中,故不在 map 中时直接调用 indegree 方法将产生新的键值对,初始值为0,恰好满足此题需求。
347
+ C++中在判断入度是否为0时将对 map 产生副作用,在求入度数时只有入度数大于等于1才会出现在 map 中,故不在 map 中时直接调用 indegree 方法将产生新的键值对,初始值为0,恰好满足此题需求。与 DFS 的解法不同,在 bfs 的实现中可以不对已加入到最终结果的节点入度数减一,因为没有上一层循环了。
288
348
289
349
### 复杂度分析
290
350
291
351
同题解1 的分析,时间复杂度为 $$ O(V+E) $$ , 空间复杂度为 $$ O(V) $$ .
292
352
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
+
293
416
## Reference
294
417
295
418
- [ Topological Sorting 参考程序 Java/C++/Python] ( http://www.jiuzhang.com/solutions/topological-sorting/ )
0 commit comments