Priority queue (maximum and minimum heap) summary

Posted by davestewart on Sat, 05 Mar 2022 09:07:42 +0100

preface

It mainly describes how to construct the maximum and minimum heap through priority queue to solve the topK problem.

1, Priority queue

1. The priority queue looks like a queue, and the bottom layer is implemented based on heap.
2. Priority queue can arrange the queued elements to their own positions according to the priority between elements.
3. The priority queue can be dynamically queued according to the priority between elements.
4. Unlike the set of sorting processing, the number of elements is fixed. The number of elements processed is dynamic, with in and out.

2, Comparison between normal queue and normal queue

Normal queue (FIFO)Priority queue
realizationBased on linked listHeap based
Time complexity of joining the teamO(N)O(logN)
Out of line (out of maximum) time complexityO(N)O(logN)

Generally, if the time complexity is O(logN) level, the high probability is related to the "tree" structure.

3, Implementation of priority queue (maximum heap)

1. Implementation of maximum heap

public class MaxHeap {
    List<Integer> data;

    /**
     * A heap of size 10 is created by default
     */
    public MaxHeap() {
        this(10);
    }

    /**
     * Creates a heap of the specified size
     * @param len
     */
    public MaxHeap(int len) {
        data = new ArrayList<>(len);
    }

    /**
     * Convert array arr to maximum heap
     * @param arr
     */
    public MaxHeap(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }
        data = new ArrayList<>();
        for (int num : arr) {
            data.add(num);
        }
        // Start from the last non leaf node and sink each non leaf node
        int k = (data.size() - 1 - 1) >>1; // Number of the last non leaf node
        while (k >= 0) {
            siftDown(k);
            k --;
        }
    }

    /**
     * Determine whether the heap is empty
     * @return
     */
    public boolean isEmpty() {
        return data.size() > 0;
    }

    /**
     * Returns the parent node number of k
     * @param k
     * @return
     */
    public int parent(int k) {
        return (k - 1) >> 1;
    }

    /**
     * Returns the number of the left child
     * @param k
     * @return
     */
    public int leftChild(int k) {
        return (k << 1) + 1;
    }

    /**
     * Returns the number of the right child
     * @param k
     * @return
     */
    public int rightChild(int k) {
        return (k << 1) + 2;
    }

    /**
     * Add an element with value val to the heap
     * @param val
     */
    public void add(int val) {
        // Add the element to the end first
        data.add(val);
        // Floating up an element
        siftUp(data.size() - 1);
    }

    /**
     * Floating up operation of element
     * @param k
     */
    public void siftUp(int k) {
        while (k > 0 && data.get(k) > data.get(parent(k))) {
            swap(k, parent(k));
            k = parent(k);
        }
    }

    /**
     * Swap two element positions
     * @param k1
     * @param k2
     */
    public void swap(int k1, int k2) {
        int tmp = data.get(k1);
        data.set(k1, data.get(k2));
        data.set(k2, tmp);
    }

    /**
     * Fetch the maximum value in the heap
     * @return
     */
    public int extraMax() {
        if (isEmpty()) {
            throw new NoSuchElementException("The heap is empty!");
        }
        int ret = data.get(0);
        // Put the last leaf node on the top of the root node, and then do the sinking operation
        int index = data.size() - 1;
        swap(0, index);
        data.remove(index);
        siftDown(0);
        return ret;
    }

    /**
     * Display maximum
     * @return
     */
    public int peek() {
        if (isEmpty()) {
            throw new NoSuchElementException("The heap is empty!");
        }
        return data.get(0);
    }

    /**
     * Sinking operation of element
     * @param k
     */
    public void siftDown(int k) {
        // Existential subtree
        while (leftChild(k) < data.size()) {
            int j = leftChild(k);
            // Judge whether there is a right tree and the value of the right tree is greater than that of the left tree
            if (j + 1 < data.size() && data.get(j) < data.get(j + 1)) {
                j = j + 1;
            }
            if (data.get(j) > data.get(k)) {
                swap(j, k);
                k = j;
            } else {
                return;
            }
        }
    }

    public String toString() {
        return data.toString();
    }
}

2. Implementation of priority queue

Based on the priority sequence of the largest heap, the higher the value, the higher the priority

// Customize the queue interface and set the basic method of queue
public interface Queue<T> {
    // Join the team
    void offer(T val);
    // Out of the team
    T poll();
    // Heap top element
    T peek();
    // Air judgment
    boolean isEmpty();
}
// Implementation of priority queue (implementation of maximum heap)
public class MyPriorityQueue implements Queue<Integer> {
    private MaxHeap heap;
    public MyPriorityQueue() {
        heap = new MaxHeap(); // Create maximum heap
    }

