You are on page 1of 51

CS142

Introduction to
Computer Science II

Stacks & Queues


4-22-2008
CS142
 Return & discuss Exam#2
 Other Data Structures:
 stack
 queue

Reading Assignment:
 Prof. Maciel’s notes for CS142, Chapter 9, “Other Data
Structures”
 Koffman & Wolfgang, selected readings from Chapters 5, 6
and 9
HW#8 posted, due Thursday, 4/24
Final Exam: Tues, 4/29, SC362, 3:15 – 6:15 pm
Reminder: Grading Policy
 Your evaluation will be based on several homework
assignments (A), which will be mostly programming
assignments, two tests (T), two test corrections (C), one
for each test, a final exam (F), and your lab attendance
(L). Your course grade will be computed using the
following formula:
21% A + 23% T1 + 2% C1 + 23% T2 + 2% C2 + 24% F + 5% L

 The lab grade (L) will be based on your attendance of the


labs, your performance on the assignments, and whether
you hand in your assignments early. Details will be given
in the Assignment Policy.
Recap: Data Structures
 Stack,
 Queue,
 Dequeue,
 Set,
 Heap,
 Map,
 Tree,
 Graph
 others?...
Stack ADT
 The stack class and its member functions:
 push, pop, top, empty, and size
 How the C++ standard library implements stack
 How to implement stack using:
 An array
 A linked list
 Using stack in applications
 Testing for balanced (properly nested)
parentheses
 Evaluating arithmetic expressions
Specification of Stack ADT

Functions
bool empty()
Stack Applications: Balancing
Brackets
 Arithmetic expressions should balance parentheses:
(a+b*(c/(d-e)))+(d/e)
 Not too hard if limited to parentheses only:
 Increment depth counter on (
 Decrement depth counter on )
 If always ≥ 0, then balanced properly
 Problem harder if also use brackets: [] {}
 A stack provides a good solution!
Balancing Brackets: Overview
 Start with empty stack of currently open brackets
 Process each char of an expression string
 If it is an opener, push it on the stack
 If it is a closer, check it against the top stack element
 If stack empty, or not a matching bracket:
not balanced, so return false
 Otherwise, it matches: pop the opener
 If stack is empty at the end, return true
 If not empty, then some bracket unmatched: false
is_balanced Code
bool is_balanced(const string& expression) {
stack<char> s;
bool balanced = true;
string::const_iterator iter = expression.begin();
while (balanced && (iter != expression.end())) {
char next_ch = *iter;
if (is_open(next_ch)) {
s.push(next_ch);
} else if (is_close(next_ch)) {
if (s.empty()) {
balanced = false;
} else {
char top_ch = s.top();
s.pop();
balanced = OPEN.find(top_ch) == CLOSE.find(next_ch);
}
}
++iter;
}
return balanced && s.empty();
}
is_balanced Code:
Helper Routines
// The set of opening parentheses.
const string OPEN = "([{";
// The corresponding set of closing parentheses.
const string CLOSE = ")]}";

bool is_open(char ch) {


return OPEN.find(ch) != string::npos;
}

