1. 程式人生 > >圖->有向無環圖->拓撲排序

圖->有向無環圖->拓撲排序

文字描述

  關於有向無環圖的基礎定義:

    一個無環的有向圖稱為有向無環圖,簡稱DAG圖(directed acycline graph)。DAG圖是一類較有向樹更一般的特殊有向圖。

  

    舉個例子說明有向無環圖的應用。假如有一個表示式: ((a+b)*(b*(c+d))+(c+d)*e)*((c+d)*e), 可以用之前討論的二叉樹來表示,也可以用有向無環圖來表示,如下圖。顯然有向無環圖實現了對相同子式的共享,從而比二叉樹更節省空間。

  

  關於拓撲排序的基礎定義:

    由某個集合上的一個偏序得到該集合上的一個全須,這個操作稱之為拓撲排序。理解起來可能有點費解,但是通俗的講,就是如下幾個操作步驟:

      1 在有向圖中選一個沒有前驅的頂點且輸出之

      2 從圖中刪除該頂點和所有以它為尾的弧。

    重複上述兩步,直至全部頂點均已輸出,或者當前圖中不存在無前驅的頂點為止。後一種情況說明有向圖中存在環。

  備註:AOV-網(Activity On Vertex Network)的意思是用頂點表示活動,用弧表示活動間的優先關係的有向圖稱為頂點表示活動的網。

 

示意圖:

 

 

 

演算法分析

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

 

