數據結構(19)--DAG應用之AOE網的拓撲排序

參考書籍:數據結構(C語言版)嚴蔚敏吳偉民編著清華大學出版社

本文中的代碼可從這裏下載:https://github.com/qingyujean/data-structure

1.有向無環圖

    有向無環圖(directed acycline graph)簡稱DAG圖,是描述一項工程或系統的進行過程的有效工具。對整個工程和系統,人們關心的是兩個方面的問題:一是工程能否順利進行;二是估算整個工程完成所必須的最短時間。
    有向無環圖的應用:1.拓撲排序;2.關鍵路徑
    在工程實踐中,一個工程項目往往由若干個子項目組成,這些子項目間往往有多種關係:
    1.先後關係,即必須在一子項目完成後,才能開始實施另一個子項目;
    2.子項目之間無次序要求,即兩個子項目可以同時進行,互不影響。

2.拓撲排序

    我們用一種有向圖來表示上述問題。在這種有向圖中,頂點表示活動,有向邊表示活動的優先關係,這種有向圖叫做頂點表示活動的網絡(Activity On Vertex Network)簡稱爲AOV網
    在AOV網絡中,如果頂點Vi的活動必須在頂點Vj的活動以前進行,則稱Vi爲Vj的前趨頂點,而稱Vj爲Vi的後繼頂點。這種前趨後繼關係有傳遞性。此外,任何活動i不能以它自己作爲自己的前驅或後繼,這叫做反自反性
    從前驅和後繼的傳遞性和反自反性來看,AOV網中不能出現迴路(有向環),迴路表示頂點之間的先後關係進入了死循環。
    判斷AOV網是否有有向環的方法是對該AOV網進行拓撲排序,將AOV網中頂點排列成一個線性有序序列,若該線性序列中包含AOV網全部頂點,則AOV網無環,否則,AOV網中存在有向環,該AOV網所代表的工程是不可行的。

    何謂“拓撲排序” ?
拓撲序列:
    在AOV網中,若不存在迴路,則所有活動可排列成一個線性序列,使得每個活動的所有前驅活動都排在該活動的前面,我們把此序列叫做拓撲序列。
拓撲排序:
    由AOV網構造拓撲序列的過程叫拓撲排序。AOV網的拓撲序列不是唯一的,滿足上述定義的任一線性序列都稱爲它的拓撲序列。

    如何進行拓撲排序?
    解決方法:
  1)從有向圖中選取一個沒有前驅的頂點,並輸出之;
  2)從有向圖中刪去此頂點以及所有以它爲尾的弧;
  3)重複上述兩步,直至圖空,或者圖不空但找不到無前驅的頂點爲止。後一種情況說明有向圖中存在環。

    拓撲排序算法的實現
    1.爲了實現拓撲排序的算法,對給定的有向圖可採用鄰接表作爲它的存儲結構。
    2.將每個鏈表的表頭結點構成一個順序表,各表頭結點的Data域存放相應頂點的入度值。每個頂點入度初值可隨鄰接表動態生成過程中累計得到。
    3.在拓撲排序過程中,凡入度爲零的頂點即是沒有前趨的頂點,可將其取出列入有序序列中,同時將該頂點從圖中刪除掉不再考慮。
刪去一個頂點時,所有它的直接後繼頂點入度均減1,表示相應的有向邊也被刪除掉。
    4.設置一個堆棧,將已檢驗到的入度爲零的頂點標號進棧,當再出現新的無前趨頂點時,也陸續將其進棧。每次選入度爲零的頂點時,只要取棧頂頂點即可。

    用鄰接表存儲AOV網絡,拓撲排序算法描述:
(1) 把鄰接表中所有入度爲零的頂點進棧;
(2) 在棧不空時:
    ① 退棧並輸出棧頂的頂點 j;
    ② 在鄰接表的第 j 個單鏈表中,查找頂點爲 j 的所有直接後繼頂點 k,並將 k 的入度減1。若頂點 k 的入度爲零,則頂點 k 進棧;
    ③ 若棧空時輸出的頂點個數不是 n,則有向圖中有環路,否則拓撲排序完畢。

示例:

3.代碼實現

3.1定義

 

/*
DAG 有向無環圖的應用--拓撲排序:能否順利完成工程,即檢查是否存在環,
AOV網:頂點表示活動的網

除了拓撲排序檢查環以外,還可以用DFS
當有向圖中無環時,從圖中某點進行深度優先遍歷時,最先退出DFS函數的頂點即出度爲0的頂點,是拓撲序列中的最後一個頂點,由此,按退出DFS函數的先後記錄下來的頂點序列,即爲逆向的拓撲有序序列
*/

//本次示例採用鄰接表作爲有向圖的存儲結構


#include<stdio.h>
#include<stdlib.h>
/*
圖的表示方法
DG(有向圖)或者DN(有向網):鄰接矩陣、鄰接表(逆鄰接表--爲求入度)、十字鏈表
UDG(無向圖)或者UDN(無向網):鄰接矩陣、鄰接表、鄰接多重表
*/
#define MAX_VERTEX_NUM 10//最大頂點數目
#define NULL 0
typedef int VRType;//對於帶權圖或網,則爲相應權值
typedef int VertexType;//頂點類型
//typedef enum GraphKind {DG, DN, UDG, UDN};  //有向圖:0,有向網:1,無向圖:2,無向

typedef struct ArcNode{	
	int adjvex;//該弧所指向的頂點的在圖中位置
	//VRType w;//弧的相應權值
	struct ArcNode *nextarc;//指向下一條弧的指針
}ArcNode;//弧結點信息

