對於堆的數據結構的介紹,在網上搜了下,具體講的不是很多。發現比較好的一篇介紹堆的博客是http://dongxicheng.org/structure/heap/。在此感謝他。
通過對上面那篇博客的學習,然后自己也去翻了下《算法導論》里面關於堆排序(heapsort)的介紹。這樣就對堆有了更加深刻的認識,在此,我結合自己的一點點理解,主要還是基於上面那篇博客的內容(主要也是《算法導論》里的內容),也把他里面程序的一些錯誤改正,我把這篇博客寫了出來,以加深對堆的認識,並供自己日后溫習。后面我還要練習poj上幾天關於堆的題目,鏈接會在后面給出。
1.堆
堆數據結構是一種數組對象,它可以被視為一科完全二叉樹結構。它的特點是父節點的值大於(小於)兩個子節點的值(分別稱為大頂堆和小頂堆)。它常用於管理算法執行過程中的信息,應用場景包括堆排序,優先隊列等。
2. 堆的基本操作
堆是一棵完全二叉樹,高度為O(lg n),其基本操作至多與樹的高度成正比。在介紹堆的基本操作之前,先介紹幾個基本術語:
A:用於表示堆的數組,下標從1開始,一直到n
PARENT(t):節點t的父節點,即floor(t/2)
RIGHT(t):節點t的左孩子節點,即:2*t
LEFT(t):節點t的右孩子節點,即:2*t+1
HEAP_SIZE(A):堆A當前的元素數目
3.保持堆的性質 Heapify(A,n,t)
該操作主要用於維持堆的基本性質。假定以RIGHT(t)和LEFT(t)為根的子樹都已經是堆,然后調整以t為根的子樹,使之成為堆。
void Heapify(int A[],int i) { int l=LEFT(i); int r=RIGHT(i); int largest; if(l<=HEAP_SIZE(A)) largest=A[l]>A[i]?l:i; if(r<=HEAP_SIZE(A)) largest=A[r]>A[largest]?r:largest; //從i,2*i,2*i+1中找出最大的一個 if(largest!=i) //i不是最大的 { swap(A[i],A[largest]); Heapify(A,largest); //交換后,子樹有可能違反最大堆性質 } }
4.建堆 BuildHeap(A,n)
操作主要是將數組A轉化成一個大頂堆。思想是,先找到堆的最后一個非葉子節點(即為第n/2個節點),然后從該節點開始,從后往前逐個調整每個子樹,使之稱為堆,最終整個數組便是一個堆。子數組A[(n/2)+1..n]中的元素都是樹中的葉子,因此都可以看作是只含有一個元素的堆。具體的過程我覺得看《算法導論》里面的圖的話理解應該很簡單,我找不到那圖。
void BuildHeap(int A[],) { int i; for(i = HEAP_SIZE(A)/2; i>=1; i--) Heapify(A, i); }
5.堆排序算法
先用BuildHeapo將數組A[1..n]構造成一個最大堆。因為數組中最大元素在根A[1],則可以通過把它與A[n]交換來達到最終正確的位置。
void HeapSort(int A[]) { BuildHeap(A); for(i=HEAP_SIZE(A),i>1; i--) { swap(A[1],A[i]); HEAP_SIZE(A)=HEAP_SIZE(A)-1; Heapify(A,1); //交換后新的根元素可能委培了最大堆的性質 } }
6.優先隊列 priority queue
優先隊列是一種用來維護由一組元素構成的集合S的數據機構。相信大家對它都有所了解。雖然說c++里面有了priority_queue,但我們還是要了解它的一些基本構成及實現的代碼。
GETMAX:
該操作主要是獲取堆中最大的元素,同時保持堆的基本性質。堆的最大元素即為第一個元素,將其保存下來,同時將最后一個元素放到A[1]位置,之后從上往下調整A,使之成為一個堆。
void GetMaximum(int A[]) { int max = A[1]; A[1] = A[n]; HEAP_SIZE--; Heapify(A, n, 1); return max; }
INSERT:
向堆中添加一個元素t,同時保持堆的性質。算法思想是,將t放到A的最后,然后從該元素開始,自下向上調整,直至A成為一個大頂堆。
void Insert(int A[], int i) { //i為插入的值 int n=++HEAP_SIZE(A); A[n] = -99999;//小無窮 int p = n; while(p >1 && A[PARENT(p)] < i) { A[p] = A[PARENT(p)]; p = PARENT(p); } A[p]=i;
}
總結。
堆的最常見應用是堆排序,時間復雜度為O(N lg N)。如果是從小到大排序,用小頂堆;從大到小排序,用大頂堆。雖然堆排序是一個很漂亮的算法,但實際中,快排的一個好的實現往往優於堆排序。盡管這樣,對數據結構還是有着很大的用處,比如說優先隊列。
例子: 在O(n lg k)時間內,將k個排序表合並成一個排序表,n為所有有序表中元素個數。
【解析】取前100 萬個整數,構造成了一棵數組方式存儲的具有小頂堆,然后接着依次取下一個整數,如果它大於最小元素亦即堆頂元素,則將其賦予堆頂元素,然后用Heapify調整整個堆,如此下去,則最后留在堆中的100萬個整數即為所求 100萬個數字。該方法可大大節約內存。
例子:一個文件中包含了1億個隨機整數,如何快速的找到最大(小)的100萬個數字?(時間復雜度:O(n lg k))
堆是一種非常基礎但很實用的數據結構,很多復雜算法或者數據結構的基礎就是堆,因而,了解和掌握堆這種數據結構顯得尤為重要。