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

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

• 本文已收录于以下专栏：

举报原因： 您举报文章：拓扑排序的原理及其实现 色情 政治 抄袭 广告 招聘 骂人 其他 (最多只允许输入30个字)