4、二叉堆——前置知識複習1
一、堆簡介
堆是一種資料結構,要求有效地完成兩種基本操作:insert(插入元素)和pop(刪除最值),其中最值可能是最大值、最小值或者其它複雜的最值。在這兩種基本操作中,必然包括top(尋找最值),但基本的堆不要求其他複雜的操作,比如合併,或者尋找任意元素。如果能夠高效地尋找任意元素,則還可能做這些操作:decrese(減小某點權值)、increase(增加某點權值)、delete(刪除某點)。
堆的這些操作對作業系統有重要意義:作業系統一般在排程時為每個任務都會分配乙個優先順序,當任務衝突時,優先順序最高的將會被優先執行。可見insert即新增新任務,pop即任務完成,decrease、increase即對某個任務的優先順序調整(比如降低某些占用太多資源的任務),delete即中止某項任務。當然,對於作業系統來講,建立堆也是必不可少的。
堆的實現有很多種,我們今天來複習最基本(但是也非常高效)的二叉堆。
二、二叉堆
二叉堆是一棵樹,它的遞迴定義如下:
1、二叉堆是一棵完全二叉樹;
2、二叉堆根節點的值與它左、右子節點的值分別都滿足同乙個序(例如都小於、都大於)(沒有子節點時預設滿足);
3、二叉堆的左右兩棵子樹也都是二叉堆。
(如果不清楚完全二叉樹是什麼:作為二叉樹,除了最後一層有可能例外,其餘層都是滿的)
完全二叉樹有乙個很好的性質:如果經過恰當的編號(根節點是1,之後向下每層從左向右編號),那麼,節點$n$的父結點就是$n/2$(向下取整),左子結點就是$2n$,右子節點則是$2n+1$。這表明,我們不需要一棵真正的樹,而只需要用陣列來模擬就好;只要我們事先能控制好堆的最大規模。
二叉堆因此具有兩個主體性質:結構性(是完全二叉樹)、堆序性(最值在最上面)。我們下面用小根堆舉例,介紹二叉堆怎麼做基本操作:
insert:空堆是平凡的。如果不是空堆,則新入的節點應該放在某個位置。為了維持堆的結構性,我們首先把它放在最後一層的末尾(即陣列的末尾),然後通過交換元素來維持堆序。具體的方法稱為向上過濾(上濾):對於這個新結點$x$,如果它的值比它的父節點還小,則交換它與它的父親。很顯然這種交換是合適的,因為它的(可能的)兄弟必然大於等於它的父親,也就大於它。不斷進行,直到不需要交換為止。從二叉堆的結構我們知道,這個操作最壞是$o(\log n)$的。但是有文章指出這個操作的平均複雜度實際上能夠達到$o(1)$,但我目前無力證明。
這一操作可以簡化,由於交換是乙個比較浪費的操作,我們用空位置來代替:首先在末尾擴充套件乙個空位置,檢查這個空位置能不能放置新的節點$x$,如果能,則放入;如果不能,則將父節點移到這個空位置裡,空位置則轉移到了父節點的原本位置,重複直到新結點進入了空位置為止。insert的某種實現如下:
1insertvoid insert(int
v) else
else15}
16 val[empt] =v;17}
18return
;19 }
pop:空堆仍然是平凡的,否則這樣做:將堆頂刪除,然後把最後乙個元素放到堆頂,之後向下過濾(下濾):首先比較該節點與左右子節點的值,如果都是滿足堆序的,就停止下濾;否則,將它與左右子節點中較小的交換,然後重複下濾直到停止或到葉子節點為止。
與上濾一樣,下濾也可以簡化,只要把應該被下濾的節點變成空位置,觀察這個節點能不能放到空位置,若不能,將空位置的左右子節點的較小者放至空位置,空位置下移即可。pop操作(包括下濾操作)的某種實現如下:
1popbuildheap:從$n$個元素的陣列開始建立乙個堆,void downadjust(int
node) else
else28}
29 } while (1
);30
return;31
}3233void
pop()
最簡單的想法就是進行$n$次插入。它的最壞可能當然是$o(n \log n)$,不過鑑於有文章提到insert的平均複雜度是$o(1)$,也可以期待對隨機數組這樣建堆達到$o(n)$。不過,建堆明顯有乙個更好的方法:
將陣列直接建為完全二叉樹(實際上不需要操作),然後從第乙個有子節點的元素開始(明顯是編號最大的葉子節點的父節點,也就是$n/2$),每個節點都下濾,就完成建堆。
看上去這個操作也會是$o(n \log n)$的,但是我們有如下定理告訴我們它實際上是$o(n)$:
定理:一棵有$2^ - 1$個節點的滿二叉樹,每個節點的高度和是$s = 2^ - 1 - (h + 1)$。
證明:高度是這樣定義的:葉子節點的高度是$0$;每個節點的高度是它的子節點的高度$+1$。這樣可以知道,定理描述的二叉樹中,根節點的高度是$h$,第二層的兩個節點的高度是$h-1$,以此類推。這樣,我們應該有如下和式:
$s = \sum_^ 2^i (h-i)$
為了求這個和,兩邊同乘$2$
$2s = \sum_^ 2^i (h-i+1)$
上下相減就有了
$s = -h + \sum_^ 2^i + 2^h$
$=2^-2-h$
$=2^-1 - (h+1)$
由於剛剛的下濾建堆操作將不會進行超過$o(s)$次比較和賦值,而二叉堆是乙個完全二叉樹,如果$n=2^h+m$,顯然有$2^h-1-h \leq s \leq 2^-1-(h+1)$,因此定理告訴我們,這種建堆的操作是$o(n)$的,可見十分優越。它的某種實現如下:
1buildheapvoid buildheap(int a, int
size)
12return
;13 }
這就是最簡單的堆——二叉堆的簡單介紹。
資料結構與演算法 二叉堆
核心操作是sift up,和sift down,其他所有操作都是建立在這兩個核心操作的基礎上的,事實上所有的堆結構都可以使用這兩個操作。const int maxsize 10001 int size 0 int min heap maxsize 0號單元不使用,因為如果使用0單元,則k 2無法找到...
資料結構與演算法 二叉堆
二叉堆本質上是一種完全的二叉樹,它分為兩個型別。1.最大堆 2.最小堆 什麼是最大堆?最大堆的任何乙個父節點的值,都大於或等於它左 右孩子節點的值。什麼是最小堆?最小堆的任何乙個父節點的值,都小於或等於它左 右孩子節點的值。二叉堆的根節點叫做堆頂。最大堆和最小堆的特點決定了 最大堆的堆頂是整個堆中的...
資料結構與演算法 01 二叉堆
保證父節點優先順序始終高於子節點。插入 判斷父子節點的優先順序 刪除 查詢 直接返回根節點的值 tip 根節點下標從1開始 include define max 1000000 using namespace std int heap max 10 int n,cnt 0 cnt為堆中元素個數 sh...