二叉堆详解实现优先级队列

文章目录

  • 二叉堆详解实现优先级队列
  • 一、二叉堆概览
  • 二、优先级队列概览
  • 三、实现 swim 和 sink
  • 四、实现 delMax 和 insert
  • 五、最后总结

二叉堆(Binary Heap)没什么神秘,性质比二叉搜索树 BST 还简单。其主要操作就两个,sink(下沉)和 swim(上浮),用以维护二叉堆的性质。其主要应用有两个,首先是一种排序方法「堆排序」,第二是一种很有用的数据结构「优先级队列」。

一、二叉堆概览

  • 首先,二叉堆和二叉树有啥关系呢,为什么人们总数把二叉堆画成一棵二叉树?
  • 因为,二叉堆其实就是一种特殊的二叉树(完全二叉树),只不过存储在数组里。一般的链表二叉树,我们操作节点的指针,而在数组里,我们把数组索引作为指针:
// 父节点的索引
int parent(int root) {return root / 2;
}
// 左孩子的索引
int left(int root) {return root * 2;
}
// 右孩子的索引
int right(int root) {return root * 2 + 1;
}

画个图你立即就能理解了,注意数组的第一个索引 0 空着不用,


PS:因为数组索引是数组,为了方便区分,将字符作为数组元素。

  • 你看到了,把 arr[1]作为整棵树的根的话,每个节点的父节点和左右孩子的索引都可以通过简单的运算得到,这就是二叉堆设计的一个巧妙之处。
  • 为了方便讲解,下面都会画的图都是二叉树结构,相信你能把树和数组对应起来。
  • 二叉堆还分为最大堆和最小堆。最大堆的性质是:每个节点都大于等于它的两个子节点。类似的,最小堆的性质是:每个节点都小于等于它的子节点
  • 两种堆核心思路都是一样的,本文以最大堆为例讲解。
  • 对于一个最大堆,根据其性质,显然堆顶,也就是 arr[1] 一定是所有元素中最大的元素

二、优先级队列概览

  • 优先级队列这种数据结构有一个很有用的功能,你插入或者删除元素的时候,元素会自动调整,这底层的原理就是二叉堆的操作。
  • 数据结构的功能无非增删查该,优先级队列有两个主要 API,分别是 insert 插入一个元素和 delMax 删除最大元素(如果底层用最小堆,那么就是 delMin)
  • 下面我们实现一个简化的优先级队列,先看下代码框架:
public class MaxPQ<Key extends Comparable<Key>> {// 存储元素的数组private Key[] pq;// 当前 Priority Queue 中的元素个数private int N = 0;public MaxPQ(int cap) {// 索引 0 不用,所以多分配一个空间pq = (Key[]) new Comparable[cap + 1];}/* 返回当前队列中最大元素 */public Key max() {return pq[1];}/* 插入元素 e */public void insert(Key e) {...}/* 删除并返回当前队列中最大元素 */public Key delMax() {...}/* 上浮第 k 个元素,以维护最大堆性质 */private void swim(int k) {...}/* 下沉第 k 个元素,以维护最大堆性质 */private void sink(int k) {...}/* 交换数组的两个元素 */private void exch(int i, int j) {Key temp = pq[i];pq[i] = pq[j];pq[j] = temp;}/* pq[i] 是否比 pq[j] 小? */private boolean less(int i, int j) {return pq[i].compareTo(pq[j]) < 0;}/* 还有 left, right, parent 三个方法 */
}

三、实现 swim 和 sink

  • 为什么要有上浮 swim 和下沉 sink 的操作呢?为了维护堆结构
  • 我们要讲的是最大堆,每个节点都比它的两个子节点大,但是在插入元素和删除元素时,难免破坏堆的性质,这就需要通过这两个操作来恢复堆的性质
  • 对于最大堆,会破坏堆性质的有有两种情况:
  • 如果某个节点 A 比它的子节点(中的一个)小,那么 A 就不配做父节点,应该下去,下面那个更大的节点上来做父节点,这就是对 A 进行下沉。
  • 如果某个节点 A 比它的父节点大,那么 A 不应该做子节点,应该把父节点换下来,自己去做父节点,这就是对 A 的上浮。
  • 当然,错位的节点 A 可能要上浮(或下沉)很多次,才能到达正确的位置,恢复堆的性质。所以代码中肯定有一个 while 循环。
  • 细心的读者也许会问,这两个操作不是互逆吗,所以上浮的操作一定能用下沉来完成,为什么我还要费劲写两个方法?
  • 是的,操作是互逆等价的,但是最终我们的操作只会在堆底和堆顶进行(等会讲原因),显然堆底的「错位」元素需要上浮,堆顶的「错位」元素需要下沉。
  • 上浮的代码实现:
