堆排序
来源:互联网 发布:js获取传入的参数 编辑:程序博客网 时间:2024/06/03 20:29
堆排序在排序算法中算是比较晦涩难懂的一种,和快速排序、归并排序一样,平均时间复杂度为
二叉堆
定义:
- 一种经过排序的完全二叉树
性质:
- 任意父节点的值都大于等于或小于等于子节点的值
- 每个节点的左右子堆也都是二叉堆
种类:
- 最大堆
也称大顶堆,即每个父节点的值都大于等于子节点的值,适用于从小到大排序. - 最小堆
也称小顶堆,即每个父节点的值都小于等于子节点的值,适用于从大到小排序.
存储方式:
- 一般存储在一个一维数组之中,利用数组下标来进行父子节点的判断,例如:下标为1的两个儿子节点的下标分别为3和4,那么可得出下标为 i 的节点的两个儿子节点分别为
i∗2+1 和i∗2+2
排序
在进行排序之前,我们先梳理一下思路,想一下我们可能会遇到的问题?
- 添加一个数到堆如何更新?
- 删除一个数后堆如何更新?
- 初始化序列必然为无序的序列,怎么将其调整为一个二叉堆?
- 调整为二叉堆后,怎么有序的进行取数?
- 取完数之后的堆是否不变?怎么变?如何解决?
注意:以下问题解决解析皆以最小堆为例.
问题解决
添加一个数到堆
此操作在一个长度不会变化的数组中几乎不会使用,但是在处理长度动态变化,有可能添加新元素的数组时,此操作可能会用到.
思想步骤:
- 将新数添加到数组的末尾
直接将新数插入末尾,此时可以发现,一条从根节点到新节点的路径出来了,而且这条路径的节点值还是递增的,我们要将新节点插入到这条路径上去,这样问题就转换成了插入排序的问题,如果有对插入排序有不理解的可以看一下这篇排序总结:常见的排序方法 - 根据插入排序的思想,很容易写出添加节点的代码,只是插入排序的时候每次下标是前进1,而在这里每次下标取当前节点的父节点,也就是
(i−1)/2
// 新加入结点,下标为ivoid insertHeapFix(int num[], int i) { int j, temp; temp = num[i]; j = (i - 1) / 2;//父结点 while (j >= 0 && i != 0) { if (num[j] <= temp){ break; } num[i] = num[j];//插入排序思想,把较大的子结点往下移动,替换它的子结点 i = j; j = (i - 1) / 2; } num[i] = temp;}void insertNumToHeap(int num[], int a, n){ //新节点置为最后 num[n] = a; //调整堆 insertHeapFix(num, n);}
删除一个数
因为二叉堆是存储在数组中的,所以说删除操作比较麻烦,而且根据定义,二叉堆每次只能删除根节点.
思想步骤:
- 当删除根节点之后,后面节点必然后前移,所以说我们可以暂时将最后一个节点值和根节点交换,然后缩短数组长度,即可实现删除根节点,但是此时二叉堆需要更新
- 首先检查原根节点的左右儿子,如果
min(左儿子,右儿子) 大于等于当前根节点,那么停止更新,二叉堆正确,如果小于当前根节点,那么交换min(左儿子,右儿子) 和当前根节点,然后继续进行检查,直到找到替换前的根节点所应该处的正确位置.
void heapFix(int num[], int id, int top) { int temp = num[id]; int i = id;//当前遍历节点,从根节点开始 int j = i * 2 + 1;//子节点 while(j < top) { //寻找左右子节点的最小值 if(j + 1 < top && num[j + 1] < num[j]) { j++; } //找到了合适位置 if(num[j] >= temp) { break; } //更新指针 num[i] = num[j]; i = j; j = i * 2 + 1; } num[i] = temp;}void deleteNum(int num[], int n){ //将原根节点置于最后 swap(num[0], num[n - 1]); heapFix(num, 0, n - 1);}
初始化序列必然为无序的序列,怎么将其调整为一个二叉堆?
这个过程也就是常说的建堆过程,这里有两种方案:
方案一:将数一个一个的加入
初始化为一个空堆,然后一个一个加入到堆中方案二:将数的后面一半初始化为一个队堆,然后将另一半加入堆并调整
因为堆构建到最后我们是构建出来了一个完全二叉树,所以说对所有的非叶子节点进行筛选,所以只需要加入一半然后调整即可
for(int i = n / 2 - 1; i >= 0; i--){ heapFix(i, n);}
怎么有序的进行取数?
因为根节点的值永远是当前堆最小的,所以说每次我们将根节点存储并从堆中删除,然后重新调整堆,直到堆中只剩下一个节点,即
for(int i = n - 1; i >= 1; i--) { swap(num[i], num[0]); heapFix(num, 0, i);}
堆排序应用
求出 100w 的数据量数中最小的10个数
分析:这道题目在很多面试题中都有出现,虽然可能不是如此直接,但是思想都一样。这个时候,我们的关键点不在于
for(int i = 5; i < n; i++) { //新来的数较小 if(num[i] < num[0]) { //直接和根节点进行交换即可,因为后面的数我们都不需要维护. swap(num[i], num[0]); //调整堆 heapFix(0, 5); }}
阅读全文
0 0
- 堆及堆排序
- 堆/堆排序特点
- 【二叉堆、堆排序】
- 二叉堆 & 堆排序
- 二叉堆 & 堆排序
- 堆与堆排序
- 堆与堆排序
- 堆与堆排序
- 堆与堆排序
- 堆与堆排序
- 堆与堆排序
- 堆和堆排序
- 堆排序(最大堆)
- 堆和堆排序
- 堆和堆排序
- 堆及堆排序
- 堆和堆排序
- 堆与堆排序
- Android Studio创建项目
- UI automator的使用
- 这里写一个小文章
- python: zip 与 * 探究
- spring集成mybatis后,打印SQL语句
- 堆排序
- 在Android项目上集成Google语音(不使用手机自带引擎)
- 详解MindManager之提醒功能
- 秒杀系统架构优化思路
- eclipse中Pull代码报错checkout conflict with files的解决方法
- 如何使用rem
- 记录一次大对象导致的Java堆内存溢出问题
- 20_反反复复
- 博客宣言