Queue(ArrayDeque&PriorityQueue)

一:ArrayDeque

1、ArrayDeque简介

ArrayDeque继承于AbstractCollection类,其同时实现了Deque接口。Queue的结构是一个单端的队列,从一端进另一端出,Deque是一个双端队列。而ArrayDeque是一个使用循环数组实现的双端队列了。双端队列可以实现单端队列的先入先出的方式,也可以实现栈结构的先入后出的方式,使用比较灵活,看具体需求。ArrayDeque是线程非安全的,所以如果需要实现线程安全,就需要自己处理了。

2、ArrayDeque的特点

(1)继承结构

public class ArrayDeque<E> extends AbstractCollection<E>
                           implements Deque<E>, Cloneable, Serializable

ArrayDeque继承于AbstractCollection类,只能存储单值;
ArrayDeque实现了Deque接口,可以用于双端队列;
ArrayDeque实现了Cloneable接口,其对象能够被克隆;
ArrayDeque实现了Serializable接口,其对象能够被序列化;
ArrayDeque内部实现了Iterator接口,其对象能够被迭代器遍历;

(2)ArrayDeque特性

  • 底层实现时循环数组;
  • 没有容量限制,在数组元素装满时自动扩容;
  • 禁止插入null元素;
  • 作为Stack和Queue时比LinkedList实现更好(前提是减少频繁的扩容和remove数组移动操作)
  • 不是线程安全的,不支持并发访问和修改;
  • 支持快速失败(fast-fail)

3、ArrayDeque常用操作

/**
 * 1.添加元素
 */
 addFirst(E e); //在数组前面添加元素
 addLast(E e); //在数组后面添加元素
 offerFirst(E e); //在数组前面添加元素,并返回是否添加成功
 offerLast(E e); //在数组后天添加元素,并返回是否添加成功

/**
 * 2.删除元素
 */
 removeFirst(); //删除第一个元素,并返回删除元素的值,如果元素为null,将抛出异常
 pollFirst(); //删除第一个元素,并返回删除元素的值,如果元素为null,将返回null
 removeLast(); //删除最后一个元素,并返回删除元素的值,如果为null,将抛出异常
 pollLast(); //删除最后一个元素,并返回删除元素的值,如果为null,将返回null
 removeFirstOccurrence(Object o); //删除第一次出现的指定元素
 removeLastOccurrence(Object o); //删除最后一次出现的指定元素

/**
 * 3.获取元素
 */
 getFirst(); //获取第一个元素,如果没有将抛出异常
 getLast(); //获取最后一个元素,如果没有将抛出异常

/**
 * 4.队列操作
 */
 add(E e); //在队列尾部添加一个元素
 offer(E e); //在队列尾部添加一个元素,并返回是否成功
 remove(); //删除队列中第一个元素,并返回该元素的值,如果元素为null,将抛出异常(其实底层调用的是removeFirst())
 poll(); //删除队列中第一个元素,并返回该元素的值,如果元素为null,将返回null(其实调用的是pollFirst())
 element(); //获取第一个元素,如果没有将抛出异常
 peek(); //获取第一个元素,如果返回null

/**
 * 5.栈操作
 */
 push(E e); //栈顶添加一个元素
 pop(E e); //移除栈顶元素,如果栈顶没有元素将抛出异常

/**
 * 6.其他
 */
 size(); //获取队列中元素个数
 isEmpty(); //判断队列是否为空
 iterator(); //迭代器,从前向后迭代
 descendingIterator(); //迭代器,从后向前迭代
 contain(Object o); //判断队列中是否存在该元素
 toArray(); //转成数组
 clear(); //清空队列
 clone(); //克隆(复制)一个新的队列

4、ArrayDeque源码分析

  • 成员变量
//用数组存储元素
transient Object[] elements; // non-private to simplify nested class access
//头部元素的索引
transient int head;
//尾部下一个将要被加入的元素的索引
transient int tail;
//最小容量,必须为2的幂次方
private static final int MIN_INITIAL_CAPACITY = 8;

