We've acted now as users of queues, so it's time to act as implementers of queues. With queue, as with stack, we basically have the choice of basing our implementation on linked lists or on arrays. Once again, arrays (at least the static variety) offer the significant disadvantage of placing a limit on the number of elements allowed in the queue at any one point in time. You may decide to use dynamic arrays which overcome this limitation. However, as some of you found out with stacks, you have to be careful if you assume knowledge of the true size of the array at any given time...it's dynamic so we never truly know!
The obvious way to implement a queue is with a linked list. Since you'll be removing elements from the front of this list and adding elements to the back of the list, a linked list with both head and tail pointers is the way to go. Just as we did with stacks, we'll look at two different versions of a queue. The first is a traditional queue, the second is a more modern version.
A linked-list implementation of Queues of doubles using the traditional Queue ADT.
MyQueue.h | MyQueue.cpp |
|
|
The implementation of a queue of doubles using the more modern queue ADT is not a whole lot different:
MyQueueB.h | MyQueueB.cpp |
|
|
Implementing queues with arrays is trickier than implementing stacks. That fact might not be immediately apparent, so let's see why. With an array-based stack implementation, the bottom of the stack always stayed the same, so the indices of stack elements were always from 0 to one less than the number of elements on the stack (which is typically not the array size!). With an array-based queue implementation, elements get added to the back, which increases the index of the last element in the queue, and elements get taken off the front, which increases the index of the first element on the queue. Thus, we rapidly get to the point at which we run off the end of the array not because there are too many elements in the queue, but because the front of the queue is at an index near the back of the array. What we really want is to have any array that "wraps back around" when the index reaches the array size. So, for example, if you have an array A of 10 elements, the access of A[10] would'nt fall off the end of the array, it'd wrap around and be the same as A[0]. Similarly, A[11] would be the same as A[1], A[12] as A[2], etc. This is called a circular array, and implementing one is as simple as changing A[i] to A[i % N], where N is the number of elements in the array. Circular arrays are what we'll use to provide an array-based implmentation of queues.
The following array-based implementation of queues of doubles using the "traditional" queue ADT doesn't check for queue overflow or underflow. You might want to think about how to add it!
MyQueueTA.h | MyQueueTA.cpp |
|
|
The following array-based implementation of queues of doubles using the more modern queue ADT doesn't check for queue overflow or underflow. You might want to think about how to add it!
MyQueueBA.h | MyQueueBA.cpp |
|
|