PriorityQueue of Java collection

Keywords: Java data structure

The difference between the priority queue and the queue is that the first element out of the priority queue is always the element with the highest priority

Java provides the PriorityQueue class to implement the priority Queue. Because it implements the Queue interface, it can also be referenced through the Queue

Queue<Integer> priorityQueue = new PriorityQueue<>((a,b)->b-a);

Unlike Queue, when defining PriorityQueue, you need to pass in a Comparator, which will determine the priority of elements. The decision method is similar to the sort() method of List, that is, when passing in a and b, if a has higher priority, it will return a negative number, if b has higher priority, it will return a positive number, and if b is equal, it will return 0. The above example shows that higher values have higher priority

You can also not pass in Comparator, so its elements need to implement the Comparable interface, such as

Queue<Integer> priorityQueue = new PriorityQueue<>();

Integer also implements the Comparable interface. The comparison method is as follows

public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

Therefore, a small value in the priorityQueue above has a higher priority

example:

Queue<Integer> priorityQueue = new PriorityQueue<>();
for(int i=0;i<10;i++){
    priorityQueue.add((int)(Math.random()*40+1));
}
System.out.println(String.format("list:%s", priorityQueue));
// list:[6, 8, 19, 18, 15, 35, 27, 40, 30, 36]

System.out.println(priorityQueue.poll()); // 6
System.out.println(priorityQueue.poll()); // 8
System.out.println(priorityQueue.poll()); // 15

Implementation principle

The implementation effect of priority queue is like sorting it, but in fact it is a heap. The heap is a complete binary tree. Taking the maximum heap as an example, the value of its parent node is always greater than that of its child node. In priority queue, the priority of the parent node is always greater than that of the child node. When fetching elements, always take the value of the root node, and then adjust the heap to conform to the nature of the heap, so that the elements with the highest priority are fetched every time

Heap has extremely high efficiency. Adding elements or taking out a root node element and maintaining the nature of heap only requires the time complexity of log2n, which is the height of binary tree

In PriorityQueue, an array Object[] queue is used to store elements because we can obtain the values of parent and child nodes through calculation:

  • Parent node: (i-1)/2
  • Left child node: i*2+1
  • Right child node: i*2+2

offer()

When adding elements to the PriorityQueue, if the queue is empty, it will be added directly. If it is not empty, it will be added to the end of the queue first, and then the heap will be adjusted

private void siftUpComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>) x;
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        if (key.compareTo((E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = key;
}

The above is the process of adjusting the heap for PriorityQueue:

  1. Gets and compares the parent node
  2. Ends if the priority is less than the parent node
  3. If the priority is higher than the parent node, exchange with the parent node, and repeat the above operation up to the root node

poll()

public E poll() {
    if (size == 0)
        return null;
    int s = --size;
    modCount++;
    E result = (E) queue[0];
    E x = (E) queue[s];
    queue[s] = null;
    if (s != 0)
        siftDown(0, x);
    return result;
}

When taking the element, get the element at the head of the team, put the element at the end of the team into the head of the team, and adjust the heap downward from the top of the heap through siftDown(0, x)

private void siftDownComparable(int k, E x) {
    Comparable<? super E> key = (Comparable<? super E>)x;
    int half = size >>> 1;        // loop while a non-leaf
    while (k < half) {
        int child = (k << 1) + 1; // assume left child is least
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
            c = queue[child = right];
        if (key.compareTo((E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = key;
}

The adjustment method is as follows:

  1. Get the value of left and right nodes
  2. Select the larger of the two nodes
  3. Compared with the selected child node, if the priority is lower than the child node, it will be exchanged with it, and the above operation will be repeated, otherwise it will end

It is worth mentioning that the range of K is limited to < half, so that valid left child nodes can always be obtained in the loop. At the same time, it also ensures that the last left child node can be accessed, that is, when k=half-1, child=k*2+1=half*2-2=size-1, that is, the last node

reference resources

Use PriorityQueue - Liao Xuefeng's official website (liaoxuefeng.com)

Posted by chrisv on Fri, 17 Sep 2021 00:55:49 -0700