什么是堆?

堆其实就是一种特殊的队列——优先队列。

普通的队列游戏规则很简单:就是先进先出;但这种优先队列搞特殊,不是按照进队列的时间顺序,而是按照每个元素的优先级来比拼,优先级高的在堆顶

这也很容易理解吧,比如各种软件都有会员制度,某软件用了会员就能加速下载的,不同等级的会员速度还不一样,那就是优先级不同呀。

还有其实每个人回复微信消息也是默默的把消息放进堆里排个序:先回男朋友女朋友的,然后再回其他人的。

这里要区别于操作系统里的那个“堆”,这两个虽然都叫堆,但是没有半毛钱关系,都是借用了 Heap 这个英文单词而已。


我们再来回顾一下「」在整个 Java 集合框架中的位置:


也就是说,

  • PriorityQueue 是一个类 (class);
  • PriorityQueue 继承自 Queue 这个接口 (Interface);

那 heap 在哪呢?

heap 其实是一个抽象的数据结构,或者说是逻辑上的数据结构,并不是一个物理上真实存在的数据结构。

heap 其实有很多种实现方式,比如 binomial heap, Fibonacci heap 等等。但是面试最常考的,也是最经典的,就是 binary heap 二叉堆,也就是用一棵完全二叉树来实现的。

那完全二叉树是怎么实现的?

其实是用数组来实现的!

所以 binary heap/PriorityQueue 实际上是用数组来实现的。

这个数组的排列方式有点特别,因为它总会维护你定义的(或者默认的)优先级最高的元素在数组的首位,所以不是随便一个数组都叫「堆」,实际上,它在你心里,应该是一棵「完全二叉树」。

这棵完全二叉树,只存在你心里和各大书本上;实际在在内存里,哪有什么树?就是数组罢了。

那为什么完全二叉树可以用数组来实现?是不是所有的树都能用数组来实现?

这个就涉及完全二叉树的性质了,我们下一篇会细讲,简单来说,因为完全二叉树的定义要求了它在层序遍历的时候没有气泡,也就是连续存储的,所以可以用数组来存放;第二个问题当然是否。

堆的特点

  1. 堆是一棵完全二叉树;

  2. 堆序性 (heap order): 任意节点都优于它的所有孩子

    a. 如果是任意节点都大于它的所有孩子,这样的堆叫大顶堆,Max Heap;

    b. 如果是任意节点都小于它的所有孩子,这样的堆叫小顶堆,Min Heap;


左图是小顶堆,可以看出对于每个节点来说,都是小于它的所有孩子的,注意是所有孩子,包括孙子,曾孙...

  1. 既然堆是用数组来实现的,那么我们可以找到每个节点和它的父母/孩子之间的关系,从而可以直接访问到它们。

比如对于节点 3 来说,

  • 它的 Index = 1,
  • 它的 parent index = 0,
  • 左孩子 left child index = 3,
  • 右孩子 right child index = 4.

可以归纳出如下规律:

  • 设当前节点的 index = x,
  • 那么 parent index = (x-1)/2,
  • 左孩子 left child index = 2*x + 1,
  • 右孩子 right child index = 2*x + 2.

有些书上可能写法稍有不同,是因为它们的数组是从 1 开始的,而我这里数组的下标是从 0 开始的,都是可以的。

这样就可以从任意一个点,一步找到它的孙子、曾孙子,真的太方便了,在后文讲具体操作时大家可以更深刻的体会到。

基本操作

任何一个数据结构,无非就是增删改查四大类:

功能 方法 时间复杂度
offer(E e) O(logn)
poll() O(logn)
无直接的 API 删 + 增
peek() O(1)

这里 peek() 的时间复杂度很好理解,因为堆的用途就是能够快速的拿到一组数据里的最大/最小值,所以这一步的时间复杂度一定是 O(1) 的,这就是堆的意义所在。

那么我们具体来看 offer(E e)poll() 的过程。

offer(E e)

比如我们新加一个 0 到刚才这个最小堆里面:


那很明显,0 是要放在最上面的,可是,直接放上去就不是一棵完全二叉树了啊。。

所以说,

  • 我们先保证加了元素之后这棵树还是一棵完全二叉树,
  • 然后再通过 swap 的方式进行微调,来满足堆序性。

这样就保证满足了堆的两个特点,也就是保证了加入新元素之后它还是个堆

那具体怎么做呢:

Step 1.

先把 0 放在最后接上,别一上来就想着上位;


OK!总算先上岸了,然后我们再一步步往上走。

这里「能否往上走」的标准在于:是否满足堆序性

也就是说,现在 5 和 0 之间不满足堆序性,那么交换位置,换到直到满足堆序性为止

这里对于最小堆来说的堆序性,就是小的数要在上面

Step 2. 与 5 交换


此时 0 和 3 不满足堆序性了,那么再交换。

Step 3. 与 3 交换


还不行,0 还比 1 小,所以继续换。

Step 4. 与 1 交换


OK!这样就换好了,一个新的堆诞生了~

总结一下这个方法:

先把新元素加入数组的末尾,再通过不断比较与 parent 的值的大小,决定是否交换,直到满足堆序性为止。

这个过程就是 siftUp(),源码如下:


时间复杂度

这里不难发现,其实我们只交换了一条支路上的元素,


也就是最多交换 O(height) 次。

那么对于完全二叉树来说,除了最后一层都是满的,O(height) = O(logn)

所以 offer(E e) 的时间复杂度就是 O(logn) 啦。

poll()

poll() 就是把最顶端的元素拿走。

对了,没有办法拿走中间的元素,毕竟要 VIP 先出去,小弟才能出去。

那么最顶端元素拿走后,这个位置就空了:


我们还是先来满足堆序性,因为比较容易满足嘛,直接从最后面拿一个来补上就好了,先放个傀儡上来。

