Minimum heap maximum heap understand? To understand the application of heap in the front end

Posted by pbdude23 on Sun, 16 Jan 2022 14:07:45 +0100

⚡ preface

We all know that a tree is a data structure, but we may rarely hear of the data structure heap. In fact, heap is a special kind of complete binary tree. For the front end, we usually know the maximum heap and minimum heap, and we often use the maximum heap and minimum heap to solve various problems. For example, the K largest element in the array, the first k high-frequency elements in the document, and so on.

In the following article, we will explain the basic knowledge of heap, manually use js to build a minimum heap, and analyze several classic leetcode algorithm problems.

Next, let's begin the explanation of this article~ 🔥

🦘 1, What is a pile?

  • Heap is a special kind of complete binary tree, which means that each node has two child nodes.
  • Maximum heap: all nodes are greater than or equal to its child nodes;
  • Minimum heap: all nodes are less than or equal to its child nodes.

🐥 2, Heap in JS

  • JS usually uses arrays to represent the heap.
  • The location of the left node is 2*index+1.
  • The position of the node on the right is 2*index+2.
  • The parent node location is (index - 1) / 2.

🐝 3, Application of heap

  • Heap can find the maximum and minimum values efficiently and quickly, and the time complexity is O(1).
  • In development, sometimes we may want to find the largest or smallest element in an array, and in the heap, we can find the K largest (small) element.

🐈 4, Build a minimum heap

1. Definitions

From the above little knowledge, we can see that for the smallest heap, all its nodes are less than or equal to its child nodes. Next, let's look at some common implementations of the data structure heap.

2. Method

methodmeaning
swap()Swap the location of two nodes
getParentIndex()Gets the location of the parent node
getLeftIndex()Gets the position of the left child node
getRightIndex()Gets the position of the child node on the right
shiftUp()Move up
shiftDown()Move down
insert()Insert the value of the node
pop()Delete heap top operation
peek()Gets the value of the heap top
size()Gets the size of the heap

3. Realize the minimum heap with js code

(1) Initialize a heap

First, we need to define an empty array to store a heap. The specific codes are as follows:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
}

(2) Swap location ()

After initializing a heap, if we want to move up and down, we need to exchange the positions of the two nodes from time to time. Then let's write a method to exchange node positions. The specific codes are as follows:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
    //Switch the position between nodes i1 and i2
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
}

(3) Get the location of the parent node getParentIndex()

As mentioned above, the location of the parent node is (index - 1) / 2. Therefore, we need to pass in the value index of the current node to perform a floor division operation to obtain the specific location of the parent node. The specific codes are as follows:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
    //Switch the position between nodes i1 and i2
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    
    //Gets the location of the parent node
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        //You can also use the following method of shift right operation
        //return (i - 1) >> 1;
    }
}

(4) Get the position of the left child node getLeftIndex()

For the left child node, its index is 2 * index + 1, that is, it is twice the index value of the current node + 1. The specific implementation code is as follows:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
    //Switch the position between nodes i1 and i2
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    
    //Gets the location of the parent node
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        //You can also use the following method of shift right operation
        //return (i - 1) >> 1;
    }
    
    //Get the left child node, i is the index of the current node
    getLeftIndex(i){
        return i * 2 + 1;
    }
}

(5) Get the position of the right child node getRightIndex()

For the child node on the right, its index is 2 * index + 2, that is, it is twice the index value of the current node + 2. The specific implementation code is as follows:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
    //Switch the position between nodes i1 and i2
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    
    //Gets the location of the parent node
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        //You can also use the following method of shift right operation
        //return (i - 1) >> 1;
    }
    
    //Get the left child node, i is the index of the current node
    getLeftIndex(i){
        return i * 2 + 1;
    }
    
    //Get the right child node, i is the index of the current node
    getRightIndex(i){
        return i * 2 + 2;
    }
}

(6) Move up ()

Above, we have implemented the operations of obtaining various indexes such as obtaining the parent node. Now, let's implement the move up operation.