private void swim(int k) {// 如果浮到堆顶,就不能再上浮了while (k > 1 && less(parent(k), k)) {// 如果第 k 个元素比上层大// 将 k 换上去exch(parent(k), k);k = parent(k);}
}



  • 下沉的代码实现:
  • 下沉比上浮略微复杂一点,因为上浮某个节点 A,只需要 A 和其父节点比较大小即可;但是下沉某个节点 A,需要 A 和其两个子节点比较大小,如果 A 不是最大的就需要调整位置,要把较大的那个子节点和 A 交换
private void sink(int k) {// 如果沉到堆底,就沉不下去了while (left(k) <= N) {// 先假设左边节点较大int older = left(k);// 如果右边节点存在,比一下大小if (right(k) <= N && less(older, right(k)))older = right(k);// 结点 k 比俩孩子都大,就不必下沉了if (less(older, k)) break;// 否则,不符合最大堆的结构,下沉 k 结点exch(k, older);k = older;}
}




至此,二叉堆的主要操作就讲完了,一点都不难吧,代码加起来也就十行。明白了 sink 和 swim 的行为,下面就可以实现优先级队列了

四、实现 delMax 和 insert

  • 这两个方法就是建立在 swim 和 sink 上的。
  • insert 方法先把要插入的元素添加到堆底的最后,然后让其上浮到正确位置


public void insert(Key e) {N++;// 先把新元素加到最后pq[N] = e;// 然后让它上浮到正确的位置swim(N);
}
  • delMax 方法先把堆顶元素 A 和堆底最后的元素 B 对调,然后删除 A,最后让 B 下沉到正确位置。
public Key delMax() {// 最大堆的堆顶就是最大元素Key max = pq[1];// 把这个最大元素换到最后,删除之exch(1, N);pq[N] = null;N--;// 让 pq[1] 下沉到正确位置sink(1);return max;
}




  • 至此,一个优先级队列就实现了,插入和删除元素的时间复杂度为O(logK),K为当前二叉堆(优先级队列)中的元素总数。因为我们时间复杂度主要花费在 sink 或者 swim上,而不管上浮还是下沉,最多也就树(堆)的高度,也就是 log 级别。

五、最后总结

  • 二叉堆就是一种完全二叉树,所以适合存储在数组中,而且二叉堆拥有一些特殊性质。
  • 二叉堆的操作很简单,主要就是上浮和下沉,来维护堆的性质(堆有序),核心代码也就十行。
  • 优先级队列是基于二叉堆实现的,主要操作是插入和删除。插入是先插到最后,然后上浮到正确位置;删除是调换位置后再删除,然后下沉到正确位置。核心代码也就十行。
  • 也许这就是数据结构的威力,简单的操作就能实现巧妙的功能,真心佩服发明二叉堆算法的人!

