集合之深入理解PriorityQueue

知识储备

数据结构知识储备:
堆的定义:百度百科
堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;(要么父节点都大于等于子节点的值,要么父节点的值都小于等于子节点的值)
  • 堆总是一棵完全二叉树。
    简而言之,堆是一棵完全二叉树,这个二叉树的节点的值要满足下面的规则。假设node表示节点,node.val 表示节点值,node.left 表示该节点的左子节点,node.right 表示该节点的右子节点。那么,堆要满足的规则是:
    (node.val <= node.left.val && node.val <= node.right.val) || (node.val >= node.left.val && node.val >= node.right.val).
    当 node.val <= node.left.val && node.val <= node.right.val 时,父节点的值都小于等于子节点的值,称为小根堆(或小顶堆)。反之,父节点的值都大于等于子节点的值,称为大根堆(或大顶堆)
    注意:完全二叉树的数据结构可以使用链表,也可以使用数组。在PriorityQueue中就是使用数组来表示完全二叉树。

PriorityQueue源码

类部分注释

An unbounded priority queue based on a priority heap.
基于优先堆的无界优先队列。 
The elements of the priority queue are ordered according to their natural ordering, 
优先队列的元素是按照它们的自然顺序排列的, 
or by a Comparator provided at queue construction time, depending on which constructor is used.
或者在**队列构造时提供的Comparator,这取决于使用哪个构造函数。  
A priority queue does not permit null elements.
优先队列不允许空元素。
public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable {
    
    // Object数组 保存队列元素
    transient Object[] queue; 
    
    // 默认的 初始化数组长度
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
    
    // 队列所含元素个数
    private int size = 0;
    
    // 队列元素的比较器
    private final Comparator<? super E> comparator;
    
    ...
}

既然优先队列是基于优先堆实现的,那么我们就必须搞清楚优先堆的优先是什么意思。优先堆的优先指的是**先使用堆中最小元素还是最大元素。**堆的定义要求堆要么是小根堆,要么是大根堆。如果是小根堆,自然先使用最小元素,如果是大根堆,自然先使用最大元素。可以通过构造函数来
注:一般情况,**看源码注释,着重看源码的第一段注释。**我一般是细读第一段的注释,剩下的注释稍微读一读。
在这里插入图片描述
父节点和子节点的编号是有联系的,更确切的说父子节点的编号之间有如下关系:
leftNo = parentNo*2+1

rightNo = parentNo*2+2

parentNo = (nodeNo-1)/2

通过上述三个公式,可以轻易计算出某个节点的父节点以及子节点的下标。这也就是为什么可以直接用数组来存储堆的原因。

add() 和 offer()

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

//offer(E e)
public boolean offer(E e) {
    if (e == null)//不允许放入null元素
        throw new NullPointerException();
    modCount++;
    int i = size;
    if (i >= queue.length)
        grow(i + 1);//自动扩容
    size = i + 1;
    if (i == 0)//队列原来为空,这是插入的第一个元素
        queue[0] = e;
    else
        siftUp(i, e);//调整
    return true;
}

siftUp(int k, E x)方法,该方法用于插入元素x并维持堆的特性。

//siftUp()
private void siftUp(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;//parentNo = (nodeNo-1)/2
        Object e = queue[parent];
        if (comparator.compare(x, (E) e) >= 0)//调用比较器的比较方法
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

新加入的元素x可能会破坏小顶堆的性质,因此需要进行调整。调整的过程为:从k指定的位置开始,将x逐层与当前点的parent进行比较并交换,直到满足x >= queue[parent] 或者 k == 0 为止。注意这里的比较可以是元素的自然顺序,也可以是依靠比较器的顺序。

remove()和poll()

在这里插入图片描述

public E poll() {
    if (size == 0)
        return null;
    int s = --size;
    modCount++;
    E result = (E) queue[0];//0下标处的那个元素就是最小的那个
    E x = (E) queue[s];
    queue[s] = null;
    if (s != 0)
        siftDown(0, x);//调整
    return result;
}

上述代码首先记录0下标处的元素,并用最后一个元素替换0下标位置的元素,之后调用siftDown()方法对堆进行调整,最后返回原来0下标处的那个元素(也就是最小的那个元素)。重点是siftDown(int k, E x)方法,该方法的作用是从k指定的位置开始,将x逐层向下与当前点的左右孩子中较小的那个交换,直到x小于或等于左右孩子中的任何一个为止。
在这里插入图片描述

//remove(Object o)
public boolean remove(Object o) {
	//通过遍历数组的方式找到第一个满足o.equals(queue[i])元素的下标
    int i = indexOf(o);
    if (i == -1)
        return false;
    int s = --size;
    if (s == i) //情况1
        queue[i] = null;
    else {
        E moved = (E) queue[s];
        queue[s] = null;
        siftDown(i, moved);//情况2
        ......
    }
    return true;
}

源码阅读建议:利用断点跟踪到源码里面看数组元素的变化。

    public static void main(String[] args) {
        // 无参构造器,默认使用小根堆
        PriorityQueue<Integer> integerPriorityQueue = new PriorityQueue<>();
        integerPriorityQueue.add(1);
        integerPriorityQueue.add(3);
        integerPriorityQueue.add(2);
        // integerPriorityQueue.add(0);
        while (!integerPriorityQueue.isEmpty()) {
            Integer poll = integerPriorityQueue.poll();
            System.out.println(poll);
        }

        // 自定义比较器:按照数字大小倒序,也就是使用大根堆
        PriorityQueue<Integer> reversePriorityQueue = new PriorityQueue<>(Comparator.reverseOrder());
        reversePriorityQueue.add(1);
        reversePriorityQueue.add(3);
        reversePriorityQueue.add(2);
        reversePriorityQueue.add(0);
        while (!reversePriorityQueue.isEmpty()) {
            Integer poll = reversePriorityQueue.poll();
            System.out.println(poll);
        }

        // 自定义 初始化数组大小,设置initialCapacity 的意义在于——如果开发者能提前预估 PriorityQueue 的大小,可以减少扩容,提升性能
        PriorityQueue<Integer> sizePriorityQueue = new PriorityQueue<>(4);
        sizePriorityQueue.add(1);
        sizePriorityQueue.add(3);
        sizePriorityQueue.add(2);
        sizePriorityQueue.add(0);
        sizePriorityQueue.add(4);
        while (!sizePriorityQueue.isEmpty()) {
            Integer poll = sizePriorityQueue.poll();
            System.out.println(poll);
        }

    }

原文地址:博客园

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值