Chapter 5
Chapter 5
As can be seen, ADT specifies what each operation does, but not how it does
it, i.e., it explains the basic idea, but not the details of the implementation in
a particular language such as C++ or Java!
▪ In this chapter, we will see three examples of ADTs:
➢ Stacks
➢ Queues
➢ Dequeues
2
3
STACKS
STACKS
▪ A stack is a data structure that stores objects which can be added or
removed according to the “Last-in First-out (LIFO)” principle.
4
STACKS
▪ Formally, a stack is an abstract data type (ADT) that supports the
following operations:
➢ push(e): Insert element e at the top of the stack.
➢ pop(): Remove the top element from the stack, but does not
return that element (error if empty).
➢ top(): Return a reference to the value of the top element on the
stack without removing that element (error if empty).
➢ size(): Return the number of elements in the stack.
➢ empty(): Return true if the stack is empty and false otherwise
#include <stack>
using std::stack; This line defines myStack as a stack in
stack<int> myStack; which the elements are of type int;
in this case we say the base type is int
7
ADT VS. STL
▪ Don’t be confused between the following:
➢ Abstract Data types (ADT), which the textbook uses to describe data structures
without C++ implementation details
➢ Standard Template Library (STL), which are readily-available data structures
provided by C++
▪ Unfortunately, in the textbook there might be some differences between the two,
which can be confusing
➢ Example: In a stack ADT, we said earlier that the methods pop() and top()
return an error if the stack is empty. It turns out that, with stacks in C++’s STL,
these two methods do not throw an exception if the stack is empty!
▪ You don’t need to worry about such subtle differences between ADT and STL in the
textbook; just don’t be surprised if you see such differences.
8
STACK IMPLEMENTATION
▪ In practice, whenever you need to use a data structure such as a stack,
you will use the readily-available implementation in C++’s STL
▪ In other words, in practice you won’t have to write code from scratch
to implement the data structure
9
ARRAY-BASED STACK
▪ We can implement a stack by storing its elements in an array S of a
fixed-size N, where the bottom element is S[0] and the top one is S[t]
top
▪ It is as if we rotated
bottom top
the stack clockwise:
bottom
10
CLASS DEFINITION
template <typename E>
class ArrayStack { This confusing line is simply equivalent to:
enum { DEF_CAPACITY = 100 }; const int DEF_CAPACITY = 100;
public: To see why this is the case, recall that we
can define types as follows:
// public members go here…
enum Day { Mon, Tue, Wed };
This would automatically set Mon=0, and
private: Tue=1, etc. But we can also specify the
numbers to be given to each element, e.g.
// private members go here… enum Day { Mon=1, Tue=3, Wed=2 };
Thus, the following is equivalent to defining
};
a constant “DEF_CAPACITY”:
enum { DEF_CAPACITY = 100 }
11
CLASS DEFINITION
template <typename E>
class ArrayStack {
enum { DEF_CAPACITY = 100 };
public:
ArrayStack(int cap = DEF_CAPACITY); /* constructor with a parameter “cap”
whose default value is DEF_CAPACITY */
private:
13
};
CLASS DEFINITION
template <typename E>
class ArrayStack {
enum { DEF_CAPACITY = 100 };
public:
ArrayStack(int cap = DEF_CAPACITY); /* constructor with a parameter “cap”
whose default value is DEF_CAPACITY */
int size() const; /* returns the number of elements currently in the stack
(we will see its implementation in the coming slides) */
bool empty() const; // returns “true” if the stack is empty
const E& top() const throw(StackEmpty); // returns the top element
void push(const E& e) throw(StackFull); // push element “e” onto stack
void pop() throw(StackEmpty); // pop the stack (i.e., remove top element)
private:
14
};
CLASS DEFINITION
template <typename E>
class ArrayStack {
enum { DEF_CAPACITY = 100 };
public:
ArrayStack(int cap = DEF_CAPACITY); /* constructor with a parameter “cap”
whose default value is DEF_CAPACITY */
int size() const; /* returns the number of elements currently in the stack
(we will see its implementation in the coming slides) */
bool empty() const; // returns “true” if the stack is empty
const E& top() const throw(StackEmpty); // returns the top element
void push(const E& e) throw(StackFull); // push element “e” onto stack
void pop() throw(StackEmpty); // pop the stack (i.e., remove top element)
private:
E* S; // We will define S as an array that stores the elements (see the picture above!)
int capacity; // stack’s maximum capacity (i.e., this is “N” in the picture above!)
int t; // index of the top of the stack (see the picture above!)
15
};
CODE
DEMONSTRATION
16
METHOD DEFINITION
// constructor
template <typename E> ArrayStack<E>::ArrayStack(int cap)
: S(new E[cap]), capacity(cap), t(−1) { }
17
CODE
DEMONSTRATION
18
METHOD DEFINITION
// constructor
template <typename E> ArrayStack<E>::ArrayStack(int cap)
: S(new E[cap]), capacity(cap), t(−1) { }
21
METHOD DEFINITION
// push element onto the stack
template <typename E> void ArrayStack<E>::push(const E& e) throw(StackFull)
{
22
METHOD DEFINITION
// push element onto the stack
template <typename E> void ArrayStack<E>::push(const E& e) throw(StackFull)
{
if (size() == capacity)
throw StackFull("Push to full stack");
S[++t] = e;
}
23
CODE
DEMONSTRATION
24
METHOD DEFINITION
// push element onto the stack
template <typename E> void ArrayStack<E>::push(const E& e) throw(StackFull)
{
if (size() == capacity)
throw StackFull("Push to full stack");
S[++t] = e;
}
25
CODE
DEMONSTRATION
26
EXAMPLE
27
COMPLEXITY
▪ What is the complexity of the different methods?
?
?
?
?
?
28
COMPLEXITY
▪ What is the complexity of the different methods?
29
IMPLEMENTATION USING A LINKED LIST
▪ Instead of implementing a stack using an array:
S
012 t N-1
...
1st element 2nd element (n-1)th element nth element
30
IMPLEMENTATION USING A LINKED LIST
bottom
bottom
...
top
top
Array = rotate clockwise Linked list = rotate anti-clockwise
top top
bottom
bottom
top
top
bottom bottom
31
CLASS DEFINITION
Compared to the array-based typedef string Elem;
implementation, this part is exactly class LinkedStack{
the same, except that “push” now public :
doesn’t throw a “StackFull” exception, LinkedStack(); // constructor
int size() const ;
since the list size has no upper limit
bool empty() const ;
const E& top() const throw( StackEmpty );
void push(const E& e);
Compared to the array-based
void pop() throw( StackEmpty );
implementation, “S” is now defined
private:
as a linked list instead of an array,
SLinkedList<Elem> S; // linked list
and we now have “n” instead of “t” int n; // number of elements in stack
and “capacity” };
bottom
...
top
32
METHOD DEFINITION
LinkedStack::LinkedStack(): S(), n(0) { } // constructor, initializes S and sets n=0
int LinkedStack::size() const {// returns the number of elements
bottom
...
top
33
METHOD DEFINITION
LinkedStack::LinkedStack(): S(), n(0) { } // constructor, initializes S and sets n=0
int LinkedStack::size() const {// returns the number of elements
return n;
}
bool LinkedStack::empty() const { // returns true if stack is empty
return n == 0;
}
const Elem& LinkedStack::top() const throw(StackEmpty) { // return top of stack
if (empty())
throw StackEmpty("Top of empty stack");
return S.front(); // “front()” is already implemented in the linked list S
}
bottom
...
top
34
METHOD DEFINITION
void LinkedStack::push(const Elem& e) // push “e” onto stack
{
}
void LinkedStack::pop() throw(StackEmpty) // pop the stack
{
bottom
...
top
35
METHOD DEFINITION
void LinkedStack::push(const Elem& e) // push “e” onto stack
{
++n;
S.addFront(e);
}
void LinkedStack::pop() throw(StackEmpty) // pop the stack
{
bottom
...
top
36
METHOD DEFINITION
void LinkedStack::push(const Elem& e) // push “e” onto stack
{
++n;
S.addFront(e);
}
void LinkedStack::pop() throw(StackEmpty) // pop the stack
{
if (empty())
throw StackEmpty("Pop from empty stack");
−−n;
S.removeFront();
}
bottom
...
top
37
CODE
DEMONSTRATION
38
39
QUEUES
QUEUES
▪ A queue is a data structure that stores objects which can be added
or removed according to the “First-in First-out (FIFO)” principle.
Stack: Last in, first out Queue: First in, first out
enqueue
40
QUEUES
▪ Formally, a queue is an abstract data type (ADT) that supports the
following operations:
o enqueue(e): Insert element e at the rear of the queue.
o dequeue(): Remove element at the front of the queue; an error
occurs if the queue is empty.
o front(): Return a reference to the value of the front element in the
queue; an error occurs if the queue is empty.
o size(): Return the number of elements in the queue.
o empty(): Return true if the queue is empty and false otherwise.
#include <queue>
using std::queue; This line defines myQueue as a queue in
queue<float> myQueue; which the elements are of type float;
in this case, the “base type” is float
43
ADT VS. STL
▪ Reminder: Don’t be confused between the following:
➢ Abstract Data types (ADT), which the textbook uses to describe data structures
➢ Standard Template Library (STL), which hass data structures implemented in C++
▪ As we said earlier, there might be subtle differences between the two. For example:
➢ The methods enqueue() and dequeue() in the queue ADT have different
names in C++’s STL, which are push() and pop(), respectively.
➢ Compared to the ADT, the queues in C++’s STL provide an additional method
called back(), which returns a reference to the element at the queue’s rear
▪ As we said earlier, in practice you won’t have to write code from scratch to
implement a data structure such as a queue. Nevertheless, to better understand data
structures, we will show how to implement them in C++ from scratch!
44
ARRAY-BASED QUEUE
▪ In the array-based implementation of this queue, it helps to imagine
the ATM machine moving forward, rather than the customers!
45
ARRAY-BASED QUEUE
f = index of front customer; r = index where a new joiner should stand;
n = No. of elements; N = array size
A potential state
of the queue: n=5
f r N-1
Three customers
finished, and four
joined the queue: n=6
r f N-1 46
QUEUE PSEUDO CODE
Algorithm size():
return n
Algorithm empty():
return (n = 0)
Algorithm front(): n=6
if empty() then
r f N-1
throw QueueEmpty exception
return Q[ f ]
Algorithm enqueue(e):
"(r+1) mod N" is the remainder after dividing
if size() = N then
(r+1) by N. The simply means:
throw QueueFull exception
Q[r] ← e "Add r by 1, and if it falls out of the array,
r ← (r+1) mod N then go back to the beginning of the array."
n ← n+1
Algorithm dequeue():
if empty() then
All methods are performed in O(1)
throw QueueEmpty exception
f ← ( f +1) mod N time, and the space usage is O(N)
47
n = n−1
CODE
DEMONSTRATION
Implementing Queues using Array
48
USING A CIRCULARLY-LINKED LIST
▪ Instead of implementing a queue using an array:
r f N-1
How about using a circularly-linked list instead?
cursor
(front) (back)
Although the list can be used to implement a queue that rotates (i.e., where the
ATM moves forward, just like with arrays); the ATM will always be on the left!
49
USING A CIRCULARLY-LINKED LIST
▪ Recall the main operations of a circularly-linked list:
➢ remove(): deletes the node at the front of the list, i.e., the node right
after the one that the curser points to.
➢ add(e): puts e in a new node, which is added it to the front of the list.
➢ advance(): moves the cursor one step forward in the list
before calling
“advance()”:
after calling
“advance()”:
50
USING A CIRCULARLY-LINKED LIST
▪ To perform a dequeue, i.e., to remove the front customer:
➢ call the method remove(), which deletes the node at the front, i.e., the
node right after the one that the curser points to
cursor
before calling
“remove()”:
(front) (back)
cursor
after calling
“remove()”:
(front) (back)
51
USING A CIRCULARLY-LINKED LIST
▪ To perform an enqueue, i.e., to add customer to the queue:
➢ call add(e), which puts e in a new node at the front of the list
➢ then call advance(), which moves the cursor one step forward
cursor
before adding e
(front) (back)
cursor
after calling
add(e)
(front) (back)
cursor
after calling
advance()
(front) (back) 52
C++ IMPLEMENTATION
typedef string Elem; // queue element type
class LinkedQueue {
public:
LinkedQueue(); // constructor
int size() const; // number of items in the queue
bool empty() const; // is the queue empty?
const Elem& front() const throw(QueueEmpty); // the front element
void enqueue(const Elem& e); // enqueue element at rear
void dequeue() throw(QueueEmpty); // dequeue element at front
private:
CircleList C; // circular list of elements
int n; // number of elements
};
cursor
(front) (back) 53
C++ IMPLEMENTATION
LinkedQueue::LinkedQueue() : C(), n(0) { } // constructor
(front) (back) 54
C++ IMPLEMENTATION
LinkedQueue::LinkedQueue() : C(), n(0) { } // constructor
56
57
DEQUES
DOUBLE-ENDED QUEUE (DEQUE)
▪ It’s a queue that supports insertion and deletion at both the front
and the back of the queue!
A potential state
of the queue:
A customer can
skip the line!!
59
DEQUES IN C++
▪ C++ provides a readily-available implementation of a deque
(i.e., you do not have to implement it yourself).
#include <deque>
using std::deque; This line defines myDeque as a deque in
deque<string> myDeque; which the elements are of type string;
in this case, the “base type” is string
60
USING A DOUBLY-LINKED LIST
▪ We will implement a deque using a doubly-linked list:
head trailer
61
C++ IMPLEMENTATION
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 throw(DequeEmpty); // the first element
const Elem& back() const throw(DequeEmpty); // the last element
void insertFront(const Elem& e); // insert new first element
void insertBack(const Elem& e); // insert new last element
void removeFront() throw(DequeEmpty); // remove first element
void removeBack() throw(DequeEmpty); // remove last element
private: // member data
DLinkedList D; // linked list of elements
int n; // number of elements
}; 62
C++ IMPLEMENTATION
void LinkedDeque::insertFront(const Elem& e) { D.addFront(e); n++; }
void LinkedDeque::insertBack(const Elem& e) { D.addBack(e); n++; }
void LinkedDeque::removeFront() throw(DequeEmpty) {
if (empty()) throw DequeEmpty("removeFront of empty deque");
D.removeFront();
n−−;
}
void LinkedDeque::removeBack() throw(DequeEmpty) {
if (empty()) throw DequeEmpty("removeBack of empty deque");
D.removeBack();
n−−;
} All methods are performed in O(1)
time, and the space usage is O(n)
63
CODE
DEMONSTRATION
DEQUEUES WITH STL
64