PriorityQuene 是最小堆(任意一个非叶子节点的权值,都不大于其左右子节点的权值)实现的,第一个元素就是最小值,想了解最小堆,必须先要了解一下完全二叉树,具体长什么样子,看下图
image.png
这个二叉树有以下特点(重点记住)
假设完全二叉树有一个父节点i,那么他的左子节点就是leftNode = (i*2)+ 1
右子节点 rightNode = (i*2)+ 2
假设有一个节点n ,那它的父节点是 (n-1)/2
size/2-1 的节点都是父节点
成员属性
transient Object[] queue; //可见是数组保存的,但是使用了完全二叉数
private static final int DEFAULT_INITIAL_CAPACITY = 11; //默认11个
/**
* The number of elements in the priority queue.
*/
private int size = 0; //元素个数
/**
* The comparator, or null if priority queue uses elements'
* natural ordering.
*/
private final Comparator<? super E> comparator; //可以传进去一个比较器
/**
* The number of times this priority queue has been
* <i>structurally modified</i>. See AbstractList for gory details.
*/
transient int modCount = 0; // 修改记录数,用于 fail-fast机制,在并发的时候,迭代器会很快的失败
add(E e)
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
if (e == null) //可见不能add null 值
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length) //元素个数大于等于数组的长度
grow(i + 1);
size = i + 1; //元素个数加1
if (i == 0) //若果i = 0 说明是第一个元素
queue[0] = e; //直接插入
else
siftUp(i, e); //不是第一个元素 ,请看下面siftUp函数分析,记住i是元素个数,e是插的值
return true;
}
//---------------------------------------------------扩容-----------------------------------------------------------------------
// 记住:当数组小于64 则是原长度+2 否则 原长度+原长度的一半
private void grow(int minCapacity) { // minCapacity = i + 1 元素个数+1
int oldCapacity = queue.length;
//oldCapacity < 64 如果旧数组长度小于64,则调整长度位 oldCapacity = oldCapacity + 2
//否则是oldCapacity = oldCapacity + oldCapacity >> 1(可理解为oldCapacity / 2) 也就是增长为原来的一半
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
//如果大于MAX_ARRAY_SIZE 则hugeCapacity(minCapacity) 看下面的函数分析
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// Arrays.copyOf根据新的数组长度拷贝原数组到新的数组,看下面copyOf()分析
queue = Arrays.copyOf(queue, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 = 2147483647 - 8 = 2147483639
//MAX_VALUE = 0x7fffffff; 最大int值 = 2147483647
//大于MAX_ARRAY_SIZE 则等于 MAX_VALUE
//否则等于 MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
public static <T> T[] copyOf(T[] original, int newLength) {
//第三个参数 original的class对象
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class) //判断是否是Object类型
? (T[]) new Object[newLength] //Object类型
//Array.newInstance方法创建一个具有指定组件类型和长度的新数组。
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//这里就是拷贝数组了,可以看我的另外一个博文,这里就不说了
//original 原数组
//0 拷贝原数组开始位置
//copy 目标数组
// Math.min(original.length, newLength) 取最小值,这里肯定是original.length
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
//返回
return copy;
}
//---------------------------------------------------扩容-----------------------------------------------------------------------
//---------------------------------------------------siftUp-----------------------------------------------------------------------
private void siftUp(int k, E x) { //记住k是元素个数,e是插的值
if (comparator != null) //看你定没定义comparator ,可以在new PriorityQuene()中传一个比较器
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x); //使用默认的比较器
}
private void siftUpComparable(int k, E x) {
//如果是String ,String 类型实现了 Comparable可以强转,如果是自定义的,需要实现Comparable,或者你自己在 new PriorityQuene()中传一个比较器
Comparable<? super E> key = (Comparable<? super E>) x;
//记住k是元素个数,e是插的值
//第一次必须大于0啊,是0不就是上面处理了吗 第一个元素直接 queue[0] = e
//至于其他次,看下面
while (k > 0) {
//记得上面需要记得的完全二叉树的公式吗?
//第一次进来的时候,k等于元素个数,不就是最后一个元素 他的下标=(k-1)/2,就是最后一个元素的父节点吗?
//当然这是循环,可能执行多次,但是,这里的意思,不就是找某个节点的父节点下标吗?
int parent = (k - 1) >>> 1;
//取出父节点的值·
Object e = queue[parent];
//记住小堆顶的定义-任意一个非叶子节点的值,都不大于其左右子节点的值
//如果这个插入值比父元素要大或者等于,不就说明了,他可以插入k这个位置吗
//否则 说明比父节点要小,那他就要把父节点放到k,自己准备上位到父节点的位置上,
//记得我只是说准备,不一定能,要一直循环直到找到能放自己的地方
if (key.compareTo((E) e) >= 0)
break;
//如果是可以插入k,这个不会执行
//如果执行,就是比父节点还要小,则把父节点放到k节点上,自己准备上位到父节点的位置上
queue[k] = e;
//k的位置就是父节点的下标了,继续判断父节点的父节点是否小于自己
k = parent;
}
queue[k] = key; // k就是key能放的位置,yes,yes
}
//---------------------------------------------------siftUp-----------------------------------------------------------------------
peek() 获取但不删除队首元素,也就是队列中权值最小的那个元素
public E peek() {
return (size == 0) ? null : (E) queue[0]; //空返回null 否则,取数组第一个元素
}
element() 获取但不删除队首元素,也就是队列中权值最小的那个元素,跟peek()比较只是null 会抛异常
public E element() {
E x = peek(); //直接调peek()
if (x != null) //不是null 返回
return x;
else //null 抛异常
throw new NoSuchElementException();
}
remove() and poll()获取并删除队首元素,区别是当方法失败时前者抛出异常,后者返回null
public E remove() {
E x = poll(); //主要看这个
if (x != null) //不是null 返回
return x;
else //null 抛异常
throw new NoSuchElementException();
}
public E poll() {
if (size == 0) //没有元素
return null; //返回null
int s = --size; //--size,元素个数先自减1,s = size-1
modCount++; //修改次数+1
E result = (E) queue[0]; // 记录第一个元素
E x = (E) queue[s]; //记录最后一个元素
queue[s] = null; // 最后一个元素变成空的
if (s != 0) //不是只有一个元素的
siftDown(0, x); //进去调整数
return result;
}
private void siftDown(int k, E x) {
//记得第一次 k =0 x=最后一个元素的值
if (comparator != null)
siftDownUsingComparator(k, x); //看你定没定义comparator ,可以在new PriorityQuene()中传一个比较器
else
siftDownComparable(k, x); //使用默认的比较器
}
private void siftDownComparable(int k, E x) {
//记得第一次 k =0 x=最后一个元素的值
//如果是String ,String 类型实现了 Comparable可以强转,如果是自定义的,需要实现Comparable,
//或者你自己在 new PriorityQuene()中传一个比较器
Comparable<? super E> key = (Comparable<? super E>)x;
//记得上面需要记得的完全二叉树的公式吗?
// size/2-1 就是所有的父节点下标
//举个例子 size = 12 则 所有的父节点 = 12/2 -1 =5 也就是 0,1,2,3,4,5是父节点
int half = size >>> 1;
//k的下标 不可能不是父节点,否则结束循坏
//上面的例子举例,size >>> 1 可以等于 12/2 = 6 ,所以 k = 0,1,2,3,4,5才能进这个循环
//第一次进来循坏,k==0
while (k < half) {
int child = (k << 1) + 1; // k的左子节点 左节点公式 =(父节点*2)+ 1
Object c = queue[child];//取出k的左子节点
int right = child + 1;// k的右子节点 child =(父节点*2)+ 1 所以 right = (父节点*2)+ 1 +1
//如果没有右节点就不比较了,这里只是想把k的两个子节点的最小值节点赋给c
//child =最小值节点的的下标
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
//如果key比k的最小值的子节点的值小,则说明k就是key需要插入的地方
//否则,说明key的值大,就把小的节点飘上去
//还记得我们是remove()删除第一个元素,第一次循环k==0
//如果key 比第一个元素的左右节点都小,那么k=0 不就是=key 了吗?
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c; //那么k的位置就给最小值,让最小值的节点飘上去,
//k =最小值的下标,如果k 还是父节点继续循环比较k的左右节点的最小值节点是否比key的大,大的话k就是放key的地方,否则继续循坏
//不明白的看一下,下面的图
k = child;
}
queue[k] = key;
}
image.png
image.png
image.png
image.png
image.png
image.png
image.png
不知明白了不
remove(Object o) 删除队列中跟o相等的某一个元素(如果有多个相等,只删除一个)
public boolean remove(Object o) {
int i = indexOf(o); //找出下标,可以看下面的函数
if (i == -1)
return false; //找不到 -1
else {
removeAt(i); //这个才是关键,看下面的分析
return true;
}
}
private int indexOf(Object o) {
if (o != null) { //不是null
for (int i = 0; i < size; i++) //循环找啊找!!!!
if (o.equals(queue[i]))
return i;
}
return -1; //null 找不到 返回-1
}
private E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++; //修改的次数+1
int s = --size; //元素个数给减一
if (s == i) // 删除最后一个元素
queue[i] = null;
else { //不是最后一个元素
E moved = (E) queue[s]; //取出最后的元素
queue[s] = null;//最后的元素被删了
siftDown(i, moved);//上面的remove()已经讲过了,这里不重复了
if (queue[i] == moved) { //删的地方就是可以放最后一个元素的地方,那就再确定一下i的父类是否正确
siftUp(i, moved);//add的时候已经解析
//不是一样,返回moved,
if (queue[i] != moved)
return moved;
}
}
return null;
}
网友评论