拓撲排序是針對無環有向圖的應用,無環:即圖中沒有回路
一:定義
(一)AOV網(Activity On Vertex)
在一個表示工程的有向圖中,用頂點表示活動,用弧表示活動之間的優先關系,這樣的有向圖為頂點表示活動的網,我們稱為AOV網
注意(重點):
AOV網中的弧表示活動之間存在的某種制約更新。其中AOV網中不能存在回路。


(二)拓撲序列
設G=(V,E)是一個具有n個頂點的有向圖,V中的頂點序列v1,v2,...,vn,滿足若從頂點vi到vj有一條路徑,則在頂點序列中頂點vi必在vj之前,則我們稱這樣的頂點序列為一個拓撲序列
(三)拓撲排序
所謂拓撲排序其實就是對一個有向圖構造拓撲序列的過程
二:應用
我們對一個有向圖進行構造時,會出現兩個結果。
1.如果此網的全部頂點都被輸出,則說明他是不存在環(回路)的AOV網 2.如果輸出的頂點數少了,則說明這個網存在環(回路),不是AOV網
一個不存在回路的AOV網,我們可以把它用於在各種工程或項目的流程圖中,滿足各種應用場景的需要,所以實現拓撲排序的算法很有價值
補充:
我們之前在克魯斯卡爾算法中提及使用並查集判斷環和這里判斷回路是不同的,使用並查集是判斷生成樹(是棵樹),而且是針對無向圖而言的,而我們這里的拓撲排序是針對有向圖

例如上圖:
並查集是判斷有環。(主要針對的是無向圖)
拓撲排序判斷無環。
三:拓撲排序算法
從AOV網中選擇一個入度為0的頂點輸出,然后刪除此頂點,並刪除以此頂點為尾的弧,重復操作指導輸出全部頂點或者AOV網中不存在入度為0的頂點為止
前面的最小生成樹和最短路徑都是使用的鄰接矩陣,由於拓撲排序過程需要刪除頂點,所以我們使用鄰接表會更加方便,而且需要在頂點表中加入入度域,我們會根據頂點的入度決定是否刪除

例如:我們將下面的AOV網轉鄰接表


