# 拓扑排序的原理及其实现

• 拓扑排序的定义和前置条件
• 和离散数学中偏序/全序概念的联系
• 典型实现算法
• 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)

public class KahnTopological
{
private List<Integer> result;   // 用来存储结果集
private Queue<Integer> setOfZeroIndegree;  // 用来存储入度为0的顶点
private int[] indegrees;  // 记录每个顶点当前的入度
private int edges;
private Digraph di;

public KahnTopological(Digraph di)
{
this.di = di;
this.edges = di.getE();
this.indegrees = new int[di.getV()];
this.result = new ArrayList<Integer>();

// 对入度为0的集合进行初始化
for(int i = 0; i < adjs.length; i++)
{
// 对每一条边 v -> w
{
indegrees[w]++;
}
}

for(int i = 0; i < indegrees.length; i++)
{
if(0 == indegrees[i])
{
setOfZeroIndegree.enqueue(i);
}
}
process();
}

private void process()
{
while(!setOfZeroIndegree.isEmpty())
{
int v = setOfZeroIndegree.dequeue();

// 将当前顶点添加到结果集中

// 遍历由v引出的所有边
{
// 将该边从图中移除，通过减少边的数量来表示
edges--;
if(0 == --indegrees[w])   // 如果入度为0，那么加入入度为0的集合
{
setOfZeroIndegree.enqueue(w);
}
}
}
// 如果此时图中还存在边，那么说明图中含有环路
if(0 != edges)
{
throw new IllegalArgumentException("Has Cycle !");
}
}

public Iterable<Integer> getResult()
{
return result;
}
}


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)的时候还未返回

public class DirectedDepthFirstOrder
{
// visited数组，DFS实现需要用到
private boolean[] visited;
// 使用栈来保存最后的结果
private Stack<Integer> reversePost;

/**
* Topological Sorting Constructor
*/
public DirectedDepthFirstOrder(Digraph di, boolean detectCycle)
{
// 这里的DirectedDepthFirstCycleDetection是一个用于检测有向图中是否存在环路的类
DirectedDepthFirstCycleDetection detect = new DirectedDepthFirstCycleDetection(
di);

if (detectCycle && detect.hasCycle())
throw new IllegalArgumentException("Has cycle");

this.visited = new boolean[di.getV()];
this.reversePost = new Stack<Integer>();

for (int i = 0; i < di.getV(); i++)
{
if (!visited[i])
{
dfs(di, i);
}
}
}

private void dfs(Digraph di, int v)
{
visited[v] = true;

{
if (!visited[w])
{
dfs(di, w);
}
}

// 在即将退出dfs方法的时候，将当前顶点添加到结果集中
reversePost.push(v);
}

public Iterable<Integer> getReversePost()
{
return reversePost;
}
}


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

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

public class DirectedDepthFirstTopoWithCircleDetection
{
private boolean[] visited;
// 用于记录dfs方法的调用栈，用于环路检测
private boolean[] onStack;
// 用于当环路存在时构造之
private int[] edgeTo;
private Stack<Integer> reversePost;
private Stack<Integer> cycle;

/**
* Topological Sorting Constructor
*/
public DirectedDepthFirstTopoWithCircleDetection(Digraph di)
{
this.visited = new boolean[di.getV()];
this.onStack = new boolean[di.getV()];
this.edgeTo = new int[di.getV()];
this.reversePost = new Stack<Integer>();

for (int i = 0; i < di.getV(); i++)
{
if (!visited[i])
{
dfs(di, i);
}
}
}

private void dfs(Digraph di, int v)
{
visited[v] = true;
// 在调用dfs方法时，将当前顶点记录到调用栈中
onStack[v] = true;

{
if(hasCycle())
{
return;
}
if (!visited[w])
{
edgeTo[w] = v;
dfs(di, w);
}
else if(onStack[w])
{
// 当w已经被访问，同时w也存在于调用栈中时，即存在环路
cycle = new Stack<Integer>();
cycle.push(w);
for(int start = v; start != w; start = edgeTo[start])
{
cycle.push(v);
}
cycle.push(w);
}
}

// 在即将退出dfs方法时，将顶点添加到拓扑排序结果集中，同时从调用栈中退出
reversePost.push(v);
onStack[v] = false;
}

private boolean hasCycle()
{
return (null != cycle);
}

public Iterable<Integer> getReversePost()
{
if(!hasCycle())
{
return reversePost;
}
else
{
throw new IllegalArgumentException("Has Cycle: " + getCycle());
}
}

public Iterable<Integer> getCycle()
{
return cycle;
}
}


/**
* Hamilton Path Detection for DAG
*/
public class DAGHamiltonPath
{
private boolean hamiltonPathPresent;
private Digraph di;
private KahnTopological kts;

// 这里使用Kahn算法进行拓扑排序
public DAGHamiltonPath(Digraph di, KahnTopological kts)
{
this.di = di;
this.kts = kts;

process();
}

private void process()
{
Integer[] topoResult = kts.getResultAsArray();

// 依次检查每一对相邻顶点，如果二者之间没有路径，则不存在哈密顿路径
for(int i = 0; i < topoResult.length - 1; i++)
{
if(!hasPath(topoResult[i], topoResult[i + 1]))
{
hamiltonPathPresent = false;
return;
}
}
hamiltonPathPresent = true;
}

private boolean hasPath(int start, int end)
{
{
if(w == end)
{
return true;
}
}
return false;
}

public boolean hasHamiltonPath()
{
return hamiltonPathPresent;
}
}


TestNG中循环依赖的检测：

https://github.com/destiny1020/algorithm_playground/tree/master/src/main/java/chap4

06-25 6万+

03-19 5万+

09-25 1万+

03-16 61

03-06 6万+

02-04 1万+

02-10 2501

07-16 1万+

07-17 3047

03-26 3万+

03-21 2万+

10-29 17万+

03-16 8万+

02-26 1万+

04-14 56万+

04-22 2万+

10-08 302

07-30 311

04-21 2869

04-29 38

05-18 711

11-01 18万+

08-27 1万+

07-24 2206

01-28 220

09-14 320

11-01 2万+

07-21 209

04-30 2万+

09-27 3737

02-02 3万+

01-05 27万+

05-10 4191

01-04 3647

01-15 1450

06-04 1241

03-19 80万+

02-03 3万+

02-15 1万+

12-05 621

04-07 5万+

03-05 5万+

07-23 864

04-06 32

08-28 354

05-07 255

02-13 28万+

03-25 1787

08-11 3655

08-01 863

12-09 1万+

03-08 37万+

03-20 3万+

02-25 37万+

06-13 848

03-22 4万+

11-06 3万+

03-26 3万+

02-27 4万+

02-19 17万+

01-08 1万+

09-05 137

09-28 97

05-12 3006

10-20 6572

02-28 4万+

11-20 7万+

06-08 1080

05-16 4686

03-04 13万+

04-03 1万+

09-30 969

03-15 1万+

05-08 4万+

03-22 2万+

04-25 6万+

03-23 3万+

04-14 6834

01-27 12万+

#### 强烈推荐10本程序员必读的书

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客