《算法4》中的堆排序
2015-07-10 11:04
169 查看
因为要写STL中的heap,所以又将这本书中的归并排序复习了一下,并且有所收获,为了避免忘记,我准备将这些思路记下来:
堆排序有两个重要的动作:swim和sink:
下面写一下两个函数
有了这两个函数,我们就可以构建堆排序了;
首先要构建堆,在构建堆的过程中,数组从左向右遍历并利用swim函数时可以构造成一个堆的,但这需要遍历数组中的全部数字;然而采用数组从右向左遍历并利用sink函数就可以仅仅遍历一般的数字,因为叶子节点不需要进行sink;
对于构建堆需要的复杂度是O(2N),因为需要2N次的比较和N次的交换(交换和比较时一个算法中的主要成本,如果没有这两者再考虑存取的成本),这里可以举例:对于一个127个元素的堆,在构建的时候:32个大小为3的堆(需要一次交换,两次比较),16个大小为7的堆(两次交换三次比较),8个大小为15的堆,4个大小为31的堆,2个大小为63的堆和1个大小为127的堆,一共就需要120交换和240比较。
之后就可以开始排序了:首先将堆顶的元素和数组的尾部元素交换,然后刷新数组的长度,让算法无法再访问这个数组尾部元素;
将每次都从最高的点插入堆尾的元素,然后利用sink下沉,重新adjust这个堆,以此类推,可以参考我这段代码;
总体的思路就是这个样子,但是,如果遇到“比较”的成本特别大的时候,还可以采用如下的方法:
1.当我们将堆顶的元素放在数组尾部后,我们利用辅助空间暂时存储之前的堆尾元素temp,并不是将它放在堆顶;
2.现在堆顶是空的,我们不将temp放在上面,而是直接将堆顶对应的子节点中较大的那个节点bigger直接放上去(这样我们只需要比较一次哪个子节点大就可以了),然后这个bigger的位置就产生了空缺;
3.这个空缺的左右子节点继续比较产生较大的节点bigger`放在父节点空缺的位置上;
4.依次类推直到空缺的节点没有子节点;
5.将空缺的节点放上temp;
6.swim上浮
这个方法需要一个辅助空间,如果条件允许的话便可以用;而这种方法产生的比较次数几乎就成为了N;这个方法是Floyd在1964年改善的,我觉得十分不错;只是《算法4》中的描述我不太习惯,换成我习惯方式^^;
堆排序有两个重要的动作:swim和sink:
下面写一下两个函数
void swim(int* num, int k, int N) { while(k > 1 && a[k/2] < a[k]) { swap(a[k/2], a[k]); k = k/2; } }
void sink(int* num, int k, int N) { while(2*k <= N) { int j = 2*k; if(2*k < N && num[2*k] < num[2*k+1]) j++; if(num[k] >= num[j]) break; swap(num[k], num[j]); k = j; } }
有了这两个函数,我们就可以构建堆排序了;
首先要构建堆,在构建堆的过程中,数组从左向右遍历并利用swim函数时可以构造成一个堆的,但这需要遍历数组中的全部数字;然而采用数组从右向左遍历并利用sink函数就可以仅仅遍历一般的数字,因为叶子节点不需要进行sink;
对于构建堆需要的复杂度是O(2N),因为需要2N次的比较和N次的交换(交换和比较时一个算法中的主要成本,如果没有这两者再考虑存取的成本),这里可以举例:对于一个127个元素的堆,在构建的时候:32个大小为3的堆(需要一次交换,两次比较),16个大小为7的堆(两次交换三次比较),8个大小为15的堆,4个大小为31的堆,2个大小为63的堆和1个大小为127的堆,一共就需要120交换和240比较。
之后就可以开始排序了:首先将堆顶的元素和数组的尾部元素交换,然后刷新数组的长度,让算法无法再访问这个数组尾部元素;
将每次都从最高的点插入堆尾的元素,然后利用sink下沉,重新adjust这个堆,以此类推,可以参考我这段代码;
while(N > 1) { swap(num[1], num[N--]); sink(num, 1, N); }
总体的思路就是这个样子,但是,如果遇到“比较”的成本特别大的时候,还可以采用如下的方法:
先下沉后上浮
我们可以这样来思考,处在数组尾部的元素一般都是比较小的元素,这样我们在把它放到堆顶后进行sink,一般它还是会下来的,那么我们为什么不直接将这个元素放到堆尾然后在swim上去呢?这样会减少很多次的比较,具体做法如下:1.当我们将堆顶的元素放在数组尾部后,我们利用辅助空间暂时存储之前的堆尾元素temp,并不是将它放在堆顶;
2.现在堆顶是空的,我们不将temp放在上面,而是直接将堆顶对应的子节点中较大的那个节点bigger直接放上去(这样我们只需要比较一次哪个子节点大就可以了),然后这个bigger的位置就产生了空缺;
3.这个空缺的左右子节点继续比较产生较大的节点bigger`放在父节点空缺的位置上;
4.依次类推直到空缺的节点没有子节点;
5.将空缺的节点放上temp;
6.swim上浮
这个方法需要一个辅助空间,如果条件允许的话便可以用;而这种方法产生的比较次数几乎就成为了N;这个方法是Floyd在1964年改善的,我觉得十分不错;只是《算法4》中的描述我不太习惯,换成我习惯方式^^;
相关文章推荐
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法
- Ruby实现的合并排序算法
- C#折半插入排序算法实现方法
- 基于C++实现的各种内部排序算法汇总
- C++线性时间的排序算法分析
- C++实现汉诺塔算法经典实例
- PHP实现克鲁斯卡尔算法实例解析
- C#常见算法面试题小结