Stacks, Queues, Deques ADT
Reading: Chapter 5
Data Structures and
Algorithms in C++
Second Edition
1
Abstract Data Types (ADTs)
• ADT is an abstraction of a data structure
• ADT is A specification of a collection of data and the operations that
can be performed on it.
– Describes what a collection does, not how it does it
– An ADT specifies:
Data stored
Operations on the data
Error conditions associated with operations
• We don't know exactly how a stack or queue is implemented, and we
don't need to.
– just need to understand the idea of the collection and what operations it can
perform.
2
ADT Example
• ADT modeling a simple stock trading system
The data stored are buy/sell orders
The operations supported are
• order buy(stock, shares, price)
• order sell(stock, shares, price)
• void cancel(order)
Error conditions:
• Buy/sell a nonexistent stock
• Cancel a nonexistent order
3
The Stack ADT
• The Stack ADT stores arbitrary objects
• Insertions and deletions follow the last-in first-out scheme
• Think of a spring-loaded plate dispenser
• Main stack operations:
push(object): inserts an element
pop(): removes the last inserted element
object top(): returns the last inserted element without removing it
• Auxiliary stack operations:
integer size(): returns the number of elements stored
boolean empty(): indicates whether no elements are stored
4
Applications of Stacks
Direct applications Indirect applications
• Page-visited history in a Web browser • Auxiliary data structure for algorithms
• Undo sequence in a text editor • Component of other data structures
• Chain of method calls in the C++ run-time
system
C++ Run-Time Stack main() { bar
int i = 5; PC = 1
• The C++ run-time system keeps track of the
foo(i); m=6
chain of active functions with a stack
}
• When a function is called, the system pushes
foo(int j) { foo
on the stack a frame containing PC = 3
int k;
• Local variables and return value j=5
k = j+1;
• Program counter, keeping track of the statement k=6
bar(k);
being executed
}
• When the function ends, its frame is popped main
from the stack and control is passed to the bar(int m) {
PC = 2
function on top of the stack …
i=5
}
• Allows for recursion
C++ STL Stack Interface
• size(): Return the number of elements in the stack.
• empty(): Return true if the stack is empty and false otherwise.
• push(e): Push e onto the top of the stack.
• pop(): Pop the element at the top of the stack.
• top(): Return a reference to the element at the top of the stack.
#include <iostream>
#include <stack>
using namespace std;
int main () {
stack<int> mystack;
for (int i=0; i<5; ++i)
[Link](i);
cout << "Popping out
elements...";
while (![Link]()){
cout << ' ' <<
[Link]();
[Link]();
}
cout << '\n'; 6
return 0;
Simple implementation of stack of int using linked nodes
Stack::Stack():head(NULL),n(0) {}
class Stack { //stack of integer Stack::~Stack(){ while (!empty()) pop();}
private:
IntNode void Stack::push(int value){ //old addFront
*head; IntNode *v = new IntNode(value);
int n; //to v->next = head;
count head = v;
public: n++;
Stack(); }
~Stack();
void push(int
bool Stack::empty() const{return
value);
(head==NULL);}
bool empty()
const;
int Stack::top() const {return head->elem;}
int top()
const;
void pop(); void Stack::pop(){//old removeFront
int size(); IntNode *v = head;
}; head = head->next;
n--;
delete v;
v=NULL;
}
7
Reusing our IntLinkedList
class Stack {
private: int main() {
IntLinkedList Stack s;
stack; [Link](3);
[Link](4);
public:
void push(int [Link](5);
value) { for (int i=0; i<5; ++i) [Link](i);
cout << "Popping out elements...";
[Link](value); while (![Link]()) {
}
cout << ' ' << [Link]();
void pop(){
[Link]();
[Link](); }
} }
int top(){
return
[Link]();
}
int size(){
return
[Link]();
} 8
bool empty(){
Reusing SLinkedList<E>
template <typename E>
class Stack {
private:
SLinkedList<E> stack;
int main() {
public:
void push(const E& value); Stack<int> s;
void pop() ; [Link](3);
E& top() const; [Link](4);
bool empty() const; [Link](5);
};
for (int i=0; i<5; ++i) [Link](i);
template <typename E> cout << "Popping out elements...";
void Stack<E>::push(const E& value) { while (![Link]()) {
[Link](value); cout << ' ' << [Link]();
} [Link]();
template <typename E>
}
void Stack<E>::pop() {
[Link](); }
}
template <typename E>
E& Stack<E>::top() const{
return [Link]();
}
template <typename E>
bool Stack<E>::empty() const{
return [Link]();
}
9
Our Stack Interface in C++
template <typename E>
• C++ interface corresponding
class Stack {
to our Stack ADT public:
• Uses an exception class int size() const;
StackEmpty bool empty() const;
• Different from the built-in const E& top() const
C++ STL class stack
throw(StackEmpty);
void push(const E& e);
void pop()
Exceptions throw(StackEmpty);
}
• Attempting the execution of an operation of ADT may sometimes cause an error
condition, called an exception
• Exceptions are said to be “thrown” by an operation that cannot be executed
• In the Stack ADT, operations pop and top cannot be performed if the stack is
empty
• Attempting pop or top on an empty stack throws a StackEmpty exception 10
Our Exception classes
// generic run-time exception
class RuntimeException {
private:
string errorMsg;
public:
RuntimeException(const string& err) { errorMsg = err;}
string getMessage() const { return errorMsg; }
};
// Exception thrown on performing top or pop of an empty stack.
class StackEmpty : public RuntimeException { public:
StackEmpty(const string& err) : RuntimeException(err) {}
};
class StackFull : public RuntimeException { public:
StackFull(const string& err) : RuntimeException(err) {}
};
class QueueEmpty : public RuntimeException { public:
QueueEmpty(const string& err) : RuntimeException(err) { }
};
11
Array-based Stack
• A simple way of implementing the Stack Algorithm size()
return t + 1
ADT uses an array
• We add elements from left to right Algorithm pop()
• A variable keeps track of the index of the if empty() then
throw
top element
StackEmpty
• The array storing the stack elements may else
become full tt1
• A push operation will then throw a return S[t +
Algorithm
1] push(o)
StackFull exception if t = [Link]() 1 then
throw
StackFull
else
tt+1
S[t] o
12
Performance and Limitations
• Performance
• Let n be the number of elements in the stack
• The space used is O(n)
• Each operation runs in time O(1)
• Limitations
• The maximum size of the stack must be defined a priori and cannot be
changed
• Trying to push a new element into a full stack causes an implementation-
specific exception
13
Array-based Stack in C++
template <typename E>
class ArrayStack {
private:
E* S; // array holding the stack
int cap; // capacity
int t; // index of top element
public:
// constructor given capacity
ArrayStack( int c):S(new E[c]), cap(c), t(-1)
{ }
void pop() {
if (empty()) throw StackEmpty(“Pop from empty
stack”);
t--;
}
void push(const E& e) {
if (size() == cap) throw
StackFull(“Push to full stack”);
S[++ t] = e;
}
… (other methods of Stack interface) 14
Example use in C++ * indicates top
ArrayStack<int> A;
// A = [ ], size = 0
[Link](7);
// A = [7*], size = 1
[Link](13);
// A = [7, 13*], size = 2
cout << [Link]() << endl; [Link](); // A = [7*], outputs: 13
[Link](9);
// A = [7, 9*], size =
2
cout << [Link]() << endl;
// A = [7, 9*], outputs: 9
cout << [Link]() << endl; [Link](); // A = [7*], outputs: 9
ArrayStack<string> B(10);
// B = [ ], size = 0
[Link]("Bob");
// B = [Bob*], size = 1
15
[Link]("Alice");
Parentheses Matching
• Each “(”, “{”, or “[” must be paired with a matching “)”, “}”, or “[”
• correct: ( )(( )){([( )])}
• correct: ((( )(( )){([( )])}
• incorrect: )(( )){([( )])}
• incorrect: ({[ ])}
Algorithm ParenMatch(X,n):
Input: An array X of n tokens, each of which is either a grouping symbol, a variable, an
arithmetic operator, or a number
Output: true if and only if all the grouping symbols in X match
Let S be an empty stack
for i=0 to n-1 do
if X[i] is an opening grouping symbol then
[Link](X[i])
else if X[i] is a closing grouping symbol then
if [Link]() then
return false {nothing to match with}
if [Link]() does not match the type of X[i] then
return false {wrong type}
if [Link]() then
return true {every symbol matched}
else return false {some symbols were never matched}
16
Evaluating Arithmetic Expressions
– 3 * 2 + 7 = (14 – (3 * 2) ) + 7
Operator precedence
* has precedence over +/–
Associativity
operators of the same precedence group
evaluated from left to right
Example: (x – y) + z rather than x – (y + z)
Idea: push each operator on the stack, but first pop and
perform higher and equal precedence operations.
17
Algorithm for Evaluating Expressions
Algorithm doOp() Two stacks:
x [Link](); opStk holds operators
y [Link](); valStk holds values
op [Link](); Use $ as special “end of input” token
[Link]( y op x ) with lowest precedence
Algorithm repeatOps( refOp ):
while ( [Link]() > 1 prec(refOp) ≤ prec([Link]())
doOp()
Algorithm EvalExp()
Input: a stream of tokens representing an arithmetic expression (with numbers)
Output: the value of the expression
while there’s another token z
if isNumber(z) then
[Link](z)
else
repeatOps(z);
[Link](z)
repeatOps($);
return [Link]()
Stacks 18
Algorithm on an Example Expression
Operator ≤ has lower
14 ≤ 4 – 3 * 2 + 7 precedence than +/–
4 –
14 ≤
3 *
$ $
4 –
7 $
14 ≤ F
+ -2 + 5
+ 14 ≤ 14 ≤
2 2
3 * 3 * 6
4 – 4 – 4 – -2 +
14 ≤ 14 ≤ 14 ≤ 14 ≤
Stacks 19
The Queue ADT
• The Queue ADT stores arbitrary objects
• Insertions and deletions follow the first-in first-out scheme
• Insertions are at the rear of the queue and removals are at the front of
the queue
• Main queue operations:
• enqueue(object): inserts an element at the end of the queue
• dequeue(): removes the element at the front of the queue
• Auxiliary queue operations:
• object front(): returns the element at the front without removing it
• integer size(): returns the number of elements stored
• boolean empty(): indicates whether no elements are stored
• Exceptions
• Attempting the execution of dequeue or front on an empty queue throws an
QueueEmpty
Queues 20
Example
Operation Output Q
enqueue(5) – (5)
enqueue(3) – (5, 3)
dequeue() – (3)
enqueue(7) – (3, 7)
dequeue() – (7)
front() 7 (7)
dequeue() – ()
dequeue() “error” ()
empty() true ()
enqueue(9) – (9)
enqueue(7) – (9, 7)
size() 2 (9, 7)
enqueue(3) – (9, 7, 3)
enqueue(5) – (9, 7, 3, 5)
dequeue() – (7, 3, 5)
21
The C++ STL Queue
size(): Return the number of elements in the queue.
empty(): Return true if the queue is empty and false otherwise.
push(e): Enqueue e at the rear of the queue.
pop(): Dequeue the element at the front of the queue.
front(): Return a reference to the element at the queue’s front.
back(): Return a reference to the element at the queue’s rear.
#include <iostream>
#include <queue>
Using namespace std;
int main (){
queue<int> myqueue;
int myint;
cout << "Please enter some integers (enter 0 to end):\n";
do {
cin >> myint;
[Link] (myint);
} while (myint);
cout << "myqueue contains: ";
while (![Link]()){
cout << ' ' << [Link]();
[Link]();
}
cout << '\n';
return 0;
} 22
Queue Interface in C++
• C++ interface corresponding to our Queue ADT
template <typename E>
class Queue {
public:
int size() const;
bool empty() const;
const E& front() const;
void enqueue (const E& e);
void dequeue();
};
23
Simple implementation of queue of int using linked nodes
Queue::Queue():head(NULL), tail(NULL), n(0) {}
Queue::~Queue(){ while (!empty()) pop();}
void Queue::enqueue(int value){//old addEnd from int
IntNode *v = new IntNode(value);
class Queue {
private: if (head==NULL) {
IntNode head = v;
*head; tail = v;
IntNode }else{
*tail;
tail->next = v;
int n; //to
count tail = v;
public: }
Queue(); n++;
~ Queue(); }
void
enqueue(int value); Void Queue::dequeue(){//old removeFront
bool
IntNode *v = head;
empty() const;
int front() head = head->next;
const; n--;
void dequeue(); delete v;
int size(); v=NULL;
}; }
bool Queue::empty() const{return (head==NULL);}
int Queue::front() const {return head->elem;}
int Queue::size(){return n;} 24
Reusing our IntLinkedList or SLinkedList
template <typename E>
class Queue {
class Queue { private:
private: SLinkedList<E> q;
IntLinkedList public:
q; void push(const E& value);
void pop() ;
E& top() const;
public: bool empty() const;
void };
enqueue(int value) {
template <typename E>
[Link](value); void Queue<E>::enqueue(const E& value) {
} [Link](value);
}
void dequeue () template <typename E>
{ void Queue<E>::dequeue() {
[Link]();
[Link](); }
}
int front(){ template <typename E>
return E& Queue<E>::top() const{
return [Link]();
[Link](); }
} template <typename E>
int size(){ bool Queue<E>::empty() const{
return return [Link]();
[Link](); }
25
}
Array-based Queue: using the Modulo Operator
to Implement a Circular Array
• Use an array of size N in a circular fashion
• Three variables keep track of the front and rear
f index of the front element
r index immediately past the rear element
n number of items in the queue
• Each time we increment f or r, we simply need to compute this
increment as “( f +1) mod N” or “(r +1) mod N,” respectively.
normal configuration
Q
0 1 2 f r
wrapped-around configuration
Q
0 1 2 r f 26
Queue Operations Algorithm size()
return n
• Use n to determine size and
emptiness Algorithm empty()
return (n = 0)
• Operation enqueue throws an
exception if the array is full Algorithm enqueue(o)
if size() = N 1 then
• Operation dequeue throws an throw QueueFull(“full
exception if the queue is empty Queue”)
else
Q[r] o
r (r + 1) mod N
nn+1
Algorithm dequeue()
if empty() then
throw
QueueEmpty(“empty queue”)
else
f (f + 1) mod 27
N
Applications of Queues
• Waiting lists, bureaucracy
• Access to shared resources (e.g., printer)
• Multiprogramming
• Round Robin Schedulers
• We can implement a round robin scheduler using a queue Q by
repeatedly performing the following steps:
1. e = [Link](); [Link]()
2. Service element e
3. [Link](e)
Queue
Dequeue Enqueue
Shared
Service
28
Double-Ended Queues
• Queue-like data structure that supports insertion and deletion at both
the front and the rear of the queue
• usually pronounced “deck”
• Main queue operations:
insertFront(e): Insert a new element e at the beginning of the deque.
insertBack(e): Insert a new element e at the end of the deque.
eraseFront(): Remove the first element of the deque.
eraseBack(): Remove the last element of the deque
• Auxiliary queue operations:
• object front(): returns the element at the front
• object back(): Return the last element of the deque
• integer size(): returns the number of elements stored
• boolean empty(): indicates whether no elements are stored
• Exceptions should be thrown when attempting remove or get
elements from an empty deque
• All operations are in the constant order of complexity O(1)
The C++ STL Deque
• size(): Return the number of elements in the deque.
• empty(): Return true if the deque is empty and false otherwise.
• push_front(e): Insert e at the beginning the deque.
• push_back(e): Insert e at the end of the deque.
• pop_front(): Remove the first element of the deque.
• pop_back(): Remove the last element of the deque.
• front(): Return a reference to the deque’s first element.
• back(): Return a reference to the deque’s last element.
#include <iostream>
#include <deque>
using namespace std
int main () {
deque<int> mydeque;
mydeque.push_back (100);
mydeque.push_back (200);
mydeque.push_back (300);
cout << "Popping out the elements in mydeque:";
while (![Link]()) {
cout << ' ' << [Link]();
mydeque.pop_front();
}
cout << "\nThe final size of mydeque is " << int([Link]()) <<
'\n'; return 0;
}
Implementing Deque using our doubly linked list
typedef string Elem; // list element type
class DNode { // doubly linked list node
private:
Elem elem; // node element value
DNode* prev; // previous node in list
DNode* next; // next node in list
public:
DNode();
DNode(const Elem &data);
friend class DLinkedList; // allow DLinkedList access
};
class DLinkedList { // doubly linked list
public:
DLinkedList(); // constructor
~DLinkedList(); // destructor
bool empty() const; // is list empty?
const Elem& front() const; // get front element
const Elem& back() const; // get back element
void addFront(const Elem &e); // add to front of list
void addBack(const Elem &e); // add to back of list
void removeFront(); // remove from front
void removeBack(); // remove from back
private: // local type definitions
DNode* header; // pointers to front and back nodes
DNode* trailer;
};
Implementing deque using doubly linked list
typedef string Elem; // deque element type
class LinkedDeque { // deque as doubly linked list
public:
LinkedDeque(); // constructor
int size() const; // number of items in the deque
bool empty() const; // is the deque empty?
const Elem& front() const; // the first element
const Elem& back() const; // the last element
void insertFront(const Elem& e); // insert new first element
void insertBack(const Elem& e); // insert new last element
void removeFront(); // remove first element
void removeBack(); // remove last element
private: // member data
DLinkedList D; // linked list of elements
int n; // number of elements
};
class RuntimeException { //defining an exception class
private:
string errorMsg;
public:
RuntimeException(const string& err) { errorMsg = err;}
string getMessage() const { return errorMsg; }
};
class DequeEmpty : public RuntimeException { public:
DequeEmpty(const string& err) : RuntimeException(err) { }
};
Implementing deque using doubly linked list
LinkedDeque::LinkedDeque():n(0){}
void LinkedDeque::insertFront(const Elem& e) {
[Link](e);
n++;
}
void LinkedDeque::insertBack(const Elem& e) {
[Link](e);
n++;
}
void LinkedDeque::removeFront() {
if (empty())
throw DequeEmpty("removeFront of empty deque");
[Link]();
n--;
}
void LinkedDeque::removeBack() {
if (empty())
throw DequeEmpty("removeBack of empty deque");
[Link]();
n--;
}
Implementing deque using doubly linked list
bool LinkedDeque::empty() const {
return [Link]();
}
const Elem& LinkedDeque::back() const {
if (empty())
throw DequeEmpty("back of empty deque");
return [Link]();
}
const Elem& LinkedDeque::front() const {
if (empty())
throw DequeEmpty("front of empty deque");
return [Link]();
}
int main() {
LinkedDeque d;
[Link]("you");
[Link]("are");
[Link]("How");
[Link]("handsom");
cout <<endl <<[Link]()<<" " << [Link]();
[Link]();
[Link]();
cout <<endl <<[Link]()<<" " << [Link]();
}