0% found this document useful (0 votes)
9 views64 pages

Chapter 5

Chapter 5 discusses abstract data types (ADTs) focusing on stacks, queues, and dequeues, explaining their operations without implementation details. Stacks operate on a Last-in First-out (LIFO) principle, while queues follow a First-in First-out (FIFO) principle, each supporting specific operations like push, pop, enqueue, and dequeue. The chapter also highlights the distinction between ADTs and C++'s Standard Template Library (STL), which provides ready-to-use implementations of these data structures.

Uploaded by

hbn3142
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views64 pages

Chapter 5

Chapter 5 discusses abstract data types (ADTs) focusing on stacks, queues, and dequeues, explaining their operations without implementation details. Stacks operate on a Last-in First-out (LIFO) principle, while queues follow a First-in First-out (FIFO) principle, each supporting specific operations like push, pop, enqueue, and dequeue. The chapter also highlights the distinction between ADTs and C++'s Standard Template Library (STL), which provides ready-to-use implementations of these data structures.

Uploaded by

hbn3142
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 64

CHAPTER 5:

STACKS, QUEUES, AND DEQUEUES


1

Dr. Mohamad Kassab


ABSTRACT DATA TYPES
▪ The authors of the textbook use what they call an abstract data type (ADT),
which is a mathematical model of a data structure that specifies:
➢ The type of the data stored
➢ The operations supported on the data
➢ The types of the parameters of those operations

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

▪ Notice how the ADT does not provide implementation details!


5
EXAMPLE
▪ In this example, if we write a
stack as follows:
(x,y,z)
it means that:
o z is the top of the stack
o x is at the bottom

In this example, under “output”,


we write one of the following:
o “error” if there is an error
o The value returned (if the
function returns a value)
o “−” if there is no error and
the function returns no value
6
STACKS IN C++
▪ C++ provides a readily-available implementation of a stack
(i.e., you do not have to implement it yourself).

▪ This comes as part of the “Standard Template Library” (STL)


of C++, e.g., here is how to define a stack of integers:

#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

▪ Nevertheless, to better understand data structures, we will show how


to implement them in C++ from scratch!

▪ Let us consider an implementation of a stack using an array…

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]

Note that N specifies


the maximum capacity

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 */

In general, you can specify the default value of a parameter, e.g.,


private:
void printMessage( string m = “Hello!” ){
cout << m;
}
Now if you don’t specify the value of “m”, it will be set to “Hello!”
};
printMessage( ); // this line prints “Hello!”
But you can still specify any value you want for “m”, e.g.,
printMessage( “Goodbye” ); // this line prints “Goodbye!” 12
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) */

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) { }

This is an initializer list! Remember,


this is the same as writing:
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) { }

// returns the number of items in the stack


template <typename E> int ArrayStack<E>::size() const {
// what’s written here??
}
// is the stack empty?
template <typename E> bool ArrayStack<E>::empty() const {
// what’s written here??
}
// return top of stack
template <typename E> const E& ArrayStack<E>::top() const throw(StackEmpty) {
// what’s written here??
} 19
METHOD DEFINITION
// constructor
template <typename E> ArrayStack<E>::ArrayStack(int cap)
: S(new E[cap]), capacity(cap), t(−1) { }

// returns the number of items in the stack


template <typename E> int ArrayStack<E>::size() const {
return (t + 1);
}
// is the stack empty?
template <typename E> bool ArrayStack<E>::empty() const {
return (t < 0);
}
// return top of stack
template <typename E> const E& ArrayStack<E>::top() const throw(StackEmpty) {
if (empty()) throw StackEmpty("Top of empty stack");
return S[t];
} 20
CODE
DEMONSTRATION

21
METHOD DEFINITION
// push element onto the stack
template <typename E> void ArrayStack<E>::push(const E& e) throw(StackFull)
{

// what’s written here?

// pop the stack


template <typename E> void ArrayStack<E>::pop() throw(StackEmpty)
{

// what’s written here?

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;
}

// pop the stack


template <typename E> void ArrayStack<E>::pop() throw(StackEmpty)
{

// what’s written here?

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;
}

// pop the stack


template <typename E> void ArrayStack<E>::pop() throw(StackEmpty)
{
if (empty())
throw StackEmpty("Pop from empty stack");
-−t;
}

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

How about implementing it using a linked list?

...
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

// What’s written here?


}
bool LinkedStack::empty() const { // returns true if stack is empty

// What’s written here?


}
const Elem& LinkedStack::top() const throw(StackEmpty) { // return top of stack

// What’s written here?

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
{

// What’s written here?

}
void LinkedStack::pop() throw(StackEmpty) // pop the stack
{

// What’s written here?

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
{

// What’s written here?

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

push pop dequeue

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.

▪ Notice how the ADT does not provide implementation details!


41
EXAMPLE
▪ In this example, if we write a
queue as follows:
(x,y,z)
it means that:
o z is the top of the stack
o x is at the bottom

In this example, under “output”,


we write one of the following:
o “error” if there is an error
o The value returned (if the
function returns a value)
o “−” if there is no error and
the function returns no value
42
QUEUES IN C++
▪ C++ provides a readily-available implementation of a queue
(i.e., you do not have to implement it yourself).

▪ This comes as part of the “Standard Template Library” (STL)


of C++, e.g., here is how to define a queue of floats:

#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

int LinkedQueue::size() const { return n; } // number of items in the queue

bool LinkedQueue::empty() const { return n == 0; } // is the queue empty?

const Elem& LinkedQueue::front() const throw(QueueEmpty) {// get front element


if (empty()) throw QueueEmpty("front of empty queue");
return C.next.; }

void LinkedQueue::enqueue(const Elem& e) { // insert after cursor


C.add(e);
C.advance();
n++; }
cursor

(front) (back) 54
C++ IMPLEMENTATION
LinkedQueue::LinkedQueue() : C(), n(0) { } // constructor

int LinkedQueue::size() const { return n; } // number of items in the queue

bool LinkedQueue::empty() const { return n == 0; } // is the queue empty?

const Elem& LinkedQueue::front() const throw(QueueEmpty) {// get front element


if (empty()) throw QueueEmpty("front of empty queue");
return C.next; }

void LinkedQueue::enqueue(const Elem& e) { // insert after cursor


C.add(e);
C.advance(); All methods are performed in O(1)
n++; }
time, and the space usage is O(n)
void LinkedQueue::dequeue() throw(QueueEmpty) {
if (empty()) throw QueueEmpty("dequeue of empty queue");
C.remove();
n−−; } 55
CODE
DEMONSTRATION
Queues with Stl

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!!

Last customer can


now leave before
reaching the ATM 58
DOUBLE-ENDED QUEUE (DEQUE)
▪ It’s a queue that supports insertion and deletion at both the front
and the back of the queue!
▪ This is done using the methods:
➢ insertFront(e)
➢ insertBack(e)
➢ eraseFront()
➢ eraseBack()

59
DEQUES IN C++
▪ C++ provides a readily-available implementation of a deque
(i.e., you do not have to implement it yourself).

▪ This comes as part of the “Standard Template Library” (STL)


of C++, e.g., here is how to define a queue of floats:

#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

You might also like