Java - PriorityQueue 优先队列

文章介绍了如何使用Java的PriorityQueue解决数据流中的中位数计算问题,通过维护一个大顶堆和一个小顶堆来保持平衡,确保能快速获取当前中位数。在插入元素和获取中位数时,均保证了O(log(n))的时间复杂度。
摘要由CSDN通过智能技术生成

Java - PriorityQueue 优先队列

最近遇到一道算法题,说难其实也不难,但是用之前的思路和知识总感觉很麻烦很耗时。在看高手解析时发现了一种新思路,即使用PriorityQueue来取最大值和最小值。在此记录。

题目描述

题目链接-牛客网

数据流中的中位数
描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
示例1
输入:
[5,2,3,4,1,6,7,0,8]
返回值:
"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "
说明:
数据流里面不断吐出的是5,2,3...,则得到的平均数分别为5,(5+2)/2,3...
示例2
输入:
[1,1,1]
返回值:
"1.00 1.00 1.00 "

解题思路

/* 大顶堆,存储左半边元素 */
private PriorityQueue<Integer> left =new PriorityQueue<>((o1, o2)-> o2 - o1);
/* 小顶堆,存储右半边元素,并且右半边元素都大于左半边 */
private PriorityQueue<Integer> right =new PriorityQueue<>();
/* 当前数据流读入的元素个数 */
private int N=0;
public void Insert(Integer val){
/* 插入要保证两个堆存于平衡状态 */
if(N%2==0){
        /* N 为偶数的情况下插入到右半边。
         * 因为右半边元素都要大于左半边,但是新插入的元素不一定比左半边元素来的大,
         * 因此需要先将元素插入左半边,然后利用左半边为大顶堆的特点,取出堆顶元素即为最大元素,此时插入右半边 */
        left.add(val);
        right.add(left.poll());
    }else{
        right.add(val);
        left.add(right.poll());
    }
    N++;
}
public Double GetMedian(){
    if( N % 2 == 0 ) return(left.peek()+ right.peek())/2.0;
    else return(double) right.peek();
}

PriorityQueue介绍

PriorityQueue(优先队列)是一种特殊的队列,它可以保证每次取出的堆顶元素都是最小值(或者最大值)。其通过堆实现,具体说是通过完全二叉树(complete binary tree)实现的小顶堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值),也就意味着可以通过数组来作为PriorityQueue的底层实现。

特点:

  • PriorityQueue是一个无限制的队列,并且动态增长。

  • 它不允许null对象。

  • 添加到PriorityQueue的对象必须具有可比性。

  • 默认情况下,优先级队列的对象按自然顺序排序

  • 比较器可用于队列中对象的自定义排序。

  • 优先级队列的头部是基于自然排序或基于比较器的排序的最小元素。当我们轮询队列时,它从队列中返回头对象。

  • 如果存在多个具有相同优先级的对象,则它可以随机轮询其中任何一个。

  • PriorityQueue 不是线程安全的PriorityBlockingQueue在并发环境中使用。

  • 它为add和poll方法提供了**O(log(n))**时间。

方法:

  • add()和offer()

add(E e)和offer(E e)的语义相同,都是向优先队列中插入元素,只是Queue接口规定二者对插入失败时的处理不同,前者在插入失败时抛出异常,后则则会返回false。对于PriorityQueue这两个方法其实没什么差别。

  • element()和peek()

element()和peek()的语义完全相同,都是获取但不删除队首元素,也就是队列中权值最小的那个元素,二者唯一的区别是当方法失败时前者抛出异常,后者返回null。根据小顶堆的性质,堆顶那个元素就是全局最小的那个;由于堆用数组表示,根据下标关系,0下标处的那个元素既是堆顶元素。所以直接返回数组0下标处的那个元素即可

  • remove()和poll()

remove()和poll()方法的语义也完全相同,都是获取并删除队首元素,区别是当方法失败时前者抛出异常,后者返回null。由于删除操作会改变队列的结构,为维护小顶堆的性质,需要进行必要的调整。

  • remove(Object object)

remove(Object o)方法用于删除队列中跟o相等的某一个元素(如果有多个相等,只删除一个),该方法不是Queue接口内的方法,而是Collection接口的方法。由于删除操作会改变队列结构,所以要进行调整;又由于删除元素的位置可能是任意的,所以调整过程比其它函数稍加繁琐。具体来说,remove(Object o)可以分为2种情况:1. 删除的是最后一个元素。直接删除即可,不需要调整。2. 删除的不是最后一个元素,从删除点开始以最后一个元素为参照调用一次siftDown()即可。此处不再赘述。

代码实现

参考文献:

[41.1 数据流中的中位数 | CS-Notes (cyc2018.xyz)](http://www.cyc2018.xyz/算法/剑指 Offer 题解/41.1 数据流中的中位数.html)

JCFInternals/8-PriorityQueue.md at master · CarpenterLee/JCFInternals (github.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值