咱們利用最大堆能夠實現數組從小到大的原址排序,利用最小堆的能夠實現對數組從大到小的原址排序。ios
最大堆與最小堆能夠看成經過數組來實現的一個徹底二叉樹,除了最底層以外其它層都是滿的,而且最底層也是從左到右填充的。在最大堆中,父結點的值大於或等於子結點的值;在最小堆中,父結點的值小於或等於子結點的值。git
當堆的元素在數組中下標從1開始時,很容易計算出父結點/左子結點/右子結點的下標:當父結點的下標爲 i 時,左孩子下標爲2i, 右孩子的下標爲2i+1;當孩子結點爲j時,父結點的下標爲 j/2。github
可是呢,數組的下標都是從0開始的,因此呢,關於父結點與子結點的關係要稍微繞一下:當父結點的下標爲 i 時,左孩子下標爲2i+1, 右孩子的下標爲2(i+1);當孩子結點爲j時,父結點的下標爲 (j-1)/2。redis
以使用最大堆進行從小到大的排序爲例,假設最大堆使用長度爲N的數組表示,數組下標爲0(對應堆根結點)的值最大。所以呢,咱們能夠把數組下標爲0的值與數組下標爲N-1的值進行交換,並把堆的大小由N減少爲N-1並維護最大堆的性質;接下來不斷重複以上過程,直到堆的大小變爲1爲止。算法
說明幾點:api
1 堆排序爲爲原址排序,它不須要額外的內存空間;數組
2 一般使用最大堆實現從小到大的排序,使用最小堆來實現從大到小的排序;less
3 堆排序不是穩定的排序算法;函數
4 堆排序的時間複雜度爲O(NlogN);測試
代碼以下:
1 /*********************************************************************** 2 * Copyright (C) 2019 Yinheyi. <chinayinheyi@163.com> 3 * 4 * This program is free software; you can redistribute it and/or modify it under the terms 5 * of the GNU General Public License as published by the Free Software Foundation; either 6 * version 2 of the License, or (at your option) any later version. 7 8 * Brief: 9 * Author: yinheyi 10 * Email: chinayinheyi@163.com 11 * Version: 1.0 12 * Created Time: 2019年05月07日 星期二 21時41分02秒 13 * Modifed Time: 2019年05月09日 星期四 22時12分52秒 14 * Blog: http://www.cnblogs.com/yinheyi 15 * Github: https://github.com/yinheyi 16 * 17 ***********************************************************************/ 18 19 20 // 堆使用一個數組表示, 它能夠看成一個徹底二叉樹,除了最底層以外,其它層都是滿的, 21 // 而且最底層也是從左到右填充的。 22 // 23 // 當堆的下標(數組的下標)從1開始時比較好計算。由於: 24 // 1. 當父結點爲i時, 左孩子爲2i, 右孩子爲2i+1; 25 // 2. 當孩子結點的下標爲j時,父結點的下標爲j/2 (根結點爲父結點); 26 // 3. 一個結點的下標爲k時, 則它全部的深度爲 floor(logK); 27 // 4. 當一個堆共n個元素時,它的高度爲floor(logN). 28 // 29 // 1 30 // / \ 31 // 2 3 32 // / \ / \ 33 // 4 5 6 7 34 // / \ / \ / \ / \ 35 // 8 9 10 11 12 13 14 15 36 // 37 // 38 // 可是呢,數組的下標都是從0開始的,因此呢,咱們下代碼時,仍是須要從0開始,而此時: 39 // 1. 當父結點的下標爲i時,左孩子爲2i+1, 右孩子爲2*(i+1) 40 // 2. 當孩子結點的下標爲j時,父結點的下標爲(j-1)/2. 41 // 42 // 0 43 // / \ 44 // 1 2 45 // / \ / \ 46 // 3 4 5 6 47 // / \ / \ / \ / \ 48 // 7 8 9 10 11 12 13 14 49 // 50 // 51 // 52 /************************** 代碼以下 ****************************/ 53 54 // 定義三個宏,分別用於求左孩子/右孩子/父結點的下標。 55 #define LEFT(i) (((i) << 1) + 1) 56 #define RIGHT(i) (((i) + 1) << 1) 57 #define PARENT(i) (((i) - 1) >> 1) 58 59 // 小於比較函數 60 bool less(int lhs, int rhs) 61 { 62 return lhs < rhs; 63 } 64 65 // 大於比較函數 66 bool greate(int lhs, int rhs) 67 { 68 return lhs > rhs; 69 } 70 typedef bool (*Comp)(int, int); 71 72 // 交換兩個元素的值 73 static inline void swap(int& lhs, int & rhs) 74 { 75 int _nTemp = lhs; 76 lhs = rhs; 77 rhs = _nTemp; 78 } 79 80 // 假設一個節點的左子樹與右子樹都知足堆的性質,而該節點不知足最大堆或最小堆的性質,該 81 // 函數實現調整節點位置,維護堆的性質。 82 // 輸入參數分別表示: 堆的數組/數組長度/節點i的下標/表示比較的二元謂詞 83 // 複雜度爲O(logN). 84 void Heapify(int array[], int nLength_, int nIndex_, Comp CompFunc) 85 { 86 if (array == nullptr || nIndex_ >= nLength_ || nIndex_ < 0 || CompFunc == nullptr) 87 return; 88 89 int _nLeft = LEFT(nIndex_); 90 int _nRight = RIGHT(nIndex_); 91 92 // 初始化最大值節點的下標; 93 int _nLargest = nIndex_; 94 if ( _nLeft < nLength_ && !CompFunc(array[_nLargest], array[_nLeft])) 95 { 96 _nLargest = _nLeft; 97 } 98 if (_nRight < nLength_ && !CompFunc(array[_nLargest], array[_nRight])) 99 { 100 _nLargest = _nRight; 101 } 102 103 /* 此時不須要維護堆的性質,直接返回 */ 104 if (_nLargest == nIndex_) 105 { 106 return; 107 } 108 109 swap(array[nIndex_], array[_nLargest]); 110 Heapify(array, nLength_, _nLargest, CompFunc); 111 } 112 113 // 使用一個數組創建一個最小堆或最大堆。 114 // 輸入參數爲:一維數組/數組的長度/表示比較的二元謂詞,用於控制建最小堆仍是最大堆 115 // 複雜度爲O(N). 116 void BulidHeap(int array[], int nLength_, Comp CompFunc) 117 { 118 if (array == nullptr || nLength_ <= 1 || CompFunc == nullptr) 119 return; 120 121 // 從最後一個元素的父節點開始調用Heapify()函數來建堆。 122 for (int i = PARENT(nLength_ - 1); i >=0; --i) 123 { 124 Heapify(array, nLength_, i, CompFunc); 125 } 126 } 127 128 // 堆排序的函數 129 // 說明:1. 經過創建[最大堆]能夠實現[從小到大]的排序; 130 // 2. 經過創建[最小堆]能夠實現[從大到小]的排序 131 // 3. 堆排序爲原址排序,它不須要藉助額外的空間.(歸併排序不是原址排序) 132 // 4. 堆排序的複雜度爲O(NlogN). 133 // 134 // 堆排序的思想 (以從小到大排序說明): 135 // 第一步:創建一個最大堆; 136 // 第二步:把首元素與最後一個元素進行交換; 137 // 第三步:把堆的大小減1,對新的堆進行維護維的性質; 138 // 第四步:把首元素與倒數第二個元素進行交換; 139 // 第五步:把堆的大小減1,對新的堆進行維護維的性質; 140 // ....... 141 // 142 void HeapSort(int array[], int nLength_, Comp CompFunc) 143 { 144 if (array == nullptr || nLength_ <=1 || CompFunc == nullptr) 145 return; 146 147 BulidHeap(array, nLength_, CompFunc); 148 for (int i = nLength_; i >= 2; /* 循環內 */) // i表示當前堆的大小 149 { 150 swap(array[0], array[--i]); 151 Heapify(array, i, 0, CompFunc); 152 } 153 } 154 155 156 /************ 測試 *****************/ 157 #include <iostream> 158 159 // 打印數組函數 160 void PrintArray(int array[], int nLength_) 161 { 162 if (nullptr == array || nLength_ <= 0) 163 return; 164 165 for (int i = 0; i < nLength_; ++i) 166 { 167 std::cout << array[i] << " "; 168 } 169 170 std::cout << std::endl; 171 } 172 173 // 主函數 174 int main(int argc, char* argv[]) 175 { 176 int array[10] = { 100, 1, 1, -1243, 0, 223, 443, 123, -12, -129}; 177 178 PrintArray(array, 10); 179 HeapSort(array, 10, greate); 180 PrintArray(array, 10); 181 182 return 0; 183 }