1. Home
C++ Tutorial

Explore C++ Tutorials: Exploring the World of C++ Programming

Discover comprehensive C++ tutorials designed for beginners and advanced programmers alike. Enhance your coding skills with step-by-step guides and practical examples.

  • 77 Lessons
  • 15 Hours
right-top-arrow
34

Priority Queues in C++: Implementation, Use Cases, and Examples

Updated on 30/09/2024414 Views

When it comes to data structures, the standard queue operates on a simple principle, first in, first out (FIFO). However, not all scenarios neatly fit this model. Enter priority queue, a versatile and powerful tool that revolutionizes how we manage and process data.

I have personally seen priority queues being extensively used in a wide range of fields, including artificial intelligence, robotics, finance, and more. In this article, I will discuss why we use priority queue C++ and what this type of queue actually is. We will also delve deeper into how priority queues work, exploring their implementation details, advanced techniques, and real-world use cases.

What is a Priority Queue C++?

Let us imagine a line in a hospital emergency room. The most critical patients are seen first, regardless of when they arrive. This is the essence of a priority queue. Each element (think patient) has an associated priority (think medical urgency). Elements with higher priority are dequeued (seen by a doctor) before those with lower priority, even if they were added to the queue later.

Formally, a priority queue is an abstract data type (ADT) that allows efficient insertion of elements and removal of the element with the highest priority. Priority queue C++ provides a powerful abstraction that enables us to focus on the logic of our algorithms and applications, leaving the intricacies of data management to the underlying heap implementation. This modularity makes priority queues a versatile and essential tool in the toolbox of any software developer.

Why Use Priority Queues?

Priority queues are the unsung heroes of many computational tasks. Their ability to efficiently manage prioritized data makes them invaluable in various domains:

  • Task scheduling in operating systems: Modern operating systems rely on priority queues to determine which process gets access to the CPU next. High-priority tasks, like those responding to user input, are given precedence over background processes.
  • Event simulation: In simulations of complex systems (like traffic or network routing), events occur at different times with varying levels of importance. Priority queues ensure that events are processed in the correct chronological order, with critical events taking priority.
  • Dijkstra's shortest path algorithm: This classic graph algorithm, used for finding the shortest path between nodes, leverages a priority queue to explore potential paths efficiently, prioritizing those that seem most promising. Priority queue C++ pair is used for this algorithm.
  • Huffman coding for data compression: This elegant compression technique uses a priority queue to build a binary tree that minimizes the average code length for each symbol in the data, resulting in smaller file sizes.

The Building Blocks of Priority Queue C++

To grasp the inner workings of priority queue C++, we must first familiarize ourselves with their foundational elements: the heap data structure and the concept of abstract data types (ADTs).

Heap Data Structure: A Pyramid of Priorities

At its core, a priority queue is built upon a heap, a specialized tree-like data structure that maintains a specific ordering of elements. There are two primary types of heaps:

  • Max heap: In a max heap, the value of each parent node is greater than or equal to the values of its children. Consequently, the element with the highest priority (largest value) resides at the root of the heap.
  • Min heap: In a priority queue C++ min heap, the value of each parent node is less than or equal to the values of its children. Therefore, the element with the lowest priority (smallest value) occupies the root.

Visualizing a heap as a pyramid helps illustrate this concept. The topmost level contains the root, followed by levels with increasing numbers of nodes. The parent-child relationships within the heap ensure that the highest (or lowest) priority element is always readily accessible at the top.

Heap Operations and Efficiency

The efficiency of a priority queue heavily relies on the underlying heap's performance. Key heap operations include:

  • Insertion: Adding a new element to the heap while maintaining the heap property.
  • Deletion (or Extraction): Removing the highest (or lowest) priority element from the heap while preserving the heap property.
  • Peek: Retrieving the highest (or lowest) priority element without removing it.

The priority queue C++ time complexity in a well-implemented heap is typically O(log n), where n is the number of elements in the heap. This logarithmic efficiency makes heaps ideal for managing large datasets in priority queues.

Priority Queue as an Abstract Data Type (ADT)

A priority queue is best understood as an abstract data type (ADT). An ADT defines a set of operations that can be performed on the data, without specifying the underlying implementation details. This separation of interface and implementation provides flexibility and allows for different heap structures (e.g., binary heap, binomial heap) to be used interchangeably without affecting how the priority queue is used.

By abstracting away the complexities of the underlying heap, the priority queue ADT offers a simplified interface for working with prioritized data. We can interact with the queue using high-level operations like "insert," "delete," and "peek," without concerning ourselves with how the heap manages the internal ordering of elements.

C++ Standard Library Implementation: std::priority_queue

The Standard Template Library (STL) provides a ready-to-use priority queue implementation in C++ with the std::priority_queue class. Let us explore how to leverage this powerful tool in your C++ projects.

Constructors and Initialization with priority queue C++ STL

