1. 程式人生 > >數據結構與算法之美-堆的應用

數據結構與算法之美-堆的應用

並且 效率 先進先出 應該 成本 最短 特性 對比 查詢

堆的應用一:優先級隊列

優先級隊列首先應該是一個隊列。隊列最大的特性就是先進先出。但是在優先級隊列中,出隊順序不是先進先出,而是按照優先級來,優先級最高的,最先出隊。

用堆來實現優先級隊列是最直接、最高效的。這是因為,堆和優先級隊列非常相似。一個堆就可以看作一個優先級隊列。很多時候,它們只是概念上的區分而已。

往優先級隊列中插入一個元素,就相當於往堆中插入一個元素。從優先級隊列中取出優先級最高的元素,就相當於取出堆頂元素。

很多數據結構和算法都要依賴它。比如,赫夫曼編碼、圖的最短路徑、最小生成樹算法等等。


合並有序小文件

假設我們有 100 個小文件,每個文件的大小都一樣,每個文件中存儲的都是有序的字符串。

我們希望將這些100 個小文件合並成一個有序的大文件。這裏就會用到優先級隊列。

我們從這 100 個文件中,各取第一個字符串,入到小頂堆中,那堆頂的元素,也就是優先級隊列隊首的元素,就是最小的字符串。我們將這個字符串放入到大文件中,並將其從堆中刪除。然後再從小文件中取出下一個字符串,放入到堆中。循環這個過程,就可以將 100 個小文件中的數據依次放入到大文件中。


高性能定時器

假設定時器中維護了很多定時任務,每個任務都設定了一個要觸發執行的時間點。

定時器每過一個很小的單位時間,就掃描一遍任務,看是否有任務到達設定的執行時間。

這樣的做法比較低效,任務的時間離當前時間可能還有很久,很多次掃描其實都是徒勞的。每次都要掃描整個任務列表,比較耗時。

使用優先級隊列來,我們按照任務設定的執行時間,將這些任務存儲在優先級隊列中,也小頂堆的堆頂存儲的是最先執行的任務。

拿隊首任務的執行時間點,與當前時間點相減,得到一個時間間隔T。這個時間間隔T就是指從當前時間開始需要等待多久才會有第一個任務需要被執行。

這樣定時器就可以設定在 T 秒之後再來執行任務。從當前時間點到(T-1)秒這段時間裏,定時器都不需要做任何事情。


堆的應用二:利用堆求 Top K

求Top K的問題可以抽象成兩類。一類是針對靜態數據集合,也就是說數據集合事先確定,不會再變。另一類是針對動態數據集合,也就是說數據集合事先並不確定,有數據動態地加入到集合中。

靜態數據

維護一個大小為K的小頂堆,順序遍歷數組,從數組中取出數據與堆頂元素比較。

如果比堆頂元素大,我們就把堆頂元素刪除,並且將這個元素插入到堆中。如果比堆頂元素小,則不做處理,繼續遍歷數組。

這樣等數組中的數據都遍歷完之後,堆的數據就是前 K 大數據了。

動態數據

一個數據集合中有兩個操作,一個是添加數據,另一個詢問當前的前K 大數據。如果每次詢問前K大的數據,我們都基於當前的數據重新計算的話就浪費了性能。

可以一直都維護一個K大小的小頂堆,當有數據被添加到集合中時,就拿它與堆頂的元素對比。

如果比堆頂元素大就把堆頂元素刪除,並且將這個元素插入到堆中。如果比堆頂元素小,則不做處理。

這樣無論任何時候需要查詢當前的前 K 大數據,都可以立刻返回。


堆的應用三:利用堆求中位數

如果數據的個數是奇數,那第n/2+1個數據就是中位數。如果數據的個數是偶數的話,第n/2個和n/2+1個這兩個數據就是中位數。可以隨意取一個作為中位數,如第n/2個。

靜態數據

中位數是固定的,我們可以先排序,第n/2個數據就是中位數。

每次詢問中位數的時候,直接返回這個固定的值。排序的代價比較大,但是邊際成本很小。

動態數據

中位數在不停地變動,靜態數據的方法每次詢問中位數的時候,都要先進行排序的話效率不會高。

借助堆這種數據結構,不用排序就可以非常高效地實現求中位數。

新加入的數據小於等於大頂堆的堆頂元素,就將這個新數據插入到大頂堆。新加入的數據大於等於小頂堆的堆頂元素,我們就將這個新數據插入到小頂堆。

這個時候可能出現兩個堆中的數據個數不符合前面約定的情況,這時可以從一個堆中不停地將堆頂元素移動到另一個堆,來讓兩個堆中的數據滿足上面的約定。

利用兩個堆,一個大頂堆一個小頂堆就可以實現在動態數據集合中求中位數的操作。

數據結構與算法之美-堆的應用