在ArrayDeque底层使用了数组来存储数据,同时用两个int值head和tail来表示头部和尾部。
不过需要注意的是tail并不是尾部元素的索引,而是尾部元素的下一位,即下一个将要被加入的元素的索引。

  • 构造方法
public ArrayDeque() {
    
      
    elements = (E[]) new Object[16]; // 默认的数组长度大小  
}

public ArrayDeque(int numElements) {
    
      
    allocateElements(numElements); // 需要的数组长度大小  
}

public ArrayDeque(Collection<? extends E> c) {
    
      
    allocateElements(c.size()); // 根据集合来分配数组大小  
    addAll(c); // 把集合中元素放到数组中  
}

ArrayDeque有三个构造函数来初始化,除了无参的构造函数使用了默认容量,其它两个构造函数会通过allocateElements来计算初始容量

  • 容量大小检测
private void allocateElements(int numElements) {
    
      
    int initialCapacity = MIN_INITIAL_CAPACITY;  
    // 找到大于需要长度的最小的2的幂整数。  
    // Tests "<=" because arrays aren't kept full.  
    if (numElements >= initialCapacity) {
    
      
        initialCapacity = numElements;  
        initialCapacity |= (initialCapacity >>>  1);  
        initialCapacity |= (initialCapacity >>>  2);  
        initialCapacity |= (initialCapacity >>>  4);  
        initialCapacity |= (initialCapacity >>>  8);  
        initialCapacity |= (initialCapacity >>> 16);  
        initialCapacity++;  
        
        if (initialCapacity < 0)   // Too many elements, must back off  
            initialCapacity >>>= 1;// Good luck allocating 2 ^ 30 elements  
    }  
    elements = (E[]) new Object[initialCapacity];  
}

ArrayDeque 对数组的大小(即队列的容量)有特殊的要求,必须是 2^n。用allocateElements方法

  • 扩容
// 扩容为原来的2倍。  
private void doubleCapacity() {
    
      
    assert head == tail;  
    int p = head;  
    int n = elements.length;  
    int r = n - p; // number of elements to the right of p  
    int newCapacity = n << 1;  
    if (newCapacity < 0)  
        throw new IllegalStateException("Sorry, deque too big");  
    Object[] a = new Object[newCapacity];  
    // 既然是head和tail已经重合了,说明tail是在head的左边。  
    System.arraycopy(elements, p, a, 0, r); // 拷贝原数组从head位置到结束的数据  
    System.arraycopy(elements, 0, a, r, p); // 拷贝原数组从开始到head的数据  
    elements = (E[])a;  
    head = 0; // 重置head和tail为数据的开始和结束索引  
    tail = n;  
} 

// 拷贝该数组的所有元素到目标数组  
private <T> T[] copyElements(T[] a) {
    
      
    if (head < tail) {
    
     // 开始索引大于结束索引,一次拷贝  
        System.arraycopy(elements, head, a, 0, size());  
    } else if (head > tail) {
    
     // 开始索引在结束索引的右边,分两段拷贝  
        int headPortionLen = elements.length - head;  
        System.arraycopy(elements, head, a, 0, headPortionLen);  
        System.arraycopy(elements, 0, a, headPortionLen, tail);  
    }  
    return a;  
}

  • 添加元素
public void addFirst(E e) {
    
      
    if (e == null)  
        throw new NullPointerException();  
    // 本来可以简单地写成head-1,但如果head为0,减1就变为-1了,和elements.length - 1进行与操作就是为了处理这种情况,这时结果为elements.length - 1。  
    elements[head = (head - 1) & (elements.length - 1)] = e;  
    if (head == tail) // head和tail不可以重叠  
        doubleCapacity();  
}

public void addLast(E e) {
    
      
    if (e == null)  
        throw new NullPointerException();  
    // tail位置是空的,把元素放到这。  
    elements[tail] = e;  
    // 和head的操作类似,为了处理临界情况 (tail为length - 1时),和length - 1进行与操作,结果为0。  
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)  
        doubleCapacity();  
}

public boolean offerFirst(E e) {
    
      
    addFirst(e);  
    return true;  
}

public boolean offerLast(E e) {
    
      
    addLast(e);  
    return true;  
}

  • 删除头部和尾部元素