程式碼實現

 

  1 //
  2 // Created by lady on 18-12-28.
  3 //
  4 #include <stdio.h>
  5 #include <stdlib.h>
  6 #define MAX_VERTEX_NUM 20 //最大頂點數
  7 typedef enum {DG,DN, UDG, UDN} GraphKind; //{有向圖,有向網,無向圖,無向網}
  8 typedef struct ArcNode{
  9     int adjvex;    //該弧所指向的頂點的位置
10 struct ArcNode *nextarc; //指向下一條弧的指標 11 char info; //該弧相關資訊的指標 12 }ArcNode; 13 typedef struct VNode{ 14 char data[10];//頂點資訊 15 ArcNode *firstarcIN;//第一條以該頂點為弧頭的弧結點,其他頂點->該結點 16 ArcNode *firstarcOUT;//第一條以該頂點為弧尾的弧結點,該結點->其他頂點 17 }VNode, AdjList[MAX_VERTEX_NUM]; 18 typedef struct{ 19 AdjList vertices; 20 int vexnum;//圖的頂點數 21 int arcnum;//圖的弧數 22 int kind; //圖的種類標誌 23 }ALGraph; 24 25 //根據頂點資訊,返回該頂點在圖中的位置座標。 26 int LocateVex(ALGraph *G, char data[]) 27 { 28 int i = 0; 29 for(i=0; i<G->vexnum; i++){ 30 if(!strncmp(G->vertices[i].data, data, strlen(G->vertices[i].data))){ 31 return i; 32 } 33 } 34 return -1; 35 } 36 37 //利用頭插法,在弧結點連結串列頭部,插入位置v的弧結點 38 int InsFirst(ArcNode *L, int v) 39 { 40 if((L==NULL) || (v<0)){ 41 return -1; 42 } 43 ArcNode *n = (ArcNode *)malloc(sizeof(struct ArcNode)); 44 n->adjvex = v; 45 n->nextarc = L->nextarc; 46 L->nextarc = n; 47 return 0; 48 } 49 50 //採用鄰接表儲存方法,建立有向圖 51 int CreateDG(ALGraph *G) 52 { 53 printf("開始建立一個有向圖,請輸入頂點數,弧數:"); 54 int i = 0, j = 0, k = 0; 55 char v1[10] = {0}, v2[10]={0}; 56 char tmp[20] = {0}; 57 G->kind = DG; 58 scanf("%d,%d", &G->vexnum, &G->arcnum); 59 for(i=0; i<G->vexnum; i++){ 60 printf("輸入第%d個頂點: ", i+1); 61 memset(G->vertices[i].data, 0, sizeof(G->vertices[i].data)); 62 scanf("%s", G->vertices[i].data); 63 G->vertices[i].firstarcOUT = (struct ArcNode *)malloc(sizeof(struct ArcNode)); 64 G->vertices[i].firstarcOUT->adjvex = -1; 65 G->vertices[i].firstarcOUT->nextarc = NULL; 66 G->vertices[i].firstarcIN = (struct ArcNode *)malloc(sizeof(struct ArcNode)); 67 G->vertices[i].firstarcIN->adjvex = -1; 68 G->vertices[i].firstarcIN->nextarc = NULL; 69 } 70 for(k=0; k<G->arcnum; k++) 71 { 72 printf("輸入第%d條弧(頂點1, 頂點2): ", k+1); 73 memset(tmp, 0, sizeof(tmp)); 74 scanf("%s", tmp); 75 sscanf(tmp, "%[^','],%s[^\\n]", v1, v2); 76 i = LocateVex(G, v1); 77 j = LocateVex(G, v2); 78 if(i<0 || j<0){ 79 printf("<%s,%s> is a invalid arch!\n", v1, v2); 80 return -1; 81 } 82 InsFirst(G->vertices[i].firstarcOUT, j); 83 InsFirst(G->vertices[j].firstarcIN, i); 84 } 85 return 0; 86 } 87 88 void printG(ALGraph *G) 89 { 90 printf("\n"); 91 if(G->kind == DG){ 92 printf("型別:有向圖;頂點數 %d, 弧數 %d\n", G->vexnum, G->arcnum); 93 }else if(G->kind == DN){ 94 printf("型別:有向網;頂點數 %d, 弧數 %d\n", G->vexnum, G->arcnum); 95 }else if(G->kind == UDG){ 96 printf("型別:無向圖;頂點數 %d, 弧數 %d\n", G->vexnum, G->arcnum); 97 }else if(G->kind == UDN){ 98 printf("型別:無向網;頂點數 %d, 弧數 %d\n", G->vexnum, G->arcnum); 99 } 100 int i = 0; 101 ArcNode *p = NULL; 102 printf("鄰接表:\n"); 103 for(i=0; i<G->vexnum; i++){ 104 printf("(%d,%s)\t", i,G->vertices[i].data); 105 p = G->vertices[i].firstarcOUT; 106 while(p){ 107 if(p->adjvex >= 0) 108 printf("(%d,%s)\t", p->adjvex, G->vertices[p->adjvex].data); 109 p = p->nextarc; 110 } 111 printf("\n"); 112 } 113 printf("逆鄰接表:\n"); 114 for(i=0; i<G->vexnum; i++){ 115 printf("(%d,%s)\t", i,G->vertices[i].data); 116 p = G->vertices[i].firstarcIN; 117 while(p){ 118 if(p->adjvex >= 0) 119 printf("(%d,%s)\t", p->adjvex, G->vertices[p->adjvex].data); 120 p = p->nextarc; 121 } 122 printf("\n"); 123 } 124 return; 125 } 126 127 #define STACK_INIT_SIZE 20 //棧的初始分配量大小 128 #define STACK_INCREMENT 5 //棧容量不足時需新增的容量大小 129 typedef struct { 130 int *base; //指向棧底指標 131 int *top; //指向棧頂指標 132 int stacksize; //棧的當前容量大小 133 }SqStack; 134 135 int InitStack(SqStack *s); //初始化一個棧 136 int StackEmpty(SqStack *s); //判斷棧是否為空 137 int Push(SqStack *S, int *e); //入棧函式 138 int Pop(SqStack *S, int *e); //出棧函式 139 140 //演算法各個頂點的入度,並將結果存放在indegree陣列中 141 int FindInDegree(ALGraph *G, int indegree[]) 142 { 143 printf("\n對各個頂點求入度...\n"); 144 int i = 0; 145 ArcNode *p = NULL; 146 for(i=0; i<G->vexnum; i++) { 147 p = G->vertices[i].firstarcIN; 148 while (p) { 149 if (p->adjvex >= 0) { 150 indegree[i] += 1; 151 } 152 p = p->nextarc; 153 } 154 } 155 for(i=0; i<G->vexnum; i++){ 156 printf("(%d,%s)的入度為%d\n", i, G->vertices[i].data, indegree[i]); 157 } 158 return 0; 159 } 160 161 //進行拓撲排序 162 int ToplogicalSort(ALGraph *G) 163 { 164 int i = 0; 165 int k = 0; 166 int count = 0; 167 int indegree[MAX_VERTEX_NUM] = {0}; 168 ArcNode *p = NULL; 169 SqStack S; 170 //求各個頂點的入度 171 FindInDegree(G, indegree); 172 if(InitStack(&S) <0){ 173 return -1; 174 } 175 //將入度為0的頂點入棧. 176 for(i=0; i<G->vexnum; i++){ 177 if(!indegree[i]) { 178 Push(&S, &i); 179 } 180 } 181 printf("\n進行拓撲排序:"); 182 while(StackEmpty(&S)){ 183 //如果棧不為空 184 Pop(&S, &i); 185 //輸入i號頂點並計數 186 printf("(%d,%s)\t", i, G->vertices[i].data); 187 ++count; 188 for(p=G->vertices[i].firstarcOUT; p; p=p->nextarc){ 189 //對i號頂點的每個鄰接點的入度減1 190 k = p->adjvex; 191 if(!(--indegree[k])) { 192 //若入度減為0,則入棧 193 Push(&S, &k); 194 } 195 } 196 } 197 printf("\n"); 198 if(count < G->vexnum){ 199 printf("警告:該圖有環路!!\n"); 200 return -1; 201 }else{ 202 return 0; 203 } 204 } 205 206 int main(int argc, char *argv[]) 207 { 208 ALGraph G; 209 //建立有向圖 210 if(CreateDG(&G)<0){ 211 printf("建立有向圖時出錯!\n"); 212 return -1; 213 } 214 //列印圖 215 printG(&G); 216 //進行拓撲排序 217 ToplogicalSort(&G); 218 return 0; 219 } 220 221 222 int InitStack(SqStack *S){ 223 S->base = (int *) malloc(STACK_INIT_SIZE * sizeof(int)); 224 if(!S->base){ 225 return -1; 226 } 227 S->top = S->base; 228 S->stacksize = STACK_INIT_SIZE; 229 return 0; 230 } 231 232 int StackEmpty(SqStack *s){ 233 if(s->base == s->top){ 234 return 0; 235 }else{ 236 return -1; 237 } 238 } 239 240 int Push(SqStack *s, int *e){ 241 if((s->top-s->base) >= s->stacksize){ 242 s->base = (int*)realloc(s->base, (s->stacksize+STACK_INCREMENT)*(sizeof(int))); 243 if(!s->base){ 244 return -1; 245 } 246 s->top = s->base + s->stacksize; 247 s->stacksize += STACK_INCREMENT; 248 } 249 if(e == NULL){ 250 return -1; 251 }else{ 252 *s->top = *e; 253 } 254 s->top += 1; 255 return 0; 256 } 257 258 int Pop(SqStack *s, int *e) 259 { 260 if(s->top == s->base) { 261 return -1; 262 }else{ 263 s->top -=1; 264 *e = *s->top; 265 return 0; 266 } 267 }
有向無環圖的拓撲排序演算法

 

 

程式碼執行

 

/home/lady/CLionProjects/untitled/cmake-build-debug/untitled
開始建立一個有向圖,請輸入頂點數,弧數:6,8
輸入第1個頂點: V1
輸入第2個頂點: V2
輸入第3個頂點: V3
輸入第4個頂點: V4
輸入第5個頂點: V5
輸入第6個頂點: V6
輸入第1條弧(頂點1, 頂點2): V1,V2
輸入第2條弧(頂點1, 頂點2): V1,V3
輸入第3條弧(頂點1, 頂點2): V1,V4
輸入第4條弧(頂點1, 頂點2): V3,V2
輸入第5條弧(頂點1, 頂點2): V3,V5
輸入第6條弧(頂點1, 頂點2): V4,V5
輸入第7條弧(頂點1, 頂點2): V6,V4
輸入第8條弧(頂點1, 頂點2): V6,V5

型別:有向圖;頂點數 6, 弧數 8
鄰接表:
(0,V1)    (3,V4)    (2,V3)    (1,V2)    
(1,V2)    
(2,V3)    (4,V5)    (1,V2)    
(3,V4)    (4,V5)    
(4,V5)    
(5,V6)    (4,V5)    (3,V4)    
逆鄰接表:
(0,V1)    
(1,V2)    (2,V3)    (0,V1)    
(2,V3)    (0,V1)    
(3,V4)    (5,V6)    (0,V1)    
(4,V5)    (5,V6)    (3,V4)    (2,V3)    
(5,V6)    

對各個頂點求入度...
(0,V1)的入度為0
(1,V2)的入度為2
(2,V3)的入度為1
(3,V4)的入度為2
(4,V5)的入度為3
(5,V6)的入度為0

進行拓撲排序:(5,V6)    (0,V1)    (2,V3)    (1,V2)    (3,V4)    (4,V5)    

Process finished with exit code 0