精通八大排序演算法系列:二、堆排序演算法
作者:July 、二零一一年二月二十日
本文參考:Introduction To Algorithms,second edition。
-------------------
此精通排序演算法系列,前一節,已講過了一、快速排序演算法,其中,快速排序每一趟比較用時O(n),要進行lgn次比較,才最終完成整個排序。所以快排的複雜度才為O(n*lgn)。而本節,我們要講的是堆排序演算法。據我所知,要真正徹底認識一個演算法,最好是去尋找此演算法的原發明者的論文或相關文獻。
ok,此節,咱們開始吧。
一、堆排序演算法的基本特性
時間複雜度:O(nlgn)...
//等同于歸並排序
最壞:O(nlgn)
空間複雜度:O(1).
不穩定。
二、堆與最大堆的建立
要介紹堆排序演算法,咱們得先從介紹堆開始,然後到建立最大堆,最後才講到堆排序演算法。
堆的介紹
如,
a),就是一個堆,它可以被視為一棵完全二叉樹。
每個堆對應於一個數組b),假設一個堆的數組A,
我們用length[A]表述數組中的元素個數,heap-size[A]表示本身存放在A中的堆的元素個數。
當然,就有,heap-size[A]<=length[A]。
樹的根為A[1],i表示某一結點的下標,
則父結點為PARENT(i),左兒子LEFT[i],右兒子RIGHT[i]的關係如下:
PARENT(i)
return |_i/2_|
LEFT(i)
return 2i
RIGHT(i)
return 2i + 1
二元堆積根據根結點與其子結點的大小比較關係,分為最大堆和最小堆。
最大堆:
根以外的每個結點i都不大於其根結點,即根為最大元素,在頂端,有
A[PARENT(i)] ≥ A[i] ,
最小堆:
根以外的每個結點i都不小於其根結點,即根為最小元素,在頂端,有
A[PARENT(i)] ≤ A[i] .
在本節的堆排序演算法中,我們採用的是最大堆;最小堆,通常在構造最小優先隊列時使用。
由前面,可知,堆可以看成一棵樹,所以,堆的高度,即為樹的高度,O(lgn)。
所以,一般的操作,已耗用時間都是為O(lgn)。
具體,如下:
The MAX-HEAPIFY:O(lgn) 這是保持最大堆的關鍵.
The BUILD-MAX-HEAP:線性時間。在無序輸入數組基礎上構造最大堆。
The HEAPSORT:O(nlgn) time, 堆排序演算法是對一個數組原地進行排序.
The MAX-HEAP-INSERT, HEAP-EXTRACT-MAX, HEAP-INCREASE-KEY, HEAP-MAXIMUM:O(lgn)。
可以讓堆作為最小優先隊列使用。
保持堆的性質(O(lgn))
為了保持最大堆的性質,我們運用MAX-HEAPIFY操作,遞迴調用此操作,使i為根的子樹成為最大堆。
MAX-HEAPIFY演算法,如下所示:
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] <-> A[largest]
10 MAX-HEAPIFY(A, largest)
如上,首先第一步,在對應的數組元素A[i], 左孩子A[LEFT(i)], 和右孩子A[RIGHT(i)]中找到最大的那一個,將其下標儲存在largest中。如果A[i]已經就是最大的元素,則程式直接結束。否則,i的某個子結點為最大的元素,將其,即A[largest]與A[i]交換,從而使i及其子女都能滿足最大堆性質。下標largest所指的元素變成了A[i]的值,會違反最大堆性質,所以對largest所指元素調用MAX-HEAPIFY。如下,是此MAX-HEAPIFY的示範過程:
由,我們很容易看出,初始構造出一最大堆之後,在元素A[i],即16,大於它的倆個子結點4、10,滿足最大堆性質。所以,i下調指向著4,小於,左子14,所以,調用MAX-HEAPIFY,4與其子,14交換位置。但4處在了14原來的位置之後,4小於其右子8,又違反了最大堆的性質,所以再遞迴調用MAX-HEAPIFY,將4與8,交換位置。於是,滿足了最大堆性質,程式結束。
MAX-HEAPIFY的已耗用時間
MAX-HEAPIFY作用在一棵以結點i為根的、大小為n的子樹上時,其已耗用時間為調整元素A[i]、A[LEFT(i)],A[RIGHT(i)]的關係時所用時間為O(1),再加上,對以i的某個子結點為根的子樹調用MAX-HEAPIFY所需的時間,且i結點的子樹大小至多為2n/3,所以,MAX-HEAPIFY的已耗用時間為
T (n) ≤ T(2n/3) + Θ(1).
我們,可以證得此式子的遞迴解為T(n)=O(lgn)。具體證法,可參考演算法導論第6章之6.2節,這裡,略過。
建堆(O(N))
BUILD-MAX-HEAP(A)
1 heap-size[A] ← length[A]
2 for i ← |_length[A]/2_| downto 1
3 do MAX-HEAPIFY(A, i) //建堆,怎麼建列?原來就是不斷的調用MAX-HEAPIFY(A, i)來建立最大堆。
BUILD-MAX-HEAP通過對每一個其它結點,都調用一次MAX-HEAPIFY,
來建立一個與數組A[1...n]相對應的最大堆。A[(|_n/2_|+1) ‥ n]中的元素都是樹中的葉子。
因此,自然而然,每個結點,都可以看作一個只含一個元素的堆。
關於此過程BUILD-MAX-HEAP(A)的正確性,可參考演算法導論 第6章之6.3節。
,是一個此過程的例子:
BUILD-MAX-HEAP的已耗用時間
因為每次調用MAX-HEAPPIFY的時間為O(lgn),而共有O(n)次調用,所以BUILD-MAX-HEAP的簡單上界為O(nlgn)。演算法導論一書提到,儘管這個時間界是對的,但從漸進意義上,還不夠精確。
那麼,更精確的時間界,是多少列?
由於,MAX-HEAPIFY在樹中不同高度的結點處啟動並執行時間不同,且大部分結點的高度都比較小,
而我們知道,一n個元素的堆的高度為|_lgn_|(向下取整),且在任意高度h上,至多有|-n/2^h+1-|(向上取整)個結點。
因此,MAX-HEAPIFY作用在高度為h的結點上的時間為O(h),所以,BUILD-MAX-HEAP的上界為:O(n)。具體推導過程,略。
三、堆排序演算法
所謂的堆排序,就是調用上述倆個過程:一個建堆的操作、BUILD-MAX-HEAP,一個保持最大堆的操作、MAX-HEAPIFY。詳細演算法如下:
HEAPSORT(A) //n-1次調用MAX-HEAPIFY,所以,O(n*lgn)
1 BUILD-MAX-HEAP(A) //建最大堆,O(n)
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) //保持堆的性質,O(lgn)
如上,即是堆排序演算法的完整表述。下面,再貼一下上述堆排序演算法中的倆個建堆、與保持最大堆操作:
BUILD-MAX-HEAP(A) //建堆
1 heap-size[A] ← length[A]
2 for i ← |_length[A]/2_| downto 1
3 do MAX-HEAPIFY(A, 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] <-> A[largest]
10 MAX-HEAPIFY(A, largest)
以下是,堆排序演算法的示範過程(通過,頂端最大的元素與最後一個元素不斷的交換,交換後又不斷的調用MAX-HEAPIFY以重新維持最大堆的性質,最後,一個一個的,從大到小的,把堆中的所有元素都清理掉,也就形成了一個有序的序列。這就是堆排序的全部過程。):
中,a->b,b->c,....之間,都有一個頂端最大元素與最小元素交換後,調用MAX-HEAPIFY的過程,我們知道,此MAX-HEAPIFY的已耗用時間為O(lgn),而要完成整個堆排序的過程,共要經過O(n)次這樣的MAX-HEAPIFY操作。所以,才有堆排序演算法的已耗用時間為O(n*lgn)。
完。
本人July對本部落格所有任何文章、內容和資料享有著作權。
轉載務必註明作者本人及出處,並通知本人。二零一一年二月二十一日。