2016/5/19 17:39:07
拓撲排序,是對有向無環圖(Directed Acylic Graph , DAG )進行的一種操作,這種操作是將DAG中的所有頂點排成一個線性序列,使得圖中的任意一對頂點u,v滿足如下條件:
若邊(u,v)∈E(G),則在最終的線性序列中出現在v的前面
好了,說人話:拓撲排序的應用常常和AOV網相聯系,在一個大型的工程中,某些項目不是獨立於其他項目的,這意味着這種非獨立的項目的完成必須依賴與其它項目的完成而完成,不妨記為u,v,則若邊(u,v)∈E(G),代表着必須在項目u完成后,v才能完成。
這樣,拓撲排序就可以表示一個工程的進度安排,這也樣也更加方便我們理解為什么一個圖如果有環,就一定不存在拓撲排序:因為這相當於在工程中你不停的重復做同一個項目,工程變成一個項目的循環,自然不存在拓撲排序。
當然,拓撲排序往往不會只有一種,通過DFS,我們可以求得拓撲排序。
拓撲排序的思路簡述如下:
- 狀態標記:共三種,-1表示訪問中,0表示未訪問,1表示已訪問,由數組c保存
- dfs終止的判別條件:如果存在環,則不存在,退出;反之把當前結點加入拓撲排序的首部(線性序列的當前第一個位置,隨着排序的進行,這個位置會不斷前移)
- 通過topo數組記錄拓撲排序
這里解釋一下書上的問題:為什么訪問完一個節點就把當前結點加入到拓撲排序首部?
答:因為由拓撲排序的性質可知,在DAG中,不妨任取從u頂點出發進行DFS,遇到v頂點,在最終的拓撲排序中始終應滿足u在v之前,而根據DFS滿足棧的FIFO性質可知,頂點v會先進入拓撲序列,頂點u后進入拓撲序列,因此,如果我們想要順序獲取拓撲序列,就應該將當前頂點(u)加入到拓撲排序的首部。當然,我們也可以通過模擬棧的FIFO特性,通過彈棧將逆序的拓撲序列變為順序,但是這樣無疑增加的操作的步驟,本質上是一樣的。
接下來給出拓撲排序的代碼:(圖通過鄰接矩陣存儲)
1 #include<cstdio> 2 #include<cstring> 3 const int maxn=100; 4 int n,m,u,v,t,topo[maxn],G[maxn][maxn],c[maxn]; 5 //DFS 6 bool dfs(int u){ 7 c[u]=-1;//正在訪問中 8 for(int i=0;i<n;i++)if(G[u][i]){ 9 if(c[i]<0)return false;//存在環,退出 10 if(!c[i]&&!dfs(i))return false;//i沒訪問過且訪問后發現有環,退出 11 } 12 c[u]=1;topo[--t]=u;//u頂點訪問完畢,修改狀態,加入拓撲序列首部 13 return true; 14 } 15 //topological ordering 16 bool toposort(){ 17 t=n; 18 memset(c,0,sizeof(c)); 19 for(int i=0;i<n;i++) 20 if(!dfs(i))return false; 21 return true; 22 }
好了,介紹完拓撲排序,照例是大餐,算法和OJ搭配食用,味道更棒哦~
傳送門:
UVa:10305 Ordering Tasks https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=838&page=show_problem&problem=1246
這是一道非常簡單的拓撲排序,由題意我們可以知道,我們無需判斷環的存在,因此代碼簡潔了不少,不過,題目中要求m和n同時為0時代表結束,一開始寫成了m&&n,導致wa,后來發現應該是m||n
另外,在解題時,我用了棧來簡化逆序操作,不然我還要計算一下輸出序列的長度,再把dfs的結果加入到拓撲排序首部,想想就覺得很麻煩,還不如自己寫一個棧來的簡單~~~
幾個地方在提交的時候要注意:
- index作為全局變量,在本地編譯可過,UVa上會complication error,所以這里改用了pos來指代棧指針
- 注意這是多輸入的題目,所以vis數組一定要用memset清空
- 本來想把結果輸出的printf整合為一句話,但是發現這樣的話,換行符無法輸出,我想過一段時間找點資料,了解一下printf的實現,就應該能明白問題的根源了。
代碼如下:

1 #include<cstdio> 2 #include<cstring> 3 #define N 100 4 int g[N+1][N+1],u,v,n,m,stack[N+1],vis[N+1],pos=0; 5 void push(int x){ 6 stack[pos++]=x; 7 } 8 int pop(){ 9 return stack[--pos]; 10 } 11 void dfs(int u){ 12 vis[u]=-1;//visting 13 for(int i=1;i<=n;i++)if(g[u][i]&&!vis[i]) 14 dfs(i); 15 push(u);vis[u]=1;//visted 16 } 17 int main(){ 18 while(scanf("%d%d",&n,&m)==2&&(m||n)){//這里是m或n,與的話會wa,我離散數學要去面壁 19 while(m--){ 20 scanf("%d%d",&u,&v); 21 g[u][v]=1; 22 } 23 memset(vis,0,sizeof(vis));//initialization 24 for(int i=1;i<=n;i++)//topological sort 25 if(!vis[i])dfs(i); 26 while(pos){//print result 27 printf("%d",pop()); 28 printf("%c",pos>0?' ':'\n');//三目運算符真的很好用 29 } 30 } 31 }
這一篇就到這里啦~~~