NOTE

PriorityQueue

1. What PriorityQueue is. 2. Usage. 3. Source analysis: fields, constructors, heapify, insertion, deletion, sift-up, and sift-down. 4. Reference.

JavaCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. What Is PriorityQueue?

It is a queue with the concept of priority added. In other words, the elements in the queue are sorted according to some rule.

2. Usage

public class PriorityQueueTest
{
    public static void main(String[] args)
    {
        List<Integer> list = Arrays.asList(20, 19, 18, 17, 16, 15, 14, 13, 12, 11);
        PriorityQueue<Integer> queue = new PriorityQueue<>(list);
        System.out.println("Initial elements: " + queue);

        System.out.println("============");
        queue.offer(11);
        System.out.println("Added an 11: " + queue);

        System.out.println("============");
        List<Integer> sort = new ArrayList<>();
        while (!queue.isEmpty())
        {
            Integer poll = queue.poll();
            System.out.println("Heap top: " + poll);
            sort.add(poll);
            System.out.println("Remaining elements: " + queue);
            System.out.println("----------");
        }

        System.out.println("============");
        System.out.println("Heap sort result: " + sort);

    }
}
  • Output
Initial elements: [11, 12, 14, 13, 16, 15, 18, 20, 17, 19]
============
Added an 11: [11, 11, 14, 13, 12, 15, 18, 20, 17, 19, 16]
============
Heap top: 11
Remaining elements: [11, 12, 14, 13, 16, 15, 18, 20, 17, 19]
----------
Heap top: 11
Remaining elements: [12, 13, 14, 17, 16, 15, 18, 20, 19]
----------
Heap top: 12
Remaining elements: [13, 16, 14, 17, 19, 15, 18, 20]
----------
Heap top: 13
Remaining elements: [14, 16, 15, 17, 19, 20, 18]
----------
Heap top: 14
Remaining elements: [15, 16, 18, 17, 19, 20]
----------
Heap top: 15
Remaining elements: [16, 17, 18, 20, 19]
----------
Heap top: 16
Remaining elements: [17, 19, 18, 20]
----------
Heap top: 17
Remaining elements: [18, 19, 20]
----------
Heap top: 18
Remaining elements: [19, 20]
----------
Heap top: 19
Remaining elements: [20]
----------
Heap top: 20
Remaining elements: []
----------
============
Heap sort result: [11, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Process finished with exit code 0

3. Source Analysis

3.1. Fields

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

    private static final long serialVersionUID = -7720805057305804111L;

    // The default size is 11
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    // Binary heap: physically an array, logically viewed as a binary tree
    transient Object[] queue; // non-private to simplify nested class access

    // Capacity
    private int size = 0;

    // Comparator used to determine the position of inserted elements in the binary heap
    private final Comparator<? super E> comparator;



    // No-argument constructor
    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

}

3.2. Constructor with Arguments

// Constructor with arguments. This is the one we mainly analyze
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);
    }
    // The passed-in List follows this else branch
    else {
        this.comparator = null;
        initFromCollection(c);
    }
  • initFromCollection
private void initFromCollection(Collection<? extends E> c) {
    initElementsFromCollection(c);
    heapify();
}

It can be seen that there are mainly two steps: put the initial elements into the array, and then maintain the heap property.

3.2.1. Initialize Elements into the Array

  • initElementsFromCollection
private void initElementsFromCollection(Collection<? extends E> c) {
    // First convert to an array. If the array is not of type Object[], convert it to an Object[] array
    // Ours is an Integer array, so this logic copies it into an Object array
    Object[] a = c.toArray();
    if (a.getClass() != Object[].class)
        a = Arrays.copyOf(a, a.length, Object[].class);
    int len = a.length;
    // Elements cannot be null
    if (len == 1 || this.comparator != null)
        for (int i = 0; i < len; i++)
            if (a[i] == null)
                throw new NullPointerException();
    // Assign queue and size; very straightforward
    this.queue = a;
    this.size = a.length;
}

3.2.2. Maintain the Heap Property

  • heapify
private void heapify() {
    // i is initialized to the index of the last non-leaf node that has a left child, a right child, or both
    // Call the sift-down operation to maintain the heap property
    // Continue until reaching the head node (i--)
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        // The element at position i is queue[i]; perform a sift-down operation on it
        siftDown(i, (E) queue[i]);
}
  • siftDown