    @Override
    public void offer(Integer val) {
        heap.add(val); // Add elements to the maximum heap
    }

    @Override
    public Integer poll() {
        return heap.extraMax(); // Use the method of removing the maximum value in the maximum heap to pop up the top element of the heap
    }

    @Override
    public Integer peek() {
        return heap.peek(); // Gets the value of the heap top element
    }

    @Override
    public boolean isEmpty() {
        return heap.isEmpty();
    }
}

4, Application of priority queue

The priority queue in JDK is the implementation of the minimum heap by default, and the queue head element is the minimum value of the current queue
Application:
1. Queue out of the priority sequence based on the maximum heap to get a non increasing sequence
2. Realize the priority sequence based on the minimum heap and get a non decreasing sequence
topK problem (i.e. the most * K elements) can generally be implemented with priority queue

1. Create priority queue

Queue<Element type> queue = new PriorityQueue<>(new Comparator<Element type>() {
					// Use the Comparator comparator to customize the priority setting. If the priority sequence implemented by the JDK default minimum heap is not used
					// The default is o1. The implementation of - o2 is the minimum heap, and the reverse is the maximum heap
            @Override
            public int compare(Element type o1, Element type o2) {
                return 0; // User defined priority, which needs to be defined according to the situation
            }
        });

2. Use priority queue

queue.offer(Element type ele); // Adds an element to the queue as set
queue.poll(); // Remove top elements
queue.isEmpty(); // Judge whether the priority queue is empty
 Element type ele = queue.peek(); // Get the maximum value of the highest priority queue (heap top element)

5, Solving problems using priority queues

Law: take the big and use the small, take the small and use the big
The priority queue realized with the minimum heap when seeking the maximum K elements and the priority queue realized with the maximum heap when seeking the minimum K elements.

Analysis: 1 When it is larger, if a minimum heap is maintained, you can know the minimum value in the current queue. If the number of elements in the priority queue is greater than K and the current element is greater than the top element (minimum value), you can push the heap out of the queue and the current element into the queue, and all the previous maximum elements are saved in the queue. When the traversal is completed, the maximum k elements of the entire array remain in the queue.
2. Take the hour. You can know the maximum value from the maximum heap. After comparison, you can leave the smaller one in the heap, the same as above.

The following questions are from: LeetCode

1.Interview question 17.14 Minimum number of K

Title Description: design an algorithm to find the minimum number of k in the array. These k numbers can be returned in any order

Tips:

	    0 <= len(arr) <= 100000
	    0 <= k <= min(100000, len(arr))
class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k]; // Use the ret array to save the final result
        // Because the data given in the above prompt indicates that the incoming array may be empty, it needs to be judged empty
        if (arr == null || arr.length == 0 || k == 0) {
            return ret;
        }
        // Create priority queue
        Queue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
            		// Take the small and use the large. In JDK, o1 - o2 is the minimum heap by default, and can be defined as the maximum heap by subtracting inversely
                return o2 - o1;
            }
        });
        // Traverse all numbers in the array
        for (int num : arr) {
            if (queue.size() < k) {
           		// When there are not enough K elements in the queue, just add them directly
                queue.offer(num);
            } else {
            	// At this time, the number of elements in the queue has reached K, and the smaller one needs to be reserved compared with the elements at the top of the heap
                if (num < queue.peek()) {
                    queue.poll();
                    queue.offer(num);
                }
            }
        }
        for (int i = 0; i < k; i++) {
       		// Pop up the top elements in turn to get a decreasing sequence of elements
            ret[i] = queue.poll();
        }
        return ret;
    }
}

2.1046. Weight of the last stone

Title Description: there is a pile of stones. The weight of each stone is a positive integer.

Each round, choose the two heaviest stones and crush them together. Suppose the weight of the stone is x and Y respectively, and x < = y. Then the possible results of crushing are as follows:

If x == y,Then both stones will be completely crushed;
If x != y,So the weight is x The stone will be completely crushed and weigh y The new weight of the stone is y-x. 

In the end, there will be only one stone left at most. Returns the weight of this stone. If there are no stones left, return 0.

class Solution {
    public int lastStoneWeight(int[] stones) {
        // Maximum heap
        Queue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        for (int stone : stones) {
            queue.offer(stone);
        }
        // When there are 2 or more stones in the priority queue
        while (queue.size() > 1) {
            int a = queue.poll();
            int b = queue.poll();
            if (a != b) {
            	// A is ejected from the maximum heap first, so the value of a must be greater than that of b, and the difference between before and after is stored in the queue
                queue.offer(a - b);
            }
        }
        if (queue.size() == 0) {
            return 0;
        }
        return queue.poll();
    }
}

