# 拓扑排序的两种实现：Kahn算法和dfs算法

31 篇文章 0 订阅

• 拓扑排序的定义和前置条件
• 和离散数学中偏序/全序概念的联系
• 典型实现算法
• Kahn算法
• 基于DFS的算法
• 解的唯一性问题
• 实际例子

Kahn算法：

L← Empty list that will contain the sorted elements
S ← Set of all nodes with no incoming edges
while S is non-empty do
remove a node n from S
insert n into L
foreach node m with an edge e from nto m do
remove edge e from thegraph
ifm has no other incoming edges then
insert m into S
if graph has edges then
return error (graph has at least onecycle)
else
return L (a topologically sortedorder)

[java]  view plain copy print ?
1. public class KahnTopological
2. {
3.     private List<Integer> result;   // 用来存储结果集
4.     private Queue<Integer> setOfZeroIndegree;  // 用来存储入度为0的顶点
5.     private int[] indegrees;  // 记录每个顶点当前的入度
6.     private int edges;
7.     private Digraph di;
8.
9.     public KahnTopological(Digraph di)
10.     {
11.         this.di = di;
12.         this.edges = di.getE();
13.         this.indegrees = new int[di.getV()];
14.         this.result = new ArrayList<Integer>();
16.
17.         // 对入度为0的集合进行初始化
19.         for(int i = 0; i < adjs.length; i++)
20.         {
21.             // 对每一条边 v -> w
23.             {
24.                 indegrees[w]++;
25.             }
26.         }
27.
28.         for(int i = 0; i < indegrees.length; i++)
29.         {
30.             if(0 == indegrees[i])
31.             {
32.                 setOfZeroIndegree.enqueue(i);
33.             }
34.         }
35.         process();
36.     }
37.
38.     private void process()
39.     {
40.         while(!setOfZeroIndegree.isEmpty())
41.         {
42.             int v = setOfZeroIndegree.dequeue();
43.
44.             // 将当前顶点添加到结果集中
46.
47.             // 遍历由v引出的所有边
49.             {
50.                 // 将该边从图中移除，通过减少边的数量来表示
51.                 edges--;
52.                 if(0 == --indegrees[w])   // 如果入度为0，那么加入入度为0的集合
53.                 {
54.                     setOfZeroIndegree.enqueue(w);
55.                 }
56.             }
57.         }
58.         // 如果此时图中还存在边，那么说明图中含有环路
59.         if(0 != edges)
60.         {
61.             throw new IllegalArgumentException("Has Cycle !");
62.         }
63.     }
64.
65.     public Iterable<Integer> getResult()
66.     {
67.         return result;
68.     }
69. }

2->8->0->3->7->1->5->6->9->4->11->10->12

L ← Empty list that will contain the sorted nodes
S ← Set of all nodes with no outgoing edges
for each node n in S do
visit(n)
function visit(node n)
if n has not been visited yet then
mark n as visited
for each node m with an edgefrom m to ndo
visit(m)

1. dfs(w)还没有被调用，即w还没有被mark，此时会调用dfs(w)，然后当dfs(w)返回之后，dfs(v)才会返回
1. dfs(w)已经被调用并返回了，即w已经被mark
1. dfs(w)已经被调用但是在此时调用dfs(v)的时候还未返回

[java]  view plain copy print ?
1. public class DirectedDepthFirstOrder
2. {
3.     // visited数组，DFS实现需要用到
4.     private boolean[] visited;
5.     // 使用栈来保存最后的结果
6.     private Stack<Integer> reversePost;
7.
8.     /**
9.      * Topological Sorting Constructor
10.      */
11.     public DirectedDepthFirstOrder(Digraph di, boolean detectCycle)
12.     {
13.         // 这里的DirectedDepthFirstCycleDetection是一个用于检测有向图中是否存在环路的类
14.         DirectedDepthFirstCycleDetection detect = new DirectedDepthFirstCycleDetection(
15.                 di);
16.
17.         if (detectCycle && detect.hasCycle())
18.             throw new IllegalArgumentException("Has cycle");
19.
20.         this.visited = new boolean[di.getV()];
21.         this.reversePost = new Stack<Integer>();
22.
23.         for (int i = 0; i < di.getV(); i++)
24.         {
25.             if (!visited[i])
26.             {
27.                 dfs(di, i);
28.             }
29.         }
30.     }
31.
32.     private void dfs(Digraph di, int v)
33.     {
34.         visited[v] = true;
35.
36.         for (int w : di.adj(v))
37.         {
38.             if (!visited[w])
39.             {
40.                 dfs(di, w);
41.             }
42.         }
43.
44.         // 在即将退出dfs方法的时候，将当前顶点添加到结果集中
45.         reversePost.push(v);
46.     }
47.
48.     public Iterable<Integer> getReversePost()
49.     {
50.         return reversePost;
51.     }
52. }

