排序算法的c++實現——堆排序

咱們利用最大堆能夠實現數組從小到大的原址排序,利用最小堆的能夠實現對數組從大到小的原址排序。ios

1  二叉堆的簡單介紹:

    最大堆與最小堆能夠看成經過數組來實現的一個徹底二叉樹,除了最底層以外其它層都是滿的,而且最底層也是從左到右填充的。在最大堆中,父結點的值大於或等於子結點的值;在最小堆中,父結點的值小於或等於子結點的值。git

    當堆的元素在數組中下標從1開始時,很容易計算出父結點/左子結點/右子結點的下標:當父結點的下標爲 i 時,左孩子下標爲2i, 右孩子的下標爲2i+1;當孩子結點爲j時,父結點的下標爲 j/2。github

    可是呢,數組的下標都是從0開始的,因此呢,關於父結點與子結點的關係要稍微繞一下:當父結點的下標爲 i 時,左孩子下標爲2i+1, 右孩子的下標爲2(i+1);當孩子結點爲j時,父結點的下標爲 (j-1)/2。redis

 

2 使用最大堆或最小堆實現排序

以使用最大堆進行從小到大的排序爲例,假設最大堆使用長度爲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 }
相關文章
相關標籤/搜索