今天博客的內容依然與圖有關,今天博客的主題是關於拓撲排序的。拓撲排序是基於AOV網的,關於AOV網的概念,我想引用下方這句話來介紹:html
AOV網:在現代化管理中,人們經常使用有向圖來描述和分析一項工程的計劃和實施過程,一個工程常被分爲多個小的子工程,這些子工程被稱爲活動(Activity),在有向圖中若以頂點表示活動,有向邊表示活動之間的前後關係,這樣的圖簡稱爲AOV網。git
說的簡單點,AOV網就是表示一個工程中某些子項的前後順序。就拿工地搬磚來講吧,只有磚廠送來磚,工人才能搬。那麼磚廠送磚就是搬磚的前提。先這麼一聊,下方會給出詳細的介紹。廢話少說進入今天的主題。github
1、AOV網與拓撲排序數組
本篇博客咱們先聊一下AOV網和拓撲排序的關係,下方是咱們列舉的一個很是簡單的例子,固然下方的這個圖就是一個簡單的AOV圖,麻雀雖小,五臟俱全。在下方的AOV圖中,送磚和找人是並列的,先執行誰都行。不過搬磚的前提是即送完了磚也找完了人,而後就能夠開始搬磚了,因此送磚和找人就是搬磚的前提。那麼讓搬磚這件事情順利進行下去的順序有"送磚->找人->搬磚"或者「找人->送磚->搬磚」這兩個序列,而這兩個序列都是拓撲序列。數據結構
生成「送磚->找人->搬磚」這個序列的過程咱們稱之爲拓撲排序。若是非得說的官方和抽象點,那麼仍是引用拓撲排序的定義吧,下方就是拓撲排序的定義:post
拓撲排序:對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中全部頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u在線性序列中出如今v以前。一般,這樣的線性序列稱爲知足拓撲次序(Topological Order)的序列,簡稱拓撲序列。簡單的說,由某個集合上的一個偏序獲得該集合上的一個全序,這個操做稱之爲拓撲排序。url
上面這個定義就比較抽象了,固然仍是咱們搬磚的例子好理解一些。在有向無環圖中的結點若是有入度的話,那麼就說明該結點優先級要低於那些能夠到達改點的結點。而那些沒有入度的結點的優先級就比較高,這些結點的完成不依賴與其餘結點。這樣說若是有些抽象的話,那麼咱們就看下方拓撲排序詳細的示例圖。spa
2、拓撲排序示意圖3d
本部分咱們將會給出拓撲排序詳細的示意圖。拓撲排序實現是依賴於棧與隊列的數據結構,棧用來暫存那些入度爲0的結點,而隊列負責存儲已經生成的拓撲序列。由於前幾篇關於圖的博客,咱們都使用了相同的圖結構。本篇博客也不例外,咱們依然會使用以前的有向圖,由於以前的圖是有向無環圖,因此是能夠生成拓撲序列的。下方就是要生成拓撲序列的有向無環圖。htm
在下方的有向無環圖每一個結點上有一個綠色的數字,該數字記錄的就是該結點的入度。入度爲零,那麼該數字就是0,若是入度爲1,那麼該數字就是1。
下方是下圖拓撲排序每一步的示意圖。接下來咱們將會給出下方每一步示意圖的詳細解說。
(1):首先將圖中入度爲0的結點入棧,由於此圖中只有A結點的入度爲0,因此咱們將A入棧。
(2):將A從棧中Pop到咱們的拓撲隊列中,將那些以A發出的邊爲入度的結點的度數減一。對於此示例來講也就是將B和F入度減1. 由於B和F的入度數減一後成了0, 因此也將B, F這兩結點入棧。
(3):將F從棧中Pop到拓撲隊列中,由於圖中有 F->G和F->E兩條邊,因此 將G和E的入度數減一。由於E的入度數減一後爲0,因此將E入棧。
(4):將E從棧中Pop到拓撲隊列中,由於圖中E->H和E->D兩條邊, 因此講H和D的兩個結點的入度減一。兩個結點減一操做後沒有入度爲零的節點,因此本步沒有結點入棧。
(5):接着從棧中Pop結點到拓撲隊列中,將B結點Pop出棧入拓撲隊列。由於C、I、G結點與B相連,B添加進拓撲序列後,這三個結點的入度都減一。C和G的入度減一後爲0,因此將其加入棧中。
(6):將C從棧中pop到拓撲隊列中,與C相連的結點是I和D, 將這兩個結點的入度減1。I的入度減一後爲0,將其Push到棧中暫存。
(7):將I結點從棧中pop到拓撲隊列中, D與I相連,將D的入度再次減一。本輪沒有要入棧的結點。
(8):將G從棧中pop到拓撲隊列中, G與H和D相連,將D與H的入度減一。H的入度減一操做後入度爲零將其入棧。
(9):將H從站中pop到拓撲隊列中, D與H相連,將D的入度減一,減一後爲0,因此將D入棧。
(10):將D從棧中pop到拓撲隊列中,此刻棧中爲空,拓撲序列生成完畢。
上面這些步驟已經很詳細了,上面這些步驟搞明白後,給出代碼實現就簡單多了。下方咱們會給出具體的代碼實現。
3、拓撲排序的代碼實現
講完概念和原理後,接下來咱們就要開始實踐了。本部分就會給出具體的代碼實現,固然咱們依然採用Swift語言來作。首先咱們建立要依賴的隊列和棧,而後再構建有向圖的鄰接鏈表,最後給出拓撲排序的代碼實現。進入本部分的主題:
1.隊列與棧
接下來咱們就要實現拓撲序列生成時要使用的棧與隊列,關於棧與隊列本篇博客就不作過多的贅述了,由於咱們以前已經對棧與隊列作了詳細的介紹。關於棧與隊列更詳細的內容請查看以前的博客《棧與隊列的線性和鏈式表示(Swift面向對象版)》。
下方這段代碼段就是咱們本篇博客要使用的棧的類,固然是簡化版的,也就是對Array作了一個簡單的封裝。棧中存儲的數據類型是咱們鄰接鏈表的結點。具體代碼以下所示。
下方則是咱們存儲拓撲序列的隊列,固然也是基於Array的簡單封裝。
2.有向圖的構建
接下來咱們來建立咱們的有向圖。本篇博客所使用的有向圖咱們是使用鄰接鏈表來表示的。下方這段代碼段就是鄰接鏈表的結點,固然在以前不知一篇博客中咱們使用到了下方這個結點。本篇博客中的weightNumber不單單隻存邊的權值,在數組中的結點的weightNumber咱們用來存儲該結點的入度。
下方這段代碼就是有向圖的建立,在網鄰接鏈表上掛入結點時,要講被掛入的結點的入度加1便可。由於下方代碼與以前圖的建立的代碼相似,在此就不作過多贅述了。
下方這兩個截圖則是上述代碼段的輸入和輸出。根據輸出的結果咱們不難看出咱們所建立的圖就是一個有向圖。
三、拓撲序列的生成
接下來就是咱們本篇博客代碼實現的核心了。咱們將基於上面建立的AOV網來生成拓撲序列。其實下方生成拓撲序列的代碼就是上述示例描述的具體實現。接下來咱們將具體的說下下方這段拓撲排序的代碼。主要歸納起來分爲下方三步:
(1):首先初始化咱們所須要的棧,而後 遍歷AOV網中全部的結點,將入度爲0的結點添加到咱們的棧中暫存。
(2):循環將咱們棧中的元素添加到拓撲隊列中。每從棧中Pop出一個結點就把與該結點相連的結點的入度減1,若是減一後該結點的度數爲0則將其入棧。而後繼續下一輪的循環。
(3):當棧中沒有暫存的結點後,說明拓撲序列生成完畢。若是拓撲隊列中的元素要小於圖結點的個數,那麼說明圖中存在環路,不能生成相應的拓撲序列。
下方截圖就是咱們以前建立的有向圖所生成的拓撲序列,以下所示:
至此,咱們本篇博客的內容也就結束了,下方依然是咱們本篇博客所涉及Demo的分享連接,以下所示:
github分享連接:https://github.com/lizelu/DataStruct-Swift/tree/master/TopoLogicalSort