8->7->2->3->0->6->9->10->11->12->1->5->4

Kahn算法不需要检测图为DAG，如果图为DAG，那么在出度为0的集合为空之后，图中还存在没有被移除的边，这就说明了图中存在环路。而基于DFS的算法需要首先确定图为DAG，当然也能够做出适当调整，让环路的检测和拓扑排序同时进行，毕竟环路检测也能够在DFS的基础上进行。

[java]  view plain copy print ?
1. public class DirectedDepthFirstTopoWithCircleDetection
2. {
3.     private boolean[] visited;
4.     // 用于记录dfs方法的调用栈，用于环路检测
5.     private boolean[] onStack;
6.     // 用于当环路存在时构造之
7.     private int[] edgeTo;
8.     private Stack<Integer> reversePost;
9.     private Stack<Integer> cycle;
10.
11.     /**
12.      * Topological Sorting Constructor
13.      */
14.     public DirectedDepthFirstTopoWithCircleDetection(Digraph di)
15.     {
16.         this.visited = new boolean[di.getV()];
17.         this.onStack = new boolean[di.getV()];
18.         this.edgeTo = new int[di.getV()];
19.         this.reversePost = new Stack<Integer>();
20.
21.         for (int i = 0; i < di.getV(); i++)
22.         {
23.             if (!visited[i])
24.             {
25.                 dfs(di, i);
26.             }
27.         }
28.     }
29.
30.     private void dfs(Digraph di, int v)
31.     {
32.         visited[v] = true;
33.         // 在调用dfs方法时，将当前顶点记录到调用栈中
34.         onStack[v] = true;
35.
36.         for (int w : di.adj(v))
37.         {
38.             if(hasCycle())
39.             {
40.                 return;
41.             }
42.             if (!visited[w])
43.             {
44.                 edgeTo[w] = v;
45.                 dfs(di, w);
46.             }
47.             else if(onStack[w])
48.             {
49.                 // 当w已经被访问，同时w也存在于调用栈中时，即存在环路
50.                 cycle = new Stack<Integer>();
51.                 cycle.push(w);
52.                 for(int start = v; start != w; start = edgeTo[start])
53.                 {
54.                     cycle.push(v);
55.                 }
56.                 cycle.push(w);
57.             }
58.         }
59.
60.         // 在即将退出dfs方法时，将顶点添加到拓扑排序结果集中，同时从调用栈中退出
61.         reversePost.push(v);
62.         onStack[v] = false;
63.     }
64.
65.     private boolean hasCycle()
66.     {
67.         return (null != cycle);
68.     }
69.
70.     public Iterable<Integer> getReversePost()
71.     {
72.         if(!hasCycle())
73.         {
74.             return reversePost;
75.         }
76.         else
77.         {
78.             throw new IllegalArgumentException("Has Cycle: " + getCycle());
79.         }
80.     }
81.
82.     public Iterable<Integer> getCycle()
83.     {
84.         return cycle;
85.     }
86. }

[java]  view plain copy print ?
1. /**
2.  * Hamilton Path Detection for DAG
3.  */
4. public class DAGHamiltonPath
5. {
6.     private boolean hamiltonPathPresent;
7.     private Digraph di;
8.     private KahnTopological kts;
9.
10.     // 这里使用Kahn算法进行拓扑排序
11.     public DAGHamiltonPath(Digraph di, KahnTopological kts)
12.     {
13.         this.di = di;
14.         this.kts = kts;
15.
16.         process();
17.     }
18.
19.     private void process()
20.     {
21.         Integer[] topoResult = kts.getResultAsArray();
22.
23.         // 依次检查每一对相邻顶点，如果二者之间没有路径，则不存在哈密顿路径
24.         for(int i = 0; i < topoResult.length - 1; i++)
25.         {
26.             if(!hasPath(topoResult[i], topoResult[i + 1]))
27.             {
28.                 hamiltonPathPresent = false;
29.                 return;
30.             }
31.         }
32.         hamiltonPathPresent = true;
33.     }
34.
35.     private boolean hasPath(int start, int end)
36.     {
38.         {
39.             if(w == end)
40.             {
41.                 return true;
42.             }
43.         }
44.         return false;
45.     }
46.
47.     public boolean hasHamiltonPath()
48.     {
49.         return hamiltonPathPresent;
50.     }
51. }

TestNG中循环依赖的检测：

• 22
点赞
• 36
收藏
觉得还不错? 一键收藏
• 7
评论
07-04 21万+
07-23 982

### “相关推荐”对你有帮助么？

• 非常没帮助
• 没帮助
• 一般
• 有帮助
• 非常有帮助

1.余额是钱包充值的虚拟货币，按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载，可以购买VIP、付费专栏及课程。