public E removeFirst() {
    
      
    E x = pollFirst();  
    if (x == null)  
        throw new NoSuchElementException();  
    return x;  
} 

public E removeLast() {
    
      
    E x = pollLast();  
    if (x == null)  
        throw new NoSuchElementException();  
    return x;  
}

public E pollFirst() {
    
      
    int h = head;  
    E result = elements[h]; // Element is null if deque empty  
    if (result == null)  
        return null;  
    // 表明head位置已为空  
    elements[h] = null;     // Must null out slot  
    head = (h + 1) & (elements.length - 1); // 处理临界情况(当h为elements.length - 1时),与后的结果为0。  
    return result;  
}

public E pollLast() {
    
      
    int t = (tail - 1) & (elements.length - 1); // 处理临界情况(当tail为0时),与后的结果为elements.length - 1。  
    E result = elements[t];  
    if (result == null)  
        return null;  
    elements[t] = null;  
    tail = t; // tail指向的是下个要添加元素的索引。  
    return result;  
}

  • 删除指定元素
public boolean removeFirstOccurrence(Object o) {
    
      
    if (o == null)  
        return false;  
    int mask = elements.length - 1;  
    int i = head;  
    E x;  
    while ( (x = elements[i]) != null) {
    
      
        if (o.equals(x)) {
    
      
            delete(i);  
            return true;  
        }  
        i = (i + 1) & mask; // 从头到尾遍历  
    }  
    return false;  
}

public boolean removeLastOccurrence(Object o) {
    
      
    if (o == null)  
        return false;  
    int mask = elements.length - 1;  
    int i = (tail - 1) & mask; // 末尾元素的索引  
    E x;  
    while ( (x = elements[i]) != null) {
    
      
        if (o.equals(x)) {
    
      
            delete(i);  
            return true;  
        }  
        i = (i - 1) & mask; // 从尾到头遍历  
    }  
    return false;  
}

private void checkInvariants() {
    
     // 有效性检查  
    assert elements[tail] == null; // tail位置没有元素  
    assert head == tail ? elements[head] == null :  
        (elements[head] != null &&  
            elements[(tail - 1) & (elements.length - 1)] != null); // 如果head和tail重叠,队列为空;否则head位置有元素,tail-1位置有元素。  
    assert elements[(head - 1) & (elements.length - 1)] == null; // head-1的位置没有元素。  
}

private boolean delete(int i) {
    
      
    checkInvariants();  
    final E[] elements = this.elements;  
    final int mask = elements.length - 1;  
    final int h = head;  
    final int t = tail;  
    final int front = (i - h) & mask; // i前面的元素个数  
    final int back  = (t - i) & mask; // i后面的元素个数  
  
    // Invariant: head <= i < tail mod circularity  
    if (front >= ((t - h) & mask)) // i不在head和tail之间  
        throw new ConcurrentModificationException();  
  
    // Optimize for least element motion  
    if (front < back) {
    
     // i的位置靠近head,移动开始的元素,返回false。  
        if (h <= i) {
    
      
            System.arraycopy(elements, h, elements, h + 1, front);  
        } else {
    
     // Wrap around  
            System.arraycopy(elements, 0, elements, 1, i);  
            elements[0] = elements[mask]; // 处理边缘元素  
            System.arraycopy(elements, h, elements, h + 1, mask - h);  
        }  
        elements[h] = null;  
        head = (h + 1) & mask; // head位置后移  
        return false;  
    } else {
    
     // i的位置靠近tail,移动末尾的元素,返回true。  
        if (i < t) {
    
     // Copy the null tail as well  
            System.arraycopy(elements, i + 1, elements, i, back);  
            tail = t - 1;  
        } else {
    
     // Wrap around  
            System.arraycopy(elements, i + 1, elements, i, mask - i);  
            elements[mask] = elements[0];  
            System.arraycopy(elements, 1, elements, 0, t);  
            tail = (t - 1) & mask;  
        }  
        return true;  
    }  
}

  • 获取元素
