演算法資料結構 | 圖論基礎演算法——拓撲排序

TechFlow2019發表於2020-08-27

今天是演算法和資料結構專題的第32篇文章,我們來聊聊拓撲排序的問題。

拓撲排序是圖論當中一個非常簡單也非常常用的演算法,它有很多的功能。它可以用來檢測有向圖當中是否存在環,也可以用來解決存在依賴的排程問題。下面我們就來看看這個演算法的廬山真面目吧。

演算法場景

拓撲排序是英文音譯,它的英文原文是Topological Sorting,是一個比較抽象的概念,沒有很信達雅的翻譯。它指的是一個DAG(Directed Acyclic Graph)即有向圖所有頂點滿足一定條件的線性序列。也就是說圖中的這些頂點的排序之間存在一定的邏輯結構和順序結構,是這兩種擰在一起的一個抽象的概念。

那麼這些頂點的排序之間應該滿足什麼樣的條件呢?

其實很簡單隻有兩點:

  1. 每個點都只出現一次,這個不用解釋了
  2. 如果存在一條從A指向B的邊,那麼A點在序列中出現的順序應該在B點之前。關於這點簡單解釋一下,我們可以把有向邊看成一條排程上的依賴。A指向B,即B依賴A,那麼顯然A應該出現在B前面。就好像淘米 -> 煮飯,我們應該先淘米才能煮飯,煮飯依賴淘米。

比如上圖當中1 2 4 3 5就是一個合法的拓撲排序,這個序列滿足上面兩條性質。

演算法原理

那麼我們怎麼得到這個拓撲排序呢?

其實原理非常簡單,就是一個陣列的事情。首先,我們用一個陣列記錄每一個點的入度。所謂的入度也就是有多少點指向它,比如上圖當中1號點的入度為0,4號點的入度為2,因為它有1和2兩個點指向它。

我們要做的就是根據入度一個點一個點的選擇,根據前面說的性質,如果一個點的入度不為0,那麼它顯然不能被選擇。因為至少還有一個點應該在它的前面,既然如此,反過來說也就是我們只能選擇入度為0的點。如果所有點的入度都不為0呢?不要問,圖中肯定有環,不然一定可以找到一個入度為0的點。關於這點有嚴謹的證明,但我們也沒必要證明了,仔細想想就能想明白。

我們選中了入度為0的點之後呢?之後我們需要把它連出去的邊全部刪掉,我們一樣從排程依賴來思考。比如我們想要做壽司也想要做飯糰,這兩者都依賴於煮飯。現在飯煮好了,這兩者的依賴已經完成了,它們應該不受任何限制了。所以我們要把煮飯連向做飯糰和做壽司的邊去掉,也就是把依賴去掉。去掉邊體現在做飯糰和做壽司的入度減一,也就是它們上游的依賴少了一個。

整個流程串起來就是拓撲排序的演算法了,怎麼樣是不是很簡單呢?

但是還有一個小問題,根據這樣我們得到的序列是唯一的嗎?如果存在多個入度為0的點怎麼辦,我們該選哪一個?

顯然拓撲排序的情況可能是不唯一的,但是我們是否要獲取所有的情況這一點就要根據實際使用的情況來確定了,一般來說我們只需要一個合法的序列就可以了,如果需要得到所有的拓撲排序也不復雜,我們可以將它看成一個帶條件限制的搜尋問題,搜尋一下所有的可能性就OK了。

程式碼實現

最後,我們來看下程式碼,真的是史詩級的簡單:

paths = [[], [24], [34], [5], [35], []]

indegree = [0 for _ in range(6)]

for u in range(6):
    for v in paths[u]:
        indegree[v] += 1


topological = set()
for i in range(5):
    for u in range(16):
        if u not in topological and indegree[u] == 0:
            topological.add(u)
            for v in paths[u]:
                indegree[v] -= 1


print(topological)

今天的文章到這裡就結束了,如果喜歡本文的話,請來一波素質三連,給我一點支援吧(關注、轉發、點贊)。

- END -

原文連結,求個關注

相關文章