For the move up operation, the implementation idea is as follows:

  • First, judge whether the current node is at the vertex of the heap. If so, do not move up; If not, continue the comparison;
  • Obtain the location index of the parent node. The purpose of obtaining the index is to obtain the specific value of the index;
  • Compare the value of the current node with the value of the parent node. If the value of the parent node is greater than the value of the current node, move it up;
  • Move up recursively until you reach the top of the heap.

The specific code implementation method is given below:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
    //Switch the position between nodes i1 and i2
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    
    //Gets the location of the parent node
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        //You can also use the following method of shift right operation
        //return (i - 1) >> 1;
    }
    
    //Shift up to move up
    shiftUp(index){
        //If it is at the vertex of the heap, the result is returned directly without moving up
        if(index === 0){
            return;
        }
        //Get the parent node (that is, get the value of the parent node of the current node, and there is only one parent node of each node)
        const parentIndex = this.getParentIndex(index);
        //Judge if the parent node of the heap is larger than the child node, and perform location exchange
        if(this.heap[parentIndex] > this.heap[index]){
            this.swap(parentIndex, index);
            //After the exchange is completed, continue to move up recursively
            this.shinftUp(parentIndex);
        }
    }
}

(7) Move down (shiftdown)

For the move down operation, the implementation idea is as follows:

  • First obtain the left and right nodes;
  • Compare the left child node with the current node. If the left child node is smaller than the current node, perform location exchange, and then continue to compare the exchanged nodes;
  • After comparing the nodes on the left, compare the nodes on the right;
  • Compare the right child node with the current node. If the right child node is smaller than the current node, perform location exchange, and then continue to compare the exchanged nodes;
  • This loops until the last node.

The specific code implementation method is given below:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
    //Switch the position between nodes i1 and i2
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    
    //Get the left child node, i is the index of the current node
    getLeftIndex(i){
        return i * 2 + 1;
    }
    
    //Get the right child node, i is the index of the current node
    getRightIndex(i){
        return i * 2 + 2;
    }
    
    // Move down
    shiftDown(index){
        // Get left and right child nodes
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        //  Exchange the left node
        if(this.heap[leftIndex] < this.heap[index]){
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        //  Exchange the right node
        if(this.heap[rightIndex] < this.heap[index]){
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
}

(8) Insert the value of the node insert()

For the operation of inserting nodes, the implementation idea is as follows:

  • Insert the value at the bottom of the heap, the end of the array.
  • Then move up: swap the value with its parent node until the parent node is less than or equal to the inserted value.
  • The time complexity of inserting elements into the heap with size k is O(logK).

The specific code implementation method is given below:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
    //Switch the position between nodes i1 and i2
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    
    //Gets the location of the parent node
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        //You can also use the following method of shift right operation
        //return (i - 1) >> 1;
    }
    
    //Shift up to move up
    shiftUp(index){
        //If it is at the vertex of the heap, the result is returned directly without moving up
        if(index === 0){
            return;
        }
        //Get the parent node (that is, get the value of the parent node of the current node, and there is only one parent node of each node)
        const parentIndex = this.getParentIndex(index);
        //Judge if the parent node of the heap is larger than the child node, and perform location exchange
        if(this.heap[parentIndex] > this.heap[index]){
            this.swap(parentIndex, index);
            //After the exchange is completed, continue to move up recursively
            this.shinftUp(parentIndex);
        }
    }
    
    //The operation of inserting node value. Value is the inserted value
    insert(value){
        //Put the new value in the last bit of the array
        this.heap.push(value);
        //Move the value up
        this.shiftUp(this.heap.length - 1);
    }
}

(9) Delete heap top operation (POP)

For the operation of deleting the heap top, the implementation idea is as follows:

  • Replace the heap top with the tail element of the array (because deleting the heap top directly will destroy the heap structure).
  • Then move down: swap the new heap top with its child nodes until the child nodes are greater than or equal to the new heap top.
  • The time complexity of deleting the top of the heap in the heap of size k is O(logK).