public E getFirst() {
    
      
    E x = elements[head];  
    if (x == null)  
        throw new NoSuchElementException();  
    return x;  
}  
  
public E getLast() {
    
      
    E x = elements[(tail - 1) & (elements.length - 1)]; // 处理临界情况(当tail为0时),与后的结果为elements.length - 1。  
    if (x == null)  
        throw new NoSuchElementException();  
    return x;  
}  
  
public E peekFirst() {
    
      
    return elements[head]; // elements[head] is null if deque empty  
}  
  
public E peekLast() {
    
      
    return elements[(tail - 1) & (elements.length - 1)];  
}

  • 队列操作
public boolean add(E e) {
    
      
    addLast(e);  
    return true;  
}  
  
public boolean offer(E e) {
    
      
    return offerLast(e);  
}  
  
public E remove() {
    
      
    return removeFirst();  
}  
  
public E poll() {
    
      
    return pollFirst();  
}  
  
public E element() {
    
      
    return getFirst();  
}  
  
public E peek() {
    
      
    return peekFirst();  
}

  • 栈操作
public void push(E e) {
    
      
    addFirst(e);  
}  
  
public E pop() {
    
      
    return removeFirst();  
}

  • 集合方法
public int size() {
    
    
    return (tail - head) & (elements.length - 1); // 和elements.length - 1进行与操作是为了处理当tail < head时的情况。  
}

public boolean isEmpty() {
    
    
    return head == tail; // tail位置的元素一定为空,head和tail相等,也为空。  
}

// 向前迭代器  
public Iterator<E> iterator() {
    
    
    return new DeqIterator();
}

// 向后迭代器  
public Iterator<E> descendingIterator() {
    
    
    return new DescendingIterator();
}

private class DeqIterator implements Iterator<E> {
    
    
    private int cursor = head;
    private int fence = tail; // 迭代终止索引,同时也为了检测并发修改。
    private int lastRet = -1; // 最近的next()调用返回的索引。据此可以定位到需要删除元素的位置。  

    public boolean hasNext() {
    
    
        return cursor != fence;
    }

    public E next() {
    
    
        if (cursor == fence)
            throw new NoSuchElementException();
        E result = elements[cursor];
        // This check doesn't catch all possible comodifications,  
        // but does catch the ones that corrupt traversal  
        if (tail != fence || result == null)
            throw new ConcurrentModificationException();
        lastRet = cursor;
        cursor = (cursor + 1) & (elements.length - 1); // 游标位置加1  
        return result;
    }

    public void remove() {
    
    
        if (lastRet < 0)
            throw new IllegalStateException();
        if (delete(lastRet)) {
    
     // 如果将元素从右往左移,需要将游标减1。  
            cursor = (cursor - 1) & (elements.length - 1); // 游标位置回退1。  
            fence = tail; // 重置阀值。  
        }
        lastRet = -1;
    }
}

private class DescendingIterator implements Iterator<E> {
    
    
    private int cursor = tail; // 游标开始索引为tail  
    private int fence = head; // 游标的阀值为head  
    private int lastRet = -1;

    public boolean hasNext() {
    
    
        return cursor != fence;
    }

    public E next() {
    
    
        if (cursor == fence)
            throw new NoSuchElementException();
        cursor = (cursor - 1) & (elements.length - 1); // tail是下个添加元素的位置,所以要减1才是尾节点的索引。  
        E result = elements[cursor];
        if (head != fence || result == null)
            throw new ConcurrentModificationException();
        lastRet = cursor;
        return result;
    }

    public void remove() {
    
    
        if (lastRet < 0)
            throw new IllegalStateException();
        if (!delete(lastRet)) {
    
     // 如果从左往右移,需要将游标加1。  
            cursor = (cursor + 1) & (elements.length - 1);
            fence = head;
        }
        lastRet = -1;
    }
}

public boolean contains(Object o) {
    
    
    if (o == null)
        return false; // ArrayDeque不可以存储null元素  
    int mask = elements.length - 1;
    int i = head;
    E x;
    while ((x = elements[i]) != null) {
    
    
        if (o.equals(x))
            return true;
        i = (i + 1) & mask; // 处理临界情况  
    }
    return false;
}

