JAVA Queue 与 PriorityQueu源码分析

什么是 Queue

数据结构中的队列,先进先出式的数据结构,所有新元素都插入队列的末尾,移除元素都移除队列的头部。主要注意的时,Java中的Queue是一个接口。

Queue 源码分析

public interface Queue<E> extends Collection<E> {

    boolean add(E e);   //往队列插入元素,如果出现异常会抛出异常
  
    boolean offer(E e); //往队列插入元素,如果出现异常则返回false

    E remove(); //移除队列元素,如果出现异常会抛出异常

    E poll();   //移除队列元素,如果出现异常则返回null

    E element();    //获取队列头部元素,如果出现异常会抛出异常

    E peek();   //获取队列头部元素,如果出现异常则返回null
}

上面六个函数总体上分为两类:安全的会进行容量检查的(add,remove,element),如果队列没有值,则取元素会抛出IlleaglStatementException异常。不安全的不进行容量控制的(offer,poll,peek )。

AbstractQueue 是Queue 的抽象实现类, AbstractQueue 也继承自 AbstractCollection 。AbstractQueue 实现的方法不多,主要就 add、remove、element 三个方法的操作失败抛出了异常。

什么是 PriorityQueue

PriorityQueue 优先级队列, ,不同于普通的遵循FIFO(先进先出)规则的队列,每次都选出优先级最高的元素出队,优先队列里实际是维护了这样的一个堆,通过堆使得每次取出的元素总是最小的(用户可以自定义比较方法,相当于用户设定优先级)。 PriorityQueue 是一个小顶堆,是非线程安全的;PriorityQueue不是有序的,只有堆顶存储着最小的元素;从数据的存储结构看, PriorityQueue 一个数组;从逻辑结构看, PriorityQueue 是一棵平衡二叉树。

什么是 PriorityQueue

PriorityQueue 优先级队列, ,不同于普通的遵循FIFO(先进先出)规则的队列,每次都选出优先级最高的元素出队,优先队列里实际是维护了这样的一个堆,通过堆使得每次取出的元素总是最小的(用户可以自定义比较方法,相当于用户设定优先级)。 PriorityQueue 是一个小顶堆,是非线程安全的;PriorityQueue不是有序的,只有堆顶存储着最小的元素;从数据的存储结构看, PriorityQueue 一个数组;从逻辑结构看, PriorityQueue 是一棵平衡二叉树。

PriorityQueue 源码分析

继承关系

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable{
        // 略
    }

PriorityQueue 继承了AbstractQueue 类,而AbstractQueue 类实现了Queue接口。

主要属性

// 默认容量
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 存储元素的数组,Object类型的
transient Object[] queue;
// 元素个数
int size;
// 比较器
private final Comparator<? super E> comparator;
// 修改次数
transient int modCount; 

transient Object[] queue
源码注释

Priority queue represented as a balanced binary heap: the two children of queue[n] are queue[2*n+1] and queue[2*(n+1)].  The priority queue is ordered by comparator, or by the elements' natural ordering, if comparator is null: For each node n in the heap and each descendant d of n, n <= d.  The element with the lowest value is in queue[0], assuming the queue is nonempty.

优先级队列是一个平衡的二叉树堆,queue[n]的两个子节点queue[2n+1] and queue[2(n+1)]。 优先级队列 按照 comparator 进行排序或者 按自然顺序排序。 如果 comparator 为null ,堆中每一个节点 n 以及 n的后代d, n<=d。假设队列为非空,则具有最小值的元素位于queue [0]中。

常用构造函数

  • 1、创建一个优先队列对象,默认大小,队列中的元素按照自然顺序排序。
public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }
  • 2、创建一个指定大小的优先队列对象,队列中的元素按照自然顺序排序。
public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }
  • 3、创建一个默认大小(11)的优先队列对象,队列中的元素按照指定比较器进行排序。
public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator);
    }
  • 4、根据指定的大小和比较器来创建一个优先队列对象。
public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        // Note: This restriction of at least one is not actually needed,
        // but continues for 1.5 compatibility
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

add(e)/offer(e)方法介绍

add方法
插入一个元素到优先队列中

add方法的源代码如下:

public boolean add(E e) {
    return offer(e);
}

从源码中可以看出add方法直接调用了offer方法。

offer(e)方法

public boolean offer(E e) {
    if (e == null)  //检查是否为null, 如果为null 则抛空指针异常
        throw new NullPointerException();
    modCount++;
    int i = size;
    if (i >= queue.length)  // 检查容量是否足够, 不足进行扩容
        grow(i + 1);    // 扩容函数
    siftUp(i, e);   // 在数组的末尾插入新的元素,向上调整,使之保持为二叉堆的特性。
    size = i + 1;
    return true;
}

1)首先检查要添加的元素e是否为null,如果为null,则抛空指针异常,如果不为null,则进行2
2)判断数组是否已满,如果已满,则进行扩容,否则将元素加入到数组末尾即可。
由于这个数组表示的是一个“二叉堆”,因此还需要进行相应的调整操作,使得这个数组在添加元素之后依然保持的是二叉堆的特性。

