数据结构 - PriorityQueue 优先级队列

简介

PriorityQueue 一个基于优先级的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法,每次出队的元素都是优先级最高的元素。该队列不允许使用 null 元素也不允许插入不可比较的对象(没有实现Comparable接口的对象)。

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

PriorityQueue 队列的头指排序规则最小的元素。如果多个元素都是最小值则随机选一个。

PriorityQueue 属性
// 默认初试长度
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 存储的元素
transient Object[] queue;
// 元素个数
private int size = 0;
// 比较器
private final Comparator<? super E> comparator;
// 修改次数
transient int modCount = 0; 
// 数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
PriorityQueue 构造函数
// 空参构造函数
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) {
    // 初始长度小于1则抛异常
    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);
    }
}
// 使用优先级队列初始化
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);
}
// 使用集合初始化时调用此方法
private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
    if (c.getClass() == PriorityQueue.class) {
        this.queue = c.toArray();
        this.size = c.size();
    } else {
        initFromCollection(c);
    }
}
// 当不是优先级队列初始化时,调用此方法
private void initElementsFromCollection(Collection<? extends E> c) {
    Object[] a = c.toArray();
    // If c.toArray incorrectly doesn't return Object[], copy it.
    if (a.getClass() != Object[].class)
        a = Arrays.copyOf(a, a.length, Object[].class);
    int len = a.length;
    if (len == 1 || this.comparator != null)
        for (int i = 0; i < len; i++)
            if (a[i] == null)
                throw new NullPointerException();
    this.queue = a;
    this.size = a.length;
}
// 集合初始化时调用此方法
private void initFromCollection(Collection<? extends E> c) {
    initElementsFromCollection(c);
    heapify();
}

从成员变量和构造函数可以看出,优先级队列内部是数组实现,并且默认长度11,初始化时默认是空数组。随着不断向优先级队列添加元素,其容量会自动扩容,无需指定容量增加策略

PriorityQueue 扩容
private void grow(int minCapacity) {
    // 获取数组长度
    int oldCapacity = queue.length;
    // 数组长度小于64时,新长度=原长度*2+2
    // 数组长度大于等于64时,新长度=原长度*1.5
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
            (oldCapacity + 2) :
            (oldCapacity >> 1));
    // 新长度是否越界
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 数组拷贝迁移
    queue = Arrays.copyOf(queue, newCapacity);
}
// 越界时确定值
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) 
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}
PriorityQueue 添加
public boolean add(E e) {
    // 优先级队列为无界队列,认为不存在放满(实际是有界的)
    return offer(e);
}
public boolean offer(E e) {
    // 不允许放空值
    if (e == null)
        throw new NullPointerException();
    modCount++;
    int i = size;
    // 元素个数大于等于数组长度时触发扩容
    if (i >= queue.length)
        grow(i + 1);
    // 长度加1
    size = i + 1;
    // 扩容后长度为0(不可能存在)
    if (i == 0)
        queue[0] = e;
    else
        siftUp(i, e);
    return true;
}
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) {
    modCount++;
    int s = --size;
    // 是尾元素直接删除
    if (s == i) 
        queue[i] = null;
    else {
        // 尾元素置空
        E moved = (E) queue[s];
        queue[s] = null;
        // 尾元素从索引处开始下移
        siftDown(i, moved);
        if (queue[i] == moved) {
            // 下移后不能平衡,需要上移
            siftUp(i, moved);
            if (queue[i] != moved)
                return moved;
        }
    }
    return null;
}
PriorityQueue 取值并删除
public E poll() {
    // 队列为空返回null
    if (size == 0)
        return null;
    // 长度减1
    int s = --size;
    modCount++;
    // 取第一个元素
    E result = (E) queue[0];
    E x = (E) queue[s];
    // 置空尾元素
    queue[s] = null;
    // 从第一位开始下移x
    if (s != 0)
        siftDown(0, x);
    // 返回原来头元素
    return result;
}
PriorityQueue 查询
public E peek() {
    // 获取第一个元素
    return (size == 0) ? null : (E) queue[0];
}
二叉堆排序
private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}
private void siftUp(int k, E x) {
    if (comparator != null)
        siftUpUsingComparator(k, x);
    else
        siftUpComparable(k, x);
}
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;
}
private void siftUpUsingComparator(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        if (comparator.compare(x, (E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}
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; 
    while (k < half) {
        int child = (k << 1) + 1; 
        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;
}
private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

二叉堆看起来比较绕,我先普及一下二叉堆。父节点的键值总是小于或等于任何一个子节点的键值,二叉堆分为最大堆和最小堆
1、最大堆:父节点的键值总是大于或等于任何一个子节点的键值;
2、最小堆:父节点的键值总是小于或等于任何一个子节点的键值。
二叉堆又可以用数组来表示,基于数组实现的二叉堆,对于数组中任意位置的n上元素,其左孩子在[2n+1]位置上,右孩子[2(n+1)]位置,它的父亲则在[(n-1)/2]上,而根的位置则是[0]。
二叉堆只有上移下移操作,jdk中在出队时,不是直接将根元素删除,然后再将下面的元素做上移,重新补充根元素;而是找出队尾的元素,并在队尾的位置上删除,然后通过根元素的下移,给队尾元素找到一个合适的位置,最终覆盖掉根元素,从而达到删除根元素的目的。这样做在一些情况下,会比直接删除在上移根元素,或者直接下移根元素再调整队尾元素的位置少操作一些步奏

猜你喜欢

转载自www.cnblogs.com/yuanjiangnan/p/12649777.html