public boolean remove(Object o) {
    
    
    return removeFirstOccurrence(o);
}

public void clear() {
    
    
    int h = head;
    int t = tail;
    if (h != t) {
    
     // clear all cells  
        head = tail = 0; // 重置首尾索引  
        int i = h;
        int mask = elements.length - 1;
        do {
    
    
            elements[i] = null; // 清除元素  
            i = (i + 1) & mask;
        } while (i != t);
    }
}

public Object[] toArray() {
    
    
    return copyElements(new Object[size()]); // 把所有元素拷贝到新创建的Object数组上,所以对返回数组的修改不会影响该双端队列。  
}

public <T> T[] toArray(T[] a) {
    
    
    int size = size();
    if (a.length < size) // 目标数组大小不够  
        a = (T[]) java.lang.reflect.Array.newInstance(
                a.getClass().getComponentType(), size); // 利用反射创建类型为T,大小为size的数组。  
    copyElements(a); // 拷贝所有元素到目标数组。  
    if (a.length > size)
        a[size] = null; // 结束标识  
    return a;
}

  • Object方法
public ArrayDeque<E> clone() {
    
      
    try {
    
      
        ArrayDeque<E> result = (ArrayDeque<E>) super.clone();  
        result.elements = Arrays.copyOf(elements, elements.length); // 深度复制。  
        return result;  
        
    } catch (CloneNotSupportedException e) {
    
      
        throw new AssertionError();  
    }  
}

二:PriorityQueue

1、PriorityQueue简介

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

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

PriorityQueue 是一个无界队列,但是初始的容量(实际是一个Object[]),随着不断向优先级队列添加元素,其容量会自动扩容,无需指定容量增加策略的细节。

2、PriorityQueue的特点

(1)继承结构

在这里插入图片描述

  • PriorityQueue继承于AbstractQueue类,说明它是一个基于优先级堆的极大优先级队列。此队列按照在构造时所指定的顺序对元素排序,既可以根据元素的自然顺序来指定排序,也可以根据 Comparator 来指定,这取决于使用哪种构造方法。优先级队列不允许 null 元素。依靠自然排序的优先级队列还不允许插入不可比较的对象。
  • PriorityQueue实现了Serializable接口,说明它支持序列化。public interface Serializable类通过实现 java.io.Serializable 接口以启用其序列化功能。
  • PriorityQueue内部实现了Iterator接口,说明它可以进行迭代器遍历。

但是需要注意的是,使用迭代器遍历,并不能保证其优先级顺序,原因是迭代器在进行遍历时采取的是层次遍历,一层一层进行遍历,并不能保证其左右孩子的准确性,如果需要按照优先级顺序进行遍历,则需要使用for循环遍历,通过poll()方法获取其队头元素

(2)PriorityQueue特性

  • PriorityQueue类在Java1.5中引入并作为 Java Collections Framework 的一部分。
    PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。
  • 优先队列不允许空值,而且不支持non-comparable(不可比较)的对象,比如用户自定义的类。
    优先队列要求使用Java Comparable和Comparator接口给对象排序,并且在排序时会按照优先级处理其中的元素。
  • 优先队列的头是基于自然排序或者Comparator排序的最小元素。如果有多个对象拥有同样的排序,那么就可能随机地取其中任意一个。当我们获取队列时,返回队列的头对象。
  • 优先队列的大小是不受限制的,但在创建时可以指定初始大小。当我们向优先队列增加元素的时候,队列大小会自动增加。
  • PriorityQueue是非线程安全的,所以Java提供了PriorityBlockingQueue(实现BlockingQueue接口)用于Java多线程环境。

3、PriorityQueue常用操作

public boolean offer(E e); //将指定的元素插入此优先级队列。不能添加null元素。否则报出空指针异常。

public boolean add(E e); //将指定的元素插入此优先级队列。不能添加null元素。与offer()方法一致,实际上,该方法内部是调用offer()方法。

public boolean remove(Object o); //删除一个指定元素,如果元素不存在则返回false。

public E peek(); //取出队头元素但不删除,如果当前队头没有元素则返回null值。

