Data structures and Algorithms (4)--priority queue and heap

Source: Internet
Author: User
Tags comparable int size java web

Preface: The graph is irrelevant, then begin to learn the knowledge of the related data structure of priority queue and heap.

Pre-order articles:

    • Data structures and algorithms (1)--arrays and linked lists (https://www.jianshu.com/p/7b93b3570875)
    • Data structures and Algorithms (2)-Stacks and queues (HTTPS://WWW.JIANSHU.COM/P/5087C751CB42)
    • Data structures and Algorithms (3)--Tree (binary, Binary search tree) (HTTPS://WWW.JIANSHU.COM/P/4EF1F50D45B5)
What is a priority queue?

Listen to this name to know that the priority queue is also a kind of queue, but the difference is that the priority queue of the order is priority, in some cases, you may need to find the element collection of the smallest or largest element, you can use the priority queue ADT to complete the operation, priority queue ADT is a data structure, It supports inserting and deleting minimum values (returning and deleting the smallest element) or deleting the maximum (the largest element is returned and deleted);

These operations are equivalent to the queue enQueue and deQueue operation, the difference is that for the priority queue, the order of elements into the queue may be different from the order of operation, job scheduling is a priority queue of an application instance, it is based on the priority level rather than the first-to-first service to schedule;

If the minimum key value element has the highest priority, then this priority queue is called the Ascending priority Queue (that is, the smallest element is always removed first), and similarly, if the maximum key value element has the highest priority, then this priority queue is called descending priority Queue (That is, always delete the largest element first); Because the two types are symmetric, you only need to focus on one of them, such as ascending priority queue;

Priority Queue ADT

The following operations comprise an ADT for the priority queue;

1. Main operations of the priority queue
The priority queue is the container for the element, and each element has a related key value;

    • insert(key, data): Inserting the data into the priority queue with key values, and the elements are sorted by their key;
    • deleteMin/deleteMax: The element that deletes and returns the minimum/maximum key value;
    • getMinimum/getMaximum: Returns the element of the minimum/maximum sword point, but does not delete it;

2. Secondary operations for priority queues

    • 第k最小/第k最大: Returns the smallest/largest element of the first priority queue with a key value of k;
    • 大小(size): Returns the number of elements in the priority queue;
    • 堆排序(Heap Sort): Sorts the elements in the priority queue based on the priority of the key value;
Application of Priority queue
    • Data compression: Huffman coding algorithm;
    • Shortest path algorithm: Dijkstra algorithm;
    • Minimum spanning tree algorithm: Prim algorithm;
    • Event-driven simulation: Customer queuing algorithm;
    • Select question: Find the smallest element of K;
    • Wait a minute .....
Implementation comparison of Priority queues
Implement Insert Delete finding the minimum value
Unordered array 1 N N
Unordered linked list 1 N N
Ordered array N 1 1
Ordered linked list N 1 1
Binary search Tree Logn (average) Logn (average) Logn (average)
Balanced binary search Tree Logn Logn Logn
Two-fork Pile Logn Logn 1
Heap and two fork heap what is the heap

A heap is a two-fork tree of a specific nature , and the basic requirement of the heap is that the value of all nodes in the heap must be greater than or equal to (or less than or equal to) the value of their child's nodes, which is also known as the nature of the heap; another property of the heap is that when H > 0 o'clock, all leaf nodes are Layer (where h is the height of the tree, the complete binary tree), that is, the heap should be a completely binary tree;

In the following example, the left tree is a heap (each element is greater than the value of its child node), and the tree on the right is not a heap (because 5 is greater than its child Node 2)

Two-fork Pile

In the binary heap, each node has a maximum of two children nodes, in practical applications, the binary heap is sufficient to meet the demand, so the next is mainly about the binary minimum heap and two fork maximum heap;

heap representation: before describing the operation of the heap, first look at how the heap is represented, one possible way is to use an array, because the heap is formally a fully binary tree, using an array to store it will not waste any space, for example:

Using arrays to represent the heap does not waste space and has some advantages:

    • The left child of each node is twice times the subscript I: the left child(i) = i * 2 right child of each node is twice times the subscript i plus 1:right child(i) = i * 2 + 1
    • The Father node of each node is the subscript one-second: parent(i) = i / 2 , note here is the integer except, 2 and 3 divided by 2 are 1, you can verify;
    • Note: here is the subscript 0 place empty out, mainly for the convenience of understanding, if 0 is not empty out only need in the calculation of the I value to the right offset a position on the line (that is, add 1, you can try, the following demonstration also take this way);
The basic structure of the related operation heap of binary heap
public class MaxHeap<E extends Comparable<E>> {    private Array<E> data;    public MaxHeap(int capacity){ data = new Array<>(capacity); }    public MaxHeap(){ data = new Array<>(); }    // 返回堆中的元素个数    public int size(){ return data.getSize(); }    // 返回一个布尔值, 表示堆中是否为空    public boolean isEmpty(){ return data.isEmpty(); }    // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引    private int parent(int index){        if(index == 0)            throw new IllegalArgumentException("index-0 doesn‘t have parent.");        return (index - 1) / 2;    }    // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引    private int leftChild(int index){ return index * 2 + 1; }    // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引    private int rightChild(int index){ return index * 2 + 2; }}
Adding elements and sift up to the heap

When inserting an element into the heap, it may not satisfy the nature of the heap, in which case it is necessary to adjust the position of the elements in the heap to re-stack them, a process known as heapifying; in the largest heap, to heap an element, you need to find its Father node. If the basic nature of the heap is not satisfied and the position of the two elements is exchanged, repeat the process until each node satisfies the nature of the heap, let's simulate this process:

Below we insert a new element 26 in the heap:

We can easily find the parent node of the newly inserted element by index (the formula above), then compare their size, and if the new element is larger then swap the position of the two elements, which is equivalent to floating the element:

Repeat the operation until 26 has reached a location that satisfies the heap condition, and the inserted operation is complete:

The corresponding code is as follows:

// 向堆中添加元素public void add(E e){    data.addLast(e);    siftUp(data.getSize() - 1);}private void siftUp(int k){    while(k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0 ){        data.swap(k, parent(k));        k = parent(k);    }}
Remove the largest element in the heap and sift down

If you understand the above procedure, it will be easy to take the largest element in the heap (the top element of the heap), but here is a trick to replace the top element with the last one, and then delete the last element, so that the total number of elements satisfies the condition. Then you just need to put the top element of the stack in order to adjust it, this operation is called sift Down (sinking):

Replace the top element with the last element, and then delete the last element:

Then compare the size of their children's nodes:

If you do not meet the conditions of the heap, then it is a larger swap location with the child's node:

Repeat this step until the 16 reaches the appropriate location:

To complete the operation of removing the largest element:

The corresponding code is as follows:

// 看堆中的最大元素public E findMax(){    if(data.getSize() == 0)        throw new IllegalArgumentException("Can not findMax when heap is empty.");    return data.get(0);}// 取出堆中最大元素public E extractMax(){    E ret = findMax();    data.swap(0, data.getSize() - 1);    data.removeLast();    siftDown(0);    return ret;}private void siftDown(int k){    while(leftChild(k) < data.getSize()){        int j = leftChild(k); // 在此轮循环中,data[k]和data[j]交换位置        if( j + 1 < data.getSize() &&                data.get(j + 1).compareTo(data.get(j)) > 0 )            j ++;        // data[j] 是 leftChild 和 rightChild 中的最大值        if(data.get(k).compareTo(data.get(j)) >= 0 )            break;        data.swap(k, j);        k = j;    }}
Replace and Heapify

Replace This operation is actually to remove the largest element in the heap and then insert a new element, the general practice is to take out the largest element, and then use the above insert new element of the operation of the heap to sift up operation, But here's a trick to replace the top element directly with the new element and then sift down, which turns two O (LOGN) operations into O (LOGN):

// 取出堆中的最大元素,并且替换成元素epublic E replace(E e){    E ret = findMax();    data.set(0, e);    siftDown(0);    return ret;}

heapify Translation is the meaning of stacking, is to organize arbitrary arrays of piles of shapes, the usual practice is to iterate through the array from 0 to add the creation of a new heap, but there is a small trick is to think of the current array as a complete binary tree, Then from the last non-leaf node to start the SIFT down operation can be, the last non-leaf knot is also very easy to find, is the last node of the Father node, you can verify:

Starting with the 22 node, start the SIFT down operation in turn:

Repeat the process until the top element of the heap:

To complete the heap operation:

n elements are inserted into an empty heap, the algorithm complexity is O (Nlogn), and the heapify process, the algorithm complexity of O (n), which is a qualitative leap, the following is the code:

public MaxHeap(E[] arr){    data = new Array<>(arr);    for(int i = parent(arr.length - 1) ; i >= 0 ; i --)        siftDown(i);}
Heap-based priority queue

First of all, our queue still needs to inherit which interface we declared before the queue Queue , and then implement the method in this interface, such as simple to write:

public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {    private MaxHeap<E> maxHeap;    public PriorityQueue(){ maxHeap = new MaxHeap<>(); }    @Override    public int getSize(){ return maxHeap.size(); }    @Override    public boolean isEmpty(){ return maxHeap.isEmpty(); }    @Override    public E getFront(){ return maxHeap.findMax(); }    @Override    public void enqueue(E e){ maxHeap.add(e); }    @Override    public E dequeue(){ return maxHeap.extractMax(); }}
Priorityqueue in Java

In Java also implemented their own priority queue java.util.PriorityQueue , and our own write the difference is that Java built in the smallest heap, and then some function name is not the same, the underlying or maintain an object type of an array, we can poke to see what is different, In addition, if you want to change the minimum heap to the maximum heap, you can pass the priorityqueue to your own comparator, for example:

// 默认为最小堆PriorityQueue<Integer> pq = new PriorityQueue<>();pq.add(5);pq.add(2);pq.add(1);pq.add(10);pq.add(3);while (!pq.isEmpty()) {    System.out.println(pq.poll() + ", ");}System.out.println();System.out.println("————————————————————————");// 使用Lambda表达式传入自己的比较器转换成最大堆PriorityQueue<Integer> pq2 = new PriorityQueue<>((a, b) -> b - a);pq2.add(5);pq2.add(2);pq2.add(1);pq2.add(10);pq2.add(3);while (!pq2.isEmpty()) {    System.out.println(pq2.poll() + ", ");}
Leetcode Related Topics collation 23. Merging K sorted lists

Reference answer: (85MS)

public ListNode mergeKLists(ListNode[] lists) {    if (lists == null || lists.length == 0) return null;    PriorityQueue<ListNode> q = new PriorityQueue<>(Comparator.comparing(node -> node.val));    for (int i = 0; i < lists.length; i++) {        if (lists[i] != null) {            q.add(lists[i]);        }    }    ListNode dummy = new ListNode(0);    ListNode tail = dummy;    while (!q.isEmpty()) {        tail.next = q.poll();        tail = tail.next;        if (tail.next != null) {            q.add(tail.next);        }    }    return dummy.next;}
215. K largest element in an array

My answer: (75MS)

public int findKthLargest(int[] nums, int k) {    // 正确性判断    if (0 == nums.length || null == nums || k <= 0 || k > nums.length) {        return -1;    }    // 构造优先队列,默认为最小堆,传入自定义的比较器转换成最大堆    PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);    for (Integer num : nums) {        pq.add(num);    }    for (int i = 0; i < k - 1; i++) {        pq.remove();    }    return pq.peek();}

Reference answer: (5ms)

public int findKthLargest(int[] nums, int k) {    if (nums.length == 1) {        return nums[0];    }    int max = nums[0];    int min = nums[0];    for (int i : nums) {        max = i > max ? i : max;        min = i < min ? i : min;    }    int[] arrs = new int[max - min + 1];    for (int i : nums) {        arrs[max - i]++;    }    int pos = 0;    for (int i = 0; i < arrs.length; i++) {        pos += arrs[i];        if (pos >= k) {            return max - i;        }    }    return nums[0];}

Also see a simple rude, also served: (4MS)

public int findKthLargest(int[] nums, int k) {    Arrays.sort(nums);    return nums[nums.length - k];}

And I randomly generated a random array of 1 million data, to test the efficiency of this simple brute method, and found that when the amount of data went up, sorting this operation became cumbersome, I myself test, the above three methods, the third one is probably more than the first (I write the method) more than 4 times times the time;

239. sliding window maximum (similar to the point of the sword refers to the offer surface question 59)

Reference answer: (88ms)

public int[] maxSlidingWindow(int[] nums, int k) {    if (nums == null || k <= 0) return new int[0];    int[] res = new int[nums.length - k + 1];    ArrayDeque<Integer> deque = new ArrayDeque<Integer>();    int index = 0;    for (int i = 0; i < nums.length; i++) {        while (!deque.isEmpty() && deque.peek() < i - k + 1) // Ensure deque‘s size doesn‘t exceed k            deque.poll();        // Remove numbers smaller than a[i] from right(a[i-1]) to left, to make the first number in the deque the largest one in the window              while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i])            deque.pollLast();        deque.offer(i);// Offer the current index to the deque‘s tail        if (i >= k - 1)// Starts recording when i is big enough to make the window has k elements             res[index++] = nums[deque.peek()];    }    return res;}

Reference Answer 2: (9ms)

  public int[] Maxslidingwindow (int[] nums, int k) {/* thought: Iterate through the array sequentially, the valid range looks for the current maximum in length k, In the result array to store the maximum value of the current length k, if there is a new nums[end in the current wheel] greater than Curmax, the direct replacement can be, if the current wheel Curmax is not a new nums[end], to reset the curmax.*/if    (Nums.length = = 0 | | k <= 0) return new int[0];    int curmax = Integer.min_value;    for (int i = 0; i < K; i++) {if (Nums[i] > Curmax) Curmax = Nums[i];    } int[] ans = new int[nums.length-k + 1];    Ans[0] = Curmax;        for (int start = 1; start + k-1 < nums.length; start++) {int end = start + k-1;        if (Nums[end] > Curmax) Curmax = Nums[end];            else if (nums[start-1] = = Curmax) {//new is not more than Curmax, reset Curmax = Integer.min_value in range;            for (int i = start; I <= end; i++) {if (Nums[i] > Curmax) Curmax = Nums[i];    }} Ans[start] = Curmax; } return ans;  
264. Ugly number II (sword point of offer question 49)

Reference answer: (7MS)

public int nthUglyNumber(int n) {    // 正确性判断    if (n < 1 || n > 1690) {        return -1;    }    int[] ugly = new int[n];    ugly[0] = 1;    int index2 = 0, index3 = 0, index5 = 0;    int factor2 = 2, factor3 = 3, factor5 = 5;    for (int i = 1; i < n; i++) {        int min = Math.min(Math.min(factor2, factor3), factor5);        ugly[i] = min;        if (factor2 == min)            factor2 = 2 * ugly[++index2];        if (factor3 == min)            factor3 = 3 * ugly[++index3];        if (factor5 == min)            factor5 = 5 * ugly[++index5];    }    return ugly[n - 1];}

If the use of each integer to determine whether the number of ugly solution, intuitive but not efficient, so we need to change a way of thinking, my first reaction is that there must be some rules, but try to find a bit, did not find ... Look at the answer to waking wake up, the earlier mentioned algorithm is inefficient, to a large extent because whether a number is not ugly, we have to calculate it, next we try to find a method that only calculates the ugly number, but not the number of non-ugly integer spend time, according to the definition of ugly number, The ugly number should be the result of another ugly number multiplied by 2, 3, or 5 (except for 1), so we can create an array in which the number is the number of ugly numbers in the order, and each ugly number is the previous ugly number multiplied by 2, 3, or 5, which is the algorithm above.

295. Median number of data streams (Sword Point 41)

Reference answer: (219MS)

public class MedianFinder {    PriorityQueue<Integer> maxHeap;    PriorityQueue<Integer> minHeap;    /**     * initialize your data structure here.     */    public MedianFinder() {        maxHeap = new PriorityQueue<>(Collections.reverseOrder());        minHeap = new PriorityQueue<>();    }    public void addNum(int num) {        maxHeap.add(num);        minHeap.add(maxHeap.poll());        if (minHeap.size() - maxHeap.size() > 0) {            maxHeap.add(minHeap.poll());        }    }    public double findMedian() {        if (maxHeap.size() == minHeap.size()) {            return (maxHeap.peek() + minHeap.peek()) / 2.0;        } else {            return maxHeap.peek();        }    }}

idea: This problem has a lot of implementation ideas, such as we can at the time of inserting each element into the correct position, so that the return of the median is an O (1) operation, the following table to illustrate the different implementation of the complexity of how much specific:

data structure time complexity of insertion
no sorted array o (1) o (n)
sorted array o (n) o (1)
sorted list o (n) o (1)
binary search tree average O (logn), worst O (n) Average O (logn), worst O (n)
avl tree o (LOGN) o (logn)
Max Heap and minimum heap o (logn) o (logn)

The AVL tree is a very efficient data structure, but in most languages there is no ready-made implementation, so consider using the largest heap and the smallest heap, for a well-ordered data container, we can be divided into two parts from it, with P1 pointing to the left half of the largest element, take P2 point to the smallest part of the element , if you can guarantee that the data on the left side of the data container is smaller than the data on the right, then even if the left and right sides of the data are not sorted, we can still get the median number according to the largest number on the left and the largest number on the right:

How do I quickly find the maximum number from a data container? We can use the maximum heap to implement this data container, because the element at the top of the heap is the largest element, and we can use the minimum heap to quickly find the smallest fraction in a data container. So according to this idea we can use a maximum heap to implement the data container to the left, using a minimum heap to implement the right data container, but note that the size difference between the two containers can not exceed 1;

347. Top K high-frequency elements (similar to the sword refers to the offer side of the question 40)

Reference answer: (131ms)

public List<Integer> topKFrequent(int[] nums, int k) {    TreeMap<Integer, Integer> map = new TreeMap<>();    // 保存频率    for (int num : nums) {        if (map.containsKey(num)) {            map.put(num, map.get(num) + 1);        } else {            map.put(num, 1);        }    }    PriorityQueue<Integer> pq = new PriorityQueue<>(Comparator.comparingInt(map::get));    for (int key : map.keySet()) {        if (pq.size() < k) {            pq.add(key);        } else if (map.get(key) > map.get(pq.peek())) {            pq.remove();            pq.add(key);        }    }    LinkedList<Integer> res = new LinkedList<>();    while (!pq.isEmpty()) {        res.add(pq.remove());    }    return res;}
692. First k high-frequency words

Reference answer: (72ms)

public List<String> topKFrequent(String[] words, int k) {    Map<String, Integer> count = new HashMap();    for (String word: words) {        count.put(word, count.getOrDefault(word, 0) + 1);    }    List<String> candidates = new ArrayList(count.keySet());    Collections.sort(candidates, (w1, w2) -> count.get(w1).equals(count.get(w2)) ?            w1.compareTo(w2) : count.get(w2) - count.get(w1));    return candidates.subList(0, k);}

This question is similar to the No. 347 question above, but the problem is in the order of return, you need to define a comparator to sort it. Then also learned a way of writing, is the first for loop above, getOrDefault() method, get√.

Reference Answer 2: (91ms)

public List<String> topKFrequent(String[] words, int k) {    Map<String, Integer> count = new HashMap();    for (String word: words) {        count.put(word, count.getOrDefault(word, 0) + 1);    }    PriorityQueue<String> heap = new PriorityQueue<String>(            (w1, w2) -> count.get(w1).equals(count.get(w2)) ?                    w2.compareTo(w1) : count.get(w1) - count.get(w2) );    for (String word: count.keySet()) {        heap.offer(word);        if (heap.size() > k) heap.poll();    }    List<String> ans = new ArrayList();    while (!heap.isEmpty()) ans.add(heap.poll());    Collections.reverse(ans);    return ans;}

This solution is a bit similar to the above 347 questions, in fact, is very much the same, is not flexible to use the comparator only, learning to learn to √ ...

Brief summary

Today is a very rewarding day, because these two data structures are particularly unfamiliar, especially in the brush some Leetcode related topics, the two kinds of data have a very different understanding, especially the application of the heap, which is a particularly suitable for the first k small/large special data structure, And in Java actually have a direct implementation, this is great, and today's efficiency is quite high, meet;

Welcome reprint, Reproduced please indicate the source!
Jane Book id:@ I don't have three hearts.
Github:wmyskxz
Welcome to follow the public number: Wmyskxz_javaweb
Share your Java Web learning path and a variety of Java learning materials

Data structures and Algorithms (4)--priority queue and heap

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.