A stack can be defined as a data structure which only permits operations at one end of the storage container, which we'll call the top. Think of this like a can of those terribly delicious Pringles® potato chips such that, short of destroying the can, we can only access (eat) the top chip in the container; access to each subsequent chip requires the removal of the chip preceding it. We can easily imagine how the chips were inserted into the can, starting with the first chip (the one at the bottom) and continually adding chips until the can is full. It turns out that the last chip to be added to the can is actually the first chip to be removed (eaten).
In this way, a Pringles can (our veritable stack) is a Last-in/First-Out (LIFO) data structure, more formally defined as a data structure where elements are removed in the reverse order of their addition.
A traditional stack supports three operations: push (adds an item to the top), pop (removes an item from the top), and isEmpty (checks to see if the data structure contains any items). There exists a more modern variation of the traditional stack that adds a size method (which returns the number of items in the stack) and splits the pop operation into two parts. We show the differences further down the page.
In the end, either the traditional or modern version will serve your needs and either is acceptable to use.
You use a stack to solve a problem when storing data last-in/first-out is what you need. Once you've made the decision to use a stack (i.e. to use last-in/first-out) it simplifies problem solving, because you simply think push to store and pop to retrieve ... there are no other decisions to make.
Consider the problem of reading in a line of characters characters and printing them in reverse order. "Reverse order" means that the last character read in is the first character you print out. So when you read a character you'll store it, when you're ready to write, you'll retrieve a character and write it, and this should happen in LIFO order. That means we can abstract the issue of storing and retrieving data to a stack for this problem. Here's our simple pseudocode:
While there are characters left to read Read a character Push the character onto the stack While there are characters left on the stack Pop a character from the stack Print the character to the screen
So, for example, suppose we followed this procedure with the input HOT. The figure below illustrates what would happen with the stack, and hopefully shows you why the word ends up getting reversed.
READ-IN: H O T | | | T | | O | O O v H v H v H H H STACK: --- push --- push --- push --- pop --- pop --- pop --- S S S S | S | S | S | | | v v v WRITE-OUT: T O H
Now, realizing this in C++ requires a bit more understanding. What kind of object is the stack? How do we push and pop and check whether the stack is empty?
Provided below is a generic class Stack
to use. By 'generic', I mean that it's not known in advance what kind of objects you're going to store in your stack. In the previous example it was characters. In lab it'll be double
s. In other contexts it'll be strings, or points, or who knows what. To deal with this, the below example is a special kind of class that is independent of the type of objects it contains. When you declare a variable of type Stack
, you'll put the type being stored in the stack, in angle brackets, immediately following the word Stack
. For example, to declare a stack named S
for storing char
s, you'd type:
Stack<char> S;
To declare a stack named G
for storing string
objects you'd write
Stack<string> G;
instead. So, we have one class for any and every kind of stack, rather than needing a separate class for each type you want to store in a stack. Don't worry for now about how this feature is implemented, we'll see later in the course! The links below give you the .h files that contain both the interface and implementation for this special stack class. There are two different flavors, one that is more modern, and one that's more traditional. In addition to the .h files, shown below are the public member functions of the class and a simple program (a solution to the reversing problem from above) using the class.
Stack.h, a more modern stack ADT: | TradStack.h, a traditional stack ADT: |
These are the public member functions. DataType refers to the type of object stored in the stack.
|
These are the public member functions. DataType refers to the type of object stored in the stack.
|
|
|
Suppose we want to read a line of characters that contains ( )'s, and we want to determine whether the ( )'s are balanced, i.e. each ( is closed of by exactly one ), and each ) closes off exactly one (. We can do this easily by keeping a stack of unmatched ('s. Whenever we encounter a ), we pop the top off the stack, because that ( has been matched. Here's the procedure in pseudocode:
Pseudocode:
Create a stack of characters Create a flag fail and set it to false While fail is false and there's input left to read Read a character c If c is a (, push it onto the stack Else if c is a ) and stack is empty, set fail to true Else if c is a ), pop one element off the stack If fail is true, or the stack isn't empty, print "unbalanced" Else, print "balanced"
C++ implementation:
|
|
Note that the source code for this example is identical in each case. This is not by coincidence! It is proof that for this problem (and indeed all problems) utilizing a stack, the three basic operations (push, pop, isEmpty) will suffice. Other operations are nice to have and may result in faster run times in some situations, but often simple is best!
One might object that a simple counter can be used in place of the stack in the previous example. This is true, but what if we allow different kinds of open/close pairs, like ( )'s, [ ]'s and { }'s, and we insist that the open and closing pairs need to match. So while
[ [ { } ] ( ) { [ ] } ]
is okay,
[ { } ( } ]
is not. Now the problem is more interesting. A simple counter won't suffice. However, it's easy to modify the above procedure using stacks to solve this more difficult problem!
Pseudocode:
Create a stack of characters Create a flag fail and set it to false While fail is false and there's input left to read Read a character c If c is a (, { or [ push it onto the stack Else if c is a ), } or ] and stack is empty, set fail to true Else Pop one element off the stack Set fail to true if the closing character you popped off doesn't match c If fail is true, or the stack isn't empty, print "unbalanced" Else, print "balanced"
C++ implementation:
|
|
Again, outside of the split of the pop operation in Stack.h
, the above codes are identical. This week's lab will give you some hands-on experience working with stacks.