我们利用最大堆可以实现数组从小到大的原址排序,利用最小堆的可以实现对数组从大到小的原址排序。
1 二叉堆的简单介绍:
最大堆与最小堆可以当作通过数组来实现的一个完全二叉树,除了最底层之外其它层都是满的,并且最底层也是从左到右填充的。在最大堆中,父结点的值大于或等于子结点的值;在最小堆中,父结点的值小于或等于子结点的值。
当堆的元素在数组中下标从1开始时,很容易计算出父结点/左子结点/右子结点的下标:当父结点的下标为 i 时,左孩子下标为2i, 右孩子的下标为2i+1;当孩子结点为j时,父结点的下标为 j/2。
但是呢,数组的下标都是从0开始的,所以呢,关于父结点与子结点的关系要稍微绕一下:当父结点的下标为 i 时,左孩子下标为2i+1, 右孩子的下标为2(i+1);当孩子结点为j时,父结点的下标为 (j-1)/2。
2 使用最大堆或最小堆实现排序
以使用最大堆进行从小到大的排序为例,假设最大堆使用长度为N的数组表示,数组下标为0(对应堆根结点)的值最大。因此呢,我们可以把数组下标为0的值与数组下标为N-1的值进行交换,并把堆的大小由N减小为N-1并维护最大堆的性质;接下来不断重复以上过程,直到堆的大小变为1为止。
说明几点:
1 堆排序为为原址排序,它不需要额外的内存空间;
2 通常使用最大堆实现从小到大的排序,使用最小堆来实现从大到小的排序;
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 }