Source code analysis of PriorityQueue

catalogue

Member variable

Constructor

add to

Return queue header element / out of queue operation

delete

Analysis of bottom binary heap algorithm

summary

Talk about the heap sorting algorithm

Member variable

public class PriorityQueue<E> extends AbstractQueue<E>
    implements java.io.Serializable {

    private static final long serialVersionUID = -7720805057305804111L;

    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    /**
		The priority queue is represented as a balanced binary heap (complete binary tree): the two children of queue[n] are queue[2*n+1] and queue[2*(n+1)]. The priority queue is sorted by the natural order of the comparator or elements. If the comparator is empty: 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 that the queue is not empty.
     */
    transient Object[] queue; // non-private to simplify nested class access

    /**
     * Number of elements in the priority queue.
     */
    private int size = 0;

    /**
     Comparator, null if the priority queue uses the natural order of elements.
     */
    private final Comparator<? super E> comparator;

    /**
		The number of times this priority queue has been structurally modified. For more information, see AbstractList.
     */
    transient int modCount = 0; // non-private to simplify nested class access
		......
}

Here, we should pay attention to the array queue. Although its bottom layer is an array, because it involves the order problem, the logic bottom layer adopts the binary heap structure and considers the array queue as a complete binary tree. That is, if a node is queue[i], its left child is queue[2*i+1], and its right child is queue[2*i+2], where comparator is the key to sorting. If it is null, it is sorted naturally, otherwise it is sorted according to the definition of this comparator.

Constructor

 /**
		Create a PriorityQueue with a default initial capacity (11), which sorts the elements according to their natural order.
     */
    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

    /**
    Create a PriorityQueue with the specified initial capacity and sort the elements according to their natural order.
    Parameters:
    initialCapacity – Initial capacity of this priority queue
    Throw:
    IllegalArgumentException – If initialCapacity is less than 1
     */
    public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }

    /**
    Create a PriorityQueue with default initial capacity, and its elements are sorted according to the specified comparator.
    Parameters:
    Comparator – the comparator that will be used to sort this priority queue. If null, the natural order of the elements is used.
    since:
    1.8
     */
    public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator);
    }

    /**
    Create a PriorityQueue with the specified initial capacity and sort its elements according to the specified comparator.
    Parameters:
    initialCapacity – Initial capacity of this priority queue
    Comparator – the comparator that will be used to sort this priority queue. If null, the natural order of the elements is used.
    Throw:
    IllegalArgumentException – If initialCapacity is less than 1
     */
    public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        // Note: This restriction of at least one is not actually needed,
        // but continues for 1.5 compatibility
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

    /**
    Creates a PriorityQueue containing the elements in the specified collection. If the specified set is an instance of SortedSet or another PriorityQueue, the priority queue will be sorted in the same order. Otherwise, this priority queue is sorted according to the natural order of its elements.
    Parameters:
    c - The collection whose elements will be placed in this priority queue
    Throw:
    ClassCastException – If the {elements of the specified set cannot be compared with each other according to the order of the priority queue
    NullPointerException – If the specified collection or any of its elements is empty
     */
    @SuppressWarnings("unchecked")
    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;
            initFromCollection(c);
        }
    }

    /**
    Creates a PriorityQueue that contains the elements in the specified priority queue. The priority queue will be sorted in the same order as the given priority queue.
    Parameters:
    c - The priority queue whose elements will be placed in the priority queue
    Throw:
    ClassCastException -If element c cannot be compared with another basis, order with one c
    NullPointerException – If the specified priority queue or any of its elements is empty
     */
    @SuppressWarnings("unchecked")
    public PriorityQueue(PriorityQueue<? extends E> c) {
        this.comparator = (Comparator<? super E>) c.comparator();
        initFromPriorityQueue(c);
    }

    /**
    Creates a PriorityQueue containing the elements in the specified sort set. This priority queue will be sorted in the same order as the given sort set.
    Parameters:
    c – Elements will be placed in an ordered collection of this priority queue
    Throw:
    ClassCastException – If the elements of the specified sort set cannot be compared with each other according to the order of the sort set
    NullPointerException – If the specified ordered collection or any of its elements is empty
     */
    @SuppressWarnings("unchecked")
    public PriorityQueue(SortedSet<? extends E> c) {
        this.comparator = (Comparator<? super E>) c.comparator();
        initElementsFromCollection(c);
    }

