[关闭]
@pastqing 2016-04-05T13:54:38.000000Z 字数 3000 阅读 2556

Java集合框架——PriorityQueue

java java-collection-framwork



一、PriorityQueue的数据结构


二、PriorityQueue源码分析

成员:

  1. priavte transient Object[] queue;
  2. private int size = 0;

1.PriorityQueue构造小顶堆的过程

这里我们以priorityQueue构造器传入一个容器为参数PriorityQueue(Collecntion<? extends E>的例子:

构造小顶堆的过程大体分两步:

  1. 复制容器数据,检查容器数据是否为null

    1. private void initElementsFromCollection(Collection<? extends E> c) {
    2. Object[] a = c.toArray();
    3. // If c.toArray incorrectly doesn't return Object[], copy it.
    4. if (a.getClass() != Object[].class)
    5. a = Arrays.copyOf(a, a.length, Object[].class);
    6. int len = a.length;
    7. if (len == 1 || this.comparator != null)
    8. for (int i = 0; i < len; i++)
    9. if (a[i] == null)
    10. throw new NullPointerException();
    11. this.queue = a;
    12. this.size = a.length;
    13. }
  2. 调整,使数据满足小顶堆的结构。
    首先介绍两个调整方式siftUpsiftDown

    • siftDown: 在给定初始化元素的时候,要调整元素,使其满足最小堆的结构性质。因此不停地从上到下将元素x的键值与孩子比较并做交换,直到找到元素x的键值小于等于孩子的键值(即保证它比其左右结点值小),或者是下降到叶子节点为止。
      例如如下的示意图,调整9这个节点:
      siftDown_1.png-50.6kB

      1. private void siftDownComparable(int k, E x) {
      2. Comparable<? super E> key = (Comparable<? super E>)x;
      3. int half = size >>> 1; // size/2是第一个叶子结点的下标
      4. //只要没到叶子节点
      5. while (k < half) {
      6. int child = (k << 1) + 1; // 左孩子
      7. Object c = queue[child];
      8. int right = child + 1;
      9. //找出左右孩子中小的那个
      10. if (right < size &&
      11. ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
      12. c = queue[child = right];
      13. if (key.compareTo((E) c) <= 0)
      14. break;
      15. queue[k] = c;
      16. k = child;
      17. }
      18. queue[k] = key;
      19. }
    • siftUp: priorityQueue每次新增加一个元素的时候是将新元素插入对尾的。因此,应该与siftDown有同样的调整过程,只不过是从下(叶子)往上调整。
      例如如下的示意图,填加key为3的节点:
      siftUp.png-28.8kB

      1. private void siftUpComparable(int k, E x) {
      2. Comparable<? super E> key = (Comparable<? super E>) x;
      3. while (k > 0) {
      4. int parent = (k - 1) >>> 1; //获取parent下标
      5. Object e = queue[parent];
      6. if (key.compareTo((E) e) >= 0)
      7. break;
      8. queue[k] = e;
      9. k = parent;
      10. }
      11. queue[k] = key;
      12. }

总体的建立小顶堆的过程就是

  1. private void initFromCollection(Collection<? extends E> c) {
  2. initElementsFromCollection(c);
  3. heapify();
  4. }

其中heapify就是siftDown的过程。


2.PriorityQueue容量扩容过程

从实例成员可以看出,PriorityQueue维护了一个Object[], 因此它的扩容方式跟顺序表ArrayList相差不多。
这里只给出grow方法的源码

  1. private void grow(int minCapacity) {
  2. int oldCapacity = queue.length;
  3. // Double size if small; else grow by 50%
  4. int newCapacity = oldCapacity + ((oldCapacity < 64) ?
  5. (oldCapacity + 2) :
  6. (oldCapacity >> 1));
  7. // overflow-conscious code
  8. if (newCapacity - MAX_ARRAY_SIZE > 0)
  9. newCapacity = hugeCapacity(minCapacity);
  10. queue = Arrays.copyOf(queue, newCapacity);
  11. }

可以看出,当数组的Capacity不大的时候,每次扩容也不大。当数组容量大于64的时候,每次扩容double。


三、PriorityQueue的应用

这里给出一个最简单的应用:从动态数据中求第K个大的数。
思路就是维持一个size = k 的小顶堆。

  1. //data是动态数据
  2. //heap维持动态数据的堆
  3. //res用来保存第K大的值
  4. public boolean kthLargest(int data, int k, PriorityQueue<Integer> heap, int[] res) {
  5. if(heap.size() < k) {
  6. heap.offer(data);
  7. if(heap.size() == k) {
  8. res[0] = heap.peek();
  9. return true;
  10. }
  11. return false;
  12. }
  13. if(heap.peek() < data) {
  14. heap.poll();
  15. heap.offer(data);
  16. }
  17. res[0] = heap.peek();
  18. return true;
  19. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注