3.347 high frequency elements

Title Description: give you an integer array nums and an integer k. please return the elements with the highest frequency of k. You can return answers in any order.

class Solution {
    // In addition to using Comparator to define priority, you can also use special internal classes to compare custom objects and set priority
    // This internal class implements the comparable < > interface and implements the compareTo () method to define the priority.
    class Freq implements Comparable<Freq> {
        int num;
        int time;

        public Freq(int num, int time) {
            this.num = num;
            this.time = time;
        }


        @Override
        public int compareTo(Freq o) {
            return this.time - o.time;
        }
    }
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int num = nums[i];
            map.put(num, map.getOrDefault(num, 1) + 1);
        }
//        Queue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
//            @Override
//            public int compare(Integer o1, Integer o2) {
//                return map.get(o1) - map.get(o2);
//            }
//        });
//        for (int num : map.keySet()) {
//            queue.offer(num);
//            if (queue.size() > k) {
//                queue.poll();
//            }
//        }
//        int[] ret = new int[k];
//        for (int i = 0; i < k && !queue.isEmpty(); i++) {
//            ret[i] = queue.poll();
//        }
//        return ret;

        // Use inner classes
        Queue<Freq> queue = new PriorityQueue<>();
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if (queue.size() < k) {
                queue.offer(new Freq(entry.getKey(), entry.getValue()));
            } else {
                if (map.get(entry.getKey()) > queue.peek().time) {
                    queue.poll();
                    queue.offer(new Freq(entry.getKey(), entry.getValue()));
                }
            }
        }
        int[] ret = new int[k];
        for (int i = 0; i < k && !queue.isEmpty(); i++) {
            ret[i] = queue.poll().num;
        }
        return ret;
    }
}

Different comparators are configured according to different requirements

Comparator's advantages over Comparable: compared with Comparable, comparator is more flexible and does not need to modify the code of comparison class. It is a non intrusive mode.
Supplement: sort (sequence to be sorted, sorting strategy) can realize specific sorting of sequences. The sorting policy can be an object of a special class (implementing the Comparable interface) that specifies the sorting method.

4.373. Find and the smallest K-pair number

Given two integer arrays nums1 and nums2 arranged in ascending order, and an integer k.

Define a pair of values (u,v), where the first element is from nums1 and the second element is from nums2.

Please find the minimum k number pairs (u1,v1), (u2,v2)... (uk,vk).

Source: LeetCode
Link: https://leetcode-cn.com/problems/find-k-pairs-with-smallest-sums
The copyright belongs to Lingkou network. For commercial reprint, please contact the official authorization, and for non-commercial reprint, please indicate the source.

class Solution {
		// Customize a number pair class to facilitate saving values and calculation
    private class Pair {
        int a;
        int b;

        Pair(int a, int b) {
            this.a = a;
            this.b = b;
        }
        
        int sum() {
            return this.a + this.b;
        }
    }
    public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        // Maximum heap
        Queue<Pair> queue = new PriorityQueue<>(new Comparator<Pair>() {
            @Override
            public int compare(Pair o1, Pair o2) {
                return (o2.a + o2.b) - (o1.a + o1.b);
            }
        });
        // Because the two arrays are in ascending order, the minimum and maximum can only traverse the first k of each array and the smaller of the array length
        for (int i = 0; i < Math.min(nums1.length, k); i++) {
            for (int j = 0; j < Math.min(nums2.length, k); j++) {
                if (queue.size() < k) {
                    queue.offer(new Pair(nums1[i], nums2[j]));
                } else {
                    int sum = nums1[i] + nums2[j];
                    if (sum < queue.peek().sum()) {
                        Pair pair = new Pair(nums1[i], nums2[j]);
                        queue.poll();
                        queue.offer(pair);
                    }
                }
            }
        }
        // Note that it cannot be written as list < list < integer > > RET = new ArrayList < > (queue); This will result in a binary tree sequence traversal instead of a sorted result
        List<List<Integer>> ret = new ArrayList<>();
        while (!queue.isEmpty() && ret.size() <= k) {
            List<Integer> list = new ArrayList<>();
            Pair pair = queue.poll();
            list.add(pair.a);
            list.add(pair.b);
            ret.add(list);
        }
        return ret;
    }
}

summary

1. When encountering the topK problem, use the priority queue to solve it, and the time complexity is O(NlogK).
2. Learn to use internal classes as auxiliary tools.

Topics: Java Algorithm