public E element(); //取出队头元素但不删除,如果当前队头没有元素则抛出异常。

public E poll(); //取出队头元素并删除,如果当前队头没有元素则返回null值。

4、PriorityQueue源码分析

  • 成员变量
// 默认初始化大小
privatestaticfinalintDEFAULT_INITIAL_CAPACITY = 11;

// 用数组实现的二叉堆,下面的英文注释确认了我们前面的说法。
/**
 * 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.
 */
private transient Object[] queue ;

// 队列的元素数量
private int size = 0;

// 比较器
private final Comparator<? super E> comparator;

// 用于快速失败机制判断
private transient int modCount = 0;

  • 构造方法
/**
 * 默认构造方法,使用默认的初始大小来构造一个优先队列,比较器comparator为空,这里要求入队的元素必须实现Comparator接口
 */
 public PriorityQueue() {
    
    
    this(DEFAULT_INITIAL_CAPACITY, null);
 }

/**
 * 使用指定的初始大小来构造一个优先队列,比较器comparator为空,这里要求入队的元素必须实现Comparator接口
 */
 public PriorityQueue( int initialCapacity) {
    
    
    this(initialCapacity, null);
 }

/**
 * 使用指定的初始大小和比较器来构造一个优先队列
 */
 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
    // 初始大小不允许小于1
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    // 使用指定初始大小创建数组
    this.queue = new Object[initialCapacity];
    // 初始化比较器
    this.comparator = comparator;
 }

/**
 * 构造一个指定Collection集合参数的优先队列
 */
 public PriorityQueue(Collection<? extends E> c) {
    
    
    // 从集合c中初始化数据到队列
    initFromCollection(c);
    // 如果集合c是包含比较器Comparator的(SortedSet/PriorityQueue),则使用集合c的比较器来初始化队列的Comparator
    if (c instanceof SortedSet)
        comparator = (Comparator<? super E>)
            ((SortedSet<? extends E>)c).comparator();
    else if (c instanceof PriorityQueue)
        comparator = (Comparator<? super E>)
            ((PriorityQueue<? extends E>)c).comparator();
    //  如果集合c没有包含比较器,则默认比较器Comparator为空
    else {
    
    
        comparator = null;
        // 调用heapify方法重新将数据调整为一个二叉堆
        heapify();
    }
 }

/**
 * 构造一个指定PriorityQueue参数的优先队列
 */
 public PriorityQueue(PriorityQueue<? extends E> c) {
    
    
    comparator = (Comparator<? super E>)c.comparator();
    initFromCollection(c);
 }

/**
 * 构造一个指定SortedSet参数的优先队列
 */
 public PriorityQueue(SortedSet<? extends E> c) {
    
    
    comparator = (Comparator<? super E>)c.comparator();
    initFromCollection(c);
 }

/**
 * 从集合中初始化数据到队列
 */
 private void initFromCollection(Collection<? extends E> c) {
    
    
    // 将集合Collection转换为数组a
    Object[] a = c.toArray();
    // If c.toArray incorrectly doesn't return Object[], copy it.
    // 如果转换后的数组a类型不是Object数组,则转换为Object数组
    if (a.getClass() != Object[].class)
        a = Arrays. copyOf(a, a.length, Object[]. class);
    // 将数组a赋值给队列的底层数组queue
    queue = a;
    // 将队列的元素个数设置为数组a的长度
    size = a.length ;
 }

第四个构造方法中,如果填入的集合c没有包含比较器Comparator,则在调用initFromCollection初始化数据后,在调用heapify方法对数组进行调整,使得它符合二叉堆的规范或者特点。

  • 添加元素
/**
 * 添加一个元素
 */
 public boolean add(E e) {
    
    
    return offer(e);
 }

