1. 程式人生 > >【資料結構和演算法17】拓撲排序

【資料結構和演算法17】拓撲排序

        這一節我們學習一個新的排序演算法,準確的來說,應該叫“有向圖的拓撲排序”。所謂有向圖,就是A->B,但是B不能到A。與無向圖的區別是,它的邊在鄰接矩陣裡只有一項(友情提示:如果對圖這種資料結構部不太瞭解的話,可以先看一下這篇博文:資料結構和演算法之 無向圖。因為拓撲排序是基於圖這種資料結構的)。

有向圖的鄰接矩陣如下表所示:

A

B

C

A

0

1

1

B

0

0

1

C

0

0

0

        所以針對前面討論的無向圖,鄰接矩陣的上下三角是對稱的,有一半資訊是冗餘的。而有向圖的鄰接矩陣中所有行列之都包含必要的資訊,它的上下三角不是對稱的。所以對於有向圖,增加邊的方法只需要一條語句:

//有向圖中,鄰接矩陣中只有一項
public void addEdge(int start, int end) {
	adjMat[start][end] = 1;
}

        如果使用鄰接表示意圖,那麼A->B表示A在它的連結串列中有B,但是B的連結串列中不包含A,這裡就不多說了,本文主要通過鄰接矩陣實現。

        因為圖是有向的,假設A->B->C->D這種,那這就隱藏了一種順序,即要想到D,必須先過C,必須先過B,必須先過A。它們無形中形成了一種順序,這種順序在實際中還是用的挺廣泛的,比如,要做web開發,必須先學java基礎等等,這些都遵循一個順序,所以拓撲排序的思想也是這樣,利用有向圖特定的順序進行排序。但是拓撲排序的結果不是唯一的,比如A->B的同時,C->B,也就是說A和C都能到B,所以用演算法生成一個拓撲排序時,使用的方法和程式碼的細節決定了會產生那種拓撲排序。

        拓撲排序的思想雖然不尋常,但是卻很簡單,有兩個必要的步驟:

        1. 找到一個沒有後繼的頂點;

        2.從圖中刪除這個頂點,在列表中插入頂點的標記

        然後重複1和2,直到所有頂點都從圖中刪除,這時候列表顯示的頂點順序就是拓撲排序的結果了。

        但是我們需要考慮一種特殊的有向圖:環。即A->B->C->D->A。這種必然會導致找不著“沒有後繼的節點”,這樣便無法使用拓撲排序了。

        下面我們分析下拓撲排序的程式碼:

public void poto() {
	int orig_nVerts = nVerts; //記錄有多少個頂點
	while(nVerts > 0) {
		//返回沒有後繼頂點的頂點
		int currentVertex = noSuccessors(); //如果不存在這樣的頂點,返回-1
		if(currentVertex == -1) {
			System.out.println("ERROR: Graph has cycles!");
			return;
		}
		
		//sortedArray中儲存排過序的頂點(從尾開始存)
		sortedArray[nVerts-1] = vertexArray[currentVertex].label;
		deleteVertex(currentVertex);//刪除該頂點,便於下一次迴圈,尋找下一個沒有後繼頂點的頂點
	}
	System.out.println("Topologically sorted order:");
	for(int i = 0; i < orig_nVerts; i++) {
		System.out.print(sortedArray[i]);
	}
	System.out.println("");
}

        主要的工作在while迴圈中進行,這個迴圈直到定點數為0時才退出:
        1. 呼叫noSuccessors()找到任意一個沒有後繼的頂點;

        2. 如果找到一個這樣的頂點,把頂點放到sortedArray陣列中,並且從圖中刪除這個頂點;

        3. 如果不存在這樣的頂點,則圖必然存在環。

        最後sortedArray陣列中儲存的就是排過序的頂點了。下面我們分析下noSuccessor()方法和deleteVertes()方法:

//return vertex with no successors
private int noSuccessors() {
	boolean isEdge;
	for(int row = 0; row < nVerts; row++) {
		isEdge = false;
		for(int col = 0; col < nVerts; col++) {
			if(adjMat[row][col] > 0) { //只要adjMat陣列中儲存了1,表示row->col
				isEdge = true;
				break;
			}
		}
		if(!isEdge) {//只要有邊,返回最後一個頂點
			return row;
		}
	}
	return -1;
}

private void deleteVertex(int delVertex) {
	if(delVertex != nVerts -1) {
		for(int i = delVertex; i < nVerts-1; i++) { //delete from vertexArray
			vertexArray[i] = vertexArray[i+1];
		}
		//刪除adjMat中相應的邊
		for(int row = delVertex; row < nVerts-1; row++) {//delete row from adjMat
			moveRowUp(row, nVerts);
		}
		
		for(int col = delVertex; col < nVerts-1; col++) {//delete column from adjMat
			moveColLeft(col, nVerts-1);
		}
	}
	nVerts--;
}

        從上面程式碼可以看出,刪除一個頂點很簡單,從vertexArray中刪除,後面的頂點向前移動填補空位。同樣的,頂點的行列從鄰接矩陣中刪除,下面的行和右面的列移動來填補空位。刪除adjMat陣列中的邊比較簡單,下面看看moveRowUp和moveColLeft的方法:

