這篇隨筆就信息學奧林匹克競賽中圖論的一個知識點——拓撲排序進行講解。拓撲排序的內容比較基礎,只要求讀者學習過並瞭解信息學中圖的相關定義和一些專業名詞,可是拓撲排序的變形題目比較多,但願讀者在看完本隨筆後認真體會練習,掌握拓撲排序。算法
顧名思義,這是一種排序,確切地說,是一種圖上排序,在一張有向無環圖(註解:有向無環圖即不少參考書和題解中所說的DAG)上進行排序,把其中的全部節點排成一個序列,使得圖中的任意一對有邊相連的節點(u,v)u要出如今v前。數組
因此我再次強調,拓撲排序只能用在有向無環圖中!!學習
這樣的線性序列咱們稱之爲拓撲序。code
注意,拓撲序不惟一!這個地方不明白的請本身畫圖理解(或者參考下面的那棵樹)。blog
在講解圖的拓撲排序以前,咱們能夠用一棵樹來加深對拓撲排序的理解(由於樹是絕對沒有環)。排序
咱們隨意地定義一棵有向樹(以下圖),若是咱們想獲得它的拓撲序,那會很簡單,只須要先把根節點8號放進隊列中,而後再放8號的任意一個兒子節點,繼續此操做。直到節點全放進去爲止。隊列
咱們會發現,問放進去的是任意的一個子節點,因此咱們說拓撲序是不惟一的(在絕大多數狀況下,你要非跟我擡槓說假如只有一條鏈,我也沒辦法)。it
講完了實現原理,咱們來進行拓撲排序的代碼實現,根據上面的原理,咱們會發現,咱們要保證拓撲序列的正確性,只須要把圖中的入度爲0的節點先放進拓撲序,而後把這個點和它全部的出邊所有刪掉,這樣就還會出現一些入度爲0的點,咱們繼續重複以上操做。io
有細心的小夥伴會發現這個和算法中的寬搜很類似,沒錯,所謂寬搜和深搜,都是基於對樹與圖的深度/寬度優先遍歷而定義的,因此拓撲排序的實現其實就是藉助了寬搜的思想。模板
上模板:
void topsort() { for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { if(rudu[j]==0) { x=j; top[++cnt]=j; rudu[j]--; break; } } for(int j=head[x];j;j=nxt[j]) rudu[j]--; } }
以上代碼的top數組存拓撲序列,使用的是鏈式前向星存圖並遍歷。比較好理解,可是時間複雜度比較低。(因此僅供理解)
因此咱們用C++STL來實現拓撲排序,這樣會快不少。
模板:
void topsort() { queue<int> q; for(int i=1;i<=n;i++) if(rudu[i]==0) q.push(i); while(q.empty()) { int x=q.front(); q.pop(); top[++cnt]=x; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; rudu[y]--; if(rudu[y]==0) q.push(y); } } }
其實也很好理解啦...
注意,以上代碼是針對於已經保證圖是DAG的狀況下而出現的,假如咱們沒有題目中DAG的保證,就要額外地判這個圖是否是DAG,即不少題目中要求的「無解」狀況。
怎麼判斷呢?
學到這裏,我以爲你應該想到,若是最後獲得的拓撲序列的長度等於節點總數,那麼這個圖就是DAG,不然就不是。
因此咱們最後進行判斷。
(你也可使用STL中的vector容器)
代碼:
if(cnt==n) //DAG操做 else //非DAG操做
拓撲排序的用途是解決一些依賴關係的題,通常來說沒有圖論的基本要素(告訴你幾個點,一眼就看出來這是一道圖論題%%%),因此,我認爲作拓撲排序題的難點在於如何創建一個和題意相符的圖(建圖坑死爹)。因此美其名曰拓撲排序是圖論中最簡單的內容,其實它的相關題目都頗有思惟含量,因此強烈建議各位同窗多刷題多刷題。
因爲拓撲排序不惟一,因此有些坑爹題目要求拓撲序列的一些內容,好比按字典序等等。
這時咱們把本來的隊列拓撲排序換成優先隊列拓撲排序。
注意,優先隊列不能提速,不要覺得找到了一份更好的模板,必定要讀題~~!!
除了定義方式有點怪異其餘的跟隊列同樣。
priority_queue<int,vector<int>,greater<int> >q; //取隊首的時候須要變成q.top();