JDK 10.0.2

前段时间在网上刷题,碰到一个求中位数的题,看到有网友使用PriorityQueue来实现,感觉其解题思想挺不错的。加上我之前也没使用过PriorityQueue,所以我也试着去读该类源码,并用同样的思想解决了那个题目。现在来对该类做个总结,需要注意,文章内容以算法和数据结构为中心,不考虑其他细节内容。如果小伙伴想看那个题目,可以直接跳转到(小测试)。

目录

一. 数据结构:queue[]、size、comparator
二. 初始化(堆化):heapify()、siftDownComparable(k, e)
三. 添加元素:offer(e)、siftUpUsingComparator(k, e)
四. 索引:indexOf(o)
五. 删除元素:remove(o)、removeAt(i)、removeEq(o)
六. 取堆顶:peek()
七. 删除堆顶:poll()
八. 清除队列:clear()
九. 遍历:iterator()、toArray()、toArray(T[] a)
十. 小测试:数据流中的中位数
一. 数据结构

我只列出了讲解需要的重要属性,不考虑其他细节。PriorityQueue(优先队列)内部是以堆来实现的。为了描述方便,接下来的内容我将用pq[ ]代替queue[ ]。

复制代码
PriorityQueue<E> {
/* 平衡二叉堆 用于存储元素

  • n : 0 -> size-1
  • pq[n].left = pq[2*n+1]
  • pq[n].right = pq[2(n+1)]
    /
    Object[] queue;
    int size; // pq中元素个数
    Comparator<? super E> comparator; // 自定义比较器
    }
    复制代码
    回到目录

二. 初始化(堆化)

如果使用已有集合来构造PriorityQueue,就会用到heapify()来对pq[ ]进行初始化(即:二叉堆化),使其满足堆的性质。而heapify()又通过调用siftDownComparable(k, e)来完成堆化。源码如下:

View Code
如果有自定义比较器的话,调用:siftDownUsingComparator(k, e),否则调用:siftDownComparable(k, e)。这两个方法只是在比较两个元素大小时的表现形式不同,其他内容相同,所以我们只需要看其中一种情况就行。为了描述方便,下面的例子中,我使用Integer作为pq[ ]存储元素类型,所以调用的是siftDownComparable(k, e)。(size >>> 1 表示 size 无符号右移1位,等价于size / 2)

我不会去细抠源码,一行一行地为大家讲解,而是尽量使用简单的例子来展示,我觉得通过例子以及后期大家自己阅读源码,会更容易理解算法内容。

现在我们来看看,使用集合{2, 9, 8, 4, 7, 1, 3, 6, 5}来构造PriorityQueue的过程。算法时间复杂度为O(n),n = size。(时间复杂度证明:《算法导论》(第3版)第6章6.3建堆)

首先,从下到上,从右到左,找到第一个父结点 i,满足规律:i = (size >>> 1) - 1,这里size = 9,i = 3;
比较pq[3, 7, 8]中的元素,将最小的元素pq[x]与堆顶元素pq[3]互换,由于pq[x] = pq[3],所以无互换;
移动到下一个父结点 i = 2,同理,比较pq[2, 5, 6]中的元素,将最小的元素pq[5]与pq[2]互换,后面的操作同理;
需要注意,当pq[1](9)和pq[3](4)互换后(如图2.d),pq[3, 7, 8]违背了最小堆的性质,所以需要进一步调整(向下调整),当调整到叶结点时(i >= size/2)结束;
回到目录

三. 添加元素

添加元素:add(e),offer(e),由于添加元素可能破坏堆的性质,所以需要调用siftUp(i, e)向上调整来维护堆性质。同样,siftUp(i, e)根据有无自定义比较器来决定调用siftUpUsingComparator(k, e)还是siftUpComparable(k, e)。在我举的例子中,使用的是siftUpComparable(k, e)。下面是添加元素的相关源码:

View Code
源码中 grow(i + 1) 是当pq[ ]容量不够时的增长策略,目前可以不用考虑。现在来看往最小堆 pq = {3, 5, 6, 7, 8, 9} 中添加元素 1的过程。算法时间复杂度为O(lgn),n = size。

首先,把要添加的元素 1 放到pq[size],然后调用siftUp(k, e)来维护堆,调整结束后 size++;
向上调整(k, e)时,先找到结点pq[k]的父结点,满足规律 parent = (k - 1) >>> 1,例子中,k = 6, parent = 2;
比较pq[k]与pq[parent],将较小者放到高处,较大者移到低处,例子中,交换pq[6](1)与pq[2](6)的位置;
此次交换结束后,令 k = parent,继续以同样的方法操作,直到 k <= 0 时(到达根结点)结束;
回到目录

四. 索引

indexOf(o)是个私有方法,但好多公开方法中都调用了它,比如:remove(o),contains(o)等,所以在这里也简单提一下。该算法并不复杂。时间复杂度为O(n),n = size。

View Code
indexOf(o)中比较两个元素是否相等,使用的是equals(),而接下来要提的removeEq(o)中直接使用了 == 来判断,请读者注意区别。

回到目录

五. 删除元素

remove(o)、removeEq(o),二者只是在判断两个元素是否相等时使用的方法不同(前者使用equals(),后者使用==),其他内容相同,它们都调用了removeAt(i)来执行删除操作。删除元素后很可能会破坏堆的性质,所以同样需要进行维护。删除元素的维护要比添加元素的维护稍微复杂一点,因为可能同时涉及了:向上调整siftUp和向下调整siftDown。源码如下:

View Code
我们还是通过例子来学习吧,通过对 pq = {0, 1, 7, 2, 3, 8, 9, 4, 5, 6} 进行一系列删除操作,来理解算法的运作过程。算法时间复杂度O(lgn),n = size。

第1步,remove(6),indexOf(6) = 9,removeAt(9)(用r(9)表示,后面同理),由于i = 9为队列末端,删除后不会破坏堆性质,所以可以直接删除;
第2步,remove(1),即r(1),根据图(5.b)可以看出,算法是拿队列尾部pq[8]去替换pq[1],替换后破坏了最小堆的性质,需要向下调整进行维护;
第3步,remove(8),即r(5),使用队列尾部元素pq[7]替换pq[5],替换后破坏了最小堆的性质,需要向上调整进行维护;
回到目录

六. 取堆顶

peek()可以在O(1)的时间复杂度下取到堆顶元素pq[0],看源码一目了然:

View Code
回到目录

七. 删除堆顶

删除堆顶使用poll()方法,其算法思想等价于removeAt(0)(时间复杂度O(lgn)),稍微有点区别的是,其只涉及到向下调整,不涉及向上调整。不清楚的朋友可以参看(五. 删除元素),下面是源码:

View Code
回到目录

八. 清除队列

清除队列clear(),就是依次把pq[i]置为null,然后size置0,但是pq.length没有改变。时间复杂度为O(n),n = size。源码如下:

View Code
回到目录

九. 遍历

可以使用迭代器(Iterator)来遍历pq[ ]本身,或者调用toArray()、toArray(T[] a)方法来生成一个pq[ ]的副本进行遍历。遍历本身的时间复杂度为O(n),n = size。

使用迭代器遍历 pq = {0, 1, 7, 2, 3, 8, 9, 4, 5, 6},方法如下:

View Code
通过拷贝pq[ ]副本来遍历,方法如下:

View Code
在使用toArray(T[] a)拷贝来进行遍历时,需要注意(x表示PriorityQueue对象):

如果ins[ ]的容量大于x.size(),请使用for (int i = 0; i < x.size(); i++) 来遍历,否则可能会获取到多余的数据;或者你使用for (int a : ins)来遍历时,可能导致NullPointerException异常;
请使用 ins = x.toArray(ins) 的写法来确保正确获取到pq[ ]副本。当ins[ ]容量大于x.size()时,写为 x.toArray(ins) 能正确获取到副本,但当ins[ ]容量小于x.size()时,该写法就无法正确获取副本。因为此情况下toArray(T[] a)内部会重新生成一个大小为x.size()的Integer数组进行拷贝,然后return该数组;
toArray(T[] a)源码如下:

View Code
回到目录

十. 小测试

下面来说说文章开头我提到的那个题目吧,如下(点击这里在线做题)(请使用PriorityQueue来完成):

复制代码
/ 数据流中的中位数
题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。
如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,
使用GetMedian()方法获取当前读取数据的中位数。
/

public class Solution {
public void Insert(Integer num) {}
public Double GetMedian() {}
}
复制代码
我写的参考代码(带解析),如下: 欢迎工作一到五年的Java工程师朋友们加入Java群: 891219277
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

View Code

转载于:https://blog.51cto.com/14084556/2326012

Java - PriorityQueue相关推荐

  1. Java PriorityQueue PriorityBlockingQueue

    2019独角兽企业重金招聘Python工程师标准>>> Java PriorityQueue && PriorityBlockingQueue 我们知道队列是遵循先进 ...

  2. Java PriorityQueue实现大顶堆

    Java中PriorityQueue通过二叉小顶堆实现,可以用一棵完全二叉树表示.PriorityQueue位于Java util包中,实际上这个队列就是具有"优先级".既然具有优 ...

  3. Java PriorityQueue clear()方法与示例

    PriorityQueue类clear()方法 (PriorityQueue Class clear() method) clear() method is available in java.uti ...

  4. Java PriorityQueue poll()方法与示例

    PriorityQueue类poll()方法 (PriorityQueue Class poll() method) poll() method is available in java.util p ...

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

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

  6. Java PriorityQueue优先队列详解(源码+图文步骤解析)

    文章目录 1.概述 2.入队分析 3.出队分析 4.总结 1.概述 PriorityQueue 称为优先队列,也是一种特殊的有序队列.为什么特殊呢? 因为其内部使用 Object[] 数组来存储数据, ...

  7. Java PriorityQueue

    heap inside Queue<Integer> qi = new PriorityQueue<Integer>(); 常用函数 add() poll() peek() r ...

  8. java中priorityqueue_详解JAVA中priorityqueue的具体使用

    Java中PriorityQueue通过二叉小顶堆实现,可以用一棵完全二叉树表示.本文从Queue接口函数出发,结合生动的图解,深入浅出地分析PriorityQueue每个操作的具体过程和时间复杂度, ...

  9. class转java_[拒绝套路,纯干货]这一百多道 Java 基础问题你掌握了吗?

    码农每日一题长按关注,工作日分享一些技术知识点. [每日一题]Java 基本数据类型基础面试题 [每日一题]Java 包装类型装箱拆箱基础面试题 [每日一题]Java 字符串(Part 1)相关面试题 ...

最新文章

  1. 计算机基本知识培训稿,计算机基础知识培训稿.doc
  2. 剑指offer-有序二维数组中的查找
  3. specgram python
  4. 无影云电脑居家办公最佳实践(便捷账号)
  5. gff文件_根据gff/gtf等注释文件取负链上的序列:先反向互补染色体再截取?还是先截取区间再反向互补序列?...
  6. java求sin函数咋写_5类“隐含条件”,题干不写但是你要会用(解三角形知识整合,建议收藏)| 真题精讲-16...
  7. silvaco-mobility models(1)
  8. 手动修改dns服务器设置,手动设置dns地址服务器
  9. MDK3358平台QT示例-ADS1110温度采集示例
  10. word服务器无响应又没有保存怎么办,电脑突然死机Word没保存?教你一个小妙招,三秒快速恢复...
  11. 求两个球面坐标点(经纬度)之间的距离
  12. Vue中使用纯CSS实现全屏网格加渐变色背景布局
  13. 使用Perl编写协议分析脚本
  14. 心态很容易受别人影响_孩子在学校受了委屈,家长该如何做?
  15. linux权限s的作用,lqc|_高级权限控制之su、sudo、特殊权限s及t位、facl
  16. Mock模拟数据的使用
  17. 压缩包打开密码解决办法
  18. android横向滑动换页,[转载]Android 左右滑动切换页面或Activity的效果实现
  19. Win7启用远程桌面批处理命令
  20. 五种全面质量管理工具

热门文章

  1. c++:用顺序表实现简单的栈
  2. VMware三种上网模型
  3. 操作系统选择成固定模式 HTML5是潜在方向
  4. linux常用命令1
  5. Apache Solr schema.xml及solrconfig.xml文件中文注解
  6. 微软宣布Azure Migrate和Site Recovery服务增强功能
  7. 791. Custom Sort String
  8. App上架应用市场,如何攻破安全过检难题
  9. [BZOJ 1014][JSOI2008]火星人prefix(Splay+二分+hash)
  10. Python导入其他文件中的.py文件 即模块