堆排序
以前的隨筆寫了棧(順序棧、鏈式棧)、隊列(循環隊列、鏈式隊列)、鏈表、二叉樹,此次隨筆來寫堆html
一、什麼是堆?
堆是一種非線性結構,(本篇隨筆主要分析堆的數組實現)能夠把堆看做一個數組,也能夠被看做一個徹底二叉樹,通俗來說堆其實就是利用徹底二叉樹的結構來維護的一維數組數組
按照堆的特色能夠把堆分爲大頂堆和小頂堆數據結構
大頂堆:每一個結點的值都大於或等於其左右孩子結點的值ide
小頂堆:每一個結點的值都小於或等於其左右孩子結點的值函數
(堆的這種特性很是的有用,堆經常被當作優先隊列使用,由於能夠快速的訪問到「最重要」的元素)post
二、堆的特色(數組實現)
(圖片來源:https://www.cnblogs.com/chengxiao/p/6129630.html)性能
咱們對堆中的結點按層進行編號,將這種邏輯結構映射到數組中就是下面這個樣子學習
(圖片來源:https://www.cnblogs.com/chengxiao/p/6129630.html)ui
咱們用簡單的公式來描述一下堆的定義就是:(讀者能夠對照上圖的數組來理解下面兩個公式)spa
大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
三、堆和普通樹的區別
內存佔用:
普通樹佔用的內存空間比它們存儲的數據要多。你必須爲節點對象以及左/右子節點指針分配額外的內存。堆僅僅使用數組,且不使用指針
(可使用普通樹來模擬堆,但空間浪費比較大,不太建議這麼作)
搜索:
四、堆排序的過程
先了解下堆排序的基本思想:
將待排序序列構形成一個大頂堆,此時,整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就爲最大值。而後將剩餘n-1個元素從新構形成一個堆,這樣會獲得n個元素的次小值,
如此反覆執行,便能獲得一個有序序列了,創建最大堆時是從最後一個非葉子節點開始從下往上調整的(這句話可能很差太理解),下面會舉一個例子來理解堆排序的基本思想
給一個無序序列以下
int a[6] = {7, 3, 8, 5, 1, 2};
如今能夠根據數組將徹底二叉樹還原出來
好了,如今咱們要作的事情就是要把7,3,8,5,1,2變成一個有序的序列,若是想要升序就是1,2,3,5,7,8 若是想要降序就是8,7,5,3,2,1 ,這兩種就是咱們要的最終結果,而後咱們就能夠根據咱們想要的結果來選擇
適合類型的堆來進行排序
升序----使用大頂堆
降序----使用小頂堆
五、爲何升序要用大頂堆呢
上面提到過大頂堆的特色:每一個結點的值都大於或等於其左右孩子結點的值,咱們把大頂堆構建完畢後根節點的值必定是最大的,而後把根節點的和最後一個元素(也能夠說最後一個節點)交換位置,那麼末尾元素此時就是最大元素了(理解這點很重要)
知道了堆排序的原理下面就能夠來操做了,在進行操做前先理清一下步驟
(假設咱們想要升序的排列)
第一步:先n個元素的無序序列,構建成大頂堆
第二步:將根節點與最後一個元素交換位置,(將最大元素"沉"到數組末端)
第三步:交換事後可能再也不知足大頂堆的條件,因此須要將剩下的n-1個元素從新構建成大頂堆
第四步:重複第二步、第三步直到整個數組排序完成
六、圖解交換過程(獲得升序序列,使用大頂堆來調整)
這裏以int a[6] = {7, 3, 8, 5, 1, 2}爲例子
先要找到最後一個非葉子節點,數組的長度爲6,那麼最後一個非葉子節點就是:長度/2-1,也就是6/2-1=2,而後下一步就是比較該節點值和它的子樹值,若是該節點小於其左\右子樹的值就交換(意思就是將最大的值放到該節點)
8只有一個左子樹,左子樹的值爲2,8>2不須要調整
下一步,繼續找到下一個非葉子節點(其實就是當前座標-1就好了),該節點的值爲3小於其左子樹的值,交換值,交換後該節點值爲5,大於其右子樹的值,不須要交換
下一步,繼續找到下一個非葉子節點,該節點的值爲7,大於其左子樹的值,不須要交換,再看右子樹,該節點的值小於右子樹的值,須要交換值
下一步,檢查調整後的子樹,是否知足大頂堆性質,若是不知足則繼續調整(這裏由於只將右子樹的值與根節點互換,只須要檢查右子樹是否知足,而8>2恰好知足大頂堆的性質,就不須要調整了,
若是運氣很差整個數的根節點的值是1,那麼就還須要調整右子樹)
到這裏大頂堆的構建就算完成了,而後下一步交換根節點(8)與最後一個元素(2)交換位置(將最大元素"沉"到數組末端),此時最大的元素就歸位了,而後對剩下的5個元素重複上面的操做
(這裏用粉紅色來表示已經歸位的元素)
剩下只有5個元素,最後一個非葉子節點是5/2-1=1,該節點的值(5)大於左子樹的值(3)也大於右子樹的值(1),知足大頂堆性質不須要交換
找到下一個非葉子節點,該節點的值(2)小於左子樹的值(5),交換值,交換後左子樹再也不知足大頂堆的性質再調整左子樹,左子樹知足要求後再返回去看根節點,根節點的值(5)小於右子樹的值(7),再次交換值
獲得新的大頂堆,以下圖,再把根節點的值(7)與當前數組最後一個元素值(1)交換,再重構大頂堆->交換值->重構大頂堆->交換值····,直到整個數組都變成有序序列
最後獲得的升序序列以下圖
七、堆排序的代碼實現
上面說了一大堆來詳細說明堆排序的操做步驟,下面開始就開始來碼代碼了
筆者將堆排序的過程分紅了兩個子函數
void Swap(int *heap, int len); /* 交換根節點和數組末尾元素的值 */
void BuildMaxHeap(int *heap, int len);/* 構建大頂堆 */
先來實現構建大堆的部分:
1 /* Function: 構建大頂堆 */
2 void BuildMaxHeap(int *heap, int len) 3 { 4 int i; 5 int temp; 6
7 for (i = len/2-1; i >= 0; i--) 8 { 9 if ((2*i+1) < len && heap[i] < heap[2*i+1]) /* 根節點大於左子樹 */
10 { 11 temp = heap[i]; 12 heap[i] = heap[2*i+1]; 13 heap[2*i+1] = temp; 14 /* 檢查交換後的左子樹是否知足大頂堆性質 若是不知足 則從新調整子樹結構 */
15 if ((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1]) || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2])) 16 { 17 BuildMaxHeap(heap, len); 18 } 19 } 20 if ((2*i+2) < len && heap[i] < heap[2*i+2]) /* 根節點大於右子樹 */
21 { 22 temp = heap[i]; 23 heap[i] = heap[2*i+2]; 24 heap[2*i+2] = temp; 25 /* 檢查交換後的右子樹是否知足大頂堆性質 若是不知足 則從新調整子樹結構 */
26 if ((2*(2*i+2)+1 < len && heap[2*i+2] < heap[2*(2*i+2)+1]) || (2*(2*i+2)+2 < len && heap[2*i+2] < heap[2*(2*i+2)+2])) 27 { 28 BuildMaxHeap(heap, len); 29 } 30 } 31 } 32 }
上述代碼中不易於理解的可能就是下面這條if判斷語句
/* 檢查交換後的左子樹是否知足大頂堆性質 若是不知足 則從新調整子樹結構 */ if ((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1]) || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2])) { BuildMaxHeap(heap, len); }
把if裏面的條件分來開看2*(2*i+1)+1 < len的做用是判斷該左子樹有沒有左子樹(可能有點繞),heap[2*i+1] < heap[2*(2*i+1)+1]就是判斷左子樹的左子樹的值是否大於左子樹,若是是,那麼就意味着交換值
事後左子樹大頂堆的性質被破環了,須要重構該左子樹
下面來實現交換部分
1 /* Function: 交換交換根節點和數組末尾元素的值*/
2 void Swap(int *heap, int len) 3 { 4 int temp; 5
6 temp = heap[0]; 7 heap[0] = heap[len-1]; 8 heap[len-1] = temp; 9 }
而後來考慮下主函數部分,由於是int a[6] = {7, 3, 8, 5, 1, 2}長度爲6,須要構建大頂堆,交換值6次才能獲得有序序列,由此能夠肯定主函數的for循環爲,for (i = len; i > 0; i--)
1 int main() 2 { 3 int a[6] = {7, 3, 8, 5, 1, 2}; 4 int len = 6; /* 數組長度 */
5 int i; 6
7 for (i = len; i > 0; i--) 8 { 9 BuildMaxHeap(a, i); 10 Swap(a, i); 11 } 12 for (i = 0; i < len; i++) 13 { 14 printf("%d ", a[i]); 15 } 16
17 return 0; 18 }
下面附上堆排序完整代碼:
1 #include <stdio.h> 2 3 void Swap(int *heap, int len); /* 交換根節點和數組末尾元素的值 */ 4 void BuildMaxHeap(int *heap, int len);/* 構建大頂堆 */ 5 6 int main() 7 { 8 int a[6] = {7, 3, 8, 5, 1, 2}; 9 int len = 6; /* 數組長度 */ 10 int i; 11 12 for (i = len; i > 0; i--) 13 { 14 BuildMaxHeap(a, i); 15 Swap(a, i); 16 } 17 for (i = 0; i < len; i++) 18 { 19 printf("%d ", a[i]); 20 } 21 22 return 0; 23 } 24 /* Function: 構建大頂堆 */ 25 void BuildMaxHeap(int *heap, int len) 26 { 27 int i; 28 int temp; 29 30 for (i = len/2-1; i >= 0; i--) 31 { 32 if ((2*i+1) < len && heap[i] < heap[2*i+1]) /* 根節點大於左子樹 */ 33 { 34 temp = heap[i]; 35 heap[i] = heap[2*i+1]; 36 heap[2*i+1] = temp; 37 /* 檢查交換後的左子樹是否知足大頂堆性質 若是不知足 則從新調整子樹結構 */ 38 if ((2*(2*i+1)+1 < len && heap[2*i+1] < heap[2*(2*i+1)+1]) || (2*(2*i+1)+2 < len && heap[2*i+1] < heap[2*(2*i+1)+2])) 39 { 40 BuildMaxHeap(heap, len); 41 } 42 } 43 if ((2*i+2) < len && heap[i] < heap[2*i+2]) /* 根節點大於右子樹 */ 44 { 45 temp = heap[i]; 46 heap[i] = heap[2*i+2]; 47 heap[2*i+2] = temp; 48 /* 檢查交換後的右子樹是否知足大頂堆性質 若是不知足 則從新調整子樹結構 */ 49 if ((2*(2*i+2)+1 < len && heap[2*i+2] < heap[2*(2*i+2)+1]) || (2*(2*i+2)+2 < len && heap[2*i+2] < heap[2*(2*i+2)+2])) 50 { 51 BuildMaxHeap(heap, len); 52 } 53 } 54 } 55 } 56 57 /* Function: 交換交換根節點和數組末尾元素的值*/ 58 void Swap(int *heap, int len) 59 { 60 int temp; 61 62 temp = heap[0]; 63 heap[0] = heap[len-1]; 64 heap[len-1] = temp; 65 }
運行結果:
雖然STL模板庫給咱們提供了兩種簡單方便堆操做的方式,不少高級語言的也有不少常見數據結構的封裝,筆者仍是建議須要學習數據結構相關的內容,至少要了解不一樣的數據結構
避免在使用高級語言的封裝好的數據結構時出現只會用不理解的尷尬狀況····