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;
    }

API Example

构造函数之其他优先级队列参数

 PriorityQueue<Integer> argQueue = new PriorityQueue<>(Arrays.asList(1, 3, 5));
 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(argQueue);
 assertEquals(priorityQueue.toArray(), "[1, 3, 5]");

构造函数之带初始容量和比较器

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(2, (o1, o2) -> o2 - o1);
 assertEquals(priorityQueue.toArray(), "[]");

构造函数之最大堆

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(((o1, o2) -> o2 - o1));
 priorityQueue.addAll(Arrays.asList(1, 3, 5));
 assertEquals(priorityQueue.toArray(), "[5, 1, 3]");

构造函数之有序集参数

 SortedSet<Integer> set = new TreeSet<>(Arrays.asList(1, 3, 5));
 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(set);
 assertEquals(priorityQueue.toArray(), "[1, 3, 5]");

构造函数之集合参数

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 3, 5));
 assertEquals(priorityQueue.toArray(), "[1, 3, 5]");

构造函数之默认参数

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
 assertTrue(priorityQueue.size() == 0);
 assertEquals(priorityQueue.toArray(), "[]");

元素删除 Remove Poll

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));

 boolean removeSuccess = priorityQueue.remove(2);
 assertEquals(priorityQueue.toArray(), "[1, 3, 4, 6, 3, 5]");

 //移除堆顶元素1,底层调用的是poll(),但是如果堆为空,该方法抛出异常
 priorityQueue.remove();
 assertEquals(priorityQueue.toArray(), "[3, 3, 4, 6, 5]");

 //移除堆顶元素3,堆为空时,返回null
 Integer ele = priorityQueue.poll();
 //ele may be null
 assertEquals(priorityQueue.toArray(), "[3, 5, 4, 6]");

元素新增 Add Offer

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 3, 5));

 //add()方法也是调用的offer()
 priorityQueue.add(2);
 assertEquals(priorityQueue.toArray(), "[1, 2, 5, 3]");

 priorityQueue.addAll(Arrays.asList(3, 4));
 assertEquals(priorityQueue.toArray(), "[1, 2, 4, 3, 3, 5]");

 priorityQueue.offer(6);
 assertEquals(priorityQueue.toArray(), "[1, 2, 4, 3, 3, 5, 6]");

元素查找 Contains

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));

 //查询算法时间复杂度为O(lgN)
 boolean contains = priorityQueue.contains(3);
 assertTrue(contains);

堆顶值读取 Element Peek

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));

 //算法时间复杂度为O(l)
 //底层调用时peek(),但是空堆时,抛出异常
 Integer element = priorityQueue.element();
 assertEquals(element, 1);

 //若队列为空,peek()返回值为空
 element = priorityQueue.peek();
 assertEquals(element, 1);

遍历队列之 Foreach无序遍历

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
 priorityQueue.forEach(e -> System.out.println(e));
1
2
4
3
3
5
6

遍历队列之 Iterator无序遍历

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
 Iterator<Integer> iterator = priorityQueue.iterator();
 while (iterator.hasNext()) {
     Integer value = iterator.next();
     System.out.println(value);
 }
1
2
4
3
3
5
6

遍历队列之有序遍历

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
 while (!priorityQueue.isEmpty()) {
     Integer value = priorityQueue.poll();
     System.out.println(value);
 }
1
2
3
3
4
5
6

集合操作之交集 Retain

 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(Arrays.asList(1, 2, 4, 3, 3, 5, 6));
 priorityQueue.retainAll(Arrays.asList(1, 2, 3));
 assertEquals(priorityQueue.toArray(), "[1, 2, 3, 3]");

引用

posted @ 2020-04-29 16:58  onion94  阅读(259)  评论(0编辑  收藏  举报