36 ImplementingQueues
36 ImplementingQueues
Outline
• Chapter 14 presents complete implementations for three of the
Implementing Queues ADTs you’ve been using since the second assignment: stacks,
queues, and vectors. We described one implementation of the
Stack class in Monday’s lecture.
• The reader presents two different strategies to represent the
Stack and Queue classes: one that uses an array to store the
elements and one that uses a linked list. For each of these
strategies, implementing a Stack turns out to be much easier.
• In today’s lecture, I am going to focus on implementing the
Queue class, leaving the details of implementing the Stack
Eric Roberts and Vector classes to the text.
CS 106B
February 13, 2013
/*
• Given this representation, the enqueue operation is extremely
* The template feature of C++ works correctly only if the compiler simple to implement. All you need to do is add the element to
* has access to both the interface and the implementation at the
* same time. As a result, the compiler must see the code for the end of the array and increment the element count. That
* the implementation at this point, even though that code is
* not something that the client needs to see in the interface.
operation runs in O(1) time.
* Using the #include facility of the C++ preprocessor allows the
* compiler to have access to the code without forcing the client • The problem with this simplistic approach is that the dequeue
* to wade through the details.
*/ operation requires removing the element from the beginning
of the array. If you’re relying on the same strategy you used
Implementation section goes here. in the array-based editor, implementing this operation requires
#endif
moving all the remaining elements to fill the hole left by the
dequeued element. That operation therefore takes O(N) time.
Structure for the Array-Based Queue Code for the Ring-Buffer Queue
/* /* Implementation section */
* Implementation notes
* -------------------- /*
* The array-based queue stores the elements in a ring buffer, which * Implementation notes: Queue constructor
* consists of a dynamic index and two indices: head and tail. Each * ---------------------------------------
* index wraps to the beginning if necessary. This design requires * The constructor must allocate the array storage for the queue
* that there is always one unused element in the array. If the queue * elements and initialize the fields of the object.
*/
* were allowed to fill completely, the head and tail indices would
* have the same value, and the queue will appear empty. template <typename ValueType>
*/ Queue<ValueType>::Queue() {
capacity = INITIAL_CAPACITY;
private: array = new ValueType[capacity];
head = 0;
static const int INITIAL_CAPACITY = 10; tail = 0;
}
/* Instance variables */
/*
ValueType *array; /* A dynamic array of the elements */ * Implementation notes: ~Queue destructor
int capacity; /* The allocated size of the array */ * ---------------------------------------
int head; /* The index of the head element */ * The destructor frees any memory that is allocated by the implementation.
int tail; /* The index of the tail element */ */
template <typename ValueType>
/* Private method prototypes */
Queue<ValueType>::~Queue() {
void expandCapacity(); delete[] array;
}
Code for the Ring-Buffer Queue Code for the Ring-Buffer Queue
/* Implementation section */
/* /*
* Implementation notes: size * Implementation notes: clear
/** -------------------------- * ---------------------------
** Implementation
The size of thenotes:
queue Queue
can beconstructor
calculated from the head and tail * The clear method need not take account of where in the
** ---------------------------------------
indices by using modular arithmetic. * ring buffer any existing data is stored and can simply
**/The constructor must allocate the array storage for the queue * set the head and tail index back to the beginning.
* elements and initialize the fields of the object. */
template <typename ValueType>
*/
int Queue<ValueType>::size() { template <typename ValueType>
return<typename
template (tail + capacity - head) % capacity;
ValueType> void Queue<ValueType>::clear() {
}
Queue<ValueType>::Queue() { head = tail = 0;
capacity = INITIAL_CAPACITY; }
/* array = new ValueType[capacity];
* head
Implementation
= 0; notes: isEmpty /*
* tail
-----------------------------
= 0; * Implementation notes: enqueue
} * The queue is empty whenever the head and tail indices are * -----------------------------
* equal. Note that this interpretation means that the queue * This method must first check to see whether there is
/** cannot be allowed to fill the capacity entirely and must * enough room for the element and expand the array storage
** Implementation
always leave one unused
notes: space.
~Queue destructor * if necessary.
**/--------------------------------------- */
* The destructor frees any memory that is allocated by the implementation.
template
*/ <typename ValueType> template <typename ValueType>
bool Queue<ValueType>::isEmpty() { void Queue<ValueType>::enqueue(ValueType value) {
return<typename
template head == tail;
ValueType> if (size() == capacity - 1) expandCapacity();
}
Queue<ValueType>::~Queue() { array[tail] = value;
delete[] array; tail = (tail + 1) % capacity;
} }
Code for the Ring-Buffer Queue Code for the Ring-Buffer Queue
/* /*
* Implementation notes: dequeue, peek * Implementation notes: expandCapacity
* ----------------------------------- * ------------------------------------
* These methods must check for an empty queue and report an * This private method doubles the capacity of the elements array
* error if there is no first element. * whenever it runs out of space. To do so, it must allocate a new
*/ * array, copy all the elements from the old array to the new one,
* and free the old storage. Note that this implementation also
template <typename ValueType> * shifts all the elements back to the beginning of the array.
ValueType Queue<ValueType>::dequeue() { */
if (isEmpty()) error("dequeue: Attempting to dequeue an empty queue");
ValueType result = array[head]; template <typename ValueType>
head = (head + 1) % capacity; void Queue<ValueType>::expandCapacity() {
return result; int count = size();
} int oldCapacity = capacity;
capacity *= 2;
template <typename ValueType> ValueType *oldArray = array;
ValueType Queue<ValueType>::peek() { array = new ValueType[capacity];
if (isEmpty()) error("peek: Attempting to peek at an empty queue"); for (int i = 0; i < count; i++) {
return array[head]; array[i] = oldArray[(head + i) % oldCapacity];
} }
head = 0;
tail = count;
delete[] oldArray;
}
#endif
–4–
/* Instance variables */
Code for the Linked-List Queue Code for the Linked-List Queue
/* /*
* Implementation notes: Queue constructor * Implementation notes: size, isEmpty, clear
* --------------------------------------- * ------------------------------------------
* The constructor must create an empty linked list and then * These implementations should be self-explanatory.
* initialize the fields of the object. */
*/
template <typename ValueType>
template <typename ValueType> int Queue<ValueType>::size() {
Queue<ValueType>::Queue() { return count;
head = tail = NULL; }
count = 0;
} template <typename ValueType>
bool Queue<ValueType>::isEmpty() {
/* return count == 0;
* Implementation notes: ~Queue destructor }
* ---------------------------------------
* The destructor frees any memory that is allocated by the implementation. template <typename ValueType>
* Freeing this memory guarantees the client that the queue abstraction void Queue<ValueType>::clear() {
* will not "leak memory" in the process of running an application. while (count > 0) {
* Because clear frees each element it processes, this implementation dequeue();
* of the destructor simply calls that method. }
*/ }
template <typename ValueType>
Queue<ValueType>::~Queue() {
clear();
}
Code for the Linked-List Queue Code for the Linked-List Queue
/* /*
* Implementation notes: enqueue * Implementation notes: dequeue, peek
* ----------------------------- * -----------------------------------
* This method allocates a new list cell and chains it in * These methods must check for an empty queue and report an
* at the tail of the queue. If the queue is currently empty, * error if there is no first element. The dequeue method
* the new cell must also become the head pointer in the queue. * must also check for the case in which the queue becomes
*/ * empty and set both the head and tail pointers to NULL.
*/
template <typename ValueType>
void Queue<ValueType>::enqueue(ValueType value) { template <typename ValueType>
Cell *cp = new Cell; ValueType Queue<ValueType>::dequeue() {
cp->data = value; if (isEmpty()) error("dequeue: Attempting to dequeue an empty queue");
cp->link = NULL; Cell *cp = head;
if (head == NULL) { ValueType result = cp->data;
head = cell; head = cp->link;
} else { if (head == NULL) tail = NULL;
tail->link = cell; count--;
} delete cell;
tail = cell; return result;
count++; }
}
template <typename ValueType>
ValueType Queue<ValueType>::peek() {
if (isEmpty()) error("peek: Attempting to peek at an empty queue");
return head->data;
}