std::priority_queue offers multiple ways to create and initialize a priority queue

  1. Empty queue:

std::priority_queue<int> pq; // Creates an empty priority queue for integers

  1. From a container:

std::vector<int> data = {5, 2, 8, 1};

std::priority_queue<int> pq(data.begin(), data.end()); // Creates a priority queue from a vector

  1. With a custom comparator:

struct Compare {

bool operator()(const int& a, const int& b) const {

return a < b; // Min-heap behavior (smallest element on top)

}

};

std::priority_queue<int, std::vector<int>, Compare> pq;

In this example, the Compare struct defines a custom comparator to achieve min-heap behavior. By default, std::priority_queue creates a max-heap.

Key Member Functions: Mastering the API

As we discussed before, std::priority_queue is a versatile tool for managing prioritized data in C++. By understanding its constructors, member functions, and common use cases, we can harness its power to solve a wide range of problems efficiently.

Here are the essential functions you'll use to interact with std::priority_queue:

  • top(): Returns a reference to the highest-priority element (without removing it).
  • push(value): Inserts a new element into the queue with the given priority.
  • pop(): Removes the highest-priority element from the queue.
  • empty(): Returns true if the queue is empty, false otherwise.
  • size(): Returns the number of elements in the queue.

Common Use Cases of Priority Queue C++

Let's explore some illustrative examples of priority queue C++:

  1. Sorting elements:

std::priority_queue<int, std::vector<int>, std::greater<int>> pq;

// ... push elements ...

while (!pq.empty()) {

std::cout << pq.top() << " ";

pq.pop();

} // Outputs elements in ascending order

  1. Task scheduler:

struct Task {

int priority;

std::string description;

};

// ... compare function for tasks based on priority ...

std::priority_queue<Task, std::vector<Task>, CompareTasks> taskQueue;

