堆排序和其他排序算法的比较
上图引自:http://hi.baidu.com/ycdoit/item/6b5f5b9571a843becc80e560,这篇文章也解释了可能的优化途径。
(sort 是STL的排序方法,qsort是库函数,quicksort是没有经过优化的快速排序实现,Heapsort是一般的堆排序实现)
堆的定义
谈堆排序首先要有堆,堆类似于一种特殊的完全二叉树,分为最大堆和最小堆。
最大堆:所有结点的值都不大于其父结点。
最小堆:所有结点的值都不小于其父结点。
关于堆的定义和特性,请参考http://blog.csdn.net/v_JULY_v/article/details/6198644,非常好的一篇关于堆排序的文章。
堆的实现
谈这个话题是写这篇文章的主要原因。
我们常说程序就是数据结构和算法的组合,具体到堆的实现上,算法体现出了非常强大的抽象作用,表现在:
1. 存储最小堆或者最大堆的数据结构就是一个简单的数组。
2. 算法实现的时候,把这个数组看作一种完全二叉树:
(1) 从根结点开始(编号为1),分层遍历这颗二叉树,每个结点的编号是上一个遍历到的结点编号+1,就是说:
1
/ \
2 3
/ \
4 5
(2) 二叉树各个结点按照编号顺序存在数组中。
(3) 对于完全二叉树来说,下面的条件成立:
- 第i个结点的父结点为第|_i/2_|个结点(|_i/2_|表示对i/2的结果向下取整)。
- 第i个结点的左孩子结点为第2i个结点。
- 第i个结点的右孩子结点为第2i+1个结点。
带着上面的逻辑来实现最小堆和最大堆,就是把父结点和左右子结点的大小关系映射为数组中第i个数据和第2i以及第2i+1个数据的大小关系。
这里我们没有定义二叉树的数据结构,而是在处理数组的时候将其看作分层遍历的完全二叉树结点,具体表现在算法的实现上面。
很多人在想到堆的时候就想到二叉树的数据结构,进而认为堆的实现就基于这种数据结构,堆排序也是对二叉树的排序,这是不准确的,其实我们的操作,包括创建一个最小堆或最大堆,以及进行堆排序都是在上面映射关系的基础上,用处理数组元素的方式进行的。
当然,可以用二叉树的数据结构,但是考虑到这种数据结构相比数组而言,每个结点的大小增加了,而堆排序的算法却没有优化,还是平均N*log2(N)(N为结点数量),必要性在哪里?
所以,有时候一种不同的对待事物的方式可以极大提高我们的编程效率,这就是算法的威力所在。
寻找M个数中的前K个最小的数并保持有序
这个算法题在这篇文章中得到了精彩详尽的讨论,强烈推荐感兴趣的同学学习一下。
这里我们使用最大堆来解决:
1. 用M个数里面的前K个数创建一个K个元素的最大堆。
2. 将第M-k个数到第M个数依次和上面最大堆的第一个元素(存储这个最大堆的第1个数据,也即是抽象的完全二叉树的根结点数据)比较,如果大于等于堆顶元素,继续下一个数进行比较(堆顶元素在最大堆中最大),如果小于堆顶元素,就把堆顶元素值设置为这个数,然后重新对这个堆进行重新最大堆化,使之符合最大堆的要求。
3. 结束之后最大堆里面放的就是最小的K个数了,这时对最大堆进行堆排序,就解决这个问题了。
时间复杂度为 :
O(K) + (M - K)*log2(K)
| |
创建K个元素最大堆的时间复杂度 对剩余M-K个数据进行比较并每次对最大堆进行重新最大堆化
在K远小于M的时候,时间复杂度相当于M*log2(K)。
完整代码实现:
P.S.如果需要在M非常大的情况下测试,可以考虑将M个数据保存在文件中,然后依次读取进行处理。
#include <iostream> using namespace std; /****************** 根据输入的int数组创建一个最大堆 input: 输入的int数组 maxHeap: 最大堆数组 maxHeapCount 最大堆中元素的个数 ******************/ void createMaxHeap(const int * input, int * maxHeap, const int maxHeapCount); /****************** 对输入int数组进行操作,使之符合最大堆的条件: 所有结点的值不大于其父结点 maxHeap: 最大堆数组 pos: 最大堆中的元素标志位,由1开始 maxHeapCount 最大堆中元素的个数 ******************/ void heapifyMaxHeap(int * maxHeap, const int pos, const int maxHeapCount); /****************** 对最大堆排序,使之由小到大有序 maxHeap: 最大堆数组 maxHeapCount 最大堆中元素的个数 ******************/ void maxHeapSort(int * maxHeap, const int maxHeapCount); /****************** 根据指定的长度初始化输入的int数组 bigData: 输入的int数组 arrayLength: 数组长度 ******************/ void initBigDataArray(int * bigData, const int arrayLength); void main() { const int M = 1000; const int K = 5; int bigDataM[M]; int maxHeap[K]; initBigDataArray(bigDataM, M); createMaxHeap(bigDataM, maxHeap, K); for(int step = K; step < M; ++step) { if(bigDataM[step] < maxHeap[0]) { maxHeap[0] = bigDataM[step]; heapifyMaxHeap(maxHeap, 1, K); } } maxHeapSort(maxHeap, K); cout<<"bigData array is: "; for(int step = 0; step < M; ++step) { cout<<bigDataM[step]<<" "; } cout<<endl; cout<<"Output maxHeap from less to larger: "; for(int step = 0; step < K; ++step) { cout<<maxHeap[step]<<" "; } cout<<endl; cout<<"Output maxHeap from larger to less: "; for(int step = 0; step < K; ++step) { cout<<maxHeap[K - 1 - step]<<" "; } cout<<endl; return; } void heapifyMaxHeap(int * maxHeap, const int pos, const int maxHeapCount) { int left = 2 * pos; int right = 2 * pos + 1; int largestElemPos = 0; if(left <= maxHeapCount && maxHeap[left - 1] > maxHeap[pos - 1]) largestElemPos = left; else largestElemPos = pos; if(right <= maxHeapCount && maxHeap[right - 1] > maxHeap[largestElemPos - 1]) largestElemPos = right; if(largestElemPos != pos) { swap(maxHeap[pos - 1], maxHeap[largestElemPos - 1]); heapifyMaxHeap(maxHeap, largestElemPos, maxHeapCount); } } void createMaxHeap(const int * input, int * maxHeap, const int maxHeapCount) { for(int step = 0; step < maxHeapCount; ++step) { maxHeap[step] = input[step]; } for(int startHeapifyPos = maxHeapCount/2; startHeapifyPos >= 1; --startHeapifyPos) { heapifyMaxHeap(maxHeap, startHeapifyPos, maxHeapCount); } } void initBigDataArray(int * bigData, const int arrayLength) { for(int i = 0; i< arrayLength; ++i) { bigData[i] = rand(); } } void maxHeapSort(int * maxHeap, const int maxHeapCount) { int maxHeapSize = maxHeapCount; for(int step = maxHeapSize; step >=2; --step) { swap(maxHeap[step - 1], maxHeap[0]); maxHeapSize -=1; heapifyMaxHeap(maxHeap, 1 , maxHeapSize); } }
如有任何错误或者不足,欢迎不吝指出。