UMBC CMSC 201 Fall '06 CSEE | 201 | 201 F'06 | lectures | news | help |
The most straightforward approach is to create an array, of some size to hold all of the data that think we will ever need to put into it. Let us also maintain head and tail in terms of the corresponding indicies.
int myQueue[5]; int head; int tail;
Enqueue
To Enqueue, all we need to do is insert at the end of the used portion of the array. In other words insert immediately past the tail and then increment tail to reflect the new end of the queue.
Initially we have head = -1 and tail = -1, as they do not really point to anything in the array, and thus should not have a valid array index.
|
|||||||||
0 | 1 | 2 | 3 | 4 |
So lets insert...
Enqueue ( myQueue, &head, &tail, 5 );
|
|||||||||
0 | 1 | 2 | 3 | 4 |
Now we have head = 0 and tail = 0, as the head and the tail are both pointing to the same thing, which is at myQueue[0].
Then after a couple more inserts...
Enqueue ( myQueue, &head, &tail, 1 ); Enqueue ( myQueue, &head, &tail, 6 );
|
|||||||||
0 | 1 | 2 | 3 | 4 |
Now we have head = 0 and tail = 2
But what happens is we want to do even more inserts...
Enqueue ( myQueue, &head, &tail, 1 ); Enqueue ( myQueue, &head, &tail, 9 ); Enqueue ( myQueue, &head, &tail, 7 ); Enqueue ( myQueue, &head, &tail, 8 );
|
|||||||||
0 | 1 | 2 | 3 | 4 |
Best case we check to tell when we have filled the array and tell the user that they have filled it. Worst case, we don't check and corrupt our memory or someone else's memory which results in a segmentation fault.
Dequeue
Well, here is where we begin to run into problems with the array implementation. What happens when we need to Dequeue an element out of the array. Well we get a gap, if we just delete it, so we will shift everything after it.So given the following queue, where head = 0 and tail = 2, lets see what happens...
|
|||||||||
0 | 1 | 2 | 3 | 4 |
Okay, lets dequeue...
tmp = Dequeue ( myQueue, &head, &tail );
|
|||||||||
0 | 1 | 2 | 3 | 4 |
Oops, now we have a gap where the first element was, so lets shift everything down. We will do this in some sort of loop internal to the Dequeue function...
|
|||||||||
0 | 1 | 2 | 3 | 4 |
We also have to decrement the tail so that tail = 1.
As you can see dequeueing is a non trivial task. We have to fill the slot that was created by dequeueing the first element. For a queue that is 5 elements in length this is not too bad. But what if we have several hundred thousand elements in the queue? It is not good to have to shift several hundred thousand elements every time we want to dequeue a single element.
Problems with this Initial Array Implementation.
Well, since we said that dequeueing was horribly inefficient, let's try to improve it. Rather than shifting elements down to the front of the array, we will allow the head to advance through the array. This time, let's look at Dequeue first to get the idea.
Dequeue
Let's assume that the user has enqueued some values into the array, and our current queue looks as follows, with head = 0 and tail = 4 ...
|
|||||||||
0 | 1 | 2 | 3 | 4 |
Now lets dequeue some values to see what happens...
tmp = Dequeue ( myQueue, &head, &tail );
|
|||||||||
0 | 1 | 2 | 3 | 4 |
Before we shifted everything down, but to be more efficient let's simply move the head forward. So head = 1 and tail = 4. Let's Do some more dequeueing...
tmp = Dequeue ( myQueue, &head, &tail ); tmp = Dequeue ( myQueue, &head, &tail );
|
|||||||||
0 | 1 | 2 | 3 | 4 |
Now head = 3 and tail = 4. All we are doing is moving the head forward. This is much more efficient than moving everything in the array, especially for large data sets.
Enqueue
Let us now look at enqueue. By improving dequeue, we have in no way hurt the performance of enqueue. Let us first look at the case where the tail is not at the end of the array yet.
For example, when head = 2 and tail = 3 ...
|
|||||||||
0 | 1 | 2 | 3 | 4 |
Doing an enqueue we still simply put the data after the tail and increment it...
Enqueue ( myQueue, &head, &tail, 6 );
|
|||||||||
0 | 1 | 2 | 3 | 4 |
So head = 2 and tail = 4.
But what about the more interesting case where there are vacancies in the array, but the tail is at the end...
Such is the case in the following example, where head = 2 and tail = 4...
|
|||||||||
0 | 1 | 2 | 3 | 4 |
Well there is room, but it is at the beginning. So it requires some more thought, but we can perform this check and go ahead and move the tail to the beginning...
Enqueue ( myQueue, &head, &tail, 1)
|
|||||||||
0 | 1 | 2 | 3 | 4 |
So after this enqueue we have a case where the tail is actually less than the head. In our case head = 2 and tail = 0. This is perfectly acceptable.
Problems with this "new and improved" Array Implementation
Building upon the previous implementation, we can take it a step further and manually allocate a contiguous chunk of memory ourselves. This will allow us to potentially allocate more memory as needed, thus in essence allowing us to be able to resize the queue. This will require us to keep track of the current size of the array allocated for queue use.
If the queue is full when we insert, we can allocate a new array copy over all of the elements, and then free the old array.
Problems with this "as good as it gets" Implementation
Benefits of using a linked list