二、堆的插入、刪除操作
三、堆的建立
堆是乙個有固定順序的完全二叉樹,通常用陣列來表示。
下圖是乙個堆常用的編號方式示意圖:根節點編號為0,根節點的左子節點編號為1、右子節點編號為2,再往下是3、4、5、6……即按照從上往下、從左往右的順序編號。
在陣列中,就按照如上所述的下標進行儲存,如上圖儲存為陣列就是。
在這樣乙個陣列中,下標為i的節點的父節點的下標就是(i - 1) / 2(除法向下取整);而下標為i的節點的左子節點的下標是2 * i + 1,右子節點的下標是2 * i + 2。
示例:
第3個元素(25)的左子節點是第**2 * 3 + 1 = 7**個元素(35),
而它的右子節點是第**2 * 3 + 2 = 8**個元素(45)。
堆一般分為兩種,即最大堆和最小堆。
最大(小)堆中每個節點的父節點一定比當前節點大(小);
或者說,每個節點的左右子樹中的所有節點,一定比這個節點小(大)。
如下圖所示則是乙個最大堆:
為了保證我們在插入、刪除任意節點後,最大(小)堆仍然能保證它的最大(小)特性,需要研究一套既定的、有規律可循的操作來完成插入和刪除。
在講堆的插入與刪除之前,要明確堆的幾個基本操作,而插入和刪除全都是由這些基本操作組成的:
shiftup()
:如果乙個節點比它的父節點大(最大堆)或者小(最小堆),那麼需要將它同父節點交換位置。這樣是這個節點在陣列的位置上公升。
shiftdown()
:如果乙個節點比它的子節點小(最大堆)或者大(最小堆),那麼需要將它向下移動。這個操作也稱作「堆化(heapify)」。
shiftup 或者 shiftdown 是乙個遞迴的過程,所以它的時間複雜度是 o(log n)。
首先,在堆的第乙個空白位置處插入這個新元素,然後遞迴呼叫上述的shiftup()
操作,直到它的父節點比它大(最大堆)或小(最小堆)。
示例:
我們通過乙個插入例子來看看插入操作的細節。我們將數字16
插入到這個堆中:
第一步是將新的元素插入到堆的第乙個空白位置處。則堆變成:
不幸運的是,現在堆不滿足堆的屬性,因為 2 在 16 的上面,我們需要將大的數字在上面(這是乙個最大堆),為了恢復堆屬性,我們需要交換16
和2
。
現在還沒有完成,因為 10 也比 16 小。我們繼續交換我們的插入元素和它的父節點,直到它的父節點比它大或者我們到達樹的頂部。這就是所謂的shiftup()
,每一次插入操作後都需要進行。它將乙個太大或者太小的數字「浮起」到樹的頂部。
最後我們得到的堆:
現在每乙個父節點都比它的子節點大,滿足了最大堆的屬性。
為了將這個節點刪除後的空位填補上,首先要將本堆中最後乙個元素的值(假設為value)移動到此位置,然後在被刪位置處,用此位置當前的值value和此處的父節點、子節點去比較,如果它與父節點的關係破壞了最大(小)堆,則遞迴呼叫shiftup()
來修復;如果它與子節點的關係破壞了最大(小)堆,則遞迴呼叫shiftdown()
來修復。
示例:
我們將這個樹中的 (10) 刪除:
現在頂部有乙個空的節點,怎麼處理?
當插入節點的時候,我們將新的值返給陣列的尾部。現在我們來做相反的事情:我們取出陣列中的最後乙個元素,將它放到樹的頂部,然後再修復堆屬性。
現在被刪除位置處的元素值為1,由於它沒有父節點,所以我們只把它與子節點進行比較,看是否違反了最大堆的屬性:現在有兩個數字( 7 和 2)可用於交換。我們選擇這兩者中的較大者稱為最大值放在樹的頂部,所以交換 7 和 1,現在樹變成了:
繼續堆化直到該節點沒有任何子節點或者它比兩個子節點都要大為止。對於我們的堆,我們只需要再有一次交換就恢復了堆屬性:
如上,就完成了堆的刪除操作。
堆的插入和刪除操作都不是最難的,最難的是堆的建立。這裡我們以最大堆為例,講講如何由乙個無序陣列,建立乙個最大堆。
建立最大(小)堆的原理是,只要保證了某節點的左右子樹是最大(小)堆,那麼只要對此節點不斷做shifdown()
操作,那麼最終就能把以此節點為根節點的樹變成最大(小)堆。也就是說,把以某節點為根節點的子樹,調整成乙個最大(小)堆。
那麼,只要在一棵二叉樹中,從最後乙個非葉子節點開始,從下往上地對每個節點都做此操作,就能把整棵樹調整成乙個最大(小)堆。
為了方便計算,最大堆並不用鍊錶的方式來儲存,而是用陣列的方式,這樣,我們就可以知道某節點n,它的父節點是**(n - 1) / 2**,左孩子是2 x n + 1,右孩子是2 x n + 2。
這樣一來,我們就可以用如下程式構建乙個基於陣列實現的最大堆了:
void
shiftdown
(vector<
int>
& nums,
int k)
}void
buildmaxheap
(vector<
int>
& nums)
// 用nums陣列表示二叉樹
}
資料結構 單鏈表建立 插入 刪除
include include include include include using namespace std typedef struct lnode linklist void initlist linklist head 初始化鍊錶 void createlista linklist ...
資料結構 堆(建立,插入,刪除,排序)
關於二次總結是否有必要,我覺得是有的,參考學習別人的知識,內化的過程是一次總結的過程。每次參考同乙個人的思維軌跡,確實能培養思維。該篇筆記整理理由 在pat advanced level的heap path一題,參考了關於堆的正序遍歷的映象。於是參考一下堆相關筆記。void createheap v...
資料結構 堆的建立,銷毀,插入,刪除
堆在物理意義上是乙個陣列。堆在邏輯意義上是乙個完全二叉樹 大堆 父 子 小堆 父 子 宣告堆的一些基本函式 介面 你別忘了測試單元!define crt secure no warnings 1 pragma once include include include typedef int hpda...