堆排序的初步学习
2016-01-01 11:19
190 查看
注:以下堆排序的代码借鉴了博客http://blog.csdn.net/morewindows/article/details/6709644/
这个排序算法的时间复杂度为O(nlogn),主要是花费在建堆和调整堆上。所以对记录数较少的文件并不值得提倡,但是由于它的最坏时间复杂度是O(nlogn),所以对于记录数较多的文件是比较有效的。
我们一般用一维数组来存储堆元素。
如果从下标0开始存储
i节点上的父节点下标为(i-1)/2(取整)左右孩子节点下标为2i+1和2i+2.
2.每次取堆顶元素为最大的(或者最小的)排序
3.调整堆,使其维持大根堆(或小根堆)的性质
4.重复2
从步骤上来说,堆排序是一种选择排序。每次选取最小的或者最大的放在有序区,然后对无序区继续选择,直到无序区空为止。
具体来讲,我们先要把一个无序序列建成堆,建堆的过程是一个从下到上调整堆的过程,即从最后一个父节点开始调整,然后向上扩展,直至整个变成堆。
然后再输出堆顶元素(最大值或者最小值),即删除堆顶元素。
然后再调整剩下的元素成为堆,即插入元素。重复以上步骤直到所有元素有序。
代码如下:
整个过程是从最后一个父节点开始比较,即先让子堆成为最小堆,然后再让父堆成为最小堆。
注意第2步的排序细节,如果是只利用原来的数组排序,那么由于每次交换都是跟最后一个未排序的数进行交换,想要得到升序排列,就要用大根堆,降序排列用小根堆。如果你只是想输出的话,顺序相反。
堆排序的时间主要花费在建堆和调整堆上面。
T(k) = 1*2^(k-1)+2*2^(k-2)+3*2^(k-3)+……+(k-1)*2+k*2^0
乘以2再将两式相减得
T(k) = 2^(k+1)-2-k,代入k = logN,T(k) = 2N -2-logN
所以时间复杂度为O(N)
其它操作是常数级别的。
所以堆排序的时间复杂度是O(NlogN)。
因为是原地排序,只需要一个记录大小的辅助空间,所以空间复杂度O(1).
所以当问到需要空间复杂度为常数级别的题目时,要想到堆排序。
这个排序算法的时间复杂度为O(nlogn),主要是花费在建堆和调整堆上。所以对记录数较少的文件并不值得提倡,但是由于它的最坏时间复杂度是O(nlogn),所以对于记录数较多的文件是比较有效的。
堆的概念介绍
完全二叉树如果满足每一个父节点上的元素都大于左右孩子的叫做大根堆,其根节点是最大的。每一个父节点上的元素都小于左右孩子的叫做小根堆,其根节点是最小的。我们一般用一维数组来存储堆元素。
如果从下标0开始存储
i节点上的父节点下标为(i-1)/2(取整)左右孩子节点下标为2i+1和2i+2.
堆排序的步骤
1.建立一个大根堆(或者小根堆)2.每次取堆顶元素为最大的(或者最小的)排序
3.调整堆,使其维持大根堆(或小根堆)的性质
4.重复2
从步骤上来说,堆排序是一种选择排序。每次选取最小的或者最大的放在有序区,然后对无序区继续选择,直到无序区空为止。
具体来讲,我们先要把一个无序序列建成堆,建堆的过程是一个从下到上调整堆的过程,即从最后一个父节点开始调整,然后向上扩展,直至整个变成堆。
然后再输出堆顶元素(最大值或者最小值),即删除堆顶元素。
然后再调整剩下的元素成为堆,即插入元素。重复以上步骤直到所有元素有序。
堆的插入:
将元素插入到最后一个节点后面,然后依次向上比较它的父节点。代码如下:
void MinHeapFixup(int a[], int i) { for (int j = (i - 1) / 2; (j >= 0 && i != 0)&& a[i] > a[j]; i = j, j = (i - 1) / 2) Swap(a[i], a[j]); } //在最小堆中加入新的数据nNum void MinHeapAddNumber(int a[], int n, int nNum) { a = nNum; MinHeapFixup(a, n); }
堆的删除:
每次只能删除第0个元素,为了便于重建堆,我们交换最后一个元素和第0个元素的值,然后自上而下的调整。每一次都是在左右孩子中取最小的或者最大的孩子与父节点进行交换。// 从i节点开始调整,n为节点总数 从0开始计算 i节点的子节点为 2*i+1, 2*i+2 void MinHeapFixdown(int a[], int i, int n) { int j, temp; temp = a[i]; j = 2 * i + 1; while (j < n) { if (j + 1 < n && a[j + 1] < a[j]) //在左右孩子中找最小的 j++; if (a[j] >= temp) break; a[i] = a[j]; //把较小的子结点往上移动,替换它的父结点 i = j; j = 2 * i + 1; } a[i] = temp; } //在最小堆中删除数 void MinHeapDeleteNumber(int a[], int n) { Swap(a[0], a[n - 1]); MinHeapFixdown(a, 0, n - 1); } //建立最小堆 void MakeMinHeap(int a[], int n) { for (int i = n / 2 - 1; i >= 0; i--)//从最后一个父节点开始建堆 MinHeapFixdown(a, i, n); }
整个过程是从最后一个父节点开始比较,即先让子堆成为最小堆,然后再让父堆成为最小堆。
注意第2步的排序细节,如果是只利用原来的数组排序,那么由于每次交换都是跟最后一个未排序的数进行交换,想要得到升序排列,就要用大根堆,降序排列用小根堆。如果你只是想输出的话,顺序相反。
复杂度的分析
下面证明一下堆排序的时间复杂度和空间复杂度:堆排序的时间主要花费在建堆和调整堆上面。
建堆
假设总长度N,树高k,则倒数第2层的元素有2^(k-1)个,最多需要进行1次下沉,最多需要的交换次数为1*2^(k-1),倒数第三层的元素最多需要2次下沉,最多需要的交换次数为2*2^(k-2).依次类推,建堆的时间为T(k) = 1*2^(k-1)+2*2^(k-2)+3*2^(k-3)+……+(k-1)*2+k*2^0
乘以2再将两式相减得
T(k) = 2^(k+1)-2-k,代入k = logN,T(k) = 2N -2-logN
所以时间复杂度为O(N)
调整堆
当我们插入一个元素再调整堆的时候,因为只需要调整堆顶的位置,最多下沉到底部,需要的时间是logN级别的,然后一共需要插入N个,所以是NlogN级别的。其它操作是常数级别的。
所以堆排序的时间复杂度是O(NlogN)。
因为是原地排序,只需要一个记录大小的辅助空间,所以空间复杂度O(1).
所以当问到需要空间复杂度为常数级别的题目时,要想到堆排序。
相关文章推荐
- html5调用手机摄像头,实现拍照上传功能
- Ubuntu 安装Nginx
- 深入理解TCP(二)
- Hadoop 2.0 中 NameNode/ResourceManager HA 总结
- 数据总线
- 告别2015,展望2016
- Linux 常用
- 详解Linux中rm与rmdir删除命令的用法
- 委托 (代码复习)
- 【PAT】1003. Emergency (25) DFS 最短路径及最短路径数
- AES加密算法C代码分析
- Spring不能注入Static变量的原因及Spring注入静态变量
- Hadoop 权威指南
- codevs 1021 玛丽卡
- 客户端在连接服务器报10061错误的原因
- codevs 1519 过路费
- oozie ErrorCode 含义
- watchman--no-pretty get-sockname returned with exit code 1 ERROR
- 20160101罗振宇的跨年演讲
- B. 沙漠之旅(分组背包)