對頂堆詳解
我們知道,堆是一種極有用的數據結構。它能在短時間內將數據維護成單調遞增/遞減的序列。但是這種“朴素堆”對於問題求解起到的效果畢竟是有限的。所以我們在朴素堆的基礎上,進行深入思考和適當變形,使之能解決一些其他的用朴素堆解決不了的問題,並使思路變得簡潔有效。
這篇隨筆就堆中的一個分支——對頂堆進行講解,讀者在閱讀前應該掌握堆的基本概念,以便於更好地理解對頂堆。
如果把堆中的大根堆想成一個上寬下窄的三角形,把小根堆想成一個上窄下寬的三角形,那么對頂堆就可以具體地被想象成一個“陀螺”或者一個“沙漏”,通過這兩個堆的上下組合,我們可以把一組數據分別加入到對頂堆中的大根堆和小根堆,以維護我們不同的需要。
那么對頂堆是干嘛用的呢?
舉一個例題:
給定\(N\)個數字,求其前\(i\)個元素中第\(K\)小的那個元素。
我們很容易想到用堆來維護這個單調遞增的序列,如果使用數組實現的話,我們直接輸入數組下標為\(K\)的元素即可。但我們使用的是堆,它的實現方式——優先隊列是不支持任意點訪問的,那么我們就無法進行單點查詢。引申對頂堆的概念,我們可以這樣解決問題:
優先隊列雖然不支持任意點訪問,但可以用\(O(1)\)的時間查詢出堆頂元素,也就是說,我們只能通過維護對頂堆中兩個堆的堆頂元素來進行單調性的維護。怎么辦呢?
原理很簡單:根據數學中不等式的傳遞原理,假如一個集合\(A\)中的最小元素比另一個集合\(B\)中的最大元素還要大,那么就可以斷定:\(A\)中的所有元素都比\(B\)中元素大。所以,我們把小根堆“放在”大根堆“上面”,如果小根堆的堆頂元素比大根堆的堆頂元素大,那么小根堆的所有元素要比大根堆的所有元素大。
回到這個問題:我們要求第\(K\)小的元素,那么我們把大根堆的元素個數限制成\(K\)個,前\(K\)個元素入隊之后,每個元素在入隊之前先與堆頂元素比較,如果比堆頂元素大,就加入小根堆,如果沒有的話,把大根堆的堆頂彈出,將新元素加入大根堆。這樣就維護出一個對頂堆。它的作用在於找出第\(K\)小的元素。
同理,對頂堆還可以用於解決其他“第\(K\)小”的變形問題:比如求前\(i\)個元素的中位數等。