文章目录
一: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;
}