typedef struct VNode{
	VertexType data;//頂點信息

	ArcNode *firstarc;//指向第一條依附該頂點的弧的指針
}VNode, AdjVexList[MAX_VERTEX_NUM];//頂點結點信息

typedef struct{
	AdjVexList vexs;//頂點向量
	int vexnum, arcnum;//圖的當前頂點數和弧數
	//GraphKind kind;//圖的種類標誌
}ALGraph;//鄰接表表示的圖
#define OK 1
#define ERROR 0
typedef int status;

static int indegree[MAX_VERTEX_NUM] = {0};//存放各個頂點的入度的數組

 

3.2鄰接表表示有向無環圖

 

 

//若圖G中存在頂點v,則返回v在圖中的位置信息,否則返回其他信息
int locateVex(ALGraph G, VertexType v){
	for(int i = 0; i < G.vexnum; i++){
		if(G.vexs[i].data == v)
			return i;
	}
	return -1;//圖中沒有該頂點
}


//採用鄰接表表示法構造有向圖G
void createDG(ALGraph &G){
	printf("輸入頂點數和弧數如:(5,3):");
	scanf("%d,%d", &G.vexnum, &G.arcnum);

	//構造頂點向量,並初始化
	printf("輸入%d個頂點(以空格隔開如:v1 v2 v3):", G.vexnum);
	getchar();//吃掉換行符
	for(int m = 0; m < G.vexnum; m++){
		scanf("v%d", &G.vexs[m].data);
		G.vexs[m].firstarc = NULL;//初始化爲空指針////////////////重要!!!
		getchar();//吃掉空格符
	}

	//構造鄰接表
	VertexType v1, v2;//分別是一條弧的弧尾和弧頭(起點和終點)
	//VRType w;//對於無權圖或網,用0或1表示相鄰否;對於帶權圖或網,則爲相應權值	
	printf("\n每行輸入一條弧依附的頂點(先弧尾後弧頭)如:v1v2:\n");
	fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
	int i = 0, j = 0;
	for(int k = 0; k < G.arcnum; k++){
		scanf("v%dv%d",&v1, &v2);
		fflush(stdin);//清除殘餘後,後面再讀入時不會出錯
		i = locateVex(G, v1);//弧起點
		j = locateVex(G, v2);//弧終點
		
		//採用“頭插法”在各個頂點的弧鏈頭部插入弧結點
		ArcNode *p1 = (ArcNode *)malloc(sizeof(ArcNode));//構造一個弧結點,作爲弧vivj的弧頭(終點)
		p1->adjvex = j;
		//p1->w = w;
		p1->nextarc = G.vexs[i].firstarc;
		G.vexs[i].firstarc = p1;
		/*因爲是有向圖,所以不必創建2個弧結點
		ArcNode *p2 = (ArcNode *)malloc(sizeof(ArcNode));//構造一個弧結點,作爲弧vivj的弧尾(起點)
		p2->adjvex = i;
		//p2->w = w;
		p2->nextarc = G.vexs[j].firstarc;
		G.vexs[j].firstarc = p2;
		*/
	}
}

 

3.3拓撲排序的實現

 

void findInDegree(ALGraph G, int indegree[]){	
	ArcNode *p;
	for(int i = 0; i < G.vexnum; i++){
		for(p = G.vexs[i].firstarc; p; p = p->nextarc){
			indegree[p->adjvex]++;
		}
	}		
}

//如有向圖無迴路,則輸出G的頂點的一個拓撲序列並返回OK,否則返回ERROR
status toplogicalSort(ALGraph G){
	//先初始化各個頂點的入度
	findInDegree(G, indegree);
	

	int stack[MAX_VERTEX_NUM];//維護一個棧來存放入度爲0的頂點,當棧爲空時,則說明圖中不存在無前驅的頂點了(即沒有入度爲0的頂點了),說明圖中無環
	//否則如果此時仍然存在頂點,而且這些頂點有前驅,則說明有環
	int top = 0;//棧頂指針

	//將入度爲0的頂點入棧
	for(int i = 0; i < G.vexnum; i++){
		if(!indegree[i]){
			stack[top++] = i;
		}
	}

	int count = 0;//對輸出的頂點計數
	ArcNode *p;
	while(top != 0){//棧不爲空
		int topElemVex_i = stack[--top];//棧頂元素出棧,即第一個無前驅的頂點
		printf("v%d ", G.vexs[topElemVex_i].data);//輸出當前結點
		count++;

		//去掉以該結點爲前驅的點與他的弧,以將相關頂點的入度減1的操作來實現
		for(p = G.vexs[topElemVex_i].firstarc; p; p = p->nextarc){
			indegree[p->adjvex]--;
			if(!indegree[p->adjvex]){
				stack[top++] = p->adjvex;//入度爲0者入棧
			}
		}
	}
	printf("\n");

	if(count < G.vexnum)//該有向圖有迴路
		return ERROR;
	else
		return OK;
}

 

3.4演示

 

 

/*測試:
6,8
v1 v2 v3 v4 v5 v6

v1v2
v1v3
v1v4

v3v2

v4v3
v4v5

v6v4
v6v5
*/
void main(){
	ALGraph G;
	createDG(G);
	//printAdjList(G);
	printf("該圖的拓撲排序序列爲:");
	toplogicalSort(G);		
}

 

分析:

    對有 n 個頂點和 e 條弧的有向圖而言,建立求各頂點的入度的時間複雜度爲O(e);建零入度頂點棧的時間複雜度爲O(n);在拓撲排序過程中,若有向圖無環,則每個頂點進一次棧,出一次棧,入度減1的操作在 WHILE語句中總共執行e次,所以,總的時間複雜度爲O(n+e)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章