UMBC CS 201, Fall 06
 UMBC CMSC 201 Fall '06 CSEE | 201 | 201 F'06 | lectures | news | help Search Notes:

# The Queue

## Implementations using arrays

### Initial Array Implementation

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 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 );
```
 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 );
```
 5 1 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 );
```
 5 1 6 1 9
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...

 5 1 6
0 1 2 3 4

Okay, lets dequeue...

```   tmp = Dequeue ( myQueue, &head, &tail );
```
 1 6
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...

 1 6
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.

• To dequeue we have to shift every element in the array - horribly inefficient.
• We have to know ahead of time how large to make the array - too big and we are wasting memory, too small and we could fill it up.

### New and Improved 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 ...

 5 1 6 1 9
0 1 2 3 4

Now lets dequeue some values to see what happens...

```   tmp = Dequeue ( myQueue, &head, &tail );
```
 1 6 1 9
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 );
```
 1 9
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 ...

 5 1
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 );
```
 5 1 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...

 5 1 6
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)
```
 1 5 1 6
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

• Dequeue is no longer a problem, just shift the head.
• There is some more overhead added to this approach, but it is trivial as compared to having to shift hundreds of thousands of elements repeatedly.
• Well, we still need to know ahead of time how large to make the array - too big and we are wasting memory, too small and we could fill it up.

### Array Implementation - as good as it gets

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

• Dequeue is still not a problem, just shift the head
• If we needed to resize in insertion, we would have to copy over all the elements from an old array into a new array.
• In this implementation the choice of how much to increase array by becomes critical. Do we increase it by some constant amount (size += 10), or do we increase it by some percentage (size *= 1.1), or do we double it (size *= 2).

## Implementation using a linked list

Since we now know about linked lists, we should consider using a linked list implementation of the queue. The queue can be implemented without wasting any space, and by keeping pointers to both the beginning of the list (the head) and the end of the list (the tail), we can make our queue work much faster than an array implementation.

Benefits of using a linked list

• If we maintain a tail enqueue can be done by simply allocating a node and putting on at the end, no traversals needed.
• We can dequeue by taking the first node out of the list, again no traversals needed
• We never have to worry about copying over all of the elements, because a linked list is dynamic in size, it can shrink and grow as needed.
• Our memory need not be contiguous anymore.