《演算法導論》筆記 第6章 堆排序

2021-09-11 17:59:36 字數 4712 閱讀 1933

第6章 堆排序

本章主要介紹了堆的基本知識和幾個基本操作過程

堆(二叉)堆資料結構是一種陣列物件,它可以被看作是一棵完全二叉樹。heap—size[a]即堆的大小是已知的,樹的根結點是a[1],某個i結點,它的父結點parent(i)為,左兒子left(i)和右兒子right(i)的下標可以簡單地計算出來:

parent(i) :return ⌊i / 2⌋

left(i) :return 2i

right :return 2i + 1

left過程可以通過左移一位的位運算(相當於乘2)來計算;right過程可以通過左移一位的位運算(相當於乘2)來計算,並在低位加一;parent過程可以通過右移一位的位運算(相當於除以2)來計算。

二叉堆分為兩種,分別是最大堆(大頂堆)和最小堆(小頂堆)。在最大堆中除根以外的每個結點i都有,

a[ parent(i) ] ≥ a(i),即所有的父結點必須大於等於它的左右子結點,值最大的結點就是根結點;在最小堆中除根以外的每個結點i都有,a[ parent(i) ] ≤ a[i]。即所有的父結點都小於等於它的左右子結點,根結點的值最小。

幾個基本操作過程及在排序演算法和優先順序佇列中的使用

max-heapify過程,其執行時間是ο(lgn),是保持最大堆性質的關鍵;

build-max-heap過程,其執行時間是ο(n),可以在無序的輸入陣列基礎上構造出最大堆;

heap-sort過程,執行時間是ο(nlgn),對乙個陣列原地進行排序(只有常數個元素儲存在陣列以外)

max-heap-insert,heap-extract-max,heap-increase-key,heap-maximum過程的執行時間是ο(lgn),這些操作保證了堆結構可以作為優先順序佇列使用。

保持最大堆性質(max-heapify過程)

對於某個結點i來說,以left(i)和right(i)為根的兩顆二叉樹都是最大堆,但有可能a[i]的值小於它的子女left(i)和right(i)的值,這就違反了最大堆的性質。所以 max-heapify這個操作就是用來使這種 i 結點的值「下降」,或者說讓i跟最大的子i結點交換值,確保它的值大於等於它的子結點,使以i為根的子樹成為最大堆,保持最大堆的性質。

max-heapify(a, i)

1. l <-- left(i)

2. r <-- right(i)

3. if l ≤ heap-size[a] and a[l] ≥ a[i]

4. then largest <-- l

5. else largest <-- i

6. if r ≤ heap-size[a] and a[r] ≥ a[largest]

7. then largest <-- r

8. if largest ≠ i

9. then exchange a[i] and a[largest]

10. max-heapity(a, largest)

在這個操作中,其執行時間是對a[i],a[left(i)],a[right(i)]的調整所用時間θ(1),再加上對以i的某個子結點為根的子樹遞迴呼叫max-heapify所需的時間。i結點的子樹大小至多為2n/3(最壞情況發生在最底層恰好半滿的時候),那麼max-heapify的執行時間可由下式描述:t(n) ≤ t(2n/3) + θ(1),根據主定理,

a=1, b=3/2, n^log(b)a = n ^0, 屬於情形2,t(n) = ο( n ^0 lgn ) = ο(lgn)。所以max-heapify的執行時間是

ο(lgn)。

對於 「i結點的子樹至多為2n/3」 的證明:

最壞情況發生在最底層恰好半滿,那麼假設左子樹比右子樹在對底層的結點數多,並設左子樹在最底層的結點數為k,已知所有的結點數是n,那麼我們設想右子樹在最底層也有k個結點,那麼這棵樹就是完全二叉樹了,總的結點是n+k;

再假設高為h,最底層的高度是0,那麼從下往上1-h的結點數就是(2 ^h) -1,最底層即高度為0的結點數為2 ^h,所以總的結點數n+k = (2 ^h) - 1 + 2 ^h ;

而我們知道k是左子樹最底層的結點數,那麼2k就是最底層即高度為0的所有結點數,上面算出來是2 ^h,所以,2k = 2 ^h,而n+k = (2 ^h) - 1 + 2 ^h,把2k = 2 ^h 代入得,n+k = 4k -1,n = 3k -1,k = (n+1) / 3;

完全二叉樹總結點數為n+k,那麼把根結點去掉再除以2,就可以得到左子樹結點數為(n+k-1) / 2,由上面得, (n+k-1)/2 = (4k-2)/2 = 2k-1 = 2(n+1) /3 - 1 = (2n -1)/3 < 2n/3,所以結點數為n的 i 結點的子樹大小至多為2n /3。

建堆(build-max-heap過程)

我們可以自底向上地用max-heapify來將乙個陣列a[1…n](n=length[a])變成乙個最大堆。因為子陣列 a[(⌊n/2⌋ + 1) , (⌊n/2⌋ + 2) … n]中的元素都是樹中的葉子(即最底層的元素),我們通過對這些元素的父結點呼叫max-heapify,之後對這些元素的父結點的父結點呼叫,如此類推,直到根結點,就可以實現建立最大堆了。

