排序演算法整理 5 堆排序

2021-06-18 14:32:14 字數 3099 閱讀 1853

程式參考了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;

}

非遞迴實現的**如下

void max_heapify_norecur(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 。(稍後另寫一下對樹相關的數量關係的總結)

如果要構造乙個最大堆,則從下標最大的非葉節點開始,到下標最小的非葉節點為止(到根節點為止),逐步呼叫max_heapify即可。如下所示,偷來一張clrs的配圖。

即從index = (int) n/2開始,到index = 1為止,逐步呼叫max_heapify,

如下圖,對10個元素進行排序,從index = 5開始,到index =1為止,逐步呼叫max_heapify。

建立堆的**如下

void build_heap(int * p_arr,int heapsize)

return;

}

5 堆排序的實現假設待排序的有n個數,放在陣列array[n+1]中,元素分別表示為array[1], array[2],…array[10],為了方便,跳過了array[0],從array[1]開始記錄。

以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 堆排序

堆排序是利用堆這種資料結構進行的排序。堆通常使用一維陣列來實現,是一種近似完全二叉樹的結構。堆排序分為大根堆和小根堆。堆排序滿足這樣的乙個特性 大根堆父節點的值總是大於子節點,小根堆的父節點值總是小於子節點。我們在拿到乙個陣列時,首先將它構造成為乙個大根堆 小根堆,這個過程我們叫做建堆。然後將根節點...

整理排序演算法(五) 堆排序

堆排序 大頂堆 將記錄看成乙個順序儲存的二叉樹 先構建大頂堆,所有節點都比他的兒子大或者等於。第一步 從最後乙個非葉子節點開始比較,一直到根。構建大頂堆成功。第二步 根與最後的葉子節點交換位置 第三步 除去剛才的葉子節點,再次構建大頂堆,這個時候構建大頂堆只要從上往下層級遍歷就好。演算法效能 堆排序...