小白学java底层源码-----PriorityQueue

Priority Queue

JDK5开始引入, 能够根据对象的优先级来进行排序,要求对象实现Comparable或Comparator接口中的比较方法,底层采用堆来实现。
根据优先级对数据进行排列的数据结构

基本介绍:
  • 根据优先级来排列的队列,存储的对象一定要实现Comparable接口,否则将抛出异常
  • 底层实际上也是通过数组来进行存储数据的, 空间不限
  • 实现了Collection和Iterator接口,通过Iterator遍历数据时并不保证有序的,如果要有序输出,可以通过Arrays.sort(pq.toArray())
  • 非同步,多个线程同时操作会发生并发异常,可以使用JUC中线程安全的PriorityBlockingQueue类
  • 常用方法:
    • 时间复杂度 O(logN)的方法:offer、poll、remove、add,
    • 线性时间方法:remove(object), contains(object)
    • 常数时间方法:peek、element、size
  • 队列中的头元素优先级最小
  • 使用堆来保证元素的有序性
源码实现:
  • 采用数组,默认大小 DEFAULT_INITIAL_CAPACITY = 11

  • 使用堆来保证数据的有序性,queue[0] 优先级最低,queue[n] 的两个孩子queue[2n+1] and queue[2(n+1)]

  • 假设Comparable接口中实现的方法类似下面:

    @Override
    public int compareTo(int o) {
    	return  this.x - o.x;
    }
    

这样我们的优先级队列中就采用小顶堆的数据结构来实现, 否则采用大顶堆

下面采用小顶堆的形式进行分析源码

对象初始化:
// 无参构造: 默认大小11
public PriorityQueue() {
    this(DEFAULT_INITIAL_CAPACITY, null);
}

public PriorityQueue(int initialCapacity) {
    this(initialCapacity, null);
}

// 指定比较器
public PriorityQueue(Comparator<? super E> comparator) {
    this(DEFAULT_INITIAL_CAPACITY, comparator);
}
/**
有参构造方法:指定初始化大小,指定比较器
*/
public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    this.queue = new Object[initialCapacity];
    this.comparator = comparator;
}

// 传入一个集合来创建PriorityQueue, 如果c中实现了Comparator接口,则使用c中的Comparator
public PriorityQueue(Collection<? extends E> c) {
    if (c instanceof SortedSet<?>) {
        SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
        this.comparator = (Comparator<? super E>) ss.comparator();
        initElementsFromCollection(ss);
    }
    else if (c instanceof PriorityQueue<?>) {
        PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
        this.comparator = (Comparator<? super E>) pq.comparator();
        initFromPriorityQueue(pq);
    }
    else {
        this.comparator = null;
        // 通过c来初始化PriorityQueue
        initFromCollection(c);
    }
}

private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
    // 如果c是由PriorityQueue实例化的,那么将c中的数据赋值给当前正在实例化的PriorityQueue
    if (c.getClass() == PriorityQueue.class) {
        this.queue = c.toArray();
        this.size = c.size();
    } else {
        initFromCollection(c);
    }
}
// 将一个Collection类型的数据初始化到当前正在创建的对象中
private void initElementsFromCollection(Collection<? extends E> c) {
    Object[] a = c.toArray();
    // If c.toArray incorrectly doesn't return Object[], copy it.
    if (a.getClass() != Object[].class)
        // copy a数组并转为 Object[]类型
        a = Arrays.copyOf(a, a.length, Object[].class);
    int len = a.length;
    // 如果a数组中含有null,那么抛出异常
    if (len == 1 || this.comparator != null)
        for (int i = 0; i < len; i++)
            if (a[i] == null)
                throw new NullPointerException();
    this.queue = a;
    this.size = a.length;
}

// 这个方法在调用initFromPriorityQueue()方法中执行,并构造一个标准的堆
private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}
插入元素:

动画演示:

新增元素:

将元素插入最后一个位置后,此时的堆并没有满足父节点小于孩子节点规则,因此需要对堆进行相应的调整。

// add 方法最终也是调用的offer
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
        // 在位置i插入e
        siftUp(i, e);
    return true;
}
// 在k位置插入元素x后,进行上浮,判断是否满足小顶堆规则
private void siftUp(int k, E x) {
    if (comparator != null)
        siftUpUsingComparator(k, x);
    else
        siftUpComparable(k, x);
}


// PriorityQueue对象没有实现比较器的插入操作,使用待插入元素的比较器进行大小比较
private void siftUpComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>) x;
    while (k > 0) {
        // 得到当前元素的父节点的位置,相当于(k - 1) / 2
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        // 如果待插入的元素大于父节点,则说明不会破坏小顶堆的特性,直接将元素放在k位置即可
        if (key.compareTo((E) e) >= 0)
            break;
        // 待插入的元素小于父节点,将父节点放到k位置,循环向上找到x的合适位置
        queue[k] = e;
        k = parent;
    }
    queue[k] = key;
}

// PriorityQueue对象有比较器的插入操作, 类似上面的方法
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;
}

private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}
移除元素:

这里展示移除元素4:

// 移除堆顶的元素,同时调用siftDown来调整堆的结构
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;
 }

public boolean remove(Object o) {
    // 遍历数组找到需要remove的元素的位置
    int i = indexOf(o);
    if (i == -1)
        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];
        // 将最后一个位置置为null
        queue[s] = null;
        // 具体删除元素的方法,根据i位置的元素与moved比较,是否对moved下调(moved是放i位置,还是i的孩子位置)
        siftDown(i, moved);
        // 成立说明,moved刚好放到了i的位置
        if (queue[i] == moved) {
            // 判断是否对i位置的元素进行上浮操作
            siftUp(i, moved);
            // i位置的元素已经上浮
            if (queue[i] != moved)
                return moved;
        }
    }
    return null;
}

// 对元素进行下浮操作, 即k位置的元素被删除后,由x来代替i位置,对x进行下浮操作
private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

// k: 待删除的元素的位置, x: 数组最后一个位置的元素,最后要将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
    // k 不能超过最后一个叶子节点的位置,如果超过,直接在相应位置填入key
    while (k < half) {
        // 得到待删除元素位置的左孩子位置
        int child = (k << 1) + 1; // assume left child is least
        // 得到左孩子的元素
        Object c = queue[child];
        // 右孩子位置
        int right = child + 1;
        // 如果右孩子存在 左孩子是否大于右孩子,目的是将最小的孩子赋值给c
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            c = queue[child = right];
        // 判断元素key是否小于c(c为待插入元素最小的孩子节点),满足则break,直接将key放到待删除元素位置,这样肯定是满足堆的特性
        if (key.compareTo((E) c) <= 0)
            break;
        // 没有找到合适的位置继续向上查找,直到找到
        queue[k] = c;
        k = child;
    }
    queue[k] = key;
}

// 跟上面方法类似
@SuppressWarnings("unchecked")
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;
}
扩容操作:
// 在插入元素时,offer方法调用: minCapacity = oldCapacity + 1
private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // Double size if small; else grow by 50%
    // 如果oldCapacity < 64 那么扩容原来的二倍 + 2,
    // 否则扩容为原来的1.5倍
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                     (oldCapacity + 2) :
                                     (oldCapacity >> 1));
    // overflow-conscious code
    // 如果计算出的扩容长度超过了默认的最大值,那么调用hugeCapacity来判断是扩容到Integer.MAX_VALUE,还是MAX_ARRAY_SIZE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 将原来的数据copy到长度为newCapacity的新数组中
    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;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值