Java集合之PriorityQueue

优先队列相较于队列的区别就在于优先队列最先出队的总是优先级最高的元素

Java提供了PriorityQueue类实现优先队列,由于它实现了Queue接口,也可以通过Queue引用

Queue<Integer> priorityQueue = new PriorityQueue<>((a,b)->b-a);

不同于Queue,定义PriorityQueue时需要传入一个比较器Comparator,这个比较器将决定元素的优先级,决定方式类似于Listsort()方法,也就是当传入ab时,如果a优先度更高,就会返回负数,如果b优先度高就返回正数,相等就返回0。上面的例子就是表示数值大的优先度更高

也可以不传入Comparator,这样的话它的元素就需要实现Comparable接口,例如

Queue<Integer> priorityQueue = new PriorityQueue<>();

Integer本身也实现了Comparable接口,比较方法如下

public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

因此上面的priorityQueue中数值小的优先度更高

实例:

Queue<Integer> priorityQueue = new PriorityQueue<>();
for(int i=0;i<10;i++){
    priorityQueue.add((int)(Math.random()*40+1));
}
System.out.println(String.format("list:%s", priorityQueue));
// list:[6, 8, 19, 18, 15, 35, 27, 40, 30, 36]

System.out.println(priorityQueue.poll()); // 6
System.out.println(priorityQueue.poll()); // 8
System.out.println(priorityQueue.poll()); // 15

实现原理

优先队列实现的效果就像是将其排序,但实际上它是一个,堆是一个完全二叉树,以最大堆为例,它的父节点的值总是大于子节点的值,在优先队列中,就是父节点的优先级总是大于子节点的优先级。在取元素时,总是取根节点的值,再调整堆使其符合堆的性质,这样就使得每次取出的都是优先度最高的元素

堆具有极高的效率,添加元素或取出一个根节点元素并维护堆的性质只需要log2n的时间复杂度,n是二叉树的高度

PriorityQueue中,使用一个数组Object[] queue储存元素,这是因为我们可以通过计算获得父节点和子节点的值:

  • 父节点:(i-1)/2
  • 左子节点:i*2+1
  • 右子节点:i*2+2
offer()

PriorityQueue中添加元素时,如果队列为空,就直接加入,非空则先加入队列末尾,再调整堆

private void siftUpComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>) x;
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        if (key.compareTo((E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = key;
}

上述为PriorityQueue调整堆的过程:

  1. 获取父节点并与其比较
  2. 如果优先级小于父节点则结束
  3. 如果优先级大于父节点,与父节点交换,重复上面的操作一直向上至根节点
poll()
public E poll() {
    if (size == 0)
        return null;
    int s = --size;
    modCount++;
    E result = (E) queue[0];
    E x = (E) queue[s];
    queue[s] = null;
    if (s != 0)
        siftDown(0, x);
    return result;
}

取元素时,获得队首的元素,再将队尾的元素放入队首,通过siftDown(0, x)从堆顶开始向下调整堆

private void siftDownComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>)x;
    int half = size >>> 1;        // loop while a non-leaf
    while (k < half) {
        int child = (k << 1) + 1; // assume left child is least
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            c = queue[child = right];
        if (key.compareTo((E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = key;
}

调整方法如下,:

  1. 获取左右节点的值
  2. 选择两个节点中较大的节点
  3. 与选出的子节点比较,优先级小于该子节点就与其交换,重复上面的操作,否则结束

值得一提的是这里将k的范围限定为< half,使得循环中总能取到有效的左子节点,同时也保证了能访问到最后一个左子节点,即k=half-1时,child=k*2+1=half*2-2=size-1也就是最后一个节点

参考

使用PriorityQueue - 廖雪峰的官方网站 (liaoxuefeng.com)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值