算法導論第六章 堆排序


原文連接誒:http://blog.csdn.net/v_JULY_v/article/details/6198644

基本算是把算法導論的內容原版照抄。

一、堆排序算法的基本特性
時間複雜度:O(nlgn)...
//等同於歸併排序
最壞:O(nlgn)
空間複雜度:O(1).
不穩定。

二、堆與最大堆的建立
要介紹堆排序算法,咱們得先從介紹堆開始,然後到建立最大堆,最後纔講到堆排序算法。

2.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)。
可以讓堆作爲最小優先隊列使用。 

 

2.2.1、保持堆的性質(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的演示過程下圖是把4調整到最底層,一趟操作,但摸索的時間爲LogN):

 

     由上圖,我們很容易看出,初始構造出一最大堆之後,在元素A[i],即16,大於它的倆個子結點4、10,滿足最大堆性質。所以,i下調指向着4,小於,左子14,所以,調用MAX-HEAPIFY,4與其子,14交換位置。但4處在了14原來的位置之後,4小於其右子8,又違反了最大堆的性質,所以再遞歸調用MAX-HEAPIFY,將4與8,交換位置。於是,滿足了最大堆性質,程序結束。

2.2.2、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節,這裏,略過。

 

2.3.1、建堆(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節。
下圖,是一個此過程的例子下圖是不斷的調用MAX-HEAPIFY操作,把所有的違反堆性質的數都要調整,共N趟操作,然,摸索時間最終精確爲O(N)):

 

2.3.2、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)
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)。

添加一份源碼實現:


#include <iostream>
using namespace std;
 
// 輸出當前堆的排序狀況
void PrintArray(int data[], int size)
{
    for (int i=1; i<=size; ++i)
        cout <<data[i]<<"  ";
    cout<<endl;
}
 
// 堆化,保持堆的性質
// MaxHeapify讓a[i]在最大堆中"下降",
// 使以i爲根的子樹成爲最大堆
void MaxHeapify(int *a, int i, int size)
{
	int lt = 2*i, rt = 2*i+1;
	int largest;
	if(lt <= size && a[lt] > a[i])
		largest = lt;
	else
		largest = i;
	if(rt <= size && a[rt] > a[largest])
		largest = rt;
	if(largest != i)
	{
		int temp = a[i];
		a[i] = a[largest];
		a[largest] = temp;
		MaxHeapify(a, largest, size);
	}
}
 
// 建堆
// 自底而上地調用MaxHeapify來將一個數組a[1..size]變成一個最大堆
//
void BuildMaxHeap(int *a, int size)
{
	for(int i=size/2; i>=1; --i)
		MaxHeapify(a, i, size);
}
 
// 堆排序
// 初始調用BuildMaxHeap將a[1..size]變成最大堆
// 因爲數組最大元素在a[1],則可以通過將a[1]與a[size]互換達到正確位置
// 現在新的根元素破壞了最大堆的性質,所以調用MaxHeapify調整,
// 使a[1..size-1]成爲最大堆,a[1]又是a[1..size-1]中的最大元素,
// 將a[1]與a[size-1]互換達到正確位置。
// 反覆調用Heapify,使整個數組成從小到大排序。
// 注意: 交換隻是破壞了以a[1]爲根的二叉樹最大堆性質,它的左右子二叉樹還是具備最大堆性質。
//        這也是爲何在BuildMaxHeap時需要遍歷size/2到1的結點才能構成最大堆,而這裏只需要堆化a[1]即可。
void HeapSort(int *a, int size)
{
	BuildMaxHeap(a, size);
	PrintArray(a, size);
 
	int len = size;
	for(int i=size; i>=2; --i)
	{
		int temp = a[1];
		a[1] = a[i];
		a[i] = temp;
		len--;
		MaxHeapify(a, 1, len);
		cout << "中間過程:";
		PrintArray(a, size);
	}
 
}
 
int main()
{
	int size;
	int arr[100];
	cout << "Input the num of elements:\n";
	cin >> size;
	cout << "Input the elements:\n";
	for(int i=1; i<=size; ++i)
		cin >> arr[i];
	cout << endl;
    HeapSort(arr, size);
	cout << "最後結果:";
    PrintArray(arr, size);
}


發佈了23 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章