JDK-In-Action-PriorityQueue

优先级队列

优先级队列介绍

基于优先级堆的无界优先级队列。优先队列的元素根据它们的自然顺序排序,或者根据使用的构造函数由队列构建时提供的比较器排序。优先队列不允许空元素。依赖于自然排序的优先级队列也不允许插入不可比较的对象(这样做可能会导致ClassCastException)。相对于指定的顺序,此队列的头是最小的元素。如果多个元素为最小值绑定,则head是这些元素之一——绑定是任意断开的。队列检索操作轮询、删除、查看和元素访问队列头部的元素。优先队列是无界的,但是有一个内部容量来控制用于在队列中存储元素的数组的大小。它总是至少与队列大小一样大。当元素被添加到优先队列时,它的容量会自动增长。增长策略的细节没有指定。该类及其迭代器实现集合和迭代器接口的所有可选方法。方法Iterator()中提供的迭代器不能保证以任何特定的顺序遍历优先队列的元素。如果需要有序遍历,可以考虑使用array.sort(pq.toArray())。注意,这个实现不是同步的。如果任何线程修改队列,多线程不应该并发地访问PriorityQueue实例。相反,应该使用线程安全的java.util.concurrent.PriorityBlockingQueue类。实现说明:此实现为入队和出队方法(offer,poll,remove()和add)提供O(log(n))时间;remove(Object)和contains(Object)方法的线性时间;以及检索方法(peek, element和size)的常数时间。该类是Java集合框架的成员。

数据结构

使用线性表来存储平衡二叉最小堆

transient Object[] queue;

基于线性表的算法设计:
最后一个非叶子节点索引: lastNonLeafIndex= size <<< 1 - 1
判断有无子节点: hasChild(n) = n < ( size << 2 )
求子节点索引: leftChildIndex=n*2-1, rightChildIndex=leftChildIndex+1,注意判断索引越界

算法图解

初始数组值(未构造最小堆): [9,8,7,6,4,3,2,1]
初始二叉堆的图形如下
1

平衡二叉最小堆的构造

首先找到最大的非叶子节点ID,与它的两个子节点比较,将其中的较小值和它交换,如果它最小的子节点还有子节点,跳到较小节点的位置,重复该过程,否则退出.
2

第二步: 移动到倒数第二个非叶子节点,重复第一步的过程.
3

第三步: 继续到下一个非叶子节点,直到根节点,重复前两步的过程
节点1和节点3交换值,因为节点数据发生交换,且节点3还有子节点,所以需要继续处理节点3;
如果未发生节点值交换,可以不用继续处理子节点;
4

交换节点3和节点7的值,节点7没有子节点了,退出本轮循环.
5

处理下一个非叶子节点0,交换节点0和节点1的值.
6

同理,继续交换节点1和节点3的值.
7

同理,继续交换节点3和节点8的值,到此,平衡二叉最小堆构建完成
8

节点新增算法

节点新增的算法步骤:

  1. 在堆的最后新增一个叶子节点,这一步可能需要扩容(见后文如何JDK的扩容策略);
  2. 和它的父节点比较,如小于等于父节点,则交换值,否则插入完成;
  3. 跳到父节点,因为发生了值交换,所以需要和当前节点的父节点再进行交换比较,重复该过程,直到根节点;
  • 若新增值为3
    9

  • 交换4,5节点的值
    10

  • 因为节点4大于父节点,故操作完成
    11

根节点移除

算法步骤:

  1. 将最后一个节点和根节点交换.
    2.以根节点未起点,向下交换较小的节点.直到无子节点或比子节点都小.
  • 移出根节点值,将节点9的值赋值给根节点, 节点9的值赋值null
    12

  • 节点1值小于根节点,交换值, 因为有子节点,且存在更小的值,需要继续该过程
    13

  • 交换节点1和节点4的值,操作完成
    14

节点值索引查询

算法步骤:
1.从0到size-1,一次比较数组值和目标值,返回相等时的索引

节点删除

算法步骤:

  1. 交换删除节点和尾节点的值;
  2. 由于发生了值交换,需要应用下沉或上浮来确保树的性质;
  • 情况1: 替换值后发生下沉操作
    15

  • 情况2: 替换值后发生上浮操作
    16

  • 情况三: 即没有上浮也没有下沉
    17

迭代器

迭代器next()会导致遍历指针+1, remove()会移除最后一次迭代的元素;
在迭代的过程中如果调用 remove()会由于上浮下沉出现

  • 未访问的尾元素被移动到已经访问的位置(节点删除情况3)或
  • 其他未访问的位置(节点删除情况2)或
  • 当前删除的元素位置(节点删除情况3)三种情况.
    所以为了能完整的遍历出所有的元素,对于情况1和情况3,需要让next的指针减1来访问尾节点或被下沉中交换上来的元素;对于情况2,需要保存尾节点元素,最后再访问这些漏掉的元素;

扩容

扩容前容量小于64:容量翻倍
否则: 增长50%
扩容后检查最大容量限制: Integer.MAX_VALUE - 8 或者 Integer.MAX_VALUE

    /**
     * Increases the capacity of the array.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50%
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                         (oldCapacity + 2) :
                                         (oldCapacity >> 1));
        // overflow-conscious code
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

引用

  • java.util.PriorityQueue Sorce Code

猜你喜欢

转载自www.cnblogs.com/onion94/p/JDK-In-Action-PriorityQueue.html