Here we mainly explain that the following formal parameters are Collection related constructors: first, we will judge whether they are SortedSet or PriorityQueue types, which will be simpler:

  private void initElementsFromCollection(Collection<? extends E> c) { //Copy directly
        Object[] a = c.toArray();
        if (c.getClass() != ArrayList.class)
            a = Arrays.copyOf(a, a.length, Object[].class);
        int len = a.length;
        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;
    }

private void initFromPriorityQueue(PriorityQueue<? extends E> c) {
        if (c.getClass() == PriorityQueue.class) {  //PriorityQueue is also copied directly
            this.queue = c.toArray();
            this.size = c.size();
        } else {
            initFromCollection(c);
        }
    }

If not, it will be different. There is a heap sorting process.

private void initFromCollection(Collection<? extends E> c) {
        initElementsFromCollection(c);
        heapify(); //Adjustment reactor
    }


    /**
		Establish heap invariants throughout the tree (as described above) without assuming the order of elements before calling.
     */
    @SuppressWarnings("unchecked")
    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--) //Sink nodes from all nodes that are not leaf nodes
            siftDown(i, (E) queue[i]);
    }

add to

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

    /**
    Inserts the specified element into this priority queue.
    return:
    true (By queue Offer (specify)
    Throw:
    ClassCastException – If the specified element cannot be compared with the element currently in this priority queue according to the order of the priority queue
    NullPointerException – If the specified element is empty
     */
    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) //If empty, the first
            queue[0] = e;
        else
            siftUp(i, e); //After inserting the end, the heap node floats up
        return true;
    }


    /**
    Increase the capacity of the array.
    Parameters:
    minCapacity – Minimum capacity required
     */
    private void grow(int minCapacity) {
        int oldCapacity = queue.length;
        // Capacity expansion strategy
        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);
    }

This involves the floating up operation of the node after adding elements to the tail, which will be described in detail later.

Return queue header element / out of queue operation

	  public E peek() {
        return (size == 0) ? null : (E) queue[0];
    }
    /**
    Retrieves but does not delete the header of this queue. This method differs from peek only in that it throws an exception if the queue is empty.
    Unless the queue is empty, this implementation will return the result of peek.
    return:
    The head of this queue
    Throw:
    NoSuchElementException – If this queue is empty
     */
    public E element() {
        E x = peek();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

     public E poll() {
          if (size == 0)
              return null;
          int s = --size;
          modCount++;
       		//The first node exchanges with the end node, and then sets null at the end to sink the first node.
          E result = (E) queue[0];
          E x = (E) queue[s];
          queue[s] = null;
          if (s != 0) 
              siftDown(0, x);//Sinking of head node
          return result;
      }

delete

/*
Removes a single instance, if any, of the specified element from this queue. More formally, if the queue contains one or more such elements, delete element e so that o.equals(e). Returns true if and only if the queue contains the specified element (or, equivalently, if the queue changes due to a call).
Parameters:
o - The element (if any) to remove from this queue
 return:
true if this queue changes due to a call
*/
public boolean remove(Object o) {
        int i = indexOf(o);
        if (i == -1)
            return false;
        else {
            removeAt(i);
            return true;
        }
    }


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


    /**
Removes the ith element from the queue. Typically, this method retains up to I-1 (including i-1) elements and is not affected. In these cases, it returns null. Sometimes, in order to keep the heap unchanged, it must swap the later elements in the list with elements earlier than I. In these cases, this method returns the element that was at the end of the list and is now somewhere before I. This fact is used by the iterator Remove is used to avoid losing traversal elements.
     */
    @SuppressWarnings("unchecked")
    private E removeAt(int i) {
        // assert i >= 0 && i < size;
        modCount++;
        int s = --size;
        if (s == i) // removed last element
            queue[i] = null;//The last element is directly set to null to facilitate gc
        else {//Middle position
            E moved = (E) queue[s]; //Swap with the last location element and set the last location to null
            queue[s] = null;
            siftDown(i, moved);//Then sink first
            if (queue[i] == moved) { 
                siftUp(i, moved);//Float up again
                if (queue[i] != moved)
                    return moved;
            }
        }
        return null;
    }

Analysis of bottom binary heap algorithm

The core of the data structure is the implementation of binary small top heap (the parent node is less than two child nodes). The insertion and deletion of data will lead to the change of binary heap structure.

Floating of heap nodes

    /**
  Insert item x at position k and keep the heap unchanged by lifting x up the tree until it is greater than or equal to its parent or root. Simplify and accelerate forcing and comparison. The Comparable and Comparator versions are divided into different methods, which are otherwise the same. (similar to siftDown.)
  Parameters:
  k - Position to fill
  x - Items to insert
     */
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            siftUpComparable(k, x); //Natural sorting
    }
	
   @SuppressWarnings("unchecked")
    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1; //Parent node
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0) //If the node is larger than the parent node, there is no need to float up
                break;
          	//Swap, and then continue to consider whether the node needs to float up
            queue[k] = e; 
            k = parent;
        }
        queue[k] = x;//assignment
    }

