Priority queue


An ordinary queue is a first in first out data structure. Elements are added at the end of the queue and deleted from the queue head. In some cases, we may need to find the maximum or minimum value in the queue. For example, a queue is used to save computer tasks. Generally, computer tasks have priority. We need to find the task with the highest priority among these computer tasks to execute first. After execution, we need to remove this task from the queue. To complete this function, ordinary queues need to traverse all elements in the queue every time, compare and find the maximum value. The efficiency is not very high. At this time, we can use a special queue to complete this demand, priority queue.

According to their different functions, priority queues can be divided into the following two types:

  1. Maximum priority queue: you can get and delete the maximum value in the queue
  2. Minimum priority queue: the smallest value in the queue can be obtained and deleted

1 maximum priority queue

We have learned about heap before, and the heap structure can easily delete the maximum value. Therefore, next, we can realize the maximum priority queue based on the heap area.

1.1 API design of maximum priority queue

1.2 code implementation of maximum priority queue

/**
 * Maximum priority queue
 * Implementation with heap
 */
public class MaxPriorityQueue<T extends Comparable<T>> {
    //Define an array to store elements in the heap
    private T[] items;
    //Number of elements in the storage heap
    private int N;

    //Constructor initialization parameters
    public MaxPriorityQueue(int capacity) {
        items = (T[]) new Comparable[capacity + 1];
        this.N = 0;
    }

    //Number of elements in the queue
    public int size() {
        return N;
    }

    //Is the queue empty
    public boolean isEmpty() {
        return N == 0;
    }

    //Queue insert element
    public void insert(T t) {
        //Add elements at the maximum index and increment
        items[++N] = t;
        //Float the element up to the correct position
        swim(N);
    }

    //The queue floats up after the element is inserted so that it is in the correct position
    public void swim(int k) {
        while (k > 1) {
            if (less(k / 2, k)) {
                exch(k / 2, k);
            }
            k = k / 2;
        }
    }

    //Delete the largest element and return this element
    public T delMax() {
        T max = items[1];
        //Swap the position of the largest element and the last element
        exch(1, N);
        //Delete last element
        items[N] = null;
        N--;
        //Sink element 1 to the correct position
        sink(1);
        return max;
    }

    //Sink after deleting elements
    public void sink(int k) {
        while (2 * k <= N) {
            int max;
            //If there are right child nodes
            if (2 * k + 1 <= N) {
                if (less(2 * k + 1, 2 * k)) {
                    max = 2 * k;
                } else {
                    max = 2 * k + 1;
                }
            } else {
                max = 2 * k;
            }

            if (!less(k, max)) {
                break;
            }
            exch(k, max);
            k = max;
        }
    }

    //Compare element sizes
    public boolean less(int i, int j) {
        return items[i].compareTo(items[j]) < 0;
    }

    //Exchange element
    public void exch(int i, int j) {
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }


    public static void main(String[] args) {
        //Create minimum priority queue object
        MaxPriorityQueue<String> queue = new MaxPriorityQueue<>(10);
        //Save data to queue
        queue.insert("G");
        queue.insert("F");
        queue.insert("E");
        queue.insert("D");
        queue.insert("C");
        queue.insert("B");
        queue.insert("A");

        //Get the elements in the minimum priority queue through a loop
        while (!queue.isEmpty()) {
            String min = queue.delMax();
            System.out.print(min + " ");
        }

    }
}

2 minimum priority queue

The implementation of the minimum priority queue is also relatively simple. We can also complete the minimum priority queue based on the heap. When we studied the heap earlier, the arrays storing data elements in the heap should meet the following characteristics:

  1. The largest element is placed at index 1 of the array.
  2. The data of each node is always greater than or equal to the data of its two child nodes.

In fact, the heap we implemented before can be called the maximum heap. We can use the opposite idea to realize the minimum heap, so that the array storing data elements in the heap meets the following characteristics:

  1. The smallest element is placed at index 1 of the array.
  2. The data of each node is always less than or equal to the data of its two child nodes.
  3. In this way, we can quickly access the smallest data in the heap.

2.1 API design of minimum priority queue

2.2 code implementation of minimum priority queue

/**
 * Minimum priority queue
 * Implementation with heap
 */
public class MinPriorityQueue<T extends Comparable<T>> {

    //Define an array to store elements in the heap
    private T[] items;
    //Record the number of elements in the heap
    private int N;

    //Constructor initialization parameters
    public MinPriorityQueue(int capacity) {
        items = (T[]) new Comparable[capacity + 1];
        this.N = 0;
    }

    //Judge whether the queue is empty
    public boolean isEmpty() {
        return N == 0;
    }

    //Get the number of elements in the queue
    public int size() {
        return N;
    }

    //Insert elements (insert elements at the maximum index and float up to adjust the order)
    public void insert(T t) {
        items[++N] = t;
        //Float up
        swim(N);
    }

    //Floating up operation after inserting an element, so that the inserted k element is in another correct position in the heap
    public void swim(int k) {
        //Comparison between k and its parent node k/2
        while (k > 1) {
            if (less(k, k / 2)) {
                exch(k, k / 2);
            }
            k = k / 2;
        }
    }

    //Delete maximum element
    public T delMin() {
        //Take out the largest element with index 1
        T minT = items[1];
        //Swap maximum index and 1 element
        exch(1, N);
        //Delete element at maximum index
        items[N] = null;
        //Element decrement
        N--;
        //Now drop the element with index 1 and adjust it to the correct position
        sink(1);
        return minT;
    }

    //Sink after deleting the largest element (the last element is exchanged with the first element)
    public void sink(int k) {
        while (2 * k <= N) {
            //Find the maximum value in the child node
            //If there is a right child node
            int min;
            if (2 * k + 1 <= N) {
                if (less(2 * k, 2 * k + 1)) {
                    min = 2 * k;
                } else {
                    min = 2 * k + 1;
                }
            } else {
                min = 2 * k;
            }

            //Interpret the current node and child node values
            if (less(k, min)) {
                break;
            }
            exch(k, min);
            //The index points to the child node
            k = min;
        }
    }

    //Compare element sizes
    public boolean less(int i, int j) {
        return items[i].compareTo(items[j]) < 0;
    }

    //Swap element location
    public void exch(int i, int j) {
        T temp = items[i];
        items[i] = items[j];
        items[j] = temp;
    }

    public static void main(String[] args) {
        //Create minimum priority queue object
        MinPriorityQueue<String> queue = new MinPriorityQueue<>(10);
        //Save data to queue
        queue.insert("G");
        queue.insert("F");
        queue.insert("E");
        queue.insert("D");
        queue.insert("C");
        queue.insert("B");
        queue.insert("A");

        //Get the elements in the minimum priority queue through a loop
        while (!queue.isEmpty()) {
            String min = queue.delMin();
            System.out.print(min + " ");
        }

    }
}

3 index priority queue

In the previously implemented maximum priority queue and minimum priority queue, they can quickly access the maximum and minimum elements in the queue respectively, but they have a disadvantage that they have no way to access the objects that already exist in the priority queue through the index and update them. In order to achieve this goal, based on the priority queue, learn a new data structure, index the priority queue. Next, we take the minimum index priority queue as an example.

3.1 implementation idea of index priority queue

Step 1:

When storing data, associate each data element with an integer, such as insert (int, k, t, t). We can regard k as an integer associated with T. then our implementation needs to quickly obtain the T element in the queue through the value of k. at this time, the value of k needs to be unique.
The most intuitive idea is that we can use a T[] items array to save data elements. When inserting (int k, t t t), we can regard K as the index of the items array and put the T element at the index k of the items array. In this way, it is very convenient for us to obtain the T element according to K, and we can get the items[k] directly.

Step 2:

As a result of the completion of step 1, although we associate an integer with each element and can use this integer to quickly obtain the element, the order of the elements in the items array is random and not heap ordered. Therefore, in order to meet this requirement, we can add an array int[]pq to save the index of each element in the items array, The PQ array needs to be heap ordered, that is, the data elements items[pq[1]] corresponding to pq[1] should be less than or equal to the data elements items[pq[2]] and items[pq[3]] corresponding to pq[2] and pq[3].

Three steps:

Through the analysis in step 2, we can find that when we make heap adjustment through floating and sinking, we actually adjust the pq array. If you need to modify the elements in items, such as making items[0] = "H", it is obvious that we need to adjust the heap of the data in pq, and adjust the position of the elements in pq[9]. But now we will encounter a problem. What we modify is the value at the 0 index in the items array. How can we quickly know the position of the element in pq[9]? The most intuitive idea is to traverse the pq array and compare each element with 0. If the current element is 0, then adjust the element at the index, but the efficiency is very low. We can add another array, int[] qp, to store the reverse order of pq. For example: in pq array: pq[1]=6; Then, in the qp array, take 6 as the index and 1 as the value, and the result is: qp[6]=1;

After having pq array, if we modify items[0] = "H", we can first find the index of qp in qp array through index 0: qp[0]=9, and then adjust pq[9] directly.

3.2 API design of index priority queue

3.3 code implementation of minimum index priority queue

/**
 * Minimum index priority queue
 */
public class IndexMinPriorityQueue<T extends Comparable<T>> {
    //Elements in the storage heap
    private T[] items;
    //Save the index of each element in the items array. The pq array needs to be heap ordered
    private int[] pq;
    //Save the reverse order of pq, with pq value as index and pq index as value
    private int[] qp;
    //Record the number of elements in the heap
    private int N;

    //Constructor
    public IndexMinPriorityQueue(int capacity) {
        items = (T[]) new Comparable[capacity + 1];
        pq = new int[capacity + 1];
        qp = new int[capacity + 1];
        this.N = 0;

        //The default queue does not store elements
        for (int i = 0; i < qp.length; i++) {
            qp[i] = -1;
        }
    }

    //Is the queue empty
    public boolean isEmpty() {
        return N == 0;
    }

    //Queue size
    public int size() {
        return N;
    }

    //Compare size
    public boolean less(int i, int j) {
        //Element size at pq index
        return items[pq[i]].compareTo(items[pq[j]]) < 0;
    }

    //Exchange element order
    public void exch(int i, int j) {
        //Index order in exchange pq
        int temp = pq[i];
        pq[i] = pq[j];
        pq[j] = temp;

        //Update data in qp
        qp[pq[i]] = i;
        qp[pq[j]] = j;
    }

    //Judge whether the index at k already exists
    public boolean contain(int k) {
        return qp[k] != -1;
    }

    //Index associated with the smallest element
    public int minIndex() {
        return pq[1];
    }

    //Insert element t at index i
    public void insert(int i, T t) {
        if (contain(i)) {
            return;
        }
        //Save the data to the i position corresponding to the items
        items[i] = t;
        //Number of elements plus 1
        N++;
        //Store i in pq
        pq[N] = i;
        //Reverse order in record qp
        qp[i] = N;
        //Complete reactor adjustment by floating up
        swim(i);
    }

    //Delete minimum index
    public int delMin() {
        //Gets the index associated with the smallest element
        int minIndex = pq[1];
        //Swap elements at 1 and N at pq index
        exch(1, N);
        //Delete inverse index
        qp[pq[N]] = -1;
        //Delete index stored in pq
        pq[N] = -1;
        //Delete element
        items[minIndex] = null;
        N--;
        //Sinking operation
        sink(1);
        return minIndex;
    }

    //Deletes the specified index
    public void delete(int i) {
        //i index at pq
        int k = qp[i];
        //Swap k and last element index
        exch(k, N);
        //Delete element
        items[k] = null;
        //Delete inverse index
        qp[pq[N]] = -1;
        //Delete the index
        pq[N] = -1;
        //Heap adjustment
        sink(k);
        swim(k);
    }

    //Change the element associated with index i to t
    public void changeItem(int i, T t) {
        //Modify the element at i position in the items array to t
        int k = qp[i];
        //Find where i appears in pq
        items[k] = t;
        //Adjustment reactor
        swim(k);
        sink(k);
    }

    //Float up
    public void swim(int k) {
        while (k > 1) {
            if (less(k, k / 2)) {
                exch(k, k / 2);
            }
            k = k / 2;
        }
    }

    //sink
    public void sink(int k) {
        while (2 * k < N) {
            //Find the smallest element index in the child node
            int min;
            //If there are right child nodes
            if (2 * k + 1 < N) {
                if (less(2 * k + 1, 2 * k)) {
                    min = 2 * k + 1;
                } else {
                    min = 2 * k;
                }
            } else {
                min = 2 * k;
            }
            //If it is larger than the child node, no swap is needed
            if (!less(k, min)) {
                break;
            }
            exch(k, min);
            k = min;
        }
    }

    public static void main(String[] args) {
        //Create index minimum priority queue object
        IndexMinPriorityQueue<String> queue = new IndexMinPriorityQueue<>(10);

        //Add element to queue
        queue.insert(0, "A");
        queue.insert(1, "C");
        queue.insert(2, "F");

        //Test modification
        queue.changeItem(2, "B");

        //Test delete
        while (!queue.isEmpty()) {
            int index = queue.delMin();
            System.out.print(index + " ");
        }
    }
}

