Heap Data Structure for Competitive Programming
Last Updated :
10 Mar, 2024
Competitive programming needs smart tools to solve problems quickly. One key tool is the Heap Data Structure, which helps organize data in a way that’s super fast. In this article, we’ll break down the Heap, looking at its types, basic moves, and how it’s a big deal in competitive programming. We’ll focus on something called Priority Queue, a special use of Heap, and show you how to use it to solve problems better. It does not matter if you are new to Heap or if you have a little knowledge already, learning it will greatly enhance your problem-solving skills in competitive programming.
A Heap is a specialized tree-based data structure that satisfies the heap property. In simple terms, it’s a way of organizing elements in a hierarchy, where each element has a priority relative to others. In a Heap, the top element is always the one with the highest (or lowest) priority, making it quick to access. There are different types of heaps, but they all share this fundamental idea of efficient data organization.
Note: A Heap is a specialized tree-based data structure that satisfies the heap property. It can be implemented as an array.
Types of Heap Data Structure:
Generally, Heaps can be of two types:
- Max-Heap: In a Max-Heap the key present at the root node must be greatest among the keys present at all of its children. The same property must be recursively true for all sub-trees in that Binary Tree.
- Min-Heap: In a Min-Heap the key present at the root node must be minimum among the keys present at all of its children. The same property must be recursively true for all sub-trees in that Binary Tree.
Operations of Heap Data Structure:
The Heap Data Structure supports fundamental operations that enable efficient management of its elements. Below are some operations of Heap Data Structure:
1. Heapify:
- It is the process to rearrange the elements to maintain the property of heap data structure.
- It takes O(log N) to balance the tree.
2. 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.
3. 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.
4. 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.
- It takes O(1) time.
A Priority Queue is an abstract data type that stores elements along with their associated priorities, and it allows for efficient retrieval of the element with the highest (or lowest) priority. In simpler terms, a priority queue is a data structure that manages a collection of elements, each assigned a priority, and provides operations to insert elements and remove the element with the highest (or lowest) priority.
Key Characteristics of a Priority Queue:
- Priority-Based Ordering: Elements in a priority queue are arranged based on their priority, not in the order of their insertion.
- Efficient Access: The primary purpose of a priority queue is to quickly access and remove the element with the highest (max priority) or lowest (min priority) value.
- Abstract Data Type: The priority queue abstracts away the specific details of how priorities are assigned or how the elements are stored internally.
- Implementation: Priority queues can be implemented using various data structures, with heaps being a common choice due to their efficiency in maintaining priorities.
Common Operations on a Priority Queue:
- Insertion (Push): Add an element to the priority queue with its associated priority.
- Deletion (Pop): Remove the element with the highest (max priority) or lowest (min priority) value.
- Peek (Top): Access the element with the highest (max priority) or lowest (min priority) value without removing it.
The priority_queue container from the C++ Standard Template Library (STL) provides a convenient way to work with priority queues. Below is a simple guide on using priority_queue in C++ for competitive programming:
Import Necessary Package:
import java.util.PriorityQueue;
Below are the Initialization and methods of Priority Queue in C++:
Initialization and Methods |
Initialization Max Heap (default) |
Initialization Min Heap (default) |
Initialization |
std::priority_queue<int> maxHeap; |
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap; |
Inserting Elements |
maxHeap.push(5); |
minHeap.push(5); |
Removing Top Element |
maxHeap.pop(); |
minHeap.pop(); |
Accessing Top Element |
int topElement = maxHeap.top(); |
int topElement = minHeap.top(); |
Size and Empty Check |
int size = maxHeap.size(); <br>bool isEmpty = maxHeap.empty(); |
int size = minHeap.size(); <br>bool isEmpty = minHeap.empty(); |
Custom Comparator in Priority Queue in C++ STL:
Priority Queue can also be used to store pairs inside of it and we can use custom comparators for ordering by first and second element. Refer to the below articles:
In Java, the PriorityQueue class from the java.util package provides a convenient way to implement a Priority Queue for competitive programming. Here’s a guide on using PriorityQueue in Java:
Import Necessary Package:
import java.util.PriorityQueue;
Below are the Initialization and methods of PriorityQueue in Java:
Initialization and Methods |
Initialization Max Heap (default) |
Initialization Min Heap |
Initialization |
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(); |
PriorityQueue<Integer> minHeap = new PriorityQueue<>(Comparator.naturalOrder()); |
Inserting Elements |
maxHeap.add(5); |
minHeap.add(5); |
Accessing Top Element |
int topElement = maxHeap.peek(); |
int topElement = minHeap.peek(); |
Removing Top Element |
maxHeap.poll(); |
minHeap.poll(); |
Size and Empty Check |
int size = maxHeap.size(); <br>boolean isEmpty = maxHeap.isEmpty(); |
int size = minHeap.size(); <br>boolean isEmpty = minHeap.isEmpty(); |
Custom Comparator in Priority Queue in Java :
We can use a custom comparator to implement a priority queue with specific ordering rules.
In Python, you can use the heapq module to implement a Priority Queue efficiently. Below is a guide on using heapq in Python for competitive programming:
Import Necessary Module:
import heapq
Below are the Initialization and methods of Priority Queue in Python:
Initialization and Methods |
Initialization Min Heap (default) |
Initialization Max Heap (using negation) |
Initialization |
min_heap = [] |
max_heap = [] |
Inserting Elements |
heapq.heappush(min_heap, 5) |
heapq.heappush(max_heap, -5) |
Accessing Top Element |
top_element_min = min_heap[0] |
top_element_max = -max_heap[0] # Remember to negate values for max heap |
Removing Top Element |
heapq.heappop(min_heap) |
heapq.heappop(max_heap) |
Size and Empty Check |
size_min = len(min_heap)
isEmpty_min = not bool(min_heap) |
size_max = len(max_heap)
isEmpty_max = not bool(max_heap) |
Problem Identification of Priority Queue:
Below are the few problems that will help to identify how or when can we use Priority Queue in competitive programming problem:
Problem 1: Given n energy drinks with values ai​ on a line, where drinking the ith drink ai​ increases stamina by ai​ if is positive, and decreases if ai is negative, and leaving the drink as it is does not affect our stamina. Starting with 0 stamina, determine the maximum number of drinks you can consume without stamina becoming negative at any point, and drinks must be consumed in order.
The problem can be solved in the following way:
- Sequential Consumption:
- Consume energy drinks one by one, moving from the leftmost to the rightmost drink.
- Maintain Count:
- Keep a running count of the number of drinks consumed.
- Check Stamina:
- After consuming each drink, assess whether the stamina becomes negative.
- Remove Most Negative Drink:
- If negative stamina occurs, identify the most negatively impactful drink taken so far.
- Utilize a priority queue to efficiently find and remove the drink with the greatest negative impact on stamina.
- Track Maximum Drinks:
- Continuously track the maximum number of drinks consumed without stamina becoming negative.
Below is the implementation of above approach:
C++
#include <bits/stdc++.h>
using namespace std;
int maxDrinksWithoutNegativeStamina(vector< int >& arr)
{
long long currentSum = 0;
int drinksConsumed = 0;
priority_queue< int >
negativeElements;
for ( int i = 0; i < arr.size(); i++) {
int currentElement = arr[i];
currentSum += currentElement;
drinksConsumed++;
negativeElements.push(-currentElement);
while (currentSum < 0) {
drinksConsumed--;
currentSum += negativeElements.top();
negativeElements.pop();
}
}
return drinksConsumed;
}
int main()
{
vector< int > energyDrinks = { 2, -3, 5, -1, 6 };
int result
= maxDrinksWithoutNegativeStamina(energyDrinks);
cout << "Maximum number of drinks without negative "
"stamina: "
<< result << endl;
return 0;
}
|
Java
import java.util.PriorityQueue;
public class MaxDrinksWithoutNegativeStamina {
public static int maxDrinksWithoutNegativeStamina( int [] arr) {
int currentSum = 0 ;
int drinksConsumed = 0 ;
PriorityQueue<Integer> negativeElements = new PriorityQueue<>();
for ( int currentElement : arr) {
currentSum += currentElement;
drinksConsumed++;
negativeElements.add(-currentElement);
while (currentSum < 0 ) {
drinksConsumed--;
currentSum += -negativeElements.poll();
}
}
return drinksConsumed;
}
public static void main(String[] args) {
int [] energyDrinks = { 2 , - 3 , 5 , - 1 , 6 };
int result = maxDrinksWithoutNegativeStamina(energyDrinks);
System.out.println( "Maximum number of drinks without negative stamina: " + result);
}
}
|
C#
using System;
using System.Collections.Generic;
class Program
{
static int MaxDrinksWithoutNegativeStamina(List< int > arr)
{
long currentSum = 0;
int drinksConsumed = 0;
PriorityQueue< int > negativeElements =
new PriorityQueue< int >(Comparer< int >.Create((a, b) => b.CompareTo(a)));
for ( int i = 0; i < arr.Count; i++)
{
int currentElement = arr[i];
currentSum += currentElement;
drinksConsumed++;
negativeElements.Enqueue(-currentElement);
while (currentSum < 0)
{
drinksConsumed--;
currentSum += negativeElements.Dequeue();
}
}
return drinksConsumed;
}
static void Main()
{
List< int > energyDrinks = new List< int > { 2, -3, 5, -1, 6 };
int result = MaxDrinksWithoutNegativeStamina(energyDrinks);
Console.WriteLine($ "Maximum number of drinks without negative stamina: {result}" );
Console.ReadLine();
}
}
public class PriorityQueue<T>
{
private List<T> heap;
private readonly IComparer<T> comparer;
public PriorityQueue() : this ( null ) { }
public PriorityQueue(IComparer<T> comparer) : this (16, comparer) { }
public PriorityQueue( int capacity, IComparer<T> comparer)
{
this .heap = new List<T>(capacity);
this .comparer = comparer ?? Comparer<T>.Default;
}
public void Enqueue(T item)
{
heap.Add(item);
int i = heap.Count - 1;
while (i > 0)
{
int parent = (i - 1) / 2;
if (comparer.Compare(heap[parent], heap[i]) <= 0)
break ;
Swap(i, parent);
i = parent;
}
}
public T Dequeue()
{
int count = heap.Count - 1;
T root = heap[0];
heap[0] = heap[count];
heap.RemoveAt(count);
count--;
int i = 0;
while ( true )
{
int left = i * 2 + 1;
if (left > count)
break ;
int right = left + 1;
if (right <= count && comparer.Compare(heap[right], heap[left]) < 0)
left = right;
if (comparer.Compare(heap[left], heap[i]) >= 0)
break ;
Swap(i, left);
i = left;
}
return root;
}
public T Peek()
{
return heap[0];
}
public int Count
{
get { return heap.Count; }
}
private void Swap( int i, int j)
{
T tmp = heap[i];
heap[i] = heap[j];
heap[j] = tmp;
}
}
|
Javascript
function maxDrinksWithoutNegativeStamina(arr) {
let currentSum = 0;
let drinksConsumed = 0;
const negativeElements = [];
for (let i = 0; i < arr.length; i++) {
const currentElement = arr[i];
currentSum += currentElement;
drinksConsumed++;
negativeElements.push(-currentElement);
while (currentSum < 0) {
drinksConsumed--;
currentSum += negativeElements.pop();
}
}
return drinksConsumed;
}
const energyDrinks = [2, -3, 5, -1, 6];
const result = maxDrinksWithoutNegativeStamina(energyDrinks);
console.log( "Maximum number of drinks without negative stamina: " , result);
|
Python3
import heapq
def max_drinks_without_negative_stamina(arr):
current_sum = 0
drinks_consumed = 0
negative_elements = []
for current_element in arr:
current_sum + = current_element
drinks_consumed + = 1
heapq.heappush(negative_elements, - current_element)
while current_sum < 0 :
drinks_consumed - = 1
current_sum + = - heapq.heappop(negative_elements)
return drinks_consumed
energy_drinks = [ 2 , - 3 , 5 , - 1 , 6 ]
result = max_drinks_without_negative_stamina(energy_drinks)
print (f "Maximum number of drinks without negative stamina: {result}" )
|
Output
Maximum number of drinks without negative stamina: 4
Problem 2: Given an array of n integers a1, a2,… an and a integer k. You can perform the following operation any number of times: Choose an index i within the range 1<=i<=n and set ai to –ai​. The task is to the value a1+ a2+…+ak minimum among all non-empty prefix sums of array a.
​The problem can be solved in the following way:
- Greedy Approach:
- Utilize a greedy strategy to solve the problem.
- Independent Cases:
- Address the problem in two independent cases.
- Case 1: Iterate from k to 1:
- For each index from k to 1, check if the prefix sum of i is less than the prefix sum of k.
- If true, apply the operation on the maximal element in the segment [i+1, k]. Since performing an operation to any element in the segment [1,x] does nothing on prefix sum of i and k.
- Case 2: Iterate from k+1 to n:
- For each index from k+1 to n, check if the prefix sum of i is less than the prefix sum of k.
- If true, apply the operation on the minimal element in the segment [k+1, i]. Since performing an operation to any element in the segment [i+1,n] does nothing on prefix sum of i and k.
Below is the implementation of above approach:
C++
#include <bits/stdc++.h>
using namespace std;
void solve(vector< int > a, int n, int k) {
vector< int > b(n + 1);
int ans = 0;
int x = 0, y = 0, z = 1e18;
priority_queue< int , vector< int >, greater< int >> pq;
priority_queue< int > pq2;
for ( int i = 1; i <= n; i++) {
b[i] += b[i - 1] + a[i-1];
}
for ( int i = k; i > 0; i--) {
while (b[i] < b[k]) {
ans++;
int m = pq2.top();
b[k] = b[k] - 2 * m;
pq2.pop();
}
pq2.push(a[i-1]);
}
for ( int i = k + 1; i <= n; i++) {
y = y + a[i-1];
pq.push(a[i-1]);
while (y < 0) {
int m = pq.top();
y = y - 2 * m;
ans++;
pq.pop();
}
}
cout << ans << "\n" ;
}
int main() {
vector< int > array = {3, -2, 5, -1, -4};
int n = array.size();
int k = 3;
solve(array, n, k);
return 0;
}
|
Java
import java.util.*;
public class Main {
static void solve(ArrayList<Integer> a, int n, int k) {
ArrayList<Integer> b = new ArrayList<>(Collections.nCopies(n + 1 , 0 ));
int ans = 0 ;
int x = 0 , y = 0 , z = ( int ) 1e18;
PriorityQueue<Integer> pq = new PriorityQueue<>();
PriorityQueue<Integer> pq2 = new PriorityQueue<>(Collections.reverseOrder());
for ( int i = 1 ; i <= n; i++) {
b.set(i, b.get(i - 1 ) + a.get(i - 1 ));
}
for ( int i = k; i > 0 ; i--) {
while (b.get(i) < b.get(k)) {
ans++;
int m = pq2.poll();
b.set(k, b.get(k) - 2 * m);
}
pq2.add(a.get(i - 1 ));
}
for ( int i = k + 1 ; i <= n; i++) {
y = y + a.get(i - 1 );
pq.add(a.get(i - 1 ));
while (y < 0 ) {
int m = pq.poll();
y = y - 2 * m;
ans++;
}
}
System.out.println(ans);
}
public static void main(String[] args) {
ArrayList<Integer> array = new ArrayList<>(Arrays.asList( 3 , - 2 , 5 , - 1 , - 4 ));
int n = array.size();
int k = 3 ;
solve(array, n, k);
}
}
|
C#
using System;
using System.Collections.Generic;
class Program {
static void Solve(List< int > a, int n, int k)
{
List< int > b = new List< int >(
new int [n + 1]);
int ans = 0;
int y = 0;
PriorityQueue< int > pq
= new PriorityQueue< int >();
PriorityQueue< int > pq2 = new PriorityQueue< int >(
(p, q) => q.CompareTo(p));
for ( int i = 1; i <= n; i++) {
b[i] = b[i - 1] + a[i - 1];
}
for ( int i = k; i > 0; i--) {
while (b[i] < b[k]) {
ans++;
int m = pq2.Peek();
b[k] = b[k] - 2 * m;
pq2.Pop();
}
pq2.Push(a[i - 1]);
}
for ( int i = k + 1; i <= n; i++) {
y = y + a[i - 1];
pq.Push(a[i - 1]);
while (y < 0) {
int m = pq.Peek();
y = y - 2 * m;
ans++;
pq.Pop();
}
}
Console.WriteLine(ans);
}
static void Main( string [] args)
{
List< int > array = new List< int >{ 3, -2, 5, -1, -4 };
int n = array.Count;
int k = 3;
Solve(array, n, k);
}
}
public class PriorityQueue<T> {
private List<T> data;
private readonly Comparison<T> comparison;
public PriorityQueue() { data = new List<T>(); }
public PriorityQueue(Comparison<T> comparison)
: this ()
{
this .comparison = comparison;
}
public void Push(T item)
{
data.Add(item);
int ci = data.Count - 1;
while (ci > 0) {
int pi = (ci - 1) / 2;
if (Compare(data[ci], data[pi]) >= 0)
break ;
T tmp = data[ci];
data[ci] = data[pi];
data[pi] = tmp;
ci = pi;
}
}
public T Peek()
{
if (data.Count == 0)
throw new InvalidOperationException(
"Queue empty" );
return data[0];
}
public T Pop()
{
if (data.Count == 0)
throw new InvalidOperationException(
"Queue empty" );
T pop = data[0];
data[0] = data[data.Count - 1];
data.RemoveAt(data.Count - 1);
int ci = 0;
while (ci < data.Count) {
int lci = 2 * ci + 1;
int rci = 2 * ci + 2;
if (lci >= data.Count)
break ;
int mini = lci;
if (rci < data.Count
&& Compare(data[rci], data[lci]) < 0)
mini = rci;
if (Compare(data[ci], data[mini]) <= 0)
break ;
T tmp = data[ci];
data[ci] = data[mini];
data[mini] = tmp;
ci = mini;
}
return pop;
}
public int Count
{
get { return data.Count; }
}
private int Compare(T i, T j)
{
if (comparison != null )
return comparison(i, j);
return Comparer<T>.Default.Compare(i, j);
}
}
|
Javascript
function solve(a, n, k) {
let b = Array(n + 1).fill(0);
let ans = 0;
let x = 0, y = 0, z = 1e18;
let pq = [];
let pq2 = [];
for (let i = 1; i <= n; i++) {
b[i] = b[i - 1] + a[i - 1];
}
for (let i = k; i > 0; i--) {
while (b[i] < b[k]) {
ans += 1;
let m = pq2.pop();
b[k] = b[k] - 2 * m;
}
pq2.push(a[i - 1]);
}
for (let i = k + 1; i <= n; i++) {
y = y + a[i - 1];
pq.push(a[i - 1]);
while (y < 0) {
let m = pq.pop();
y = y - 2 * m;
ans += 1;
}
}
console.log(ans);
}
let array = [3, -2, 5, -1, -4];
let n = array.length;
let k = 3;
solve(array, n, k);
|
Python3
import heapq
def solve(a, n, k):
b = [ 0 ] * (n + 1 )
ans = 0
x, y, z = 0 , 0 , 1e18
pq = []
pq2 = []
for i in range ( 1 , n + 1 ):
b[i] = b[i - 1 ] + a[i - 1 ]
for i in range (k, 0 , - 1 ):
while b[i] < b[k]:
ans + = 1
m = heapq.heappop(pq2)
b[k] = b[k] - 2 * m
heapq.heappush(pq2, a[i - 1 ])
for i in range (k + 1 , n + 1 ):
y = y + a[i - 1 ]
heapq.heappush(pq, a[i - 1 ])
while y < 0 :
m = heapq.heappop(pq)
y = y - 2 * m
ans + = 1
print (ans)
array = [ 3 , - 2 , 5 , - 1 , - 4 ]
n = len (array)
k = 3
solve(array, n, k)
|
Priority Queue Use Cases in Competitive Programming:
Here are some common use cases for priority queues in competitive programming:
Priority queues are commonly employed to implement Dijkstra’s algorithm for finding the shortest paths in a graph. The priority queue efficiently selects the vertex with the smallest distance at each step. It is also used in solving problems which are variation of Dijkstra’s Algorithm
Similar to Dijkstra’s, priority queues play a crucial role in implementing Prim’s algorithm for finding the minimum spanning tree in a weighted graph. The priority queue helps choose the edge with the smallest weight at each iteration.
Heaps can be employed to efficiently find the median of a stream of numbers, a common problem in competitive programming. By maintaining two heaps (a max heap and a min heap), the median can be efficiently updated as new elements are added.
Priority queues help find the Kth largest or smallest element efficiently in an array or stream of numbers. For example, a max heap can be used to find the Kth largest element, while a min heap can find the Kth smallest element.
Priority queues are crucial in Huffman coding, a widely used algorithm for data compression. The algorithm builds a variable-length prefix coding tree, and a priority queue helps in efficiently merging nodes based on their frequencies.
Competitive programming problems often involve scheduling tasks based on their priority or execution time. A priority queue helps efficiently manage and execute tasks in the order of their priority.
Practice Problems of Heap Data Structure for Competitive Programming:
Here are some practice problems involving heap data structures that you can use to enhance your skills in competitive programming:
Share your thoughts in the comments
Please Login to comment...