The specific code implementation method is given below:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
    //Switch the position between nodes i1 and i2
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    
    //Get the left child node, i is the index of the current node
    getLeftIndex(i){
        return i * 2 + 1;
    }
    
    //Get the right child node, i is the index of the current node
    getRightIndex(i){
        return i * 2 + 2;
    }
    
    // Move down
    shiftDown(index){
        // Get left and right child nodes
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        //  Exchange the left node
        if(this.heap[leftIndex] < this.heap[index]){
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        //  Exchange the right node
        if(this.heap[rightIndex] < this.heap[index]){
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    
    //Delete heap top operation
    pop(){
        //Assign the value of the tail to the top of the heap
        this.heap[0] = this.heap.pop();
        //Move down
        this.shiftDown(0);
    }
}

(10) Gets the value peek() of the heap top

For the operation of obtaining the value of the top of the heap, the implementation idea is relatively simple, that is, the value of the top of the heap can be obtained by returning the header of the array. The specific implementation code is as follows:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
    
    //Gets the value of the heap top
    peek(){
        return this.heap[0];
    }
}

(11) Gets the size of the heap (size)

For the operation of obtaining the size of the heap, the implementation idea is to obtain the length of the whole heap, that is, the length of the returned array. The specific implementation code is as follows:

class MinHeap{
	//Create a constructor to store a heap
	constructor(){
		this.heap = [];
	}
    
    //Gets the size of the heap
    size(){
        return this.heap.length;
    }
}

(12) Result display

After completing the above operations, let's write a set of test cases to demonstrate the specific results. The specific codes are as follows:

const h = new MinHeap();
h.insert(3);
h.insert(2);
h.insert(1);
h.pop();
console.log(h); // MinHeap { heap: [ 2, 4, 3 ] }
h.peek();
h.size();
console.log(h.peek()); // 2
console.log(h.size()); // 3

🐤 5, Analysis of classic topics of leetcode

Next, we cite several classic leetcode algorithms to consolidate the knowledge of trees and binary trees.

1. The kth largest element in leetcode215 array (medium)

(1) Title Meaning

Attach title link: The kth largest element in the leetcode215 array

Given the integer array nums and integer k, please return the K largest element in the array.

Note that you need to find the K largest element after the array is sorted, not the k different element.

Input / output example:

  • Input: [3,2,1,5,6,4] and k = 2
  • Output: 5

(2) Problem solving ideas

  • See "K-th largest element".
  • Consider choosing to use the smallest heap.

(3) Problem solving steps

  • Build a minimum heap to insert the values of the array into the heap.
  • When the capacity of the heap exceeds K, the top of the heap is deleted.
  • After the insertion, the top of the heap is the K-th largest element.

(4) Code implementation

According to the minimum heap we built above, next, we use this minimum heap to complete this problem. The specific codes are as follows:

class MinHeap{
    constructor(){
        this.heap = [];
    }
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        // return (i - 1) >> 1;
    }
    getLeftIndex(i){
        return i*2 + 1;
    }
    getRightIndex(i){
        return i*2 + 2;
    }
    shiftUp(index){
        if(index === 0){
            return;
        }
        const parentIndex = this.getParentIndex(index);
        if(this.heap[parentIndex] > this.heap[index]){
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }
    }
    shiftDown(index){
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if(this.heap[leftIndex] < this.heap[index]){
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        if(this.heap[rightIndex] < this.heap[index]){
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    insert(value){
        this.heap.push(value);
        this.shiftUp(this.heap.length - 1);
    }
    pop(){
        this.heap[0] = this.heap.pop();
        this.shiftDown(0);
    }
    peek(){
        return this.heap[0];
    }
    size(){
        return this.heap.length;
    }
}

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
let findKthLargest = function(nums, k){
    const h = new MinHeap();
    nums.forEach(n => {
        h.insert(n);
        if(h.size() > k){
            h.pop();
        }
    });
    return h.peek();
}

console.log(findKthLargest([3,2,1,5,6,4],2)); // 5

2. leetcode347 top K high frequency elements (medium)

(1) Title Meaning

Attach title link: leetcode347 top K high frequency elements

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.

Input / output example:

  • Input: num = [1,1,1,2,2,3], k = 2
  • Output: [1,2]

(2) Problem solving ideas

  • Dictionary solution: convert the dictionary into an array and sort the heap array;
  • Heap solution: build a minimum heap and use the key value relationship of the dictionary to record the frequency of elements.

(3) Code implementation

We use two methods to solve this problem, one is the dictionary solution, the other is the heap solution. The details are as follows:

1) Dictionary solution:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
// Dictionary solution
let topKFrequent1 = function(nums, k) {
    //Define an array
    const map = new Map();
    //First, the elements in the array are stored in the dictionary
    nums.forEach(n => {
        map.set(n, map.has(n) ? map.get(n) + 1 : 1 );
    });
    // Convert the dictionary to an array and sort the array
    // Sort the second item in the array in descending order (from large to small), from large to small
    const list = Array.from(map).sort((a, b) => b[1] - a[1]);
    //Use the map() method to create a new array to store the first k elements
    return list.slice(0, k).map(n => n[0]);
};

console.log(topKFrequent1([1, 1, 1, 2, 2, 3], 2)); // [1, 2]

2) Heap solution:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
// Heap method
class MinHeap{
    constructor(){
        this.heap = [];
    }
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        // return (i - 1) >> 1;
    }
    getLeftIndex(i){
        return i*2 + 1;
    }
    getRightIndex(i){
        return i*2 + 2;
    }
    shiftUp(index){
        if(index === 0){
            return;
        }
        const parentIndex = this.getParentIndex(index);
        if(this.heap[parentIndex] && this.heap[parentIndex].value > this.heap[index].value){
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }
    }
    shiftDown(index){
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if(this.heap[leftIndex] && this.heap[leftIndex].value < this.heap[index].value){
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        if(this.heap[rightIndex] && this.heap[rightIndex].value < this.heap[index].value){
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    insert(value){
        this.heap.push(value);
        this.shiftUp(this.heap.length - 1);
    }
    pop(){
        this.heap[0] = this.heap.pop();
        this.shiftDown(0);
    }
    peek(){
        return this.heap[0];
    }
    size(){
        return this.heap.length;
    }
}

let topKFrequent2 = function(nums, k) {
    //Initialize a dictionary
    const map = new Map();
    //Traverse the array one by one and record the number of occurrences
    nums.forEach(n => {
        map.set(n, map.has(n) ? map.get(n) + 1 : 1 );
    });
    //Instantiate a minimum heap
    const h = new MinHeap();
    //Traverse all key value pairs in the dictionary
    map.forEach((value, key) => {
        //Every time you traverse one, one is inserted into the heap
        h.insert({value, key});
        //Judge whether the size of the current heap is greater than the k value
        if(h.size() > k){
            h.pop();
        }
    });
    //Return value, traverse the dictionary, and the key after traversal is the result;
    //And create a new array through the map() method to store the specific values.
    return h.heap.map(a => a.key);
};

console.log(topKFrequent2([1, 1, 1, 2, 2, 3], 2)); // [2, 1]

3. leetcode23 merge K sorting linked lists (difficult)

(1) Title Meaning

Give you a linked list array, each linked list has been arranged in ascending order.

Please merge all linked lists into an ascending linked list and return the merged linked list.

Input / output example:

  • Input: lists = [[1,4,5],[1,3,4],[2,6]]

  • Output: [1,1,2,3,4,4,5,6]

  • Explanation:

    The linked list array is as follows:
    [
      1->4->5,
      1->3->4,
      2->6
    ]
    Combine them into an ordered linked list.
    1->1->2->3->4->4->5->6
    

(2) Problem solving ideas

  • The next node of the new linked list must be the smallest node in the k chain headers.
  • Consider choosing to use the smallest heap.

(3) Problem solving steps

  • Build a minimum heap to insert the chain header into the heap.
  • Pop up the heap top to the output linked list, and insert the new chain header of the linked list where the heap top is located into the heap.
  • When all the heap elements pop up, the merge is completed.

(4) Code implementation

class MinHeap{
    constructor(){
        this.heap = [];
    }
    swap(i1, i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;
    }
    getParentIndex(i){
        return Math.floor((i - 1)/2);
        // return (i - 1) >> 1;
    }
    getLeftIndex(i){
        return i*2 + 1;
    }
    getRightIndex(i){
        return i*2 + 2;
    }
    shiftUp(index){
        if(index === 0){
            return;
        }
        const parentIndex = this.getParentIndex(index);
        if(this.heap[parentIndex] && this.heap[parentIndex].val > this.heap[index].val){
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }
    }
    shiftDown(index){
        const leftIndex = this.getLeftIndex(index);
        const rightIndex = this.getRightIndex(index);
        if(this.heap[leftIndex] && this.heap[leftIndex].val < this.heap[index].val){
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        if(this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[index].val){
            this.swap(rightIndex, index);
            this.shiftDown(rightIndex);
        }
    }
    insert(val){
        this.heap.push(val);
        this.shiftUp(this.heap.length - 1);
    }
    pop(){
        // If the heap has only one element, the result is returned directly
        if(this.size() === 1){
            return this.heap.shift();
        }
        const top = this.heap[0];
        this.heap[0] = this.heap.pop();
        this.shiftDown(0);
        return top;
    }
    peek(){
        return this.heap[0];
    }
    size(){
        return this.heap.length;
    }
}
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */
var mergeKLists = function(lists) {
    //Instantiate an empty linked list
    const res = new ListNode(0);
    //Define a p pointer to the empty linked list
    let p = res;
    //Instantiate a minimum heap
    const h = new MinHeap();
    //Give the linked list of topics and traverse them one by one
    lists.forEach(l => {
        //If the traversed linked list is not empty, it will be inserted into the minimum heap
        if(l){
            h.insert(l);
        }
    });
	//Determine whether there is content in the heap
    while(h.size()){
        //Delete and return to the top of the heap
        const n = h.pop();
        //Let the next node of the p pointer point to the top of the heap element
        p.next = n;
        //p. The value of next is assigned to the P pointer
        p = p.next;
        //If the heap top element has the next node, it is inserted into the heap
        if(n.next){
            h.insert(n.next);
        }
    }
    return res.next;
};

🐪 6, Conclusion

When I finished learning this data structure, I thought of an algorithm problem when I looked back at the facial Sutra last time. The question said, suppose there is a file with many words, please find the top 10 words with the highest frequency.

What I thought at that time was: traversal? But in fact, after learning this data structure today, in retrospect, the way to solve this problem is to use the minimum heap.

Therefore, heap is interlinked in daily applications. As long as you understand the ideas, it can also be applied to corresponding scenarios indirectly.

Here, the explanation on the application of heap in the front end is over! I hope it will help you~

If the article is wrong or incomprehensible, please leave a message in the comment area 💬

🐣 One More Thing

(: recommended in previous periods)

Stack 👉 Stack in the front-end application, by the way to understand the next deep copy and shallow copy!

queue 👉 Explain the application of queue in the front end, deeply analyze the event loop in JS, and then understand micro tasks and macro tasks

Linked list 👉 Explain the application of linked list in the front end, and understand the prototype and prototype chain by the way!

Dictionaries and collections 👉 Do you know the Set and Map of ES6? Understand the application of Set and dictionary in the front end

tree 👉 Understand the application of tree in the front end and master the lifeline of tree in data structure

chart 👉 How to solve the Pacific Atlantic current problem? This paper understands the application of graph in front end

Dynamic rules and divide and conquer algorithm 👉 This paper understands the application of divide and conquer and dynamic rule algorithm in the front end

Greedy algorithm and backtracking algorithm 👉 This paper understands the application of greedy algorithm and backtracking algorithm in the front end

(: Fan Wai Pian)

  • Pay attention to the public Monday research room. First, pay attention to learning dry cargo. More column will be open for you to unlock official account.
  • If this article is useful to you, remember to leave a footprint jio before you go~
  • The above is the whole content of this article! See you next time! 👋👋👋

Topics: Javascript Front-end Algorithm data structure