⚡ 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
method | meaning |
---|---|
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!
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
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! 👋👋👋