Stacks
Steven J. Zeil
Like vectors and lists, stack and queues are also ordered collections, but the construction and access rules are purposely limited.
-
high recognition factor — people reading your code can more easily recognize what you are doing.
-
improved choices for the implementing data structure — as a general rule, the more elaborate the ADT interface, the fewer options you will have for implementation.
1 The Stack ADT
Stacks organize the data in a Last-In, First-Out (LIFO) manner. We can think of a stack as a sequence of elements where we always add to and remove from the same end.
-
Data is pushed onto a stack or
-
popped off of a stack.
-
We can only examine data on the top of the stack.
1.1 Overview of Implementation
Stacks can be easily implemented using array/vector-like data structures or via linked lists. You can see both possibilities below.
Actually, implementation of a stack is pretty trivial using either the std::vector<T>
or std::list<T>
types as an underlying implementation.
-
To
push
onto the stack, do apush_back
onto the underlying vector or list -
To
pop
from the stack,do apop_back
on the underlying vector or list -
To access the
top
of the stack, get theback()
of the vector or list.
Stacks are easily implemented using either arrays, vectors, or linked lists. Try out the linked list implementation of a stack in an animation.
2 std Interface
The stack and queue abstractions do not allow access to arbitrary contained items.
-
Therefore, many
std
conventions do not apply-
[i]
, iterators,insert
, etc.
-
-
Stacks and queues in
std
are not “full-fledged” containers
The std
library treats stack and queue as special cases by letting you convert
-
a
vector
,list
ordeque
(we’ll introducedeque
s later in this course Section) into astack
, or -
a
deque
orlist
into aqueue
These “converted” ADTs are called “adaptors” in std
.
2.1 Using a Stack
stack<string, vector<string> > stk;
stk.push("abc");
stk.push("de");
assert (stk.top() == "de");
stk.pop();
assert (stk.size() == 1);
stk.pop();
assert (stk.empty());
Here are some examples of typical stack manipulation.
(All the assert
s should be OK.)
Notice that when instantiating the stack
, we specify both the type of elements to go on the stack:
stack<string, vector<string> >
and the sequence to be used as the implementing data structure:
stack<string, vector<string> >
By the way, be careful with things like stack<string, vector<string>
>
. The blank between the two “>
” is important. Without that, C++ assumes that you are writing the >>
operator (as in cin >> n;
), which would not be legal here.[^This annoying glitch in the language design is fixed in C++11.]
3 Applications & Examples
Stacks are among the most common data structures and crop up in a variety of applications. They are especially useful in parsing and translation of computer languages, mathematics, and other formal notations.
-
parentheses matching
-
postfix translation and evaluation
-
compilers
Your text gives examples of some of these, and you will do one in the next assignment.
3.1 Expression Evaluation
Postfix (also known as Reverse Polish Notation or RPN) is a parentheses-free notation for mathematical expressions:
-
operators appear after their operands, e.g.:
-
1 2 +
instead of1+2
-
1 2 3 * +
instead of1+2*3
-
1 2 + 3 *
instead of(1+2)*3
Postfix is easily evaluated using a stack:
-
when you see a constant, push it onto the stack
-
when you see an operator that needs k operands,
-
pop k numbers from the stack
-
apply the operator to them
-
push the result back onto the stack
-
Try out the RPN calculator in an animation.
Now, you might find this example a bit artificial-looking. Most people don’t write their expressions out in postfix, preferring the more conventional infix notation. Postfix does have the slight virtue that it can represent any algebraic expression without parentheses.
In fact, back in the not-so-distant past when scientific calculators did not have parentheses keys, anyone using such a calculator was accustomed to entering their intended calculation in postfix form. It’s still common for calculators, compilers, and other software that must process expressions to convert those into postfix form and then use the above algorithm to evaluate the resulting postfix expression.
Conversion to postfix is, itself, generally performed via a stack (or via recursion, which we will later see has a close relationship to stack-based algorithms). For example, to convert a “normal” infix algebraic expression without parentheses into postfix, you could do this:
S = an empty stack;
while (more input available) {
read the next token, T;
if T is a variable name or a number,
print it;
else { // T is an operator
while (S is not empty and T has lower precedence than top operator on S) {
print top operator on S;
pop S;
}
push T onto S
}
while (S is not empty) {
print top operator on S;
pop S;
}
Try this with an expression like “1 + 2*3 + 4”. The output will be “1 2 3 * + 4 +”.
This algorithm can be modified, without too much trouble, to work with parentheses as well.
Another common application of stacks is in converting recursive algorithms to iterative, which we will discuss later.