That is, each time a node is placed from the middle or tail, it needs to compare with its parent node whether it is smaller than the parent node. If it is smaller, it needs to float up until it is equal to or larger than the parent node. This is necessary to ensure a minimum heap.

Sinking of reactor node

    /**
    Insert the item x at position k and keep the heap unchanged by repeatedly demoting x to the tree until it is less than or equal to its children or leaves.
    Parameters:
    k - Position to fill
    x - Items to insert
     */
    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }


    @SuppressWarnings("unchecked")
    private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;        // 
        while (k < half) {//Make sure there are child node comparisons
            int child = (k << 1) + 1; // If there is a child node, there must be a left child node
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&//If the right child node exists, select the smaller of the two
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0) //If the node is smaller than the smaller one inside, there is no need to sink
                break;
          	//Otherwise, switch nodes.
            queue[k] = c;
            k = child;
        }
        queue[k] = key;//assignment
    }

Each time a node is inserted from the head or middle, it needs to be compared whether it is larger than its child node. If so, it needs to sink. This is necessary to ensure a minimum heap.

summary

  • Each time an element is added from the end of the array, the element may not be the largest and needs to float up to the appropriate position.

  • Each time a queue element is popped, the first element needs to be popped. The method is to exchange with the last element first, and then set the last element to null. In this way, the current first element may not be the smallest, so it needs to sink to the appropriate position.

  • Every time the element is deleted, the node is exchanged with the end node, and then the node is sunk / floated to ensure the structure of the small top pile.

Talk about the heap sorting algorithm

Heap sorting algorithm can actually start with small top heap or large top heap. The small top heap is sorted from front to back, and the large top heap is sorted from back to front.

Here is the idea of large top reactor:

The essence of the heap sorting algorithm is to wipe the first 0~n-i nodes in each I round:

Find the maximum value for n-i nodes in each round:

Start with the first n-i non leaf nodes and see their relationship with the size of child nodes. If the child node is larger than the parent node, the child node needs to float up.

In this way, we can finally find the maximum node value of the round and put it on the top of the heap, which is a formal parameter of the big top heap. Swap this value with the end node.

Code Demo:

public class HeapSort {
    /**
     *
     * @param list Sorted list
     * @param k Investigate the location of nodes
     * @param n Furthest index to consider
     */
    public static void heapAdjust(ArrayList<? extends Comparable> list,int k,int n){
        while(2*k+1<=n-1){
            int i=2*k+1;
            if(i<=n-2&&list.get(i+1).compareTo(list.get(i))>0){
                i+=1;//right child
            }
            if(list.get(i).compareTo(list.get(k))>0){ //The child node is larger than the parent node
                Collections.swap(list,k,i);
                k=i; //continue
            }else{
                break;
            }
        }
    }

    /**
     *
     * @param list Sort list
     */
    public static void HeapSort(ArrayList<? extends Comparable> list){
        int n=list.size();
        //Adjust to large top pile
        for(int i=n/2-1;i>=0;i--){
            heapAdjust(list,i,n);
        }

        //sort
        for(int i=n-1;i>0;i--){
            Collections.swap(list,i,0);
            heapAdjust(list,0,i);
        }
    }

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Random random = new Random(47);
        for (int i = 0; i < 10; i++) {
            list.add(random.nextInt(20));
        }
        System.out.println(list); //Before sorting
        HeapSort(list);
        System.out.println(list);//After sorting
    }
}

Keywords: Java data structure queue

Added by kimbhoot on Mon, 17 Jan 2022 22:49:59 +0200