@@ -54,9 +54,9 @@ The topological order can be:
5454
5555Can 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
61611 . 找出不依赖其他顶点的顶点,即入度为0,这一定是符合要求的某一个拓扑排序的第一个顶点。
62622 . 在图中去掉入度为 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
2072031 . 获得图中各节点的入度。
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+
242238private:
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