引言
工程中嘗嘗氛圍很多步驟完成,有些步驟可以同步進行,而有些步驟需要某些步驟完成后才能進行,如何安排它們的流程是項目實施要考慮的重要問題,這就是拓撲排序問題。拓撲排序,它可以決定哪些子工程必須要先執行,哪些子工程要在某些工程執行后才可以執行。為了形象地反映出整個工程中各個子工程(活動)之間的先后關系,可用一個有向圖來表示,圖中的頂點代表活動(子工程),圖中的有向邊代表活動的先后關系,即有向邊的起點的活動是終點活動的前序活動,只有當起點活動完成之后,其終點活動才能進行。通常,我們把這種頂點表示活動、邊表示活動間先后關系的有向圖稱做頂點活動網(Activity On Vertex network),簡稱AOV網。
一個AOV網應該是一個有向無環圖,即不應該帶有回路,因為若帶有回路,則回路上的所有活動都無法進行(對于數據流來說就是死循環)。在AOV網中,若不存在回路,則所有活動可排列成一個線性序列,使得每個活動的所有前驅活動都排在該活動的前面,我們把此序列叫做拓撲序列(Topological order),由AOV網構造拓撲序列的過程叫做拓撲排序(Topological sort)。AOV網的拓撲序列不是唯一的,滿足上述定義的任一線性序列都稱作它的拓撲序列。
拓撲排序思想
1.在有向圖中選一個沒有前驅的頂點并且輸出;
2.從圖中刪除該頂點和所有以它為尾的?。ò自捑褪牵簞h除所有和它有關的邊);
3.重復上述兩步,直至所有頂點輸出,或者當前圖中不存在無前驅的頂點為止,后者代表我們的有向圖是有環的,因此,也可以通過拓撲排序來判斷一個圖是否有環。
拓撲排序有點像抽絲剝繭,每一次遍歷都會去掉最外層的"殼",抽完為止。
圖解
1.針對下圖:
有入度0的頂點V1和V6,先把他們放入集合S=[V1,V6],這里隨機取出V6遍歷,S=[V1]。
2.V6遍歷完,去掉和V4、V5的邊(入度減1),圖結構如下:
3.從集合中取出V1,刪除邊,V2、V3、V4入度減一后,V4和V3入度為0,S=[V3,V4],圖結構如下:
4.從集合中取出V4或者V3(取哪一個看具體算法),這里取V4,V5入度為減一不為0,S為[V3],圖結構為:
5,從集合中取出V3,V2、V5入度為0,S=[V2,V5],圖結構如下:
6.繼續輸出集合中蒸魚頂點,最后的該圖的一個拓撲排序結果為:
v6—>v1—>v4—>v3—>v5—>v2。
注意:拓撲排序僅反應頂點間的層序關系,輸出結果不唯一。
代碼實現
代碼實現,我們引入一個集合(?;蛘哧犃芯?,存放未遍歷的入度為0的頂點;引入計數變量檢測圖是否遍歷完。具體實現如下:
package graphic;
import queue.Queue;
/**
* Created by chenming on 2018/6/16
* 拓撲排序
*/
public class TopologySort {
private class VNode {
int value;//值
int inDegree;//入度
}
private int[][] map;//鄰接矩陣
private VNode[] nodes;
public TopologySort(int[][] map) {
this.map = map;
nodes = new VNode[map.length];
for (int i = 0; i < map.length; i++) {
//初始化入度
VNode node = new VNode();
node.value = i;
node.inDegree = getIndegree(i);
nodes[i] = node;
}
}
/**
* 從鄰接矩陣中得到頂點入度
*
* @param index
* @return
*/
private int getIndegree(int index) {
int in = 0;
for (int i = 0; i < map.length; i++) {
if (0 < map[i][index] && map[i][index] < Integer.MAX_VALUE) {
in++;
}
}
return in;
}
/**
* 有向圖的拓撲排序,,思路如下:
* 1,類似廣度優先遍歷,引入隊列保存入度為0的未遍歷節點
* 2.每去掉一個頂點,則去掉該頂點的邊,即它所有鄰接頂點的頂點入度-1,如果為0則加入隊列
* 3.循環執行步驟2,直到隊列為空
* 4.循環結束檢測是否遍歷完整,如果沒有遍歷完全,則表示有回環
*/
public void topologySort() {
Queue<Integer> queue = new Queue<>();//保存入度為0的索引
int numb = map.length;
int count = 0;//校驗用
//收集入度為0的頂點
for (int i = 0; i < numb; i++) {
if (nodes[i].inDegree == 0) {
queue.enquene(i);
}
}
while (!queue.isEmpty()) {
int index = queue.dequeue();
System.out.println("拓撲排序:" + index);
count++;
//遍歷完"刪除"頂點index的所有邊,即減少它鄰接頂點的入度
for (int i = 0; i < numb; i++){
if(map[index][i] > 0 && map[index][i] < Integer.MAX_VALUE){
if(--nodes[i].inDegree == 0){
//入度減一后,加入隊列
queue.enquene(i);
}
}
}
}
if(count != numb){
throw new RuntimeException("圖中存在回環");
}
}
}
測試圖用例:
測試代碼:
@Test
public void testTopol() {
int INF = Integer.MAX_VALUE;
int[][] map = {
{0, INF, INF, INF, 1, 1, INF, INF, INF, INF, INF, 1, INF, INF},//0
{INF, 0, 1, INF, 1, INF, INF, INF, 1, INF, INF, INF, INF, INF},//1
{INF, INF, 0, INF, INF, 1, 1, INF, INF, 1, INF, INF, INF, INF},//2
{INF, INF, 1, 0, INF, INF, INF, INF, INF, INF, INF, INF, INF, 1},//3
{INF, INF, INF, INF, 0, INF, INF, 1, INF, INF, INF, INF, INF, INF},//4
{INF, INF, INF, INF, INF, 0, INF, INF, 1, INF, INF, INF, 1, INF},//5
{INF, INF, INF, INF, INF, 1, 0, INF, INF, INF, INF, INF, INF, INF},//6
{INF, INF, INF, INF, INF, INF, INF, 0, INF, INF, INF, INF, INF, INF},//7
{INF, INF, INF, INF, INF, INF, INF, 1, 0, INF, INF, INF, INF, INF},//8
{INF, INF, INF, INF, INF, INF, INF, INF, INF, 0, 1, 1, INF, INF},//9
{INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 0, INF, INF, 1},//10
{INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 0, INF, INF},//11
{INF, INF, INF, INF, INF, INF, INF, INF, INF, 1, INF, INF, 0, INF},//12
{INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, INF, 0},//13
};
TopologySort ts = new TopologySort(map);
ts.topologySort();
}
輸出:
拓撲排序:0
拓撲排序:1
拓撲排序:3
拓撲排序:4
拓撲排序:2
拓撲排序:6
拓撲排序:5
拓撲排序:8
拓撲排序:12
拓撲排序:7
拓撲排序:9
拓撲排序:10
拓撲排序:11
拓撲排序:13
完整代碼地址:數據結構與算法學習JAVA描述GayHub地址