目录

1、前言

2、使用堆的原因

3、堆的特点

4、堆和普通树的区别

5、堆排序的过程

6、堆排序的代码实现

来源: jianshu.com/p/15a29c0ace73

1、前言

堆是一种非线性结构,可以把堆看作一个数组,也可以被看作一个完全二叉树,通俗来讲堆其实就是利用完全二叉树的结构来维护的一维数组但堆并不一定是完全二叉树

按照堆的特点可以把堆分为大顶堆和小顶堆
大顶堆:每个结点的值都大于或等于其左右孩子结点的值
小顶堆:每个结点的值都小于或等于其左右孩子结点的值

2、使用堆的原因

如果仅仅是需要得到一个有序的序列,使用排序就可以很快完成,并不需要去组织一个新的数据结构。但是如果我们的需求是对于一个随时会有更新的序列,我要随时知道这个序列的最小值或最大值是什么。显然如果是线性结构,每次插入之后,假设原数组是有序的,那使用二分把它放在正确的位置也未尝不可,但是插入的时候从数组中留出空位就需要O(n)的时间复杂度,删除的时候亦然。

可是如果我们将序列看作是一个集合,我们需要的是这个集合的一个最小值或者最大值,并且,在它被任意划分成为若干个子集的时候,这些子集的最小值或者最大值我们也是知道的,这些子集被不断划分,我们依然知道这些再次被划分出来的子集的最小值或者最大值。而且我们去想办法去保持这样的一个性质,那么这个问题是不是变得非常好解决了呢?那么问题就转换成了一种集合之间的关系,并且是非常明显的一种包含关系,那么最适合于解决这种集合上的关系的数据结构是什么呢?那么就是树,所以就形成了这样的一种树,他的每一个节点都比它的子节点们小或者大。当我们插入一个新的节点的时候,实际上我们需要去调整的大部分时候只是这棵树上的一条路径,也就是决定它在哪一个集合里面,树上的路径长度相对于这个集合,由于是对数级别的,所以非常可以接受,那么这种数据结构也就应运而生,而这个数据结构为什么叫做堆,那就不知道了。

3、堆的特点

我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子
大顶堆 arr : 50 45 40 20 25 35 30 10 15
小顶堆 arr : 10 20 15 25 50 30 40 35 45
我们用简单的公式来描述一下堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
其中arr[2i+1]是左节点 arr[2i+2]是右节点

4、堆和普通树的区别

内存占用:
普通树占用的内存空间比它们存储的数据要多。你必须为节点对象以及左/右子节点指针分配额外的内存。堆仅仅使用数组,且不使用指针

平衡:
二叉搜索树必须是“平衡”的情况下,其大部分操作的复杂度才能达到O(nlog2n)。你可以按任意顺序位置插入/删除数据,或者使用 AVL 树或者红黑树,但是在堆中实际上不需要整棵树都是有序的。我们只需要满足对属性即可,所以在堆中平衡不是问题。因为堆中数据的组织方式可以保证O(nlog2n) 的性能

搜索:
在二叉树中搜索会很快,但是在堆中搜索会很慢。在堆中搜索不是第一优先级,因为使用堆的目的是将最大(或者最小)的节点放在最前面,从而快速的进行相关插入、删除操作

5、堆排序的过程

堆排序的基本思想 假设是构建大顶堆

1.将待排序的关键字序列(R1,R2,...Rn)构建大顶堆,此堆为初始的无序区.
2.将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
3.由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

举例如下

1.给一个无序序列如下
int a[6] = {7, 3, 8, 5, 1, 2}
2.根据数组将完全二叉树还原出来

现在我们要做的事情就是要把7,3,8,5,1,2变成一个有序的序列,如果想要升序就是1,2,3,5,7,8如果想要降序就是8,7,5,3,2,1这两种就是我们要的最终结果,然后我们就可以根据我们想要的结果来选择
适合类型的堆来进行排序

  • 升序----使用大顶堆

  • 降序----使用小顶堆

3.为什么升序要用大顶堆呢
大顶堆的特点:每个结点的值都大于或等于其左右孩子结点的值,我们把大顶堆构建完毕后根节点的值一定是最大的,然后把根节点和最后一个元素(也可以说最后一个节点)交换位置,那么末尾元素此时就是最大元素了

4.图解交换过程
先要找到最后一个非叶子节点,数组的长度为6,那么最后一个非叶子节点就是:长度/2-1,也就是6/2-1=2,然后下一步就是比较该节点值和它的子树值,如果该节点小于其左\右子树的值就交换(意思就是将最大的值放到该节点)
8只有一个左子树,左子树的值为2,8>2不需要调整

下一步,继续找到下一个非叶子节点
(其实就是当前坐标-1就行了这里为2-1=1),该节点的值为3小于其左子树的值5,交换值,交换后该节点值为5,大于其右子树的值1,不需要交换

交换后