對於算法的實現,我們需要借助棧或者隊列來實現,都可以
四:代碼實現
我們使用的是創建一個臨時棧來存放頂點,也可以使用隊列實現(實現方法不唯一,輸出結果也不唯一)
Status TopologicalSort(AdjGraphList AG)
{
EdgeNode* e;
int i, j,k, gettop;
int count = 0; //用於統計輸出頂點個數
int top = -1; //這是我們要創建的棧的指針
int *stack = (int*)malloc(sizeof(int)*AG.numVertexes); //這是我們創建的臨時棧
//最開始將所有入度為0的頂點入棧
for (i = 0; i < AG.numVertexes; i++)
if (!AG.adjlist[i].in)
stack[++top] = i;
//下面進入主循環,直到棧中無數據結束(全部頂點輸出,或者剩余的成環,入度都不為0)
while (top!=-1)
{
//出棧數據
gettop = stack[top--]; //出棧
printf("%c -> ", AG.adjlist[gettop].data);
count++;
//對他出棧數據的所有鄰接點的入度減一
for (e = AG.adjlist[gettop].firstedge; e;e=e->next)
{
k = e->adjvex;
if (!(--AG.adjlist[k].in))
stack[++top] = k;
}
}
printf("\n");
//進行判斷,若是count小於頂點數,則有環
if (count < AG.numVertexes)
return ERROR;
return OK;
}
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define MAXVEX 100 //最大頂點數
#define INFINITY 65535 //用0表示∞
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
typedef char VertexType; //頂點類型,字符型A,B,C,D...
typedef int EdgeType; //邊上權值類型10,15,...
//鄰接矩陣結構
typedef struct
{
VertexType vers[MAXVEX]; //頂點表
EdgeType arc[MAXVEX][MAXVEX]; //鄰接矩陣,可看作邊表
int numVertexes, numEdges; //圖中當前的頂點數和邊數
}MGraph;
typedef struct EdgeNode //邊表結點
{
int adjvex;
int weight;
struct EdgeNode* next;
}EdgeNode;
typedef struct VertexNode //頂點表結點
{
int in;
VertexType data;
EdgeNode* firstedge;
}VertexNode,AdjList[MAXVEX];
//鄰接表結構
typedef struct
{
AdjList adjlist;
int numVertexes, numEdges; //圖中當前的頂點數和邊數
}AdjGraphList;
//創建鄰接矩陣
void CreateMGraph(MGraph* G);
//顯示鄰接矩陣
void showGraph(MGraph G);
//鄰接矩陣轉鄰接表
void MGragp2AdjList(MGraph G,AdjGraphList* AG);
//拓撲排序
Status TopologicalSort(AdjGraphList AG);
int main()
{
MGraph MG;
AdjGraphList AG;
CreateMGraph(&MG);
showGraph(MG);
MGragp2AdjList(MG, &AG);
if (TopologicalSort(AG))
printf("no ring\n");
else
printf("ring\n");
system("pause");
return 0;
}
//生成鄰接矩陣
void CreateMGraph(MGraph* G)
{
int i, j, k, w;
G->numVertexes = 14;
G->numEdges = 20;
//讀入頂點信息
G->vers[0] = 'A';
G->vers[1] = 'B';
G->vers[2] = 'C';
G->vers[3] = 'D';
G->vers[4] = 'E';
G->vers[5] = 'F';
G->vers[6] = 'G';
G->vers[7] = 'H';
G->vers[8] = 'I';
G->vers[9] = 'J';
G->vers[10] = 'K';
G->vers[11] = 'L';
G->vers[12] = 'M';
G->vers[13] = 'N';
//getchar(); //可以獲取回車符
for (i = 0; i < G->numVertexes; i++)
for (j = 0; j < G->numVertexes; j++)
G->arc[i][j] = INFINITY; //鄰接矩陣初始化
//創建了有向鄰接矩陣
G->arc[0][4] = 1;
G->arc[0][5] = 1;
G->arc[0][11] = 1;
G->arc[1][4] = 1;
G->arc[1][2] = 1;
G->arc[1][8] = 1;
G->arc[2][5] = 1;
G->arc[2][6] = 1;
G->arc[2][9] = 1;
G->arc[3][2] = 1;
G->arc[3][13] = 1;
G->arc[4][7] = 1;
G->arc[5][8] = 1;
G->arc[5][12] = 1;
G->arc[6][5] = 1;
G->arc[8][7] = 1;
G->arc[9][10] = 1;
G->arc[9][11] = 1;
G->arc[10][13] = 1;
G->arc[12][9] = 1;
}
//顯示鄰接矩陣
void showGraph(MGraph G)
{
for (int i = 0; i < G.numVertexes; i++)
{
for (int j = 0; j < G.numVertexes; j++)
{
if (G.arc[i][j] != INFINITY)
printf("%5d", G.arc[i][j]);
else
printf(" 0");
}
printf("\n");
}
}
void MGragp2AdjList(MGraph G, AdjGraphList* AG)
{
int i, j;
EdgeNode* edge;
AG->numEdges = G.numEdges;
AG->numVertexes = G.numVertexes;
for (i = 0; i < G.numVertexes;i++)
{
AG->adjlist[i].data = G.vers[i];
AG->adjlist[i].in = 0;
AG->adjlist[i].firstedge = NULL;
}
for (i = 0; i < G.numVertexes; i++)
{
for (j = 0; j < G.numVertexes; j++)
{
if (G.arc[i][j] != INFINITY)
{
edge = (EdgeNode*)malloc(sizeof(EdgeNode));
edge->adjvex = j;
edge->weight = G.arc[i][j];
edge->next = AG->adjlist[i].firstedge;
AG->adjlist[i].firstedge = edge;
AG->adjlist[j].in++;
}
}
}
}
Status TopologicalSort(AdjGraphList AG)
{
EdgeNode* e;
int i, j, k, gettop;
int count = 0; //用於統計輸出頂點個數
int top = -1; //這是我們要創建的棧的指針
int *stack = (int*)malloc(sizeof(int)*AG.numVertexes); //這是我們創建的臨時棧
//最開始將所有入度為0的頂點入棧
for (i = 0; i < AG.numVertexes; i++)
if (!AG.adjlist[i].in)
stack[++top] = i;
//下面進入主循環,直到棧中無數據結束
while (top != -1)
{
//出棧數據
gettop = stack[top--]; //出棧
printf("%c -> ", AG.adjlist[gettop].data);
count++;
//對他出棧數據的所有鄰接點的入度減一
for (e = AG.adjlist[gettop].firstedge; e; e = e->next)
{
k = e->adjvex;
if (!(--AG.adjlist[k].in))
stack[++top] = k;
}
}
printf("\n");
//進行判斷,若是count小於頂點數,則有環
if (count < AG.numVertexes)
return ERROR;
return OK;
}
全部代碼

若要測試有環,我們只需要加一條邊v9-v5即可,記得邊數加一
五:性能分析
對一個具有n個頂點e條弧的AOV網,我們幾乎要對每個頂點進行出入棧操作,時間復雜度為O(n),我們要對每個頂點的入度減一,就是減少一條邊,時間復雜度取決於邊數,為O(e)。所以整個算法時間復雜度為O(n+e)
拓撲排序主要是為解決一個過程能否順利進行的問題,但有時我們還需要解決過程完成需要的最短時間問題,這時我們就需要關鍵路徑問題來解決