build-max-heap(a)

1.heapsize[a] <-- length[a]

2.for i <-- ⌊length[a]/2⌋ downto 1

3. do max-heapify(a, i)

我們知道堆的高h=⌊lgn⌋,而max-heapify的執行時間是ο(lgn),我們也可以這樣說,max-heapify作用於乙個高度為h的結點所需執行的時間是ο(h)。並且在任意高度h上,至多有 ⌈n/2 ^(h+1)⌉個結點,所以build-max-heap的執行時間為作用每乙個高度h上每乙個結點所需執行時間之和,即t(n)=σ⌈n/2 ^(h+1)⌉ ο(h) (h = 0 … lgn),據計算t(n) = ο(n),所以build-max-heap的執行時間是ο(n)。

堆排序演算法

heapsort(a)

1. build-max-heap(a)

2. for i <-- length[a] downto 2

3. do exchange a[1] <--> a[i]

4. heap-size[a] <-- heap-size[a] - 1

5. max-heapify(a, 1)

heapsort過程的時間代價為ο(nlgn),其中呼叫build-max-heap的時間是ο(n),n-1次max-heapify呼叫中每一次的時間代價是ο(lgn)。

優先順序佇列

優先順序佇列可以用堆來實現。優先順序佇列分為兩種,分別是最大優先順序佇列和最小優先順序佇列。優先順序佇列是一種用來維護由一組元素構成的集合s的資料結構,這一組元素中的每乙個都有關鍵字key。乙個最大優先順序佇列支援一下操作:

insert(s, x):把元素x插入集合s;

maximum(s):返回s中具有最大關鍵字的元素;

extract-max(s):去掉並返回s中的具有最大關鍵字的元素;

increase-key(s, x, k);將元素x的關鍵字的值增加到k,這裡k不能小於x的原關鍵字的值。

最大優先順序佇列的乙個應用是在一台分時計算機上進行作業排程。這種佇列對要執行的各作業及它們之間的相對優先關係加以記錄,當乙個作業完成或中斷時,用extract-max操作從所有等待的作業中,選擇出具有最高優先順序的作業。在任何時候,乙個新的作業都可以用insert加入到佇列中去。

最小優先順序佇列可被用在基於事件驅動的模擬器中。在這種應用中,佇列中的各項是要模擬的事件,每乙個都有乙個發生時間作為其關鍵字。時間模擬要按照各事件發生事件順序進行,所以在模擬程式的每一步都用extract-min來選擇下乙個模擬事件。當乙個新事件產生,使用insert將其放入佇列中。

heap-maximum(a)

1. return a[1]

heap-extract-max(a)

2. if heap-size[a] < 1

3. then error"heap underflow" 堆下溢了

4. max <-- a[1]

5. a[1] <-- a[heap-size[a]]

6. heap-size[a] <-- heap-size[a] - 1

7. max-heapify(a, 1)

8. return max

heap-increase-key(a, i, key)

1 if key < a[i]

2 then error"new key is smaller than current key"

3 a[i] <-- key

4 while i > 1 and a[parent(i)] < a[i]

5 do exchange a[i] <--> a[parent(i)]

6 i <-- parent(i)

max-heap-insert(a, key)

1 heap-size[a] <-- heap-size[a] + 1

2 a[heap-size[a]] <-- -∞

3 heap-increase-key(a, heap-size[a], key)

將乙個很小的值(這裡用無窮小表示)賦給a[heap-size[a]],然後用heap-increase-key可以將關鍵字大小為key的元素插入到堆中。

上面四種操作的執行時間都為ο(lgn)。

《演算法導論》 第6章堆排序

include include using namespace std 定義結構體,其中包含陣列長alength,堆長heap size。struct dui 返回堆中元素i的父結點的下標 i 2向下取整,即i進行左移一位操作。int parent int i 返回堆中元素i的左孩子的下標 i 2 ...

演算法導論第6章 堆排序

本章開始介紹了堆的基本概念,然後引入最大堆和最小堆的概念。全章採用最大堆來介紹堆的操作,兩個重要的操作是調整最大堆和建立最大堆,接著著兩個操作引進了堆排序,最後介紹了採用堆實現優先順序佇列。1 堆 堆給人的感覺是乙個二叉樹,但是其本質是一種陣列物件,因為對堆進行操作的時候將堆視為一顆完全二叉樹,樹種...

《演算法導論》筆記 第6章 6 4堆排序演算法

反覆將大根堆的根與最後乙個結點交換,堆的大小減一,對根結點執行max heapify維護堆的性質。最終 a 陣列按公升序排列。void heapsort 6.4 1 說明 heapsort 在陣列 a 5,13,2,25,7,17,20,8,4 上的操作過程。6.4 2 討論在使用如下迴圈不變式時,...