grow(int minCapacity)方法

//增加数组的容量
private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // Double size if small; else grow by 50%
    //如果比较小,则扩容为原来的2倍,否则只扩容为原来的1.5倍
    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);
}

hugeCapacity(int minCapacity) 方法

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

注:
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

public static final int MAX_VALUE = 0x7fffffff;

当 大于 MAX_ARRAY_SIZE 时扩容记性了特殊处理

siftUp(int k, E x)方法

将元素x插入到queue[k]位置上,并进行调整使之具有二叉堆特性

private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);    // 使用比较器 comparator
        else
            siftUpComparable(k, x);
}

siftUpUsingComparator和siftUpComparable源码如下

从 位置 k 向数组起始位置 调整使之具有二叉堆特性,自下而上的堆化,

也就是 二叉堆插入元素思想操作:

可参见 拜托,面试别再问我堆(排序)了!

siftUpComparable(int k, E x)方法

@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>) x;
    while (k > 0) {
        int parent = (k - 1) >>> 1; // 找 parent 父节点
        Object e = queue[parent];
        if (key.compareTo((E) e) >= 0) // 比较 按 自然顺序 排列
            break;
        queue[k] = e;   
        k = parent; 
    }
    queue[k] = key;
}

siftUpUsingComparator(int k, E x)方法

@SuppressWarnings("unchecked")
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)  // 使用比较器 comparator
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

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

poll方法:取出queue[0]元素,然后将queue[size-1]插入到queue[0],然后自上而下堆化来保持二叉堆的特性。

siftDown(int k, E x)方法

//将元素x存储在queue[k],并进行相应的调整
private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

siftDownComparable(int k, E 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;
}

siftDownUsingComparator(int k, E x)方法

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

remove(Object o) 方法介绍

移除指定元素

public boolean remove(Object o) {
    int i = indexOf(o);     // 判断位置
    if (i == -1)    //没有在数组中找到
        return false;
    else {
        removeAt(i);    //进行删除并调整
        return true;
    }
}

indexOf(Object o)方法

private int indexOf(Object o) {
    if (o != null) {
        for (int i = 0; i < size; i++)  // 数组遍历过程
            if (o.equals(queue[i]))
                return i;
    }
    return -1;
}

找到对象o在数组中出现的第一个索引

emoveAt(int i)方法

E removeAt(int i) {
    // assert i >= 0 && i < size;
    modCount++;
    int s = --size;
    if (s == i) // removed last element
        queue[i] = null;
    else {
        E moved = (E) queue[s]; // 取出最后一个元素
        queue[s] = null;
        siftDown(i, moved); //从i向下调整堆
        if (queue[i] == moved) {    // 如果发现调整后 moved 还在 i 位置没有下沉,向上调整看是否上浮
            siftUp(i, moved);
            if (queue[i] != moved)
                return moved;
        }
    }
    return null;
}

堆插入元素与删除堆顶元素操作

以下面这个对为例
图片来自参考中链接
用数组来存储为:
图片来自参考中链接

插入元素

往堆中插入一个元素后,需要继续满足堆的两个特性,即:

(1)堆是一颗完全二叉树;
(2)堆中某个节点的值总是不大于(或不小于)其父节点的值。

把元素插入到最后一层最后一个节点往后一位的位置,也就是在数组的末尾插入新的元素
,插入之后可能不再满足条件(2),这时候我们需要调整,自下而上的堆化。

上面那个堆插入元素2,我们把它放在9后面,这时不满足条件(2)了,我们就需要堆化。(这是一个小顶堆)

图片来自参考中链接
从插入元素的过程,我们知道每次与 n / ( 2 x ) n/(2^x) 的位置进行比较,所以,插入元素的时间复杂度为 O ( l o g n ) O(log n)

删除堆顶元素

删除了堆顶元素后,要使得还满足堆的两个特性。把最后一个元素移到根节点的位置,这时候就满足条件(1),之后就需要堆化了使它满足条件(2)

图片来自参考中链接
从删除元素的过程,我们知道把最后一个元素拿到根节点后,每次与 2 n 2n ( 2 n + 1 ) (2n+1) 位置的元素比较,取其小者,所以,删除元素的时间复杂度也为 O ( l o g n ) O(log n)

回到 remove(Object o) 方法 ,不是删除堆顶元而是删除指定元素,源码中会先找Object o在数组中位置 i,再取出最后一个元素从i自上而下堆化,如果最后一个元素没有下沉,会从i自下而上堆化

参考:

拜托,面试别再问我堆(排序)了!
https://mp.weixin.qq.com/s/AF2tMHfofG8b51yIyaIReg

发布了170 篇原创文章 · 获赞 16 · 访问量 2818

猜你喜欢

转载自blog.csdn.net/leacock1991/article/details/102643894