Introduction to Heap – Data Structure and Algorithm Tutorials
Last Updated :
07 Aug, 2023
What is Heap Data Structure?
A Heap is a special Tree-based Data Structure in which the tree is a complete binary tree.
Types of heaps:
Generally, heaps are of two types.
Max-Heap:
In this heap, the value of the root node must be the greatest among all its child nodes and the same thing must be done for its left and right sub-tree also.
The total number of comparisons required in the max heap is according to the height of the tree. The height of the complete binary tree is always logn; therefore, the time complexity would also be O(logn).
Min-Heap:
In this heap, the value of the root node must be the smallest among all its child nodes and the same thing must be done for its left and right sub-tree also.
The total number of comparisons required in the min heap is according to the height of the tree. The height of the complete binary tree is always logn; therefore, the time complexity would also be O(logn).
Properties of Heap:
Heap has the following Properties:
- Complete Binary Tree: A heap tree is a complete binary tree, meaning all levels of the tree are fully filled except possibly the last level, which is filled from left to right. This property ensures that the tree is efficiently represented using an array.
- Heap Property: This property ensures that the minimum (or maximum) element is always at the root of the tree according to the heap type.
- Parent-Child Relationship: The relationship between a parent node at index ‘i’ and its children is given by the formulas: left child at index 2i+1 and right child at index 2i+2 for 0-based indexing of node numbers.
- Efficient Insertion and Removal: Insertion and removal operations in heap trees are efficient. New elements are inserted at the next available position in the bottom-rightmost level, and the heap property is restored by comparing the element with its parent and swapping if necessary. Removal of the root element involves replacing it with the last element and heapifying down.
- Efficient Access to Extremal Elements: The minimum or maximum element is always at the root of the heap, allowing constant-time access.
Operations Supported by Heap:
Operations supported by min – heap and max – heap are same. The difference is just that min-heap contains minimum element at root of the tree and max – heap contains maximum element at the root of the tree.
Heapify:
It is the process to rearrange the elements to maintain the property of heap data structure. It is done when a certain node creates an imbalance in the heap due to some operations on that node. It takes O(log N) to balance the tree.
- For max-heap, it balances in such a way that the maximum element is the root of that binary tree and
- For min-heap, it balances in such a way that the minimum element is the root of that binary tree.
Insertion:
- If we insert a new element into the heap since we are adding a new element into the heap so it will distort the properties of the heap so we need to perform the heapify operation so that it maintains the property of the heap.
This operation also takes O(logN) time.
Examples:
Assume initially heap(taking max-heap) is as follows
8
/ \
4 5
/ \
1 2
Now if we insert 10 into the heap
8
/ \
4 5
/ \ /
1 2 10
After heapify operation final heap will be look like this
10
/ \
4 8
/ \ /
1 2 5
Deletion:
- If we delete the element from the heap it always deletes the root element of the tree and replaces it with the last element of the tree.
- Since we delete the root element from the heap it will distort the properties of the heap so we need to perform heapify operations so that it maintains the property of the heap.
It takes O(logN) time.
Example:
Assume initially heap(taking max-heap) is as follows
15
/ \
5 7
/ \
2 3
Now if we delete 15 into the heap it will be replaced by leaf node of the tree for temporary.
3
/ \
5 7
/
2
After heapify operation final heap will be look like this
7
/ \
5 3
/
2
getMax (For max-heap) or getMin (For min-heap):
It finds the maximum element or minimum element for max-heap and min-heap respectively and as we know minimum and maximum elements will always be the root node itself for min-heap and max-heap respectively. It takes O(1) time.
removeMin or removeMax:
This operation returns and deletes the maximum element and minimum element from the max-heap and min-heap respectively. In short, it deletes the root element of the heap binary tree.
Implementation of Heap Data Structure:-
The following code shows the implementation of a max-heap.
Let’s understand the maxHeapify function in detail:-
maxHeapify is the function responsible for restoring the property of the Max Heap. It arranges the node i, and its subtrees accordingly so that the heap property is maintained.
- Suppose we are given an array, arr[] representing the complete binary tree. The left and the right child of ith node are in indices 2*i+1 and 2*i+2.
- We set the index of the current element, i, as the ‘MAXIMUM’.
- If arr[2 * i + 1] > arr[i], i.e., the left child is larger than the current value, it is set as ‘MAXIMUM’.
- Similarly if arr[2 * i + 2] > arr[i], i.e., the right child is larger than the current value, it is set as ‘MAXIMUM’.
- Swap the ‘MAXIMUM’ with the current element.
- Repeat steps 2 to 5 till the property of the heap is restored.
C++
#include <bits/stdc++.h>
using namespace std;
class MaxHeap {
int * arr;
int maxSize;
int heapSize;
public :
MaxHeap( int maxSize);
void MaxHeapify( int );
int parent( int i)
{
return (i - 1) / 2;
}
int lChild( int i)
{
return (2 * i + 1);
}
int rChild( int i)
{
return (2 * i + 2);
}
int removeMax();
void increaseKey( int i, int newVal);
int getMax()
{
return arr[0];
}
int curSize()
{
return heapSize;
}
void deleteKey( int i);
void insertKey( int x);
};
MaxHeap::MaxHeap( int totSize)
{
heapSize = 0;
maxSize = totSize;
arr = new int [totSize];
}
void MaxHeap::insertKey( int x)
{
if (heapSize == maxSize) {
cout << "\nOverflow: Could not insertKey\n" ;
return ;
}
heapSize++;
int i = heapSize - 1;
arr[i] = x;
while (i != 0 && arr[parent(i)] < arr[i]) {
swap(arr[i], arr[parent(i)]);
i = parent(i);
}
}
void MaxHeap::increaseKey( int i, int newVal)
{
arr[i] = newVal;
while (i != 0 && arr[parent(i)] < arr[i]) {
swap(arr[i], arr[parent(i)]);
i = parent(i);
}
}
int MaxHeap::removeMax()
{
if (heapSize <= 0)
return INT_MIN;
if (heapSize == 1) {
heapSize--;
return arr[0];
}
int root = arr[0];
arr[0] = arr[heapSize - 1];
heapSize--;
MaxHeapify(0);
return root;
}
void MaxHeap::deleteKey( int i)
{
increaseKey(i, INT_MAX);
removeMax();
}
void MaxHeap::MaxHeapify( int i)
{
int l = lChild(i);
int r = rChild(i);
int largest = i;
if (l < heapSize && arr[l] > arr[i])
largest = l;
if (r < heapSize && arr[r] > arr[largest])
largest = r;
if (largest != i) {
swap(arr[i], arr[largest]);
MaxHeapify(largest);
}
}
int main()
{
MaxHeap h(15);
int k, i, n = 6, arr[10];
cout << "Entered 6 keys:- 3, 10, 12, 8, 2, 14 \n" ;
h.insertKey(3);
h.insertKey(10);
h.insertKey(12);
h.insertKey(8);
h.insertKey(2);
h.insertKey(14);
cout << "The current size of the heap is "
<< h.curSize() << "\n" ;
cout << "The current maximum element is " << h.getMax()
<< "\n" ;
h.deleteKey(2);
cout << "The current size of the heap is "
<< h.curSize() << "\n" ;
h.insertKey(15);
h.insertKey(5);
cout << "The current size of the heap is "
<< h.curSize() << "\n" ;
cout << "The current maximum element is " << h.getMax()
<< "\n" ;
return 0;
}
|
Java
import java.util.Arrays;
import java.util.Scanner;
public class MaxHeap {
int [] arr;
int maxSize;
int heapSize;
MaxHeap( int maxSize) {
this .maxSize = maxSize;
arr = new int [maxSize];
heapSize = 0 ;
}
void MaxHeapify( int i) {
int l = lChild(i);
int r = rChild(i);
int largest = i;
if (l < heapSize && arr[l] > arr[i])
largest = l;
if (r < heapSize && arr[r] > arr[largest])
largest = r;
if (largest != i) {
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
MaxHeapify(largest);
}
}
int parent( int i) {
return (i - 1 ) / 2 ;
}
int lChild( int i) {
return ( 2 * i + 1 );
}
int rChild( int i) {
return ( 2 * i + 2 );
}
int removeMax() {
if (heapSize <= 0 )
return Integer.MIN_VALUE;
if (heapSize == 1 ) {
heapSize--;
return arr[ 0 ];
}
int root = arr[ 0 ];
arr[ 0 ] = arr[heapSize - 1 ];
heapSize--;
MaxHeapify( 0 );
return root;
}
void increaseKey( int i, int newVal) {
arr[i] = newVal;
while (i != 0 && arr[parent(i)] < arr[i]) {
int temp = arr[i];
arr[i] = arr[parent(i)];
arr[parent(i)] = temp;
i = parent(i);
}
}
int getMax() {
return arr[ 0 ];
}
int curSize() {
return heapSize;
}
void deleteKey( int i) {
increaseKey(i, Integer.MAX_VALUE);
removeMax();
}
void insertKey( int x) {
if (heapSize == maxSize) {
System.out.println( "\nOverflow: Could not insertKey\n" );
return ;
}
heapSize++;
int i = heapSize - 1 ;
arr[i] = x;
while (i != 0 && arr[parent(i)] < arr[i]) {
int temp = arr[i];
arr[i] = arr[parent(i)];
arr[parent(i)] = temp;
i = parent(i);
}
}
public static void main(String[] args) {
MaxHeap h = new MaxHeap( 15 );
int k, i, n = 6 ;
System.out.println( "Entered 6 keys:- 3, 10, 12, 8, 2, 14 \n" );
h.insertKey( 3 );
h.insertKey( 10 );
h.insertKey( 12 );
h.insertKey( 8 );
h.insertKey( 2 );
h.insertKey( 14 );
System.out.println( "The current size of the heap is "
+ h.curSize() + "\n" );
System.out.println( "The current maximum element is " + h.getMax()
+ "\n" );
h.deleteKey( 2 );
System.out.println( "The current size of the heap is "
+ h.curSize() + "\n" );
h.insertKey( 15 );
h.insertKey( 5 );
System.out.println( "The current size of the heap is "
+ h.curSize() + "\n" );
System.out.println( "The current maximum element is " + h.getMax()
+ "\n" );
}
}
|
Python3
class MaxHeap:
arr = []
maxSize = 0
heapSize = 0
def __init__( self , maxSize):
self .maxSize = maxSize
self .arr = [ None ] * maxSize
self .heapSize = 0
def MaxHeapify( self , i):
l = self .lChild(i)
r = self .rChild(i)
largest = i
if l < self .heapSize and self .arr[l] > self .arr[i]:
largest = l
if r < self .heapSize and self .arr[r] > self .arr[largest]:
largest = r
if largest ! = i:
temp = self .arr[i]
self .arr[i] = self .arr[largest]
self .arr[largest] = temp
self .MaxHeapify(largest)
def parent( self , i):
return (i - 1 ) / / 2
def lChild( self , i):
return ( 2 * i + 1 )
def rChild( self , i):
return ( 2 * i + 2 )
def removeMax( self ):
if self .heapSize < = 0 :
return None
if self .heapSize = = 1 :
self .heapSize - = 1
return self .arr[ 0 ]
root = self .arr[ 0 ]
self .arr[ 0 ] = self .arr[ self .heapSize - 1 ]
self .heapSize - = 1
self .MaxHeapify( 0 )
return root
def increaseKey( self , i, newVal):
self .arr[i] = newVal
while i ! = 0 and self .arr[ self .parent(i)] < self .arr[i]:
temp = self .arr[i]
self .arr[i] = self .arr[ self .parent(i)]
self .arr[ self .parent(i)] = temp
i = self .parent(i)
def getMax( self ):
return self .arr[ 0 ]
def curSize( self ):
return self .heapSize
def deleteKey( self , i):
self .increaseKey(i, float ( "inf" ))
self .removeMax()
def insertKey( self , x):
if self .heapSize = = self .maxSize:
print ( "\nOverflow: Could not insertKey\n" )
return
self .heapSize + = 1
i = self .heapSize - 1
self .arr[i] = x
while i ! = 0 and self .arr[ self .parent(i)] < self .arr[i]:
temp = self .arr[i]
self .arr[i] = self .arr[ self .parent(i)]
self .arr[ self .parent(i)] = temp
i = self .parent(i)
if __name__ = = '__main__' :
h = MaxHeap( 15 )
k, i, n = 6 , 0 , 6
print ( "Entered 6 keys:- 3, 10, 12, 8, 2, 14 \n" )
h.insertKey( 3 )
h.insertKey( 10 )
h.insertKey( 12 )
h.insertKey( 8 )
h.insertKey( 2 )
h.insertKey( 14 )
print ( "The current size of the heap is "
+ str (h.curSize()) + "\n" )
print ( "The current maximum element is " + str (h.getMax())
+ "\n" )
h.deleteKey( 2 )
print ( "The current size of the heap is "
+ str (h.curSize()) + "\n" )
h.insertKey( 15 )
h.insertKey( 5 )
print ( "The current size of the heap is "
+ str (h.curSize()) + "\n" )
print ( "The current maximum element is " + str (h.getMax())
+ "\n" )
|
Javascript
class MaxHeap {
constructor(maxSize) {
this .arr = new Array(maxSize).fill( null );
this .maxSize = maxSize;
this .heapSize = 0;
}
MaxHeapify(i) {
const l = this .lChild(i);
const r = this .rChild(i);
let largest = i;
if (l < this .heapSize && this .arr[l] > this .arr[i]) {
largest = l;
}
if (r < this .heapSize && this .arr[r] > this .arr[largest]) {
largest = r;
}
if (largest !== i) {
const temp = this .arr[i];
this .arr[i] = this .arr[largest];
this .arr[largest] = temp;
this .MaxHeapify(largest);
}
}
parent(i) {
return Math.floor((i - 1) / 2);
}
lChild(i) {
return 2 * i + 1;
}
rChild(i) {
return 2 * i + 2;
}
removeMax() {
if ( this .heapSize <= 0) {
return null ;
}
if ( this .heapSize === 1) {
this .heapSize -= 1;
return this .arr[0];
}
const root = this .arr[0];
this .arr[0] = this .arr[ this .heapSize - 1];
this .heapSize -= 1;
this .MaxHeapify(0);
return root;
}
increaseKey(i, newVal) {
this .arr[i] = newVal;
while (i !== 0 && this .arr[ this .parent(i)] < this .arr[i]) {
const temp = this .arr[i];
this .arr[i] = this .arr[ this .parent(i)];
this .arr[ this .parent(i)] = temp;
i = this .parent(i);
}
}
getMax() {
return this .arr[0];
}
curSize() {
return this .heapSize;
}
deleteKey(i) {
this .increaseKey(i, Infinity);
this .removeMax();
}
insertKey(x) {
if ( this .heapSize === this .maxSize) {
console.log( "\nOverflow: Could not insertKey\n" );
return ;
}
let i = this .heapSize;
this .arr[i] = x;
this .heapSize += 1;
while (i !== 0 && this .arr[ this .parent(i)] < this .arr[i]) {
const temp = this .arr[i];
this .arr[i] = this .arr[ this .parent(i)];
this .arr[ this .parent(i)] = temp;
i = this .parent(i);
}
}
}
const h = new MaxHeap(15);
console.log( "Entered 6 keys:- 3, 10, 12, 8, 2, 14 \n" );
h.insertKey(3);
h.insertKey(10);
h.insertKey(12);
h.insertKey(8);
h.insertKey(2);
h.insertKey(14);
console.log(
"The current size of the heap is " + h.curSize() + "\n"
);
console.log(
"The current maximum element is " + h.getMax() + "\n"
);
h.deleteKey(2);
console.log(
"The current size of the heap is " + h.curSize() + "\n"
);
h.insertKey(15);
h.insertKey(5);
console.log(
"The current size of the heap is " + h.curSize() + "\n"
);
console.log(
"The current maximum element is " + h.getMax() + "\n"
);
|
Output
Entered 6 keys:- 3, 10, 12, 8, 2, 14
The current size of the heap is 6
The current maximum element is 14
The current size of the heap is 5
The current size of the heap is 7
The current maximum element is 15
Applications of Heap Data Structure:
- Priority Queues: Priority queues can be efficiently implemented using Binary Heap because it supports insert(), delete() and extractmax(), decreaseKey() operations in O(log N) time.
- Binomial Heap and Fibonacci Heap are variations of Binary Heap. These variations perform union also in O(log N) time which is an O(N) operation in Binary Heap.
- Order statistics: The Heap data structure can be used to efficiently find the kth smallest (or largest) element in an array. You can see this gfg article to know more about the kth smallest or largest element.
Advantages of Heaps:
- Fast access to maximum/minimum element (O(1))
- Efficient Insertion and Deletion operations (O(log n))
Flexible size
- Can be efficiently implemented as an array
- Suitable for real-time applications
Disadvantages of Heaps:
- Not suitable for searching for an element other than maximum/minimum (O(n) in worst case)
- Extra memory overhead to maintain heap structure
- Slower than other data structures like arrays and linked lists for non-priority queue operations.
Like Article
Suggest improvement
Share your thoughts in the comments
Please Login to comment...