/**
 * 入队
 */
 public boolean offer(E e) {
    
    
    // 如果元素e为空,则排除空指针异常
    if (e == null)
        throw new NullPointerException();
    // 修改版本+1
    modCount++;
    // 记录当前队列中元素的个数
    int i = size ;
    // 如果当前元素个数大于等于队列底层数组的长度,则进行扩容
    if (i >= queue .length)
        grow(i + 1);
    // 元素个数+1
    size = i + 1;
    // 如果队列中没有元素,则将元素e直接添加至根(数组小标0的位置)
    if (i == 0)
        queue[0] = e;
    // 否则调用siftUp方法,将元素添加到尾部,进行上移判断
    else
        siftUp(i, e);
    return true;
 }

这里的add方法依然没有按照Queue的规范,在队列满的时候抛出异常,因为PriorityQueue和前面讲的ArrayDeque一样,会进行扩容,所以只有当队列容量超出int范围才会抛出异常。

  • 扩容方法
/**
 * 数组扩容
 */
 private void grow(int minCapacity) {
    
    
    // 如果最小需要的容量大小minCapacity小于0,则说明此时已经超出int的范围,则抛出OutOfMemoryError异常
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    // 记录当前队列的长度
    int oldCapacity = queue .length;
    // Double size if small; else grow by 50%
    // 如果当前队列长度小于64则扩容2倍,否则扩容1.5倍
    int newCapacity = ((oldCapacity < 64)?
                       ((oldCapacity + 1) * 2):
                       ((oldCapacity / 2) * 3));
    // 如果扩容后newCapacity超出int的范围,则将newCapacity赋值为Integer.Max_VALUE
    if (newCapacity < 0) // overflow
        newCapacity = Integer. MAX_VALUE;
    // 如果扩容后,newCapacity小于最小需要的容量大小minCapacity,则按找minCapacity长度进行扩容
    if (newCapacity < minCapacity)
        newCapacity = minCapacity;
    // 数组copy,进行扩容
    queue = Arrays.copyOf( queue, newCapacity);
 }

  • 向上调整
     

在这里插入图片描述
结合上面的图解,我们来说明一下二叉堆的添加元素过程:
1、将元素2添加在最后一个位置(队尾)(图2)。
2、由于2比其父亲6要小,所以将元素2上移,交换2和6的位置(图3)。
3、然后由于2比5小,继续将2上移,交换2和5的位置(图4),此时2大于其父亲(根节点)1,结束。
这里的节点颜色是为了凸显,应便于理解,跟红黑树的中的颜色无关,不要弄混。

/**
 * 上移,x表示新插入元素,k表示新插入元素在数组的位置
 */
 private void siftUp(int k, E x) {
    
    
    // 如果比较器comparator不为空,则调用siftUpUsingComparator方法进行上移操作
    if (comparator != null)
        siftUpUsingComparator(k, x);
    // 如果比较器comparator为空,则调用siftUpComparable方法进行上移操作
    else
        siftUpComparable(k, x);
 }

 private void siftUpComparable(int k, E x) {
    
    
    // 比较器comparator为空,需要插入的元素实现Comparable接口,用于比较大小
    Comparable<? super E> key = (Comparable<? super E>) x;
    // k>0表示判断k不是根的情况下,也就是元素x有父节点
    while (k > 0) {
    
    
        // 计算元素x的父节点位置[(n-1)/2]
        int parent = (k - 1) >>> 1;
        // 取出x的父亲e
        Object e = queue[parent];
        // 如果新增的元素k比其父亲e大,则不需要"上移",跳出循环结束
        if (key.compareTo((E) e) >= 0)
            break;
        // x比父亲小,则需要进行"上移"
        // 交换元素x和父亲e的位置
        queue[k] = e;
        // 将新插入元素的位置k指向父亲的位置,进行下一层循环
        k = parent;
    }
    // 找到新增元素x的合适位置k之后进行赋值
    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;
 }

  • 删除元素
/**
 * 删除元素
 */
 public boolean remove(Object o) {
    
    
    //获取指定元素的下标位置
    int i = indexOf(o);
    if (i == -1)
        //如果没有该元素,则返回false
        return false;
    else {
    
    
        //根据元素下标删除元素
        removeAt(i);
        return true;
    }
 }

