天天看点

优先队列 PriorityQueue源码解析PriorityQueue

PriorityQueue

逻辑上使用堆结构(完全二叉树)实现,物理上使用动态数组实现,并非像TreeMap一样完全有序,但是如果按照指定方式出队,结果可以是有序的。

先看题目

在未排序的数组中找到第 k个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
输入:3,2,3,1,2,4,5,5,6,k =4。输出就是4,因此重复的并不考虑。
           

一般解法

public static int findKthLargest(int[] nums, int k) {
    Arrays.sort(nums);
    return nums[nums.length - k];
  }
           

优先队列解法

public static int findKthLargest(int[] nums, int k) {
    Queue<Integer> queue = new PriorityQueue<>();
    for (int num : nums) {
      queue.add(num);
      if (queue.size() > k) {
        //返回队首元素,队首元素出队列
        queue.poll();
      }
    }
      //返回队首元素
    return queue.peek();
  }
           

适用场景

一般用于队列中某些元素需要优先处理的情况

成员变量

//默认容量 数据结构实现基于数组 支持扩容
	private static final int DEFAULT_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.
     */
    transient Object[] queue; // non-private to simplify nested class access

    /**
     * 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; // non-private to simplify nested class access
           

构造方法

//指定容量
public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }
//指定比较接口(一般用于比较对象优先级时使用 默认按照自然升序)
public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator);
    }
           

主要方法

添加 add(E e)

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

 public boolean offer(E e) {
     // 元素不能为空
        if (e == null)
            throw new NullPointerException();
     //修改次数加1
        modCount++;
        int i = size;
     	// 容量不够 需要扩容
        if (i >= queue.length)
            grow(i + 1);
     	// 容量+1
        size = i + 1;
    	 //如果不存在元素 添加的首部
        if (i == 0)
            queue[0] = e;
        else
         // 添加元素
            siftUp(i, e);
        return true;
    }
           

扩容 grow(int minCapacity)

/**
     * Increases the capacity of the array.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Double size if small; else grow by 50% 小的话加倍;否则增长 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);
    }
           

添加(上移) siftUp(int k, E x)

/**
     * Inserts item x at position k, maintaining heap invariant by
     * promoting x up the tree until it is greater than or equal to
     * its parent, or is the root.
     *
     * To simplify and speed up coercions and comparisons. the
     * Comparable and Comparator versions are separated into different
     * methods that are otherwise identical. (Similarly for siftDown.)
     *
     * @param k the position to fill 添加的位置
     * @param x the item to insert 插入的元素
     */ 
private void siftUp(int k, E x) {
    	// 定义比较器
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
          // 没有定义了比较器
            siftUpComparable(k, x);
    }
           

未定义了比较器的上移 siftUpComparable(int k, E x)

/**
     * 上移,x表示新插入元素,k表示新插入元素在数组的位置
     */
    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)

// jdk 1.8   同上面的操作 
	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 E peek() {
        return (size == 0) ? null : (E) queue[0];
    }
           

获取首元素 首元素弹出

public E poll() {
        if (size == 0)
            return null;
    	// 容量减少1
        int s = --size;
   		 // 修改次数+1
        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) {
         // 比较器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之后进行赋值