優先級隊列,顧名思義,它首先應該是一個隊列。隊列最大的特性就是先進先出,而在優先級隊列中,數據的出隊順序則是按照優先級來,優先級高的先出隊。算法
實現優先級隊列的方法有不少,可是用堆來實現是最直接、最高效的。堆和優先級隊列很是類似,一個堆就能夠看做一個優先級隊列。從優先級隊列中取出優先級最高的元素,就至關於取出堆頂元素。數組
假設咱們有 100 個小文件,每一個文件的大小是 100 MB,每一個文件中存儲的都是有序的字符串,如今咱們要將這些小文件合併成一個有序的大文件,就要用到優先級隊列。數據結構
總體思路有點像歸併排序的合併操做。咱們從這 100 個文件中各自取出第一個字符串放入到數組中,而後比較大小,將最小的字符串放入合併後的文件,並從數組中刪除。性能
假如這個最小的字符串來自文件 1.txt,那麼咱們就再從這個文件中取出下一個字符串放入數組,從新進行比較,找出最小的字符串加入到大文件中,而後從數組中刪除。以此類推,直到咱們遍歷完全部小文件爲止。spa
這裏,咱們每次從數組中找出最小的字符串都要進行一遍遍歷。顯然,這不是很高效。排序
其實,咱們就能夠把數組改爲優先級隊列,或者說是堆。咱們把從小文件中取出來的字符串放入到小頂堆,那麼,堆頂的元素就是最小的也就是優先級最高的元素。每次,咱們都將堆頂元素放入到大文件中並將其從堆頂刪除,而後再取出下一個字符串放入堆中,直到全部小文件都遍歷完畢便可。接口
刪除堆頂元素和往堆中插入數據的時間複雜度都爲 $O(logn)$,$n$ 表明堆中的數據個數,這裏就是 100,比數組快多了。隊列
假設咱們有一個定時器,定時器中維護了不少定時任務,每一個任務都設定了一個 要觸發執行的時間點。定時器每隔一個很小的時間單位(好比 1 秒)就會掃描一遍任務,看是否有任務須要執行。rem
可是,這樣作就很是低效。首先,若是距離任務執行時間點還太遠,那麼許多的掃描都是徒勞的。其次,每次咱們都須要掃描整個任務列表,若是任務列表很大的話,就會比較耗時。字符串
針對此,咱們就能夠按照任務執行時間的前後順序來創建一個優先級隊列,優先級最高的任務就是小頂堆的堆頂元素。
咱們拿堆頂元素的執行時間點,與當前時間點相減,獲得一個時間間隔 T。也就是說,從當前時間點再等待 T 時間,纔有第一個任務須要執行。在這期間,咱們就無需再查詢了。等到 T 時間後,咱們取出堆頂任務執行,而後再從新計算差值,繼續等待。
求 Top K 的問題能夠分爲兩類。一類是靜態數據,數據集合事先知道,不會再變。另外一類是動態數據,數據集合事先並不知道,有數據動態地加入到數據集合中。
針對靜態數據,咱們能夠維護一個大小爲 K 的小頂堆。順序遍歷數組,若是數據小於堆頂元素,則不做處理繼續遍歷;若是數據大於堆頂元素,則刪除堆頂元素並將當前數據插入到堆中。遍歷完數組後,堆中數據即爲前 K 大元素。
遍歷數據須要 $O(n)$ 的時間複雜度,而每一次堆操做須要 $O(logk)$ 的時間複雜度,因此最壞狀況下,$n$ 個元素都入堆,時間複雜度爲 $O(nlogk)$。
針對動態數據,咱們能夠一直維護一個大小爲 K 的小頂堆,每當有新數據加入到集合中時,咱們就拿它和堆頂元素進行比較,而後按照和上面靜態數據同樣的策略更新堆。這樣,不管任什麼時候候須要查詢前 K 大數的時候,咱們均可以直接返回隊中的元素便可。
所謂中位數,就是處於中間位置的數字。若是數據的個數爲奇數,那麼第 $\frac{n}{2}+1$ 個數據就是中位數;若是數據的個數爲偶數,那麼於中間位置的數字有兩個,咱們能夠取第 $\frac{n}{2}+1$ 或第 $\frac{n}{2}$ 個數據做爲中位數。
對於靜態數據,咱們能夠先對數據進行排序,而後取出中間位置的數據便可。雖然排序的代價比較大,可是邊際成本會很小,咱們只須要排序一次。
可是,針對動態數據,若是每次查詢中位數的時候都對數據進行排序,那效率就不高了。藉助堆這種數據結構,咱們不用排序,就能夠很是高效地找到中位數。
咱們須要維護兩個堆,一個大頂堆,一個小頂堆。大頂堆中存儲前半部分數據,小頂堆中存儲後半部分數據,並且小頂堆中的數據都大於大頂堆中的數據。這時候,大頂堆的堆頂元素也就是咱們要找的中位數。
若是是偶數狀況,那麼大頂堆就有 $\frac{n}{2}$ 個數據,小頂堆就有 $\frac{n}{2}$ 個數據;若是是奇數狀況,那麼大頂堆就有 $\frac{n}{2}+1$ 個數據,小頂堆就有 $\frac{n}{2}$ 個數據。
若是新加入的數據小於等於大頂堆的堆頂元素,咱們就將這個數據插入到大頂堆;不然,咱們就將這個數據插入到小頂堆。
這時候,就有可能會出現兩個堆中的數據個數不符合前面的約定。咱們能夠從一個堆中不停地將堆頂元素移動到另外一個堆中,來讓兩個堆中的數據個數從新知足上面的約定。
除此以外,咱們還能夠利用一樣的原理來快速求出其餘百分位的數據。假如咱們要找出接口的 99% 響應時間?
所謂 99% 的響應時間,就是對響應時間排完序後處於 99%n 位置的數據。咱們依然創建一個大頂堆和一個小頂堆,其中大頂堆中保存 99%n 的數據,而小頂堆中保存 1%*n 的數據,而後依然按照上面處理中位數的操做對兩個堆進行維護。
獲取更多精彩,請關注「seniusen」!