bool is_close(char ch) {


return CLOSE.find(ch) != string::npos;
}
Testing is_balanced
 Expressions without brackets
 Expressions with just one level
 Expressions with multiple levels, same kind
 Expressions with multiple levels, different kinds
 Expressions with same # open/close, but bad order:
 )(
 Expressions with open/close order ok, wrong bracket:
 (]
 Expressions with too many openers
 Expressions with too many closers
Implementing stack as
an adapter of vector
 The standard library implements the stack as an
adapter of any sequential container.
 The member functions are delegated to the
member functions of the container.
 Any of the three sequential containers can be
used: vector, list, or deque.
 The standard library uses the deque by default.
 We will use the vector, and describe the deque in
the next chapter.
Implementing Stack as an
Extension of Vector (2)

Top element of the


Stack is at the
highest index
stack Code

template<typename Item_Type>
void stack<Item_Type>::push(const Item_Type& item) {
container.push_back(item);
}

template<typename Item_Type>
Item_Type& stack<Item_Type>::top() {
return container.back();
}

template<typename Item_Type>
const Item_Type& stack<Item_Type>::top() const {
return container.back();
}
Stack Code (2)
template<typename Item_Type>
void stack<Item_Type>::pop() {
container.pop_back();
}
template<typename Item_Type>
bool stack<Item_Type>::empty() const {
return container.empty();
}
template<typename Item_Type>
size_t stack<Item_Type>::size() const {
return container.size();
}
Implementing stack as a
Linked Structure
 We can implement Stack using a linked
list:
Linked_Stack Code
template<typename Item_Type>
class stack {

private:
// Data fields
// Insert definition of class Node here
#include "Node.h"

/** A pointer to the top of the stack. */


Node* top_of_stack;

}; // End class stack


Linked_Stack Code (2)
template<typename Item_Type>
void stack<Item_Type>::push(const Item_Type& item) {
top_of_stack = new Node(item, top_of_stack);
}

template<typename Item_Type>
Item_Type& stack<Item_Type>::top() {
return top_of_stack->data;
}

template<typename Item_Type>
const Item_Type& stack<Item_Type>::top() const {
return top_of_stack->data;
}
Linked_Stack Code (3)
template<typename Item_Type>
void stack<Item_Type>::pop() {
Node* old_top = top_of_stack;
top_of_stack = top_of_stack->next;
delete old_top;
}

template<typename Item_Type>
bool stack<Item_Type>::empty() const {
return top_of_stack == NULL;
}
Comparison of stack
Implementations
 The code for the vector version is very similar to the
implementation in the C++ standard library.
 By using delegation, we avoid having to implement the
operations ourselves and are assured of O(1)
performance.
 Using the standard containers has a space penalty.
 vector allocates additional space to provide ammortized
constant insertion time.
 list uses a double linked list which has unneeded pointers.
 Using a single linked list allocates space for the links,
but these are necessary.
 Using a single linked list also provides O(1)
performance.
Additional Stack Applications:
Evaluating Arithmetic Expressions
 Expressions normally written in infix form
 Binary operators appear between their
operands:
a+b c*d e*f-g
 A computer normally scans in input order
 One must always compute operand values first
 So easier to evaluate in postfix form:
ab+ cd* ef*g-
The Queue Abstract Data Type
 Visualization: queue = line of customers waiting for
some service
 In Britain, it is the common word for this
 Next person served is one who has waited longest:
 First-in, First-out = FIFO
 New elements are placed at the end of the line
 Next served is one at the front of the line
A Print Queue
 Operating systems use queues to
 Track tasks waiting for a scarce resource
 Ensure tasks carried out in order generated
 Print queue:
 Printing slower than selecting pages to print
 So use a queue, for fairness
 A stack would be inappropriate (more in a
moment)
 (Consider multiple queues, priorities, etc., later)
A Print Queue (continued)
Unsuitability of a Print Stack
 Stacks are last-in, first-out (LIFO)
 Most recently selected document would print next
 Unless queue is empty, your job may never execute
 ... if others keep issuing print jobs
Specification of the Queue ADT

Function
bool empty()
Maintaining a Queue of
Customers
Problem: Write a menu-driven program that:
 Maintains a list of customers waiting for service

 Can add a new customer at the end

 Can display name of next customer to serve

 Can display the length of the line


Maintaining a Queue of
Customers (2)
Analysis: Because service is in FIFO order, a queue
is the appropriate data structure
 Top level algorithm: while user not finished,

 Display menu and obtain choice, then


 Perform the selected operation
 The operations are all basic queue operations ...
Maintain_Queue.cpp
int main() {
queue<string> customers;
string name;
int choice_num = 0;
string choices[] = {"push", "front", "pop", "size",
"quit"};
const int NUM_CHOICES = 5;
while (choice_num < NUM_CHOICES - 1) {
cout << "Select an operation on customer queue\n";
for (int i = 0; i < NUM_CHOICES; i++) {
cout << i << ": " << choices[i] << endl;
}
cin >> choice_num;
switch (choice_num) {
. . .
} // End while.
return 0;
}
Insert a new customer
case 0:
cout << "Enter new customer
name\n";
cin >> name;
customers.push(name);
break;
Display the Next Customer
case 1:
cout << "Customer " <<
customers.front()
<< " is next in line\n";
break;
Remove the Next Customer
case 2:
cout << "Customer " <<
customers.front()
<< " removed from line\n";
customers.pop();
break;
Display the Length of the Line
case 3:
cout << "Size of line is " <<
customers.size() << endl;
break;
Exit the Program

case 4:
cout << "Leaving customer queue.\n"
<< "Number of customers in queue is "

<< customers.size()
<< endl;
break;

default:
cout << "Invalid selection\n";
break;
Implementing Queue:
adapter of std::list
 This is a simple adapter class, with following mappings:
 Queue push maps to push_back
 Queue front maps front
 Queue pop maps to pop_front
 ...
 This is the approach taken by the C++ standard library.
 Any sequential container that supports push_back,
front, and pop_front can be used.
 The list
 The deque
Implementing Queue:
Singly-Linked List
This requires front and rear Node pointers:
template<typename Item_Type>
class queue {
. . .
private:
// Insert implementation-specific data fields
// Insert definition of Node here
#include "Node.h"
// Data fields
Node* front_of_queue;
Node* back_of_queue;
size_t num_items;
};
Using a Single-Linked List to
Implement a Queue (continued)
Implementing Queue:
Singly-Linked List (2)
 Insert at tail, using back_of_queue for speed
 Remove using front_of_queue
 Adjust size when adding/removing
 No need to iterate through to determine size
Implementing Queue:
Singly-Linked List (3)
template<typename Item_Type>
void queue<Item_Type>::push(const Item_Type& item) {
if (front_of_queue == NULL) {
back_of_queue = new Node(item, NULL);
front_of_queue = back_of_queue;
back_of_queue = new Node(item, NULL);
front_of_queue = back_of_queue;
} else {
back_of_queue->next = new Node(item, NULL);
back_of_queue = back_of_queue->next;
}
num_items++;
}
Implementing Queue:
Singly-Linked List (4)
template<typename Item_Type>
Item_Type& queue<Item_Type>::front() {
return front_of_queue->data;
}
Implementing Queue:
Singly-Linked List (4)
template<typename Item_Type>
void queue<Item_Type>::pop() {
Node* old_front = front_of_queue;
front_of_queue = front_of_queue->next;
if (front_of_queue == NULL) {
back_of_queue = NULL;
}
delete old_front;
num_items--;
}
Analysis of the Space/Time Issues
 Time efficiency of singly- or doubly-linked list good:
O(1) for all Queue operations
 Space cost: ~3 extra words per item
 vector uses 1 word per item when fully packed
 2 words per item when just grown
 On average ~1.5 words per item, for larger lists
Analysis of the Space/Time Issues (2)
 vector Implementation
 Insertion at end of vector is O(1), on average
 Removal from the front is linear time: O(n)
 Removal from rear of vector is O(1)
 Insertion at the front is linear time: O(n)
Implementing queue
with a Circular Array
Basic idea: Maintain two integer indices into an array
 front: index of first element in the queue

 rear: index of the last element in the queue

 Elements thus fall at front through rear

Key innovation:
 If you hit the end of the array wrap around to slot 0

 This prevents our needing to shift elements around

 Still have to deal with overflow of space


Comparing the Three
Implementations
 All three are comparable in time: O(1) operations
 Linked-lists require more storage
 Singly-linked list: ~3 extra words / element
 Doubly-linked list: ~4 extra words / element
 Circular array: 0-1 extra word / element
 On average, ~0.5 extra word / element
Recap: deque ADT

double-ended queue (“dee-queue” or “deck”)

 The deque is an abstract data type that combines


the features of a stack and a queue.
 The name deque is an abbreviation for double-
ended queue.
 The C++ standard defines the deque as a full-
fledged sequential container that supports
random access.
The deque class

Member Fu
const Item_T
operator[](s
The deque class (2)

Member Fu
void push_fr
item)
The deque class (3)

Member Fu
iterator end
Implementing the Deque Using a
Circular Array
 We can add the additional functions to the circular
array based queue.
 queue::push is the same as deque::push_back
 queue::pop is the same as deque::pop_front.
 Implementing deque::push_front and deque::pop_back
are left as exercises.
 Operator[] is implemented as follows:
Item_Type& operator[](size_t index){
return the_data[(index +
front_index) % capacity];
}
The Standard Library
Implementation
 The standard library uses a randomly accessible
circular array.
 Each item in the circular array points to a fixed
size, dynamically allocated array that contains the
data.
 The advantage of this implementation is that when
reallocation is required, only the pointers need to
be copied into the new circular array.

You might also like