Professional Documents
Culture Documents
Introduction to
Computer Science II
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
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 = ")]}";
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"
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
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
Key innovation:
If you hit the end of the array wrap around to slot 0
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.