// ... push tasks ...

  1. Graph algorithms (Dijkstra's algorithm):

std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<std::pair<int, int>>> pq;

// ... push distances and nodes ...

C++ Priority Queue Example Programs

Let us look at three priority queue C++ examples.

1. Sorting Elements in Descending Order

#include <iostream>

#include <queue>

int main() {

std::priority_queue<int> maxHeap; // Default max-heap

maxHeap.push(3);

maxHeap.push(10);

maxHeap.push(1);

maxHeap.push(5);

while (!maxHeap.empty()) {

std::cout << maxHeap.top() << " ";

maxHeap.pop();

} // Output: 10 5 3 1

}

In the above priority queue C++ example, we create a std::priority_queue without any custom comparator, so it defaults to a max-heap. Elements are pushed in any order and the while loop repeatedly extracts and prints the top (highest) element until the queue is empty, resulting in descending order.

2. Sorting Custom Structures (Tasks by Priority)

#include <iostream>

#include <queue>

#include <string>

struct Task {

int priority;

std::string description;

};

struct CompareTasks {

bool operator()(const Task& a, const Task& b) const {

return a.priority < b.priority; // Higher priority value is "smaller"

}

};

int main() {

std::priority_queue<Task, std::vector<Task>, CompareTasks> taskQueue;

taskQueue.push({3, "Low Priority Task"});

taskQueue.push({10, "Urgent Task"});

taskQueue.push({5, "Medium Priority Task"});

while (!taskQueue.empty()) {

Task topTask = taskQueue.top();

std::cout << topTask.priority << ": " << topTask.description << std::endl;

taskQueue.pop();

}

}

In the above example, we define a Task struct to hold priority and description. The CompareTasks struct is a custom comparator to define how tasks are ordered (higher priority value goes first). The queue is now of type Task, and we use the custom comparator. The tasks are extracted in order of decreasing priority.

3. Simple Graph Traversal (Dijkstra's Algorithm - Core Idea)

Here is the core structure behind the priority queue C++ example for a simple graph traversal:

#include <iostream>

#include <queue>

#include <vector>

// ... (Assume you have a graph structure)

void dijkstra(int startNode) {

std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<std::pair<int, int>>> pq;

// ... (Initialization of distances, visited, etc.)

pq.push({0, startNode}); // Start with distance 0 from the starting node

while (!pq.empty()) {

int currentDistance = pq.top().first;

int currentNode = pq.top().second;

pq.pop();

// ... (Process the current node, update distances of neighbors, and push them into pq if needed)

}

}

In this example of Dijkstra's algorithm, the priority queue C++ pair holds distance and node. The std::greater comparator makes it a min-heap, prioritizing shorter distances. The algorithm repeatedly explores the node with the shortest known distance from the start.

If you wish to learn how to code in C++, you can check out upGrad’s software engineering courses.

Priority Queue C++ Customization

Once you've mastered the basics of std::priority_queue, it's time to explore the advanced features and techniques that can elevate your priority queue usage to new heights. The standard std::priority_queue is a versatile tool, but there may be situations where you need more specialized behavior. Here are a few ways to customize your priority queues:

  1. Custom comparators: As we've seen, you can provide a custom comparator function or functor to dictate the priority order of your elements. This is particularly useful when working with complex data types or when you need a non-standard ordering criterion.
  1. Lambda functions for flexibility: C++11 introduced lambda functions, which offer a concise way to define comparators inline. This can make your code more readable and maintainable:

std::priority_queue<int, std::vector<int>,

[](const int& a, const int& b) { return a > b; }> pq; // Max-heap

  1. Custom priority queue classes: For more complex scenarios, you can create your own priority queue class by inheriting from std::priority_queue and overriding specific member functions. This gives you fine-grained control over how the queue operates.

Performance Considerations: Optimizing Priority Queue C++ for Efficiency

While std::priority_queue offers excellent performance in most cases, it's important to be aware of its time complexity characteristics:

  1. top(), empty(), size(): These operations typically have constant time complexity (O(1)).
  1. push(), pop(): These operations generally have logarithmic time complexity (O(log n)), where n is the number of elements in the queue. This is due to the underlying heap operations required to maintain the priority order.

In scenarios where we need even faster performance, you might consider alternative heap implementations, such as the Fibonacci heap. However, be mindful of the trade-offs involved. Fibonacci heaps can offer better amortized time complexity for certain operations, but they might introduce additional overhead and complexity.

Alternatives to std::priority_queue

While std::priority_queue is the go-to choice for most priority queue needs, other C++ data structures can sometimes serve a similar purpose. If you need a set-like behavior (no duplicates) and want to maintain elements in a specific order, std::set with a custom comparator can be an alternative. However, it might not offer the same optimized performance as a specialized priority queue for certain operations.

Final Tips

Priority queues are a necessity for efficient data management, empowering us to tackle a wide range of computational challenges with elegance and speed. By prioritizing elements based on their importance or urgency, we can optimize algorithms, streamline simulations, and manage complex systems effectively.

Priority queues break free from the linear constraints of standard queues, allowing us to process data in a more intelligent, context-aware manner. If you wish to learn programming languages such as C++, you can check out upGrad’s computer science programs such as the Master’s in Computer Science Program.

Frequently Asked Questions

  1. What is a priority queue in C++?

A priority queue is a container adapter in C++ that prioritizes elements based on their values or a custom comparison function, allowing for efficient retrieval of the highest (or lowest) priority element.

  1. How do I use a priority queue in C++?

Include the <queue> header and use the std::priority_queue class. You can customize it with a different container type or comparator to change the underlying storage or priority order.

  1. What are the operations supported by a priority queue in C++?

Key operations are top() (access highest priority), push() (insert), pop() (remove highest priority), empty() (check if empty), and size() (number of elements).

  1. Is C++ priority queue a min or max heap?

By default, it's a max heap, meaning the highest value is at the top. You can use a custom comparator like std::greater<T> to make it a min heap.

  1. What is the difference between heap and priority queue?

A heap is a specific tree-based data structure used to implement a priority queue. A priority queue is an abstract data type (ADT) that can be implemented using different underlying structures, including heaps.

  1. Is the priority queue always sorted in C++?

The priority queue only guarantees that the highest (or lowest) priority element is easily accessible. The rest of the elements are not necessarily in sorted order.

  1. What are the advantages of priority queue?

Efficiently retrieve the highest or lowest priority element in constant time (O(1)), while insertion and removal operations are typically logarithmic (O(log n)).

  1. What are the applications of queue?

Queues are used in various applications like breadth-first search, task scheduling, event handling, and managing resources.

  1. Can priority queues have duplicates?

Yes, C++ priority queues can store duplicate values.

  1. What are the limitations of the priority queue?

The priority queue only provides direct access to the highest/lowest priority element, not the second highest/lowest or other elements in a specific order. Additionally, accessing or modifying elements in the middle of the queue is not efficient.

Abhimita Debnath

Abhimita Debnath

Abhimita Debnath is one of the students in UpGrad Big Data Engineering program with BITS Pilani. She's a Senior Software Engineer in Infosys. She…Read More

Need Guidance? We're Here to Help!
form image
+91
*
By clicking, I accept theT&Cand
Privacy Policy
image
Join 10M+ Learners & Transform Your Career
Learn on a personalised AI-powered platform that offers best-in-class content, live sessions & mentorship from leading industry experts.
right-top-arrowleft-top-arrow

upGrad Learner Support

Talk to our experts. We’re available 24/7.

text

Indian Nationals

1800 210 2020

text

Foreign Nationals

+918045604032

Disclaimer

upGrad does not grant credit; credits are granted, accepted or transferred at the sole discretion of the relevant educational institution offering the diploma or degree. We advise you to enquire further regarding the suitability of this program for your academic, professional requirements and job prospects before enr...