下一步,继续找到下一个非叶子节点1-1=0,该节点的值为7,大于其左子树的值,不需要交换,再看右子树,该节点的值小于右子树的值8,需要交换值

交换后

下一步,检查调整后的子树,是否满足大顶堆性质,如果不满足则继续调整(这里因为只将右子树的值与根节点互换,只需要检查右子树是否满足,而7>2刚好满足大顶堆的性质,就不需要调整了。如果运气不好整个数的根节点的值是1,那么就还需要调整右子树

到这里大顶堆的构建就算完成了
然后下一步交换根节点8与最后一个元素2交换位置(将最大元素"沉"到数组末端),此时最大的元素就归位了,然后对剩下的5个元素重复上面的操作
这里用紫色来表示已经归位的元素

剩下只有5个元素,最后一个非叶子节点是5/2-1=1,该节点的值5大于左子树的值3也大于右子树的值1,满足大顶堆性质不需要交换

找到下一个非叶子节点,该节点的值2小于左子树的值5,交换值,交换后左子树的2不再满足大顶堆的性质再调整左子树,左子树满足要求后再返回去看根节点,根节点的值5小于右子树的值7,再次交换值

得到新的大顶堆,再把根节点的值7与当前数组最后一个元素值1交换,再重构大顶堆->交换值->重构大顶堆->交换值····,直到整个数组都变成有序序列

最后升序

6、堆排序的代码实现

将堆排序的过程分成了两部分,构建一个大顶堆,就沉下去最大值,然后断开与最大值的链接,重新构建大顶堆

// 构建堆
function buildHeap(arr){// 最后一个非叶子节点for(let i = arr.length/2 -1; i >= 0; i--) {headAdjust(arr, i, arr.length)}
}
// 调整函数
function headAdjust(arr, i, n) {// 最后一个非叶子节点let largest = i // largest表示此时最大值的位置// left rootlet l = 2*i + 1let r = 2*i + 2if (l < n &&  arr[l] > arr[largest]) {largest = l}if (r < n && arr[r] > arr[largest]) {largest = r}if(largest != i) {let swap = arr[i]arr[i] = arr[largest]arr[largest] =swapheadAdjust(arr, largest, n )}
}
// 堆排序
function sort(arr){buildHeap(arr)console.log(arr)let n = arr.length;// 依次把最大值沉下去。从右到左断开最后一个元素,重新构造大顶console.log(arr.length)for (let i = arr.length - 1; i >= 0; i--) {console.log(11)let temp = arr[0]arr[0]= arr[i]arr[i] =  tempheadAdjust(arr, 0, i)}return arr
}
let arr = [7, 3, 8, 5, 1, 2 ]
console.log(sort(arr))
// console.log(arr)

堆排序时间复杂度

堆排序的时间复杂度,主要在初始化堆过程和每次选取最大数后重新建堆的过程

初始化建堆过程时间:O(n)

推算过程:
首先要理解怎么计算这个堆化过程所消耗的时间,可以直接画图去理解;
假设高度为k,则从倒数第二层右边的节点开始,这一层的节点都要执行子节点比较然后交换(如果顺序是对的就不用交换);倒数第三层呢,则会选择其子节点进行比较和交换,如果没交换就可以不用再执行下去了。如果交换了,那么又要选择一支子树进行比较和交换;
那么总的时间计算为:

s = 2^( i - 1 ) * ( k - i );

其中 i 表示第几层,2^( i - 1) 表示该层上有多少个元素,( k - i) 表示子树上要比较的次数,如果在最差的条件下,就是比较次数后还要交换;因为这个是常数,所以提出来后可以忽略;

S = 2^(k-2) * 1 + 2(k-3)*2.....+2*(k-2)+2(0)*(k-1) ===> 因为叶子层不用交换,所以 i 从 k-1 开始到 1;

这个等式求解,我想高中已经会了:等式左右乘上2,然后和原来的等式相减,就变成了:

S = 2^(k - 1) + 2^(k - 2) + 2^(k - 3) ..... + 2 - (k-1)

除最后一项外,就是一个等比数列了,直接用求和公式:S = {a1[ 1- (q^n) ] } / (1-q);

S = 2^k -k -1;

又因为k为完全二叉树的深度,所以

(2^k) <= n < (2^k -1 )

总之可以认为:k = logn (实际计算得到应该是 log(n+1) < k <= logn );

综上所述得到:S = n - longn -1,所以时间复杂度为:O(n)

更改堆元素后重建堆时间:O(nlogn)

推算过程:
循环 n -1 次,每次都是从根节点往下循环查找,所以每一次时间是 logn,总时间:

logn(n-1) = nlogn - logn ;

综上所述:建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,所以堆排序的时间复杂度是O(n)+O(nlgn) ~ O(nlgn)

作者:高思阳
链接:https://www.jianshu.com/p/37e717640ff5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

谈谈堆排序,大顶堆,小顶堆相关推荐

  1. 大顶堆小顶堆优先队列

    特性和应用场景 大顶堆小顶堆,也叫优先队列,是一种基于数组+平衡二叉树的数据结构. 主要用于排序,增减操作的速度较快(O(logn)) 适合带有优先级的排序场景,比如处理订单的时候,VIP用户的优先级 ...

  2. 剑指Offer之寻找数据流中的中位数【包含大顶堆小顶堆解释】

    数据流中的中位数 题目描述 题解 最小堆和最大堆解释 参考链接 题目描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值.如果从数据流中读出偶 ...

  3. Java堆排序(大顶堆小顶堆及应用实例)

    自己理解所得,如有错误欢迎随时指摘: 目录: 堆概念 堆结构 堆排序步骤 大顶堆代码.小顶堆代码 实际应用及实例代码 小顶堆删除图解代码.插入代码 小顶堆插入图解 时间复杂度分析 1.百度->概 ...

  4. 大顶堆小顶堆java_《排序算法》——堆排序(大顶堆,小顶堆,Java)

    十大算法之堆排序:堆的定义例如以下: n个元素的序列{k0,k1,...,ki,-,k(n-1)}当且仅当满足下关系时,称之为堆. " ki<=k2i,ki<=k2i+1;或ki ...

  5. java大顶堆小顶堆使用案例

    使用优先级队列实现大小顶堆 例题: class MedianFinder {PriorityQueue<Integer> left; //创建大顶堆PriorityQueue<Int ...

  6. leetcode 面试题 17.14. 最小K个数 大顶堆 小顶堆 快排

    leetcode 面试题 17.14. 最小K个数 [难度:中等] 设计一个算法,找出数组中最小的k个数.以任意顺序返回这k个数均可. 示例: 输入: arr = [1,3,5,7,2,4,6,8], ...

  7. 优先队列 -- 大顶堆,小订堆

    什么是堆(Heap) 优先队列(Priority Queue):特殊的"队列",取出元素的顺序优先权的大小,而不是元素在队列的先后顺序; 1.使用完成二叉树; 2.数组表述; 堆中 ...

  8. 《漫画算法》源码整理-4 大顶堆 小顶堆 优先队列

    堆操作 import java.util.Arrays;public class HeapOperator {/*** 上浮调整* @param array 待调整的堆*/public static ...

  9. 堆排序:大顶堆和小顶堆 + 前K个高频元素

    堆 一.堆排序 小顶堆 举个栗子 大顶堆 二.前K个高频元素 思路分析 三.构造器代码解析 一.堆排序 要了解大顶堆和小顶堆,我们先简单了解一下堆排序. 堆排序(Heapsort)是指利用堆这种数据结 ...

  10. C++大顶堆和小顶堆

    C++大顶堆和小顶堆 原理 大顶堆 小顶堆 大顶堆和小顶堆对比图 大顶堆和小顶堆的实现代码 vector和push_heap.pop_heap实现堆 建堆 调整堆 priority_queue实现堆 ...

最新文章

  1. JS-undefined与null的区别
  2. AndroidVerifyBoot
  3. Synchronized和Lock有什么区别
  4. 小马哥spring编程核心思想_Spring核心思想理解
  5. php数组去重的函数,php数组去重的函数代码
  6. Error: no such column
  7. php正则表达式应用,PHP 正则表达式应用
  8. 【51单片机快速入门指南】2:GPIO LED与按键
  9. brainfuck 在线_酒店在线声誉持久战 重视社媒舆论 保持品牌一致性
  10. 【2016年第4期】分布式协商:建立稳固分布式 大数据系统的基石
  11. vlc-android编译流程
  12. html打印预览出现重叠,html – 使用打印模式css打印网页时页眉和正文内容重叠...
  13. 看了一些东西,发现一些用css实现一些东西的小技巧就记录下来
  14. Github readme语法-- markdown
  15. 把grid第一列设置为行号
  16. Postman安装以及使用
  17. CodeForces - 1384
  18. 止血、回血 苏宁易购正在复苏路上
  19. 硅光电二极管检测电路
  20. 在线考试答题系统,操作简单/实用免费/更新无感知

热门文章

  1. 【工具推荐】常用前端开源静态网站推荐
  2. Java集合这样子学习
  3. 研究区分onbeforeunload事件是刷新还是关闭
  4. 4款暗藏惊喜的Windows软件,硬核又实用,满足你工作中各种需求
  5. 百度对网站就像西门庆对潘金莲
  6. 玲珑学院OJ 1130 - 喵哈哈村的魔法大师╳灬兲笙疯癫°月【强连通+可相交最小路径覆盖+背包】
  7. unsw计算机专业排名,新南威尔士大学UNSW计算机科学Computer Science专业排名第54位(2021年THE世界大学商科排名)...
  8. 淘宝店铺运营经验分享,影响宝贝转化率的因素有哪些,如何提高转化
  9. iOS 模拟各种网络环境
  10. java传纸条问题_小学生考试传纸条作弊,老师看到纸条内容,表示让人“着急”...