NOTE
PriorityQueue
1. What PriorityQueue is. 2. Usage. 3. Source analysis: fields, constructors, heapify, insertion, deletion, sift-up, and sift-down. 4. Reference.
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;
}
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub