Module 6 ¡ stack and queue: Last In First Out, First In First Out
stack and queue: The Newest Leaves First, or the Oldest
In this lesson
- Declare a
std::stackand astd::queue, push, look and pop on each, and say which element each one gives back first. - Explain what an adaptor is: a container with a narrower door, a deque inside that you reach only at the ends the job needs.
- Write the two-line read-then-pop idiom, because
pop()returns nothing, and checkempty()before every look or pop.
Bob types a paragraph, makes a word bold, then deletes a line by mistake. When he presses Undo, the editor must take back the newest change first, like the top plate of a pile. Alice's bakery has the opposite job: it serves customers in the order they came, which is a line. A vector or a deque could do both jobs, but each also allows moves these jobs must never make. This lesson gives each job a container that allows only the right moves.
Two jobs, two orders
Bob's undo list and Alice's line hold the same kind of thing, a few items in arrival order. What differs is which item leaves first. In Bob's list the newest item leaves first, an order called LIFO: last in, first out. In Alice's line the oldest item leaves first, an order called FIFO: first in, first out.
A stack is a container in LIFO order, and a queue (say "cue") is a container in FIFO order. A stack is open at one end only, called its top. A queue is open at two ends: things join at the back and leave from the front.
So ask one question of any job: who leaves first, the newest or the oldest? The newest means a stack, and the oldest means a queue.
A stack moves at one end
Now watch a stack move. Each step below is one call on a stack of plates, numbered 1, 2 and 3 in the order they arrive. Look at where every push lands and where every pop takes from.
The plates went in as 1, 2, 3 and came out as 3, 2, 1. Nothing ever touched plate 1 until the plates above it had gone. So a stack gives its items back in the reverse of the order they went in.
A queue moves at both ends
The same kind of steps on a queue now. Three tickets join Alice's line, two leave, and one more joins. Watch the front and the back separately.
On a queue, push works at the back and pop at the front, so they never meet. Ticket 104 came last and waits behind everyone. So a queue gives its items back in the same order they went in.
The syntax you need on day one
std::stack and std::queue, and their everyday calls
#include <stack>
#include <queue>
stack<T> s; an empty stack of T
s.push(x); put x on the top
s.top() the top element, the newest
s.pop(); remove the top element; returns nothing
queue<T> q; an empty queue of T
q.push(x); add x at the back
q.front() the front element, the oldest
q.back() the back element, the newest
q.pop(); remove the front element; returns nothing
s.size() s.empty() how many, and whether there are none (q too)
stack<T>andqueue<T>: one thing in the angle brackets, the element type, as for a vector. Each has its own header.pushandpop: the same two names on both. A stack uses its one end for both; a queue pushes at the back and pops at the front.top(),front()andback(): looks. They take nothing away.pop(),top(),front()andback(): only when the container is not empty. Checkempty()first.
So a stack has three working calls, push, top and pop, and a queue has four: push, front, back and pop.
An adaptor: a container with a narrower door
std::stack and std::queue do not store anything themselves. Each keeps another container inside, a deque unless you choose otherwise, and passes your calls on to it. A class that wraps another one like this is called an adaptor. Module 4's lesson 04 already showed it: its container_type program printed queue keeps a deque inside: yes.
The adaptor's whole job is the narrower door. The deque inside has a front, a back and a middle you can index. The stack lets you reach only its top, and the queue only its two ends. A call that is not on the door does not compile, and lesson 02 shows each message.
Every call on the door is one call on the deque inside, at one of its ends. Module 4 showed that a deque's ends are cheap, so every push, look and pop here is cheap too. So an adaptor adds no new storage: it takes moves away, and keeps the ones the job needs.
push, top and pop on a stack
push(x) puts x on the top. top() tells you what is on the top now. pop() removes it. This program prints the top and the size after each call.
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> s;
s.push(10);
s.push(20);
s.push(30);
cout << "top " << s.top() << ", size " << s.size() << '\n';
s.pop();
cout << "top " << s.top() << ", size " << s.size() << '\n';
s.push(40);
cout << "top " << s.top() << ", size " << s.size() << '\n';
return 0;
}
top 30, size 3
top 20, size 2
top 40, size 3
After the pop, 20 came back to the top, because it was under 30. Then 40 went on top of 20. The 10 at the bottom was never seen: a stack shows you only its top. So top() always answers with the most recent push that has not been popped.
push, front, back and pop on a queue
A queue's push(x) adds x at the back. front() is the oldest element and back() the newest. pop() removes the front one.
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> q;
q.push(10);
q.push(20);
q.push(30);
cout << "front " << q.front() << ", back " << q.back() << ", size " << q.size() << '\n';
q.pop();
cout << "front " << q.front() << ", back " << q.back() << ", size " << q.size() << '\n';
q.push(40);
cout << "front " << q.front() << ", back " << q.back() << ", size " << q.size() << '\n';
return 0;
}
front 10, back 30, size 3
front 20, back 30, size 2
front 20, back 40, size 3
These are the same calls as the stack program, with front and back in place of top. Yet the pop took 10, not 30. Module 4 said a queue has three moves: push at the back, look at the front, pop from the front. back() is one more look, and it never moves anything. So on a queue, the pop always takes the element that has waited longest.
pop returns nothing: read first, then pop
Bob wants the top value and its removal in one line, so he writes this.
stack<int> s;
s.push(4);
int x = s.pop();
GCC 12 stops at the runner's flags with error: void value not ignored as it ought to be. void means "no value": pop() removes the element and hands nothing back. The same holds for a queue's pop(). Lesson 05 explains why the library was designed this way. The fix is two lines, a read and then a pop.
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> s;
s.push(4);
s.push(9);
int x = s.top();
s.pop();
cout << "took " << x << ", now the top is " << s.top() << '\n';
return 0;
}
took 9, now the top is 4
On a queue the idiom is the same, with front() in place of top(). So you will write this pair, a look and then a pop, in almost every stack or queue program.
The empty adaptor: Zara's first test
Zara always tries the empty case first. She asks an empty stack for its top plate.
stack<int> plates;
cout << "top plate: " << plates.top() << '\n';
It compiled with no message at the runner's flags. On Compiler Explorer's GCC 12.2, in one run, the program printed nothing and crashed with SIGSEGV, the signal for reading memory it does not own. Even the words top plate: never appeared, because cout still held them when it crashed. Then she asked an empty queue for its front. That run printed first in line: 0 and ended normally.
Both are undefined behaviour: the C++ standard says nothing about what happens. So one run crashes, and another prints a 0 that is not a customer. pop() on an empty adaptor is undefined too. So write if (!s.empty()) before every top(), front(), back() or pop() whose input you do not control.
The smallest stack program: three pushes, then a loop that reads and pops until the stack is empty.
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> s;
s.push(1);
s.push(2);
s.push(3);
while (!s.empty()) {
cout << "pop " << s.top() << '\n';
s.pop();
}
return 0;
}
pop 3
pop 2
pop 1
The loop test !s.empty() is Zara's check built into the loop, so top() never meets an empty stack. If each popped value were pushed onto a second stack, that stack would hold 3 at the bottom: pouring one stack onto another reverses it.
The same three pushes on a queue, and the same loop with front().
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
while (!q.empty()) {
cout << "pop " << q.front() << '\n';
q.pop();
}
return 0;
}
pop 1
pop 2
pop 3
Put the two outputs side by side: 3 2 1 from the stack, 1 2 3 from the queue. That is LIFO and FIFO in six lines.
Run in CompilerBob's three changes go on a stack of strings. Undo reads the top, says what it takes back, and pops it.
#include <iostream>
#include <stack>
#include <string>
using namespace std;
int main() {
stack<string> changes;
changes.push("typed a paragraph");
changes.push("made a word bold");
changes.push("deleted a line");
for (int press = 1; press <= 2; press++) {
cout << "undo: " << changes.top() << '\n';
changes.pop();
}
cout << "next undo would take back: " << changes.top() << '\n';
cout << changes.size() << " change left\n";
return 0;
}
undo: deleted a line
undo: made a word bold
next undo would take back: typed a paragraph
1 change left
The delete came off first because it went on last. Bob never has to remember the order: the stack keeps it for him.
Run in CompilerFour tickets join in order. Alice serves two of them, a new customer arrives, and the program reports the line before and after.
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> line;
for (int ticket = 101; ticket <= 104; ticket++) {
line.push(ticket);
}
cout << line.size() << " waiting, front " << line.front() << ", back " << line.back() << '\n';
for (int served = 0; served < 2; served++) {
cout << "serving ticket " << line.front() << '\n';
line.pop();
}
line.push(105);
cout << line.size() << " waiting, front " << line.front() << ", back " << line.back() << '\n';
return 0;
}
4 waiting, front 101, back 104
serving ticket 101
serving ticket 102
3 waiting, front 103, back 105
Ticket 105 went behind 104, so nobody who came earlier is served later. Module 4 built the same line on a deque, where a VIP could jump to the front. A queue has no door at the front for joining, so here nobody can.
Run in CompilerWhere this is used
- The call stack of every C and C++ program. Each function call pushes a frame with its local variables, and a return pops it. The function called last returns first, as the C track's call-stack picture showed.
- CUPS, the print system of Linux and macOS. It keeps the jobs sent to a printer in a queue. Jobs of the same priority print in the order they were sent.
- Microsoft Word's Undo button. Its drop-down list shows your most recent action at the top. Undoing an action further down also undoes every action above it, which is a stack popped several times.
Common mistakes
1. Calling a vector's name on a stack.
stack<int> s;
s.push_back(5);
An error at the runner's flags: error: 'class std::stack<int>' has no member named 'push_back'. The door is called push on both adaptors. You will type push_back because Module 2 wrote it a hundred times.
2. Popping before reading.
stack<int> s;
s.push(4);
s.push(9);
s.pop();
cout << "took " << s.top() << '\n';
No message at any command line, and it prints took 4. The 9 was already gone, so top() answered with the element under it. With one element pushed, the same lines would read an empty stack. Read first, then pop. You will swap them because "take it, then look" sounds like the right order.
3. Forgetting the header.
#include <iostream>
using namespace std;
int main() {
queue<int> q;
q.push(5);
cout << q.front() << '\n';
return 0;
}
An error at the runner's flags: error: 'queue' was not declared in this scope, then GCC 12's own fix: note: 'std::queue' is defined in header '<queue>'; did you forget to '#include <queue>'?. A stack needs <stack> the same way. You will forget it because the deque inside seems to come for free.
Alice's bakery gives every customer a ticket, and the counter serves them in the order they came. A customer who arrives joins the back of the line. When the counter is free, Alice calls the customer at the front. Sometimes she calls when nobody is waiting, and then nobody comes.
Input. A line with n, then n commands, one per line. join NAME puts NAME at the back of the line, and serve calls the customer at the front, who leaves the line.
Output. One line for every serve: the name of the customer served, or the word nobody if the line is empty. Then one last line: the names still waiting from the front to the back, separated by single spaces, or the word empty if nobody is waiting.
Constraints. 1 <= n <= 100000. A name is 1 to 10 lowercase letters.
Sample. Input 7, then serve, join amara, join bob, serve, join zara, serve, join kenji gives nobody, amara, bob and zara kenji. The first serve finds an empty line, and Zara and Kenji are still waiting at the end.
#include <iostream>
#include <queue>
#include <string>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n = 0;
cin >> n;
queue<string> line;
for (int i = 0; i < n; i++) {
string op;
cin >> op;
if (op == "join") {
string name;
cin >> name;
// Add name at the back of line.
} else {
// op is "serve": print the name at the front and remove it,
// or print nobody if line is empty.
}
}
// Print the names still waiting, front to back, separated by single
// spaces, or the word empty if nobody is left.
return 0;
}
Graded as ticket-queue, a free problem in this module's problem set. The hidden tests include a serve before anyone joins and a line that empties and fills again. They also include a run that ends with nobody waiting, and 100000 commands.
Maria's word game has one rule: a word wins when it reads the same from both ends, like level. She checks it with this lesson's two containers. Every letter goes into a stack and into a queue. Then the stack gives the letters last first, and the queue gives them first first.
Input. A line with t, then t words, one per line.
Output. One line per word: yes if it reads the same from both ends, or no.
Constraints. 1 <= t <= 1000. Each word is 1 to 1000 lowercase letters.
Sample. Input 3, then level, stack and a gives yes, no and yes. A word of one letter always wins.
#include <iostream>
#include <queue>
#include <stack>
#include <string>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
for (int i = 0; i < t; i++) {
string word;
cin >> word;
stack<char> from_end;
queue<char> from_start;
// Push every letter of word into both containers.
// Then compare from_end.top() with from_start.front(), pop both,
// and repeat until they are empty. Print yes or no.
}
return 0;
}
Not graded on its own. Two indexes moving inward would do the same job; the point here is to watch the two orders meet, letter by letter.
Run in CompilerKenji stacks crates in a warehouse, and a crate may only sit on a crate that is heavier or equal. His crates arrived in any order, in one pile. He has one free spot beside it for a second pile, and he can hold one crate in his hands. He moves one crate at a time, always from the top of a pile to the top of a pile.
Input. A line with n, then n weights: the crates of the pile from its bottom to its top.
Output. The weights of the finished pile from its top to its bottom, on one line separated by single spaces.
Constraints. 1 <= n <= 2000. Each weight is between 1 and 1000000. Use only the two stacks and one variable for the crate in his hands.
Sample. Input 5 and 3 1 4 1 5 gives 1 1 3 4 5. The lightest crates end up on top.
#include <iostream>
#include <stack>
using namespace std;
int main() {
int n;
cin >> n;
stack<int> pile;
for (int i = 0; i < n; i++) {
int w;
cin >> w;
pile.push(w);
}
stack<int> sorted;
// Move crates one at a time until pile is empty, so that every crate
// in sorted sits on a heavier or equal one. You may hold one crate in
// a variable, and you may move crates from sorted back onto pile.
// Then print sorted from its top to its bottom.
return 0;
}
Not graded on its own. The idea the lesson only pointed at, in Example 1: pouring one stack onto another reverses it. So a crate in the way can step back onto the old pile and return later.
Run in CompilerCommon doubts
Why not use a vector or a deque for everything?
You can, and contest code often uses a vector as a stack. But a
std::stacksays what the job is, and the compiler refuses every move a stack must not make. Lesson 04 weighs the two with a measurement.Why doesn't
pop()give me the element it removed?Because the library chose to keep removing and reading apart. The reason has to do with copying an element safely, and lesson 05 tells the story. Until then, read with
top()orfront(), then pop.Is a queue's
back()allowed? Module 4 said a queue has three moves.Yes. The three moves are push at the back, look at the front and pop from the front.
back()is one more look: it shows the newest element and moves nothing.Can I see the element under the top?
Not without popping the top first. That is the narrower door working as designed. If a job needs the middle too, it wanted a deque or a vector.
Key takeaways
- A stack gives back the newest element first (LIFO); a queue gives back the oldest first (FIFO).
- A stack works at its top:
push,top,pop. A queue pushes at the back, pops at the front, and can look at both withfrontandback. - Both are adaptors: a deque inside, and a narrower door that lets only the right moves through.
pop()returns nothing, so read withtop()orfront()first;int x = s.pop();getsvoid value not ignored as it ought to be.- A look or a pop on an empty adaptor is undefined: one run crashed, another printed a quiet 0. Check
empty()first. - Go deeper: Under the Hood, the container inside and why pop returns nothing (Pro).
Next, lesson 02 goes through every operation of both adaptors, one by one, each with its cost, and everything they refuse.
End of lesson 1
Mark it done, and your progress moves with you.
Next: Every stack and queue Operation, One by One, With Its Cost