Stack, Queue, Priority Queue — Container Adaptors and Underlying Containers
In this tutorial, you will learn about Stack, Queue, Priority Queue. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ stack, queue, and priority_queue are container adaptors that wrap sequence containers (vector, deque, list) with restricted interfaces for LIFO, FIFO, and priority-based element access.
What You'll Learn
You will use std::stack for LIFO (last-in-first-out) operations, use std::queue for FIFO (first-in-first-out) operations, use std::priority_queue for heap-based priority ordering, customize the underlying container for each adaptor, provide custom comparison for priority ordering, and implement a monotonic stack for algorithmic problem solving.
Why It Matters
Container adaptors simplify code by providing exactly the interface you need. They prevent accidental misuse of the full container API. priority_queue is the go-to data structure for scheduling, pathfinding, and any algorithm that needs to repeatedly extract the maximum or minimum element.
Learning Path
graph LR
A["33: Map & Multimap"] --> B["34: Stack, Queue, Priority Queue"]
B --> C["35: String & Span"]
C --> D["36: Algorithms Overview"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
std::stack
#include <iostream>
#include <stack>
#include <vector>
int main() {
std::stack<int> s; // default: deque
s.push(10);
s.push(20);
s.push(30);
std::cout << "Top: " << s.top() << "\n"; // 30
s.pop(); // removes 30
std::cout << "Size: " << s.size() << "\n";
std::cout << "Top: " << s.top() << "\n"; // 20
// Custom underlying container
std::stack<int, std::vector<int>> vecStack;
vecStack.push(1);
vecStack.push(2);
// Iterate (destructive)
while (!s.empty()) {
std::cout << s.top() << " ";
s.pop();
}
std::cout << "\n";
}
Stack operations: push(), pop(), top(), empty(), size().
std::queue
#include <iostream>
#include <queue>
#include <list>
int main() {
std::queue<int> q; // default: deque
q.push(10);
q.push(20);
q.push(30);
std::cout << "Front: " << q.front() << "\n"; // 10
std::cout << "Back: " << q.back() << "\n"; // 30
q.pop(); // removes 10
std::cout << "Front: " << q.front() << "\n"; // 20
// Custom underlying container
std::queue<int, std::list<int>> listQueue;
listQueue.push(1);
listQueue.push(2);
// Iterate (destructive)
while (!q.empty()) {
std::cout << q.front() << " ";
q.pop();
}
std::cout << "\n";
}
Queue operations: push(), pop(), front(), back(), empty(), size().
std::priority_queue
#include <iostream>
#include <queue>
#include <vector>
int main() {
// Max heap (default)
std::priority_queue<int> pq;
pq.push(30);
pq.push(10);
pq.push(50);
pq.push(20);
pq.push(40);
std::cout << "Top: " << pq.top() << "\n"; // 50
pq.pop(); // removes 50
std::cout << "Top: " << pq.top() << "\n"; // 40
// Min heap
std::priority_queue<int, std::vector<int>, std::greater<int>> minPq;
minPq.push(30);
minPq.push(10);
minPq.push(50);
std::cout << "Min top: " << minPq.top() << "\n"; // 10
// Custom comparator
struct CustomCompare {
bool operator()(int a, int b) const {
return (a % 10) > (b % 10); // sort by last digit
}
};
std::priority_queue<int, std::vector<int>, CustomCompare> customPq;
customPq.push(23);
customPq.push(45);
customPq.push(12);
customPq.push(38);
while (!customPq.empty()) {
std::cout << customPq.top() << " "; // smallest last digit first
customPq.pop();
}
std::cout << "\n";
}
Container Adaptor Characteristics
| Adaptor | Underlying Default | Allowed Containers | Key Operations |
|---|---|---|---|
stack |
deque |
deque, vector, list |
push, pop, top |
queue |
deque |
deque, list |
push, pop, front, back |
priority_queue |
vector |
vector, deque |
push, pop, top |
Customizing priority_queue
#include <iostream>
#include <queue>
#include <vector>
struct Task {
int priority;
std::string description;
};
struct TaskCompare {
bool operator()(const Task& a, const Task& b) const {
return a.priority < b.priority; // higher priority first (max heap)
}
};
int main() {
std::priority_queue<Task, std::vector<Task>, TaskCompare> taskQueue;
taskQueue.push({3, "Low priority"});
taskQueue.push({5, "High priority"});
taskQueue.push({4, "Medium priority"});
taskQueue.push({1, "Lowest priority"});
while (!taskQueue.empty()) {
const auto& task = taskQueue.top();
std::cout << task.priority << ": " << task.description << "\n";
taskQueue.pop();
}
}
Monotonic Stack Example
#include <iostream>
#include <stack>
#include <vector>
// Find the next greater element for each element in the array
std::vector<int> nextGreaterElement(const std::vector<int>& arr) {
std::vector<int> result(arr.size(), -1);
std::stack<size_t> s; // stack of indices
for (size_t i = 0; i < arr.size(); ++i) {
while (!s.empty() && arr[s.top()] < arr[i]) {
result[s.top()] = arr[i];
s.pop();
}
s.push(i);
}
return result;
}
int main() {
std::vector<int> arr = {13, 7, 6, 12};
auto result = nextGreaterElement(arr);
for (size_t i = 0; i < arr.size(); ++i) {
std::cout << arr[i] << " -> " << result[i] << "\n";
}
}
Expected output:
13 -> -1
7 -> 12
6 -> 12
12 -> -1
Common Mistakes
Mistake 1: Accessing top() or front() on Empty Adaptor
std::stack<int> s;
// s.top(); // undefined behavior if empty
Always check !empty() first.
Mistake 2: Forgetting pop() Does Not Return Value
int val = s.pop(); // Error: pop returns void
int val = s.top(); s.pop(); // correct
Mistake 3: Using Wrong Comparator for priority_queue
std::priority_queue<int, std::vector<int>, std::greater<int>> minHeap; // min heap
std::priority_queue<int> maxHeap; // max heap (default)
std::greater creates a min heap because priority_queue uses the comparator as "less than" for ordering.
Mistake 4: Modifying Elements After Push
auto& top = pq.top();
top = 999; // may break the heap property!
Push a modified copy instead.
Mistake 5: Using stack When vector Would Suffice
If you only need to access the most recently added element, stack adds safety by restricting the interface. But if you later need iteration, vector is more flexible.
Mistake 6: Expecting priority_queue Iteration in Order
priority_queue does not support iteration. To get elements in order, you must repeatedly pop().
Practice Questions
- What is a container adaptor and why would you use one instead of the underlying container?
- How would you implement a min-heap using
priority_queue? - Write a function that checks if a string of parentheses is balanced using a stack.
- What underlying containers can a
queueuse? Why notvector? - Implement a simple task scheduler using
priority_queue.
Challenge
Use a priority queue to implement the "merge k sorted lists" algorithm. Given k sorted vectors, merge them into a single sorted vector using a min-heap of iterators.
FAQ
Mini Project
Build a simple expression evaluator using a stack:
#include <iostream>
#include <stack>
#include <string>
#include <sstream>
#include <cctype>
int evaluate(const std::string& expression) {
std::stack<int> values;
std::stack<char> ops;
auto applyOp = [](int a, int b, char op) {
switch (op) {
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/': return b != 0 ? a / b : 0;
default: return 0;
}
};
auto precedence = [](char op) {
return (op == '+' || op == '-') ? 1 : 2;
};
std::istringstream stream(expression);
char token;
while (stream >> token) {
if (std::isdigit(token)) {
stream.putback(token);
int value;
stream >> value;
values.push(value);
} else if (token == '(') {
ops.push(token);
} else if (token == ')') {
while (!ops.empty() && ops.top() != '(') {
int b = values.top(); values.pop();
int a = values.top(); values.pop();
values.push(applyOp(a, b, ops.top()));
ops.pop();
}
if (!ops.empty()) ops.pop(); // remove '('
} else if (token == '+' || token == '-' || token == '*' || token == '/') {
while (!ops.empty() && precedence(ops.top()) >= precedence(token)) {
int b = values.top(); values.pop();
int a = values.top(); values.pop();
values.push(applyOp(a, b, ops.top()));
ops.pop();
}
ops.push(token);
}
}
while (!ops.empty()) {
int b = values.top(); values.pop();
int a = values.top(); values.pop();
values.push(applyOp(a, b, ops.top()));
ops.pop();
}
return values.top();
}
int main() {
std::cout << evaluate("3 + 4 * 2") << "\n"; // 11
std::cout << evaluate("( 3 + 4 ) * 2") << "\n"; // 14
std::cout << evaluate("10 / 2 + 3 * 4") << "\n"; // 17
}
What's Next
Container adaptors simplify common data structure patterns. The next lesson covers string_view and span: non-owning views into character and contiguous data sequences.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro