Priority queue of [data structure (java)]

preface

This blog will give you the implementation and application of priority queue. We know that the queue in the data structure follows the principle of first in first out (FIFO), but in reality, tasks usually have the concept of priority, so we have to apply the method of priority queue. The bottom layer of priority queue is a heap.

heap

Basic concepts

1) Storage mode
The usual representation of heap is to store a complete binary tree in an array by sequence traversal.

2) Subscript relation
If the subscript of the parent is known, then

Left (subscript) = 2 * parent + 1;
Right (right subscript) = 2 * parent + 2;

If the child subscript is known, then

Parent (subscript of parent node) = (child - 1) / 2;

1) Heap is logically a complete binary tree
2) The heap is physically stored in an array
3) Large root heap: the value of the parent node is greater than that of the node in the subtree
4) Small root heap: the value of the parent node is less than that of the node in the subtree
5) The basic function of heap is to quickly find the most important value of a set
Small root pile:

Big root pile

Downward adjustment algorithm

The premise of downward adjustment is that the left and right subtrees are already a large or small pile.
1) The idea of downward adjustment (taking jiandui as an example):

1. Starting from the root node, select the maximum value of the left and right children;
2. Compare the maximum value of the child with the value of the parent node;
3a. If it is greater than the value of the parent node, exchange the child node with the parent node;
3b. If it is less than the value of the parent node, the heap is a lot and no adjustment is needed.
4. Then take the exchanged child node as the parent node and repeat step 2.3;

2) Illustration:




3) Code implementation

public void ShiftDown2(int parent){
        int child = 2 * parent + 1;
        //When the subscript of the child node exceeds the length of the heap, the adjustment ends
        while (child < this.usedSize){
            //Get the maximum value in the child node
            if (child + 1 < this.usedSize && this.elem[child] < this.elem[child + 1]){
                child++;
            }
            //Compare with the parent node, and exchange if it is greater than
            if (this.elem[child] > this.elem[parent]){
                int tmp = this.elem[child];
                this.elem[child] = this.elem[parent];
                this.elem[parent] = tmp;
                parent = child;
                child = 2 * parent + 1;
            }else {
                break;
            }
        }
    }

Time complexity: the worst case is to compare to the leaf node. The number of comparisons is the height of the complete binary tree, i.e. O (logn).

Build pile

Logically, the array can be regarded as a complete binary tree, but the array is not a heap. Now we build it into a heap through the downward adjustment algorithm. We adjust from the parent node of the last node to the root node. Logically, this array becomes a heap.

Specific steps:
1) The subscript of the last node is obtained through the formula parent = (child - 1) / 2.
2) After the adjustment, add one to the subscript of father's day and adjust it to the root node to build a lot.

Code implementation:

public void createBigHeap1(int[] array){
        int parent = (array.length - 1 - 1) / 2;
        for (int i = parent; i >= 0 ; i--) {
            shiftDown2(i);
        }
    }
    public void shiftDown2(int parent){
        int child = 2 * parent + 1;
        //When the subscript of the child node exceeds the length of the heap, the adjustment ends
        while (child < this.usedSize){
            //Get the maximum value in the child node
            if (child + 1 < this.usedSize && this.elem[child] < this.elem[child + 1]){
                child++;
            }
            //Compare with the parent node, and exchange if it is greater than
            if (this.elem[child] > this.elem[parent]){
                int tmp = this.elem[child];
                this.elem[child] = this.elem[parent];
                this.elem[parent] = tmp;
                parent = child;
                child = 2 * parent + 1;
            }else {
                break;
            }
        }
    }

Time complexity analysis:

The worst case of heap building is that it needs to be exchanged every time, so the total time complexity is: the total time complexity is:
Number of nodes to be adjusted * number of nodes to be adjusted

T(N) = 1*(h - 1) + 2 * (h - 2) + 2^2 * (h - 3) + ... + 2^(h - 2) * 1;
Multiply both sides by two
2 T(N) = 2*(h - 1) + 2^ 2* (h - 2) + 2^3 * (h - 3) + ... + 2^(h - 1) * 1;
Subtractive
T(N) = 1 + 2 + 2^2 + ... + 2 ^ (h - 2) + 2 ^(h - 1) - h;
T(N) = 2^h - h -1;
The properties of binary tree are N = 2^h -1, h= log(N + 1)
T(N) = N - log(N + 1)
When N tends to infinity
T(N) = O(N);

Therefore, the time complexity of heap building is O (N).

Application of Heap - priority queue

In many applications, we usually need to process the objects according to the priority. For example, first process the objects with the highest priority, and then process the objects with the second highest priority. The simplest example is that when playing games on the mobile phone, if there is an incoming call, the system should deal with the incoming call limited. In this case, our data structure should provide two basic operations, one is to return the highest priority object, and the other is to add a new object. This data structure is priority queue

Queue

1) (take a large number as an example) queue up steps (this process is also called upward adjustment):

1. First, put the new object into the array by tail interpolation.
2. Compare its value with that of its parents. If it is less than the value of the parent node, it meets the nature of the heap and the insertion ends
3. Otherwise, exchange values with the parent node and repeat step 2.3
4. Know the end of the root node.

2) Illustration



3) Code implementation

public void shitfUp1(int child){
        int parent = (child - 1) / 2;
        while (parent >= 0){
            if (this.elem[parent] < this.elem[child]){
                int tmp = this.elem[parent];
                this.elem[parent] = this.elem[child];
                this.elem[child] = tmp;
                child = parent;
                parent = (child - 1) / 2;
            }else {
                break;
            }
        }
    }
public void push1(int val){
        if (isFull()){
            this.elem = Arrays.copyOf(this.elem,2*this.usedSize);
        }
        this.elem[this.usedSize] = val;
        this.usedSize++;
        shitfUp1(this.usedSize-1);
    }

Out of queue

In order to prevent the structure of the left and right subtree heap from being destroyed, the top element of the heap is not deleted directly, but the last element of the array is used to replace the top element of the heap, and then it is adjusted to a large heap by downward adjustment.
code implementation

public void poll1(){
        if (isEmpty()){
            throw  new RuntimeException("Queue is empty");
        }
        int tmp = this.elem[this.usedSize - 1];
        this.elem[this.usedSize - 1] = this.elem[0];
        this.elem[0] = tmp;
        
        this.usedSize--;
        shiftDown2(0);
    }

Precautions for using PriorityQueue

  1. Import package when using;
  2. Elements placed in PriorityQueue must be able to compare sizes (only classes that implement Comparable and Comparator interfaces can compare sizes). Objects that cannot be compared cannot be inserted, otherwise ClassCastException will be thrown;
    3. Null object cannot be inserted, otherwise null pointer exception will occur;
    4. There is no capacity limit, and the internal capacity will be expanded automatically;
    5. The time complexity of inserting and deleting elements is O(logn);
    6. The bottom layer is implemented by heap.

Application of priority queue TOPK problem

Problem solving idea: first, use the first k elements to generate a large top heap, which is used to store the current minimum k elements. Then, start scanning from the k+1 element and compare it with the top of the heap (the smallest element in the heap). If the scanned element is smaller than the top of the heap, replace the elements on the top of the heap and adjust the heap to ensure that the k elements in the heap are always the current maximum k elements. After scanning all n-k elements, the k elements in the final heap are the obscene TopK.

Title: link: Find the minimum number of K.

class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];
        if(k == 0){
            return ret;
        }
        
        //Build a pile
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>(){
            @Override
            public int compare(Integer o1,Integer o2){
                return o2 - o1;
            }
        });
        
        for(int i = 0; i < arr.length; i++){
        //First put k elements into a pile
            if(maxHeap.size() < k){
                maxHeap.offer(arr[i]);
            }else{
            //Later elements are compared with stack top elements, and small elements are interchanged with heap top elements
                int top = maxHeap.peek();
                if(top > arr[i]){
                    maxHeap.poll();
                    maxHeap.offer(arr[i]);
                }
            }
        }
           //Put the elements in the heap into an array 
        for(int i = 0; i < k; i++){
            ret[i] = maxHeap.poll();
        }
        return ret;
    }
}

Application of heap -- heap sorting

Heap sorting is described in detail in this article:
Link: Ranking of seven classics.

It's not easy to create. Give it a compliment

Tags: Java data structure

Posted on Mon, 25 Oct 2021 08:27:08 -0400 by skeppens