// The element at position k in the array is x. Perform a sift-down operation on it to maintain the heap property of this subtree
private void siftDown(int k, E x) {
    // If a custom comparator is set, use it
    if (comparator != null)
        siftDownUsingComparator(k, x);
    // We did not set one, so this branch is used
    else
        siftDownComparable(k, x);
}
3.2.2.1. Sift-Down Operation
private void siftDownComparable(int k, E x) {
    // Comparable must be implemented
    Comparable<? super E> key = (Comparable<? super E>)x;
    // Half the size of the array; this is the termination condition for the sift-down operation
    // That is, keep adjusting downward until reaching a leaf node
    int half = size >>> 1;
    while (k < half) {
        // Get the left child
        int child = (k << 1) + 1; // assume left child is least
        Object c = queue[child];
        // Get the right child
        int right = child + 1;
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            c = queue[child = right];
        // c is the smaller of the left and right children. Compare it with the parent (myself).
        // If the parent is smaller, no adjustment is needed; break directly
        if (key.compareTo((E) c) <= 0)
            break;
        // Otherwise, overwrite the parent's position with the smaller child
        queue[k] = c;
        // Use the smaller child as the next parent and continue the sift-down operation
        k = child;
    }
    // Put key (which is x) where it belongs
    queue[k] = key;
}

3.3. Insertion

  • offer
public boolean offer(E e) {
    // The inserted element cannot be null
    if (e == null)
        throw new NullPointerException();
    modCount++;
    // Expand capacity
    int i = size;
    if (i >= queue.length)
        grow(i + 1);
    // Update capacity
    size = i + 1;
    // If there are no elements, this element becomes the heap top
    if (i == 0)
        queue[0] = e;
    // If there are elements, insert it at the last position and sift it up to maintain the heap property
    else
        siftUp(i, e);
    return true;
}
  • siftUp
// The element at position k in the array is x. Perform a sift-up operation on it to maintain the heap property of this subtree
private void siftUp(int k, E x) {
    if (comparator != null)
        siftUpUsingComparator(k, x);
    // If there is no comparator, this branch is used
    else
        siftUpComparable(k, x);
}

3.3.1. Sift-Up Operation

private void siftUpComparable(int k, E x) {
    // Comparable must be implemented
    Comparable<? super E> key = (Comparable<? super E>) x;
    while (k > 0) {
        // Get the parent
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        // If I am greater than the parent, no adjustment is needed; break directly
        if (key.compareTo((E) e) >= 0)
            break;
        // Overwrite my position with the parent
        queue[k] = e;
        // Use the parent as the next node and continue the sift-up operation
        k = parent;
    }
    // Put key (which is x) where it belongs
    queue[k] = key;
}

3.4. Deletion

  • poll
public E poll() {
    // If there are no elements, return null
    if (size == 0)
        return null;
    // Update capacity
    int s = --size;
    modCount++;
    // Take the first element as the desired result
    E result = (E) queue[0];
    // Put the original last element in the first position
    E x = (E) queue[s];
    queue[s] = null;
    // Perform a sift-down operation to maintain the heap property
    if (s != 0)
        siftDown(0, x);
    return result;
}
  • siftDown
// The element at position k in the array is x. Perform a sift-down operation on it to maintain the heap property of this subtree
private void siftDown(int k, E x) {
    // If a custom comparator is set, use it
    if (comparator != null)
        siftDownUsingComparator(k, x);
    // We did not set one, so this branch is used
    else
        siftDownComparable(k, x);
}

3.4.1. Sift-Down Operation

private void siftDownComparable(int k, E x) {
    // Comparable must be implemented
    Comparable<? super E> key = (Comparable<? super E>)x;
    // Half the size of the array; this is the termination condition for the sift-down operation
    // That is, keep adjusting downward until reaching a leaf node
    int half = size >>> 1;
    while (k < half) {
        // Get the left child
        int child = (k << 1) + 1; // assume left child is least
        Object c = queue[child];
        // Get the right child
        int right = child + 1;
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            c = queue[child = right];
        // c is the smaller of the left and right children. Compare it with the parent (myself).
        // If the parent is smaller, no adjustment is needed; break directly
        if (key.compareTo((E) c) <= 0)
            break;
        // Otherwise, overwrite the parent's position with the smaller child
        queue[k] = c;
        // Use the smaller child as the next parent and continue the sift-down operation
        k = child;
    }
    // Put key (which is x) where it belongs
    queue[k] = key;
}

4. Reference

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub