Linear Structure¶
Execute the following two cells for the setup:
Introduction
Stacks
Queues
Deques
3.1~3.2 What Are Linear ADT?¶
They are data collections whose items are ordered depending on how they are added or removed. Once an item is added, it stays in that position relative to the other elements that came before and came after it. Collections such as these are often referred to as linear ADT.
Linear ADT can be thought of as having two ends. Sometimes these ends are referred to as the left and the right, or in some cases the front and the rear.
What distinguishes one linear structure from another is the way in which items are added and removed, in particular the location where these additions and removals occur.
3.3 Stacks¶
A stack (sometimes called a push-down stack) is an ordered collection of items where the addition of new items and the removal of existing items always takes place at the same end. This end is commonly referred to as the top. The end opposite the top is known as the base.
The base of the stack is significant since items stored in the stack that are closer to the base represent those that have been in the stack the longest. The most recently added item is the one that is in position to be removed first. This ordering principle is sometimes called LIFO, or last in, first out.
It provides an ordering based on length of time in the collection. Newer items are near the top, while older items are near the base.
Almost any cafeteria has a stack of trays or plates where you take the one at the top, uncovering a new tray or plate for the next customer in line. Imagine a stack of books on a desk. The only book whose cover is visible is the one on top. To access others in the stack, we need to remove the ones that are sitting on top of them.

Assume you start out with a clean desktop. Now place books one at a time on top of each other. You are constructing a stack. Consider what happens when you begin removing books. The order that they are removed is exactly the reverse of the order that they were placed.
Stacks are fundamentally important, as they can be used to reverse the order of items. The order of insertion is the reverse of the order of removal:

For example, every web browser has a Back button. As you navigate from web page to web page, those pages are placed on a stack (actually it is the URLs that are going on the stack). The current page that you are viewing is on the top and the first page you looked at is at the base. If you click on the Back button, you begin to move in reverse order through the pages!
3.4 The Stack Abstract Data Type¶
The stack operations are given below:
Stack()creates a new stack that is empty. It needs no parameters and returns an empty stack.
push(item)adds a new item to the top of the stack. It needs the item and returns nothing.pop()removes the top item from the stack. It needs no parameters and returns the item. The stack is modified.
peek()returns the top item from the stack but does not remove it. It needs no parameters. The stack is not modified.is_empty()tests to see whether the stack is empty. It needs no parameters and returns a boolean value.size()returns the number of items on the stack. It needs no parameters and returns an integer.
Under Stack Contents, the top item is listed at the far right:
| Stack Operation | Stack Contents | Return Value |
|---|---|---|
s.is_empty() |
[] | True |
s.push(4) |
[4] | |
s.push('dog') |
[4, 'dog'] | |
s.peek() |
[4, 'dog'] | 'dog' |
s.push(True) |
[4, 'dog', True] | |
s.size() |
[4, 'dog', True] | 3 |
s.is_empty() |
[4, 'dog', True] | False |
s.push(8.4) |
[4, 'dog', True, 8.4] | |
s.pop() |
[4, 'dog', True] | 8.4 |
s.pop() |
[4, 'dog'] | True |
s.size() |
[4, 'dog'] | 2 |
3.5 Implementing a Stack in C++¶
Now that we have clearly defined the stack as an ADT, we will turn our attention to using C++ to implement the stack. We build a class whose underlying storage is a vector — the direct counterpart of the Python list implementation. (A fixed-size array version is in pythonds3/cppds/basic/stack.cpp.)
The implementation of choice for an abstract data type such as a stack is the creation of a new class. The stack operations are implemented as methods. Further, to implement a stack, which is a collection of elements, it makes sense to utilize the power and simplicity of the list provided by Python.
Recall that the list class in Python provides an ordered collection mechanism and a set of methods. We need only to decide which end of the list will be considered the top of the stack and which will be the base. Once that decision is made, the operations can be implemented using the list methods such as append() and pop().
Assume that the end (back) of the vector will hold the top element of the stack. Note one difference from Python: our template stack holds elements of a single type chosen when it is created:
template <typename T>
class Stack {
private:
vector<T> items;
public:
bool isEmpty() {
return items.empty();
}
void push(T item) {
items.push_back(item);
}
int size() {
return items.size();
}
};
T pop() {
T top = items.back();
items.pop_back();
return top;
}
T peek() {
return items.back();
}
void display() {
for (T x : items) cout << x << " ";
cout << endl;
}
(complete class: dscpp/stack.hpp)
Notice that the definition of the Stack class is imported from the pythonds3 module that is included with the materials for this book.
A fixed-capacity array version of the same class is collected in pythonds3/cppds/basic/stack.cpp. Here is the vector-backed stack in action:
#include <iostream>
#include "dscpp/stack.hpp" // the vector-backed Stack of this section
using namespace std;
int main() {
Stack<double> s;
cout << boolalpha << s.isEmpty() << endl;
s.push(4);
s.push(7.5);
cout << s.peek() << endl;
s.push(8.4);
s.display();
cout << s.pop() << endl;
cout << s.size() << endl;
return 0;
}
true
7.5
4 7.5 8.4
8.4
2
We could instead keep the top at the beginning of the vector:
template <typename T>
class Stack2 {
private:
vector<T> items;
public:
bool isEmpty() {
return items.empty();
}
void push(T item) {
items.insert(items.begin(), item); // O(n)!
}
T pop() {
T top = items.front();
items.erase(items.begin()); // O(n)!
return top;
}
T peek() {
return items.front();
}
int size() {
return items.size();
}
};
In this case, push_back() and pop_back() no longer touch the top, so we use insert(begin()) and erase(begin()) instead — every push and pop now shifts all elements, turning $O(1)$ operations into $O(n)$.
#include <iostream>
#include "dscpp/stack.hpp" // Stack2: top at the front, O(n) ops
using namespace std;
int main() {
Stack2<double> s;
cout << boolalpha << s.isEmpty() << endl;
s.push(4);
s.push(7.5);
cout << s.peek() << endl;
s.push(2);
cout << s.size() << endl;
cout << s.pop() << endl;
cout << s.pop() << endl;
cout << s.size() << endl;
return 0;
}
true
7.5
3
2
7.5
1
This ability to change the physical implementation of an ADT while maintaining the logical characteristics is an example of abstraction at work. However, even though the stack will work either way, if we consider the performance of the two implementations, there is definitely a difference!
Recall that the append() and pop() operations were both $O(1)$. This means that the first implementation will perform push() and pop() in constant time no matter how many items are on the stack.
The performance of the second implementation suffers in that the insert(0) and pop(0) operations will both require $O(n)$ for a stack of size $n$. Clearly, even though the implementations are logically equivalent, they would have very different timings when performing benchmark testing!













