【圖論】拓撲排序詳解

前言

在正文開始前,我們先來了解一下有向無環圖(Directed Acyclic Graph簡稱DAG)

如下圖就是一個DAG圖,DAG圖是我們討論拓撲排序的基礎。

AOV網:數據在頂點 可以理解爲面向對象 AOE網:數據在邊上,可以理解爲面向過程!

1. 什麼是拓撲排序

拓撲排序(Topological Order),很多人聽說過,但是不瞭解的一種算法。或許很多人只知道它是圖論的一種排序,至於幹什麼的不清楚。又或許很多人可能還會認爲它是一種啥排序。

而實質上它只是將DAG圖的頂點排成一個線性序列,得到一個頂點的全序集合。其排序的順序依據就是節點的指向關係。比如前言的DAG圖:

  • ...
  • 節點5在節點4和節點3的後面
  • 節點9在節點6和節點7的後面
  • ...

那麼最後得到的節點的線性序列結果,也一定要滿足上面的指向順序。

每一個節點都擁有入度(有多少點導向它,也就是開始它有多少前提)和出度(它導向多少點,也就是它是多少其他節點開始的前提)。例如節點5的入度爲3和4,出度爲7。

拓撲排序的結果不是唯一的,只要符合上面的條件,那麼它就是拓撲序列,比如1 2 4 3 6 5 7 92 1 3 4 5 6 7 9,這兩個結果都是可行的。

官方一點的定義:將有向圖中的節點以線性方式進行排序。即對於任何連接自節點u到節點v的有向邊uv,在最後的排序結果中,節點u總是在節點v的前面。

2. 現實案例

看了上面關於拓撲排序的概念如果還覺得十分抽象的話,那麼不妨考慮一個非常非常經典的例子——選課。

假設我非常想學習一門《jsp入門》的課程,但是在修這麼課程之前,我們必須要學習一些基礎課程,比如《JAVA語言程序設計》,《HTML指南》等等。那麼這個制定選修課程順序的過程,實際上就是一個拓撲排序的過程,每門課程相當於有向圖中的一個頂點,而連接頂點之間的有向邊就是課程學習的先後關係。

只不過這個過程不是那麼複雜,從而很自然的在我們的大腦中完成了。將這個過程以算法的形式描述出來的結果,就是拓撲排序。

可以看到,上圖中的學習順序,就是拓撲序列,其不止一個結果。

拓撲排序算法在工程學中十分重要。

節點成環的圖,無法被拓撲排序,因爲這在工程上本身沒有意義,比如A——>B——>C——>A,那麼這個工程永遠無法被開始。

3. 算法實現

拓撲排序的最優時間複雜度是O(m+n),其中m和n是DAG圖中節點數和邊數。因爲拓撲排序至少要對DAG圖的節點和邊進行一次完整的遍歷。

拓撲排序的最優空間複雜度是O(m+n),其中m和n是DAG圖中節點數和邊數。我們一般使用鄰接表來存儲DAG圖,因此空間複雜度是O(m+n)。

3.1 廣度優先搜索法(BFS)

3.1.1 BFS實現拓撲排序

廣度優先搜索法的思路很簡單:

  1. 從DAG圖中找到入度爲0的節點A(也就是沒有箭頭指向它的節點),將其放入拓撲序列的結果集。
  2. 同時刪除由節點A出發的所有邊。
  3. 在剩下的DAG圖中重複1-2兩步。
  4. 如果最後可以把全部的節點都刪除並加入到結果集,那表示DAG圖可以被拓撲排序;否則,如果最後有節點被剩下,那說明該圖是有環圖,無法被拓撲排序。

如下圖

3.1.2 BFS實現拓撲排序的優化

如果有時候,我們只需要知道某個DAG圖是否可以拓撲排序,而不需要真正得到拓撲排序後的結果,那麼可以不需要結果集列表,只需要統計被刪除的節點的數量即可,如果該數量等於DAG圖的節點數,那麼DAG圖可以被拓撲排序

3.2 深度優先搜索法(DFS)

3.2.1 DFS實現拓撲排序

深度優先搜索法是廣度優先搜索法的逆向思路,它的步驟如下:

  1. 選取圖中任意一個節點A,將其狀態標記爲“搜索中”
  2. 尋找節點A的鄰接點(沿着箭頭指向尋找相鄰的節點)
    1. 如果A存在鄰接點
      1. 如果A的鄰接點中存在狀態爲“搜索中”的鄰接點,那麼表示DAG圖有環路,不可拓撲排序。
      2. 否則,那麼任意選擇一個狀態爲“未搜索”的鄰接點B,使用遞歸對B重複做1和2操作,注意此時B的鄰接點判斷不包含來路(也就是A節點)。等到A的所有鄰接點都被搜索到,遞歸回溯回A節點的時候,那麼A節點也會被標記爲“已搜索”,並壓入結果棧。
    2. 如果A不存在鄰接點,那麼將節點A的狀態改爲“已完成”,並且將其壓入一個結果集的棧中。
  3. A節點及其相鄰節點都搜索完畢後,如果還有未搜索的節點,那麼任意選取一個節點當做出發點,繼續重複1,2,3步驟。
  4. 直到所有的節點都被搜索並壓入棧,那麼此時結果棧中,從棧頂到棧底的順序,就是拓撲排序的順序。

3.2.2 DFS實現拓撲排序的優化

如果有時候,我們只需要知道某個DAG圖是否可以拓撲排序,而不需要真正得到拓撲排序後的結果,那麼可以不需要結果棧,只需要判斷整個深度優先搜索過程,沒有發生“搜索中”節點的相鄰節點(不包含來路的節點)也是“搜索中”就行。

4 算法題解

4.1 課程表I

leetcode-207. 課程表I

4.2 課程表II

leetcode-210.課程表II

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章