private void moveRowUp(int row, int length) {
	for(int col = 0; col < length; col++) {
		adjMat[row][col] = adjMat[row+1][col];
	}
}

private void moveColLeft(int col, int length) {
	for(int row = 0; row < length; row++) {
		adjMat[row][col] = adjMat[row][col+1];
	}
}

        這樣便介紹完了拓撲排序的所有過程了。下面附上完整的程式碼:

package graph;
/**
 * 有向圖的拓撲排序:
 * 拓撲排序是可以用圖模擬的另一種操作,它可以用於表示一種情況,即某些專案或事件必須按特定的順序排列或發生。
 * 有向圖和無向圖的區別是:有向圖的邊在鄰接矩陣中只有一項。
 * 拓撲排序演算法的思想雖然不尋常但是很簡單,有兩個步驟是必須的:
 * 1. 找到一個沒有後繼的頂點
 * 2. 從圖中刪除這個頂點,在列表的前面插入頂點的標記
 * 重複這兩個步驟,直到所有頂點都從圖中刪除,這時,列表顯示的頂點順序就是拓撲排序的結果。
 * 刪除頂點似乎是一個極端的步驟,但是它是演算法的核心,如果第一個頂點不處理,演算法就不能計算出要處理的第二個頂點。
 * 如果需要,可以再其他地方儲存圖的資料(頂點列表或者鄰接矩陣),然後在排序完成後恢復它們。
 * @author eson_15
 * @date 2016-4-20 12:16:11
 * 
 */
public class TopoSorted {
	private final int MAX_VERTS = 20;
	private Vertex vertexArray[]; //儲存頂點的陣列
	private int adjMat[][]; //儲存是否有邊界的矩陣陣列, 0表示沒有邊界,1表示有邊界
	private int nVerts; //頂點個數
	private char sortedArray[]; //儲存排過序的資料的陣列
	
	public TopoSorted() {
		vertexArray = new Vertex[MAX_VERTS];
		adjMat = new int[MAX_VERTS][MAX_VERTS];
		nVerts = 0;
		for(int i = 0; i < MAX_VERTS; i++) {
			for(int j = 0; j < MAX_VERTS; j++) {
				adjMat[i][j] = 0;
			}
		}
		sortedArray = new char[MAX_VERTS];
	}
	
	public void addVertex(char lab) {
		vertexArray[nVerts++] = new Vertex(lab);
	}
	
	//有向圖中,鄰接矩陣中只有一項
	public void addEdge(int start, int end) {
		adjMat[start][end] = 1;
	}
	
	public void displayVertex(int v) {
		System.out.print(vertexArray[v].label);
	}
	
	/*
	 * 拓撲排序
	 */
	public void poto() {
		int orig_nVerts = nVerts; //remember how many verts
		while(nVerts > 0) {
			//get a vertex with no successors or -1
			int currentVertex = noSuccessors();
			if(currentVertex == -1) {
				System.out.println("ERROR: Graph has cycles!");
				return;
			}
			
			//insert vertex label in sortedArray (start at end)
			sortedArray[nVerts-1] = vertexArray[currentVertex].label;
			deleteVertex(currentVertex);
		}
		System.out.println("Topologically sorted order:");
		for(int i = 0; i < orig_nVerts; i++) {
			System.out.print(sortedArray[i]);
		}
		System.out.println("");
	}

	//return vertex with no successors
	private int noSuccessors() {
		boolean isEdge;
		for(int row = 0; row < nVerts; row++) {
			isEdge = false;
			for(int col = 0; col < nVerts; col++) {
				if(adjMat[row][col] > 0) {
					isEdge = true;
					break;
				}
			}
			if(!isEdge) {
				return row;
			}
		}
		return -1;
	}

	private void deleteVertex(int delVertex) {
		if(delVertex != nVerts -1) {
			for(int i = delVertex; i < nVerts-1; i++) { //delete from vertexArray
				vertexArray[i] = vertexArray[i+1];
			}
			
			for(int row = delVertex; row < nVerts-1; row++) {//delete row from adjMat
				moveRowUp(row, nVerts);
			}
			
			for(int col = delVertex; col < nVerts-1; col++) {//delete column from adjMat
				moveColLeft(col, nVerts-1);
			}
		}
		nVerts--;
	}

	private void moveRowUp(int row, int length) {
		for(int col = 0; col < length; col++) {
			adjMat[row][col] = adjMat[row+1][col];
		}
	}

	private void moveColLeft(int col, int length) {
		for(int row = 0; row < length; row++) {
			adjMat[row][col] = adjMat[row][col+1];
		}
	}
}

        拓撲排序就介紹到這吧,如有錯誤之處,歡迎留言指正~

        文末福利:“程式設計師私房菜”,一個有溫度的公眾號~ 
        程式設計師私房菜

_____________________________________________________________________________________________________________________________________________________

-----樂於分享,共同進步!