JDK源码阅读(8):PriorityQueue

PriorityQueue

version:1.8

今天看一种特殊的队列“优先级队列

优先级队列名为队列,但不满足一般队列先进先出的特性,而是优先级最高(或最低)的先出,其底层使用实现。

是一种特殊的二叉树,其不同于二叉搜索树,是不完全有序的,其从根节点到叶子节点形成的每条路径都是有序的,但各条路径之间是不要求有序的,如此保证了根节点是全局的最大(或最小)的节点。同时,堆是完全二叉树

根据根节点是最大还是最小,可称为大根堆或小根堆。

public class PriorityQueue<E> extends AbstractQueue<E>

由于其为完全二叉树,通过数组存储,如此可通过父节点与子节点的下标大小关系快速找到父节点(n,从0开始)或子节点(2n+1,2n+2),也不会造成数组存储大量的空叶子节点。

transient Object[] queue;

查看首节点

查看很简单:

@SuppressWarnings("unchecked")
public E peek() {
    return (size == 0) ? null : (E) queue[0];
}

element 继承自AbstractQueue

public E element() {
    E x = peek();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

添加节点

private int size = 0;

public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    
    int i = size;
    if (i >= queue.length)  //容量不足,扩容
        grow(i + 1);
        
    size = i + 1;
    if (i == 0)             //根节点
        queue[0] = e;
    else
        siftUp(i, e);       //上浮
        
    return true;
}

先看看扩容:
其实就是确定下新的容量大小,然后将老的数组拷贝到新数组中去即可(各元素位置不变):

private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // Double size if small; else grow by 50%
    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);
}
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :                   //实际生效
        MAX_ARRAY_SIZE;
}

然后看下其如何调整树的结构:分成两种情况,使用Comparator或使用Comparable

private void siftUp(int k, E x) {
    if (comparator != null)
        siftUpUsingComparator(k, x);
    else
        siftUpComparable(k, x);
}

看一种即可:

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

在这里插入图片描述
如图所示,首先将节点插入在最后,然后顺着路径一步步向上比较,直到插入的节点大于等于当前父节点,类似于冒泡排序,由于原路径是有序的,不需要每次比较都交换,只需将父节点下沉,直到最后一步再将插入节点赋值给当前父节点。

堆只要求每条路径有序即可,其调整相对红黑树简单许多。

其不存在容量限制问题(见Queue),addoffer相同。

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

移除头节点

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

移除节点与添加节点相类似,移除尾节点,将尾节点的值赋予根节点。
接下来就将根节点下沉以调整树的结构:

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

在这里插入图片描述
每个父节点至少涉及两条路径,因此需要选取父节点与其两个子节点中最小的节点作为新的父节点,一步步下沉根节点,直到根节点当前位置的两个子节点的值都大于其父节点的值或根节点到了叶子节点的位置。

remove()继承自AbstractQueue

public E remove() {
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

移除指定节点

public boolean remove(Object o) {
    int i = indexOf(o);
    if (i == -1)
        return false;
    else {
        removeAt(i);
        return true;
    }
}

首先找到节点下标:

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

然后移除并调整:

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

这里与移除根节点不同的地方在于下沉后还需要判断是否需要上浮。

在这里插入图片描述
如图所示:要移除的节点可能与尾节点不再同一条路径上。
如此,若其下沉,代表其肯定比父节点大,但若未下沉,则不一定,此时还需要上浮。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值