排序算法的c++实现——堆排序

我们利用最大堆可以实现数组从小到大的原址排序,利用最小堆的可以实现对数组从大到小的原址排序。

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 }

 

posted @ 2019-05-09 22:18  殷大侠  阅读(3445)  评论(0编辑  收藏  举报