3.4 implementation of maximum index priority queue

package com.bcl.algorithm.priority;

/**
 * Index maximum priority queue
 */
public class IndexMaxPriorityQueue<T extends Comparable<T>> {
    //Elements in the storage heap
    private T[] items;
    //Store items index value
    private int[] pq;
    //Store the reverse order of the indexes in pq. The value of pq is the index and the index is the value
    private int[] qp;
    //Storing data in heap
    private int N;

    //Constructor initialization parameters
    public IndexMaxPriorityQueue(int capacity) {
        items = (T[]) new Comparable[capacity + 1];
        pq = new int[capacity + 1];
        qp = new int[capacity + 1];
        this.N = 0;

//        for (int i : qp) {
//            qp[i] = -1;
//        }
        //The default queue does not store elements
        for (int i = 0; i < qp.length; i++) {
            qp[i] = -1;
        }
    }

    //Is the queue empty
    public boolean isEmpty() {
        return N == 0;
    }

    //Queue size
    public int size() {
        return N;
    }

    public boolean max(int i, int j) {
        return items[pq[i]].compareTo(items[pq[j]]) > 0;
    }

    //Exchange element order
    public void exch(int i, int j) {
        //Index order in exchange pq
        int temp = pq[i];
        pq[i] = pq[j];
        pq[j] = temp;

        //Update data in qp
        qp[pq[i]] = i;
        qp[pq[j]] = j;
    }

    //Include corresponding index
    public boolean contain(int i) {
        return qp[i] != -1;
    }

    //Maximum index
    public int maxIndex() {
        return pq[1];
    }

    //Insert element at index i
    public void insert(int i, T t) {
        if (contain(i)) {
            return;
        }
        //Insert element t at items
        items[i] = t;
        //Self increasing number
        N++;
        //pq add index mapping
        pq[N] = i;
        //qp increases in reverse order
        qp[i] = N;
        //Float up and adjust the reactor sequence
        swim(N);
    }

    //Delete maximum index
    public int delMaxIndex() {
        //Maximum element index found
        int maxIndex = pq[1];
        //Exchange element
        exch(1, N);
        //Delete element
        items[maxIndex] = null;
        //Delete inverse index
        qp[pq[N]] = -1;
        //Delete index
        pq[N] = -1;
        //Element reduction
        N--;
        //Sinking adjustment reactor sequence
        sink(1);
        return maxIndex;
    }

    //Delete element at index i
    public void delete(int i) {
        //Find the position of index i in pq and adjust the order of pq
        int k = qp[i];
        //Swap element positions at k
        exch(k, N);
        //Delete element
        items[k] = null;
        qp[pq[N]] = -1;
        //Delete elements in pq
        pq[N] = -1;
        //Heap adjustment sequence
        sink(k);
        swim(k);
    }

    //Modify the value at index i to t
    public void changeItems(int i, T t) {
        items[i] = t;
        int k = qp[i];
        swim(k);
        sink(k);
    }

    //Adjust the stacking order
    public void swim(int k) {
        while (k > 1) {
            if (max(k, k / 2)) {
                exch(k, k / 2);
            }
            k = k / 2;
        }
    }

    //Adjust the stacking sequence by sinking
    public void sink(int k) {
        while (2 * k <= N) {
            //Maximum child node found
            int max;
            if (2 * k + 1 <= N) {
                if (max(2 * k + 1, 2 * k)) {
                    max = 2 * k + 1;
                } else {
                    max = 2 * k;
                }
            } else {
                max = 2 * k;
            }
            if (max(k, max)) {
                break;
            }
            exch(k, max);
            k = max;
        }
    }

    public static void main(String[] args) {
        //Create index minimum priority queue object
        IndexMaxPriorityQueue<String> queue = new IndexMaxPriorityQueue<>(10);

        //Add element to queue
        queue.insert(0, "A");
        queue.insert(1, "C");
        queue.insert(2, "F");

        //Test modification
        queue.changeItems(2, "B");

        //Test delete
        while (!queue.isEmpty()) {
            int index = queue.delMaxIndex();
            System.out.print(index + " ");
        }
    }
}

Keywords: Algorithm

Added by Vivid Lust on Mon, 07 Feb 2022 15:01:59 +0200