最大堆,最小堆類似,以下以最小堆為例進行講解。
最小堆是滿足以下條件的資料結構:
它是一棵完全二叉樹
所有父節點的值小於或等於兩個子節點的值
除了最後一層之外的其他每一層都被完全填充,並且所有結點都保持向左對齊。
對於topk問題,解決方法有很多:
方法一:對源資料中所有資料進行排序,取出前k個資料,就是topk。
但是當資料量很大時,只需要k個最大的數,整體排序很耗時,效率不高。
方法二:維護乙個k長度的陣列a,先讀取源資料中的前k個放入陣列,對該陣列進行公升序排序,再依次讀取源資料第k個以後的資料,和陣列中最小的元素(a[0])比較,如果小於a[0]直接pass,大於的話,就丟棄最小的元素a[0],利用二分法找到其位置,然後該位置前的陣列元素整體向前移位,直到源資料讀取結束。
這比方法一效率會有很大的提高,但是當k的值較大的時候,長度為k的資料整體移位,也是非常耗時的。
對於這種問題,效率比較高的解決方法是使用最小堆。
最小堆(小根堆)是一種資料結構,它首先是一棵完全二叉樹,並且,它所有父節點的值小於或等於兩個子節點的值。
最小堆的儲存結構(物理結構)實際上是乙個陣列。如下圖:
堆有幾個重要操作:
buildheap:將普通陣列轉換成堆,轉換完成後,陣列就符合堆的特性:所有父節點的值小於或等於兩個子節點的值。
heapify(int i):當元素i的左右子樹都是小根堆時,通過heapify讓i元素下降到適當的位置,以符合堆的性質。
回到上面的取topk問題上,用最小堆的解決方法就是:先去源資料中的k個元素放到乙個長度為k的陣列中去,再把陣列轉換成最小堆。再依次取源資料中的k個之後的資料和堆的根節點(陣列的第乙個元素)比較,根據最小堆的性質,根節點一定是堆中最小的元素,如果小於它,則直接pass,大於的話,就替換掉跟元素,並對根元素進行heapify,直到源資料遍歷結束。
最小堆的實現:
public
class
minheap
// 將陣列轉換成最小堆
private
void
buildheap()
}private
void
heapify
(int i)
// 獲取右結點的陣列下標
private
intright
(int i)
// 獲取左結點的陣列下標
private
intleft
(int i)
// 交換元素位置
private
void
swap
(int i,
int j)
// 獲取對中的最小的元素,根元素
public
intgetroot()
// 替換根元素,並重新heapify
public
void
setroot
(int root)
}
利用最小堆獲取topk:
public
class
topk
;// 獲取top5
int[
] top5 =
topk
(data,5)
;for
(int i=
0;i<
5;i++)}
// 從data陣列中獲取最大的k個數
private
static
int[
]topk
(int
data,
int k)
// 轉換成最小堆
minheap heap =
newminheap
(topk)
;// 從k開始,遍歷data
for(
int i= k;i
return topk;
}}
前面在介紹最小堆解決topk問題的時候,已經涉及到建堆、新增元素的過程,接下來介紹最小堆的刪除過程。
操作原理是:當刪除節點的數值時,原來的位置就會出現乙個孔,填充這個孔的方法就是,
把最後的葉子的值賦給該孔並下調到合適位置,最後把該葉子刪除。
如圖中要刪除72,先用堆中最後乙個元素來35替換72,再將35下沉到合適位置,最後將葉子節點刪除。
「結點下沉」
最大堆 最小堆
堆是一種經過排序的完全二叉樹,其中任一非終端節點的資料值均不大於 或不小於 其左孩子和右孩子節點的值。最大堆和最小堆是 二叉堆的兩種形式。最大堆 根結點的鍵值是所有堆結點鍵值中最大者。最小堆 根結點的鍵值是所有堆結點鍵值中最小者。而最大 最小堆集結了最大堆和最小堆的優點,這也是其名字的由來。最大 最...
最大堆 最小堆
堆的定義是 n個元素的序列,當且僅當滿足如下關係時被成為堆 1 ki k2i 且 ki k2i 1 或 2 ki k2i 且 ki k2i 1 i 1,2,n 2 當滿足 1 時,為最小堆,當滿足 2 時,為最大堆。若將此序列對應的一維陣列堪稱是乙個完全二叉樹,則2i和2i 1個節點分別是節點i的左...
最大堆 最小堆
堆的定義是 n個元素的序列,當且僅當滿足如下關係時被成為堆 1 ki k2i 且 ki k2i 1 或 2 ki k2i 且 ki k2i 1 i 1,2,n 2 當滿足 1 時,為最小堆,當滿足 2 時,為最大堆。若將此序列對應的一維陣列堪稱是乙個完全二叉樹,則2i和2i 1個節點分別是節點i的左...