zoukankan      html  css  js  c++  java
  • 集合框架系列 Queue(八):PriorityQueue

    目录

    1 概述

    2 源码分析

    1 概述

    PriorityQueue 是一个优先级队列,其底层原理采用二叉堆实现。我们先来看看它的类声明:

    public class PriorityQueue<E> extends AbstractQueue<E>
        implements java.io.Serializable

    PriorityQueue 继承了 AbstractQueue 抽象类,具有队列的基本特性。

    2 源码分析

    2.1 类成员变量

    // 队列数据
    transient Object[] queue;  
    // 大小
    private int size = 0;
    // 比较器
    private final Comparator<? super E> comparator;

    从类成员变量我们可以知道 PriorityQueue 底层采用数组存储数据,comparator 的实现决定了其实一个最大堆还是最小堆。默认情况下 PriorityQueue 是个最小堆。

    2.2 构造方法

    PriorityQueue 一共有 7 个构造方法。

    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }
        
    public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }
        
    public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator);
    }
        
    public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) { 
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }
    // 传入集合初始值
    public PriorityQueue(Collection<? extends E> c) {
        if (c instanceof SortedSet<?>) {
            SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
            this.comparator = (Comparator<? super E>) ss.comparator();
            initElementsFromCollection(ss);
        }
        else if (c instanceof PriorityQueue<?>) {
            PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
            this.comparator = (Comparator<? super E>) pq.comparator();
            initFromPriorityQueue(pq);
        }
        else {
            this.comparator = null;
            initFromCollection(c);
        }
    }
    // 传入PriorityQueue初始值
    public PriorityQueue(PriorityQueue<? extends E> c) {
        this.comparator = (Comparator<? super E>) c.comparator();
        initFromPriorityQueue(c);
    }
    // 传入SortedSet初始值
    public PriorityQueue(SortedSet<? extends E> c) {
        this.comparator = (Comparator<? super E>) c.comparator();
        initElementsFromCollection(c);
    }

    PriorityQueue 的构造方法比较多,但其功能都类似。如果传入的是普通集合,那么会将其数据复制,最后调用 heapify 方法进行二叉堆的初始化操作。但如果传入的数据是 SortedSet 或 PriorityQueue 这些已经有序的数据,那么就直接按照顺序复制数据即可。

    2.3 核心方法

    对于 PriorityQueue 来说,其核心方法有:获取、插入、删除、扩容。

    2.3.1 获取

    PriorityQueue 没有查询方法,取而代之的是获取数据的 peek 方法。

    public E peek() {
        return (size == 0) ? null : (E) queue[0];
    }

    如果队列为空,那么返回 null 值,否则返回队列的第一个元素(即最大或最小值)。

    2.3.2 插入

    PriorityQueue 的数据插入过程,其实就是往二叉堆插入数据的过程。

    public boolean add(E e) {
        return offer(e);
    }
        
    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        // 1.容量不够,进行扩容
        if (i >= queue.length)
            grow(i + 1);
        size = i + 1;
        // 2.如果队列为空那么直接插入第一个节点
        // 否则插入末尾节点后进行上浮操作
        if (i == 0)
            queue[0] = e;
        else
            siftUp(i, e);
        return true;
    }
    
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            // 3.采用默认的比较器
            siftUpComparable(k, x);
    }
        
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            // 4.将插入节点与父节点比较
            // 如果插入节点大于等于父节点,那么说明符合最小堆性质
            // 否则交换插入节点与父节点的值,一直到堆顶
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }

    插入的代码最终的逻辑是在 siftUpComparable 方法中,而该方法其实就是我们上面所说二叉堆插入逻辑的实现。

    2.3.3 删除

    PriorityQueue 的数据删除过程,其实就是将数据从二叉堆中删除的过程。

    public boolean remove(Object o) {
        int i = indexOf(o);
        if (i == -1)
            return false;
        else {
            removeAt(i);
            return true;
        }
    }
        
    private E removeAt(int i) {
        // assert i >= 0 && i < size;
        modCount++;
        int s = --size;
        // 1.删除的是末尾节点,那么直接删除即可
        if (s == i) // removed last element
            queue[i] = null;
        else {
            E moved = (E) queue[s];
            queue[s] = null;
            // 2.对删除节点做下沉操作
            siftDown(i, moved);
            if (queue[i] == moved) {
                // 3.queue[i] == moved 表示删除节点根本没下沉
                // 意思是其就是该子树最小的节点
                // 这种情况下就需要进行上浮操作
                // 因为可能出现删除节点父节点大于删除节点的情况
                siftUp(i, moved);
                if (queue[i] != moved)
                    return moved;
            }
        }
        return null;
    }
        
    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, 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;
    }

    2.3.4 offer

    因为 PriorityQueue 是队列,所以有 offer 操作。

    对于 offer 操作来说,其实就是相当于往数组未插入数据,其逻辑细节我们在插入 add 方法中已经说到。

    2.3.5 poll

    因为 PriorityQueue 是队列,同样会有 poll 操作。而 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;
    }

    之前我们说过删除节点的逻辑,即拿末尾节点值替代删除节点,然后做下沉操作。但是这里因为删除节点是根节点了,所以不需要做上浮操作。

    扩容

    当往队列插入数据时,如果队列容量不够则会进行扩容操作。

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

    PriorityQueue 的扩容非常简单。如果原来的容量小于 64,那么扩容为原来的两倍,否则扩容为原来的 1.5 倍。

    3 总结

    PriorityQueue 的实现是建立在二叉堆之上的,所以弄懂二叉堆就相当于弄懂了 PriorityQueue。PriorityQueue 默认情况下是最小堆,我们可以改变传入的比较器,使其成为最大堆。

    0

  • 相关阅读:
    【小程序】文本超出则省略号
    【wx小程序】读懂app.js
    【js】某字符串多次替换
    【小程序】本地资源图片无法通过 WXSS 获取
    【小程序】(一)注册开始小程序开发
    【小程序】配置本地接口开发
    【css】文本超出行数以省略号显示
    【webstorm】project目录树显示不出
    【Nodejs】Browsersync同步浏览器测试
    获取指定包名下继承或者实现某接口的所有类(扫描文件目录和所有jar)
  • 原文地址:https://www.cnblogs.com/youngao/p/12518310.html
Copyright © 2011-2022 走看看