Step1. 末尾元素上位


这样一来,堆序性又不满足了,开始交换元素。

那 8 比 7 和 3 都大,应该和谁交换呢?

假设与 7 交换,那么 7 还是比 3 大,还得 7 和 3 换,麻烦。

所以是与左右孩子中较小的那个交换。

Step 2. 与 3 交换


下去之后,还比 5 和 4 大,那再和 4 换一下。

Step 3. 与 4 交换


OK!这样这棵树总算是稳定了。

总结一下这个方法:

先把数组的末位元素加到顶端,再通过不断比较与左右孩子的值的大小,决定是否交换,直到满足堆序性为止。

这个过程就是 siftDown(),源码如下:


时间复杂度

同样道理,也只交换了一条支路上的元素,也就是最多交换 O(height) 次。

所以 offer(E e) 的时间复杂度就是 O(logn) 啦。

heapify()

还有一个大名鼎鼎的非常重要的操作,就是 heapify() 了,它是一个很神奇的操作,

可以用 O(n) 的时间把一个乱序的数组变成一个 heap。

但是呢,heapify() 并不是一个 public API,看:


所以我们没有办法直接使用。

唯一使用 heapify() 的方式呢,就是使用PriorityQueue(Collection extends E> c)

这个 constructor 的时候,人家会自动调用 heapify() 这个操作。

那具体是怎么做的呢?

哈哈源码已经暴露了:

从最后一个非叶子节点开始,从后往前做 siftDown().

因为叶子节点没必要操作嘛,已经到了最下面了,还能和谁 swap?

举个例子:


我们想把这个数组进行 heapify() 操作,想把它变成一个最小堆,拿到它的最小值。

那就要从 3 开始,对 3,7,5进行 siftDown().

Step 1.


尴尬

优先队列默认是小顶堆吗_堆和堆傻傻分不清?进来!包教会!相关推荐

  1. 优先队列默认是小顶堆吗_一分钟带你读懂什么是堆?

    堆其实就是一种特殊的队列--优先队列. 普通的队列游戏规则很简单:就是先进先出:但这种优先队列搞特殊,不是按照进队列的时间顺序,而是按照每个元素的优先级来比拼,优先级高的在堆顶. 这也很容易理解吧,比 ...

  2. java 二叉堆 建立_二叉堆创建算法以及 java源程序

    java程序实现: 主要的功能函数: public static int left(int i){ return 2*i+1; } public static int right(int i){ re ...

  3. 房子网签后房产局查不到信息_日本房子:1R、1LDK分不清?凶宅如何分辨?

    别说初次接触日本房产相关问题时会被搞的一头雾水,就算是在日本生活了一阵子,也不一定能弄清楚"1R.1K.1DK.1LDK.1LDK"等都代表了什么呢.另外,大家最担心的就是,万一住 ...

  4. bread是可数还是不可数_可数名词不可数名词分不清?出题老师告诉你方法

    我是瓶子老师.我有10多年初中英语教学经验,参与过中考一模.二模命题,同时我也是<学生双语报>的撰稿人,为本市超过5万名中考生出过中考模拟试题. 在初中英语的学习过程中,我们会讲到可数名词 ...

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

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

  6. python中大顶堆和小顶堆

    python中默认是小顶堆,解决的是最大的k问题 想要变成大顶堆,java里面直接在参数里面用lambda表达式解决即可,但是python里面没有,此时需要借助一些奇淫技巧,那就是给你的那个目标值添加 ...

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

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

  8. 大顶堆及小顶堆求最大或最小K个数

    小顶堆 根节点数值小于或等于左右孩子节点数值,其中顶节点数最小 大顶堆 根节点数值大于或等于左右孩子节点数值,其中顶节点数最大 PriorityQueue PriorityQueue为优先级队列,遵循 ...

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

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

最新文章

  1. java 数组的基本操作
  2. 客户服务业热切渴望“按需客户体验”模式
  3. 沈向洋出任董事长李笛任CEO,「微软」小冰变身「中国」小冰
  4. Jquery mobile技术咖们走进来瞧瞧吧
  5. 2017 年 VR 将走的 3 个方向 你更认可哪一个?
  6. 视频编码中常用熵编码介绍
  7. 3.3 目标检测-深度学习第四课《卷积神经网络》-Stanford吴恩达教授
  8. keySet和Map.Entry的理解
  9. 八、Flume的构架,安装和基本使用
  10. subprocess installed post-installation script returned error exit status 127
  11. android linux截图库,Android中截图(surfaceView)源码
  12. python3.4安装matplotlib_在python3.7下怎么安装matplotlib
  13. Python——匿名函数lambda
  14. 锐捷服务器虚拟化技术_用它!锐捷“双擎”云桌面助力检察机关统一业务应用系统2.0上线...
  15. PHP设计模式——概述
  16. Access优已成忧,一年后,还是离开了秋色园了
  17. win7不支持新主板键盘鼠标没驱动怎么办?看一篇就够了。
  18. 公众号推送长图最佳尺寸_微信公众平台图片尺寸是多少
  19. Vim快捷键(一):文档操作与文本插入
  20. WPF实现VS界面效果

热门文章

  1. Mac升级Catalina,根目录下无法创建个人文件夹
  2. 撸一个聊天室(vue+koa2+websokect+mongodb)
  3. web ui 套件_复古UI套件
  4. 使用C大调编排一首歌曲
  5. matplotlib的简介
  6. VMware虚拟机安装Linux系统(详细版)
  7. 人工智能期末考试复习(贲可荣 张彦铎)
  8. 使用微信号开通检测软件的成功案例(一)
  9. 火车票订票小助手,帮助了很多同事和朋友,安全无毒
  10. 19年电赛B题设计总结