private 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) {
    
    
            siftUp(i, moved);//向上调整
            if (queue[i] != moved)
                return moved;
        }
    }
    return null;
}

  • 向下调整
     
    在这里插入图片描述
    结合上面的图解,我们来说明一下二叉堆的出队过程:
    1、将找出队尾的元素8,并将它在队尾位置上删除(图2)。
    2、此时队尾元素8比根元素1的最小孩子3要大,所以将元素1下移,交换1和3的位置(图3)。
    3、然后此时队尾元素8比元素1的最小孩子4要大,继续将1下移,交换1和4的位置(图4)。
    4、然后此时根元素8比元素1的最小孩子9要小,不需要下移,直接将根元素8赋值给此时元素1的位置,1被覆盖则相当于删除(图5),结束。
/**
 * 下移,x表示队尾的元素,k表示被删除元素在数组的位置
 */
 private void siftDown(int k, E x) {
    
    
    // 如果比较器comparator不为空,则调用siftDownUsingComparator方法进行下移操作
    if (comparator != null)
        siftDownUsingComparator(k, x);
    // 比较器comparator为空,则调用siftDownComparable方法进行下移操作
    else
        siftDownComparable(k, x);
 }

 private void siftDownComparable(int k, E x) {
    
    
    // 比较器comparator为空,需要插入的元素实现Comparable接口,用于比较大小
    Comparable<? super E> key = (Comparable<? super E>)x;
    // 通过size/2找到一个没有叶子节点的元素
    int half = size >>> 1;        // loop while a non-leaf
    // 比较位置k和half,如果k小于half,则k位置的元素就不是叶子节点
    while (k < half) {
    
    
         // 找到根元素的左孩子的位置[2n+1]
        int child = (k << 1) + 1; // assume left child is least
         // 左孩子的元素
        Object c = queue[child];
         // 找到根元素的右孩子的位置[2(n+1)]
        int right = child + 1;
        // 如果左孩子大于右孩子,则将c复制为右孩子的值,这里也就是找出左右孩子哪个最小
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue [right]) > 0)
            c = queue[child = right];
        // 如果队尾元素比根元素孩子都要小,则不需"下移",结束
        if (key.compareTo((E) c) <= 0)
            break;
        // 队尾元素比根元素孩子都大,则需要"下移"
        // 交换跟元素和孩子c的位置
        queue[k] = c;
        // 将根元素位置k指向最小孩子的位置,进入下层循环
        k = child;
    }
    // 找到队尾元素x的合适位置k之后进行赋值
    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;
 }

  • 获取队头元素并删除
/**
 * 删除并返回队头的元素,如果队列为空则抛出NoSuchElementException异常(该方法在AbstractQueue中)
 */
 public E remove() {
    
    
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
 }

/**
 * 删除并返回队头的元素,如果队列为空则返回null
 */
 public E poll() {
    
    
    // 队列为空,返回null
    if (size == 0)
        return null;
    // 队列元素个数-1
    int s = --size ;
    // 修改版本+1
    modCount++;
    // 队头的元素
    E result = (E) queue[0];
    // 队尾的元素
    E x = (E) queue[s];
    // 先将队尾赋值为null
    queue[s] = null;
    // 如果队列中不止队尾一个元素,则调用siftDown方法进行"下移"操作
    if (s != 0)
        siftDown(0, x);
    return result;
 }

  • 获取队头元素但不删除
/**
 * 删除并返回队头的元素,如果队列为空则抛出NoSuchElementException异常(该方法在AbstractQueue中)
 */
 public E remove() {
    
    
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
 }

/**
 * 删除并返回队头的元素,如果队列为空则返回null
 */
 public E poll() {
    
    
    // 队列为空,返回null
    if (size == 0)
        return null;
    // 队列元素个数-1
    int s = --size ;
    // 修改版本+1
    modCount++;
    // 队头的元素
    E result = (E) queue[0];
    // 队尾的元素
    E x = (E) queue[s];
    // 先将队尾赋值为null
    queue[s] = null;
    // 如果队列中不止队尾一个元素,则调用siftDown方法进行"下移"操作
    if (s != 0)
        siftDown(0, x);
    return result;
 }

猜你喜欢

转载自blog.csdn.net/Super_Powerbank/article/details/109148767
今日推荐