學習筆記 | 優先隊列Priority Queue

優先隊列(Priority Queue)

特點

  • 能保證每次取出的元素都是隊列中優先級別最高的。
  • 優先級別可以是自定義的,例如,數據的數值越大,優先級越高;或者數據的數值越小,優先級越高。優先級別甚至可以通過各種複雜的計算得到。

應用場景

  • 從一堆雜亂無章的數據當中按照一定的順序(或者優先級)逐步地篩選出部分乃至全部的數據。
舉例:任意一個數組,找出前 k 大的數。
解法 1:先對這個數組進行排序,然後依次輸出前 k 大的數,複雜度將會是 O(nlogn),其中,n 是數組的元素個數。這是一種直接的辦法。
解法 2:使用優先隊列,複雜度優化成 O(k + nlogk)。
  • 當數據量很大(即 n 很大),而 k 相對較小的時候,顯然,利用優先隊列能有效地降低算法複雜度。因爲要找出前 k 大的數,並不需要對所有的數進行排序。

實現

優先隊列的本質是一個二叉堆結構。堆在英文裏叫 Binary Heap,它是利用一個數組結構來實現的完全二叉樹。 換句話說,優先隊列的本質是一個數組,數組裏的每個元素既有可能是其他元素的父節點,也有可能是其他元素的子節點,而且,每個父節點只能有兩個子節點,很像一棵二叉樹的結構。

牢記下面優先隊列有三個重要的性質。

  1. 數組裏的第一個元素 array[0] 擁有最高的優先級別。
  2. 給定一個下標 i,那麼對於元素 array[i] 而言:
  • 它的父節點所對應的元素下標是 (i-1)/2
  • 它的左孩子所對應的元素下標是 2×i + 1
  • 它的右孩子所對應的元素下標是 2×i + 2
  1. 數組裏每個元素的優先級別都要高於它兩個孩子的優先級別。

優先隊列最基本的操作有兩個。

1. 向上篩選(sift up / bubble up)
  • 當有新的數據加入到優先隊列中,新的數據首先被放置在二叉堆的底部。
  • 不斷進行向上篩選的操作,即如果發現該數據的優先級別比父節點的優先級別還要高,那麼就和父節點的元素相互交換,再接着往上進行比較,直到無法再繼續交換爲止。
  • 時間複雜度:由於二叉堆是一棵完全二叉樹,並假設堆的大小爲 k,因此整個過程其實就是沿着樹的高度往上爬,所以只需要 O(logk) 的時間。
2. 向下篩選(sift down / bubble down)
  • 當堆頂的元素被取出時,要更新堆頂的元素來作爲下一次按照優先級順序被取出的對象,需要將堆底部的元素放置到堆頂,然後不斷地對它執行向下篩選的操作。
  • 將該元素和它的兩個孩子節點對比優先級,如果優先級最高的是其中一個孩子,就將該元素和那個孩子進行交換,然後反覆進行下去,直到無法繼續交換爲止。
  • 時間複雜度:整個過程就是沿着樹的高度往下爬,所以時間複雜度也是 O(logk)。因此,無論是添加新的數據還是取出堆頂的元素,都需要 O(logk) 的時間。

初始化

  • 優先隊列的初始化是一個最重要的時間複雜度,是分析運用優先隊列性能時必不可少的,也是經常容易弄錯的地方。

舉例:有 n 個數據,需要創建一個大小爲 n 的堆。
誤區:每當把一個數據加入到堆裏,都要對其執行向上篩選的操作,這樣一來就是 O(nlogn)。
解法:在創建這個堆的過程中,二叉樹的大小是從 1 逐漸增長到 n 的,所以整個算法的複雜度經過推導,最終的結果是 O(n)。

  • 注意:算法面試中是不要求推導的,你只需要記住,初始化一個大小爲 n 的堆,所需要的時間是 O(n) 即可
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章