程式參考了clrs的《演算法導論》,第六章 heap sort。
由於最大堆和最小堆是對稱的,下文都說最大堆。
1 堆的定義
最大堆就是這樣一種樹,每個節點而言,如果它有兒子,那麼此節點比它的兒子都大。
需要注意的有兩點。
首先,堆幾乎是一種完全二叉樹,也可能是不完全的,下面第4部分中的圖一。
其次,如果要用陣列array來表示堆。那麼必需兩個元素,陣列名和堆的大小heapsize。具體方式接下來解釋。
第三,本文為了實現方便,忽略了array[0]。比如,如果要排序10個數,本文採取的方式是建立array[11],用array[1], array[2], … ,array[10]來表示這10個數。
2 堆排序的基本方法
堆排序的方法是
step 1把輸入的陣列改造成乙個最大堆(使用max_heapify( )函式)
step 2 交換堆頂(最大元素)和堆底的最後乙個元素
step 3 堆的大小heapsize減一
step 4 對剩下的元素重複step1 ~ step3的過程
3 建立堆的核心函式max_heapify
max_heapify是進行堆的建立和進行堆排序的核心函式。
可以遞迴和非遞迴兩種方式來進行實現。
先說遞迴的方式,
max_heapify這個函式的輸入是陣列,array,下標index。
使用這個函式的前提條件是,由array[index]的左子樹和右子樹都已經是最大堆了。
但是array[index]可能並不比它的左兒子大,或者並不比它的右兒子大。此時需要調整array[index]和它的左兒子,右兒子的位置,使得array[index]和它的左兒子節點,右兒子節點,這3個節點重組為最大堆,也就說,array[index]比左兒子和右兒子中最大的那個兒子要大。
經過這次調整以後,array[index]和array[index]的左兒子,array[index]的右兒子構成了最大堆。但是,觀察array[index]的左子樹,左子樹可能並不是乙個最大堆了。於是,對左子樹進行調整,遞迴地進行剛才的調整過程。或者,右子樹可能不是乙個最大堆了,同理應該對右子樹進行調整。
再說非遞迴的方式。
如果非遞迴的進行,則從root到葉節點的方向,依次地進行每個節點和它的兩個兒子的比較,不斷地進行當前節點和左兒子(或者右兒子,取決於誰大)的比較,直到到了葉節點為止。
遞迴實現的**如下
非遞迴實現的**如下void max_heapify_recur(int * p_arr,int i,int heap_size)
return;
}
4 建立堆的完整實現首先應用乙個小結論,如果乙個堆有n個節點(用陣列array[1] ,array[2]…,array[n]來表示),那麼這個堆的葉節點的下標是(int)n/2+1, (int)n/2+2…n 。(稍後另寫一下對樹相關的數量關係的總結)void max_heapify_norecur(int * p_arr,int i,int heap_size)
} return;
}
如果要構造乙個最大堆,則從下標最大的非葉節點開始,到下標最小的非葉節點為止(到根節點為止),逐步呼叫max_heapify即可。如下所示,偷來一張clrs的配圖。
即從index = (int) n/2開始,到index = 1為止,逐步呼叫max_heapify,
如下圖,對10個元素進行排序,從index = 5開始,到index =1為止,逐步呼叫max_heapify。
建立堆的**如下
5 堆排序的實現假設待排序的有n個數,放在陣列array[n+1]中,元素分別表示為array[1], array[2],…array[10],為了方便,跳過了array[0],從array[1]開始記錄。void build_heap(int * p_arr,int heapsize)
return;
}
以n=10為例子來看。
對這array[1]這1個數,構造最大堆,顯然根據定義和建立最大堆的過程,array[1]構成的最大堆也就是它自己了,最大節點在堆頂,為array[1],交換array[1]和array[10]
發生交換後,對剩下9個數進行堆排序,此時的最大元素依然在堆頂,依然為array[1],交換array[1]和array[9],
發生交換後,對剩下8個數進行堆排序,此時的最大元素依然在堆頂,依然為array[1],交換array[1]和array[8],
發生交換後,對剩下2個數進行堆排序,此時的最大元素依然為array[1],交換array[1]和array[2],
發生交換後,剩下1個數,沒有排序的必要。
整理,輸出陣列。
**如下
void heap_sort(int * p_arr, int length)
for(int i=0;i
5 時間複雜度
max_heapify( )的時間複雜度為olg(n),因為max_heapify( )的時間複雜度和堆的高度成線性關係,所以和lg(n)成線性關係。
建立堆的的時間負責度為o(n), clrs《演算法導論》第六章 上有證明,比較複雜,這裡就不搬運了。
堆排序的的時間負責度為o(nlgn),n個元素,每次挑出當前的最大的1個元素(時間複雜度為常數),然後進行max_heapify( )操作(時間複雜度為lg(n)),所以時間複雜度為o(nlgn)。
6 堆資料結構的應用
下文, 排序演算法整理(6)堆排序的應用,top k 問題 將講講堆這種資料結構的乙個很奇妙的應用,top k問題,挑出一堆數中最大的k個數。
排序演算法 5 堆排序
這篇部落格從以下幾個方面來說 什麼是最大堆以及 實現 堆排序基礎 一次優化 提高效率 二次優化 原地堆排序,無需額外空間 1.什麼是最大堆以及 實現 這裡可以參考言簡意賅的部落格 堆與最大堆 2.堆排序基礎 import com.heap.maxheap import utils.createran...
排序演算法5 堆排序
堆排序是利用堆這種資料結構進行的排序。堆通常使用一維陣列來實現,是一種近似完全二叉樹的結構。堆排序分為大根堆和小根堆。堆排序滿足這樣的乙個特性 大根堆父節點的值總是大於子節點,小根堆的父節點值總是小於子節點。我們在拿到乙個陣列時,首先將它構造成為乙個大根堆 小根堆,這個過程我們叫做建堆。然後將根節點...
整理排序演算法(五) 堆排序
堆排序 大頂堆 將記錄看成乙個順序儲存的二叉樹 先構建大頂堆,所有節點都比他的兒子大或者等於。第一步 從最後乙個非葉子節點開始比較,一直到根。構建大頂堆成功。第二步 根與最後的葉子節點交換位置 第三步 除去剛才的葉子節點,再次構建大頂堆,這個時候構建大頂堆只要從上往下層級遍歷就好。演算法效能 堆排序...