二叉堆详解实现优先级队列相关推荐

  1. 二叉堆(TopK问题,优先级队列)

    目录 实现一个大根堆 优先级队列 Comparable和Compator区别 compareTo方法 TopK问题 TopK问题常见题型为求最大(最小)的K个值. 我们一般拿堆来解决. 堆:二叉堆首先 ...

  2. AVL树(二叉平衡树)详解与实现

    公众号文章链接 AVL树概念 前面学习二叉查找树和二叉树的各种遍历,但是其查找效率不稳定(斜树),而二叉平衡树的用途更多.查找相比稳定很多.(欢迎关注数据结构专栏) AVL树是带有平衡条件的二叉查找树 ...

  3. 二叉搜索树详解--实现插入和删除

    目录 BST树概念 BST树操作 BST树的查找 BST树的插入 BST树的删除 实现一个自己的BST树 BSTNode类和BSTree类 查找操作; 插入操作: 删除操作: 应用: 二叉搜索树性能分 ...

  4. java二叉搜索树详解

    文章目录 一.概念 二.相关操作 2.0节点相关代码: 2.1查找 2.2插入 2.3删除(重难点) 2.4测试用例 三.小结 提示:以下是本篇文章正文内容,下面案例可供参考 一.概念 二叉搜索树又称 ...

  5. 《恋上数据结构第1季》二叉堆实现优先级队列

    优先级队列(Priority Queue) 优先级队列简介 优先队列的底层实现 二叉堆实现优先级队列源码 测试代码 数据结构与算法笔记目录:<恋上数据结构> 笔记目录 想加深 Java 基 ...

  6. Java PriorityQueue(优先级队列/二叉堆)的使用及题目应用

    目录 PriorityQueue有几个需要注意的点: 重写比较器的方法 应用题目 LeetCode 1845. 座位预约管理系统 LeetCode 215. 数组中的第 K 个最大元素(同剑指 Off ...

  7. 大根堆的删除c语言,二叉堆(一)之 C语言详解

    本文介绍二叉堆,二叉堆就是通常我们所说的数据结构"堆"中的一种.和以往一样,本文会先对二叉堆的理论知识进行简单介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现: ...

  8. 【数据结构与算法】二项队列与二叉堆的比较

    导语 二叉堆确实是入门级的重要数据结构了,而二项队列也是慢慢要去掌握的一种支持高效合并的优先队列实现.本文稍作比较,望抛砖引玉. 列个表格比较基本操作性能 基本操作 insert(平均) delete ...

  9. 排序算法之——优先队列经典实现(基于二叉堆)

    许多应用都需要处理有序的元素,但有时,我们不要求所有元素都有序,或是一定要一次就将它们排序,许多情况下,我们会收集这些元素里的最大值或最小值. 这种情况下一个合适的数据结构应该支持两种操作:插入元素. ...

最新文章

  1. C#中将dll汇入exe,并加壳
  2. asp.net的几个错误
  3. CTF(pwn) 堆利用 之 unlink 介绍
  4. BugkuCTF-reverse:Easy_vb + Easy_Re
  5. 4gl 上传文件_文件管理密码相册工具app下载-文件管理密码相册工具app安卓版下载v1.8...
  6. iOS和android的屏幕适配
  7. 微软将人工智能嵌入Windows 10更新
  8. android蓝牙传输的是字符吗,Android蓝牙接收到的串行数据乱码
  9. web存储机制localStorage和sessionStorage
  10. Docker存储卷基本操作
  11. 2012二级java真题_2012年计算机二级JAVA模拟试题及答案详解汇总
  12. 剑指offer面试题48. 最长不含重复字符的子字符串(滑动窗口)
  13. 斐讯盒子T1_【YYF固件】夏杰语音实用版刷机固件及教程分享
  14. linux rz sz使用
  15. arXiv 注册完整过程(图文详解)
  16. 牛客网软通动力软件测试机试_软件测试员工作经验分享?
  17. python中正实数怎么表示_是的”数学地板(x) “和”int(x)“在Python中对正实数产生不同的结果?...
  18. WPF 开源二维绘画小工具 GeometryToolDemo 项目
  19. springboot采用协同过滤算法的家政服务平台的设计与实现毕业设计源码260839
  20. 知识图谱论文阅读(八)【转】推荐系统遇上深度学习(二十六)--知识图谱与推荐系统结合之DKN模型原理及实现

热门文章

  1. python导出数据找不到csv_找不到Python/CSV文件
  2. matlab插值与拟合例题_菜鸟进阶系列:MATLAB数学建模·数据插值与拟合
  3. 200806C阶段一结构体
  4. 基于matlab 读取文件夹 保存文件夹
  5. 浅谈JAVA中如何利用socket进行网络编程(二)
  6. POJ 3259 Wormholes【最短路/SPFA判断负环模板】
  7. 远程管理,无需在机房来回穿梭
  8. 疯狂了!当游戏爱上MongoDB会怎么样???
  9. readSerializableObj
  10. Exchange 2007 申请多域名证书