Queues and Deques - Data Structures - Notes | CS 261, Study notes of Data Structures and Algorithms

Material Type: Notes; Class: DATA STRUCTURES; Subject: Computer Science; University: Oregon State University; Term: Fall 2006;

Typology: Study notes

Pre 2010

Uploaded on 08/31/2009

koofers-user-9na
koofers-user-9na 🇺🇸

10 documents

1 / 9

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Chapter Q: Queues and Deques
1
Chapter Q: Queues and Deques
After the stack, the next simplest data abstraction is the queue. As with the stack, the
queue can be visualized with many examples you are already familiar with from
everyday life. A simple illustration is a line of people waiting to enter a theater. The
fundamental property of the queue is that items are inserted at one end (the rear of the
line) and removed from the other (the door to the theater). This means that the order that
items are removed matches the order that they are inserted. Just as a stack was described
as a LIFO container, this means a queue can be described as FIFO (first in, first out).
A variation is termed the deque, which stands for double-ended queue, and is pronounced
“deck”. In a deque values can be inserted at either the front or the back, and similarly the
deck allows values to be removed from either the front or the back.
Queues and deques are used in a number of ways in computer applications. A printer, for
example, can only print one job at a time. During the time it is printing there may be
many different requests for other output to be printed. To handle these the printer will
maintain a queue of pending print tasks. Since you want the results to be produced in the
order that they are received, a queue is the appropriate data structure.
The Queue ADT specification
The classic definition of the queue abstraction as an ADT includes the following
operations:
addBack (newElement)
Insert a value into the queue
Front()
Return the front (first) element in queue
removeFront()
Remove the front (first) element in queue
isEmpty()
Determine whether the queue has any elements
The deque will add the following:
addFront(newElement)
Insert a value at front of deque
Back()
Return the last element in the queue
removeBack()
Return the last element in the queue
Notice that the queue is really just a special case of the deque. Any implementation of the
deque will also work as an implementation of the queue. (A deque can also be used to
implement a stack, a topic we will explore in the exercises).
As with the stack, it is the FIFO (first in, first out) property that is, in the end, the
fundamental defining characteristic of the queue, and not the names of the operations.
Some designers choose to use names such as “add”, “push” or “insert”, leaving the
location unspecified. Similarly some implementations elect to make a single operation
that will both return and remove an element, while other implementations separate these
pf3
pf4
pf5
pf8
pf9

Partial preview of the text

Download Queues and Deques - Data Structures - Notes | CS 261 and more Study notes Data Structures and Algorithms in PDF only on Docsity!

Chapter Q: Queues and Deques

After the stack, the next simplest data abstraction is the queue. As with the stack, the

queue can be visualized with many examples you are already familiar with from

everyday life. A simple illustration is a line of people waiting to enter a theater. The

fundamental property of the queue is that items are inserted at one end (the rear of the

line) and removed from the other (the door to the theater). This means that the order that

items are removed matches the order that they are inserted. Just as a stack was described

as a LIFO container, this means a queue can be described as FIFO (first in, first out).

A variation is termed the deque , which stands for double-ended queue, and is pronounced

“deck”. In a deque values can be inserted at either the front or the back, and similarly the

deck allows values to be removed from either the front or the back.

Queues and deques are used in a number of ways in computer applications. A printer, for

example, can only print one job at a time. During the time it is printing there may be

many different requests for other output to be printed. To handle these the printer will

maintain a queue of pending print tasks. Since you want the results to be produced in the

order that they are received, a queue is the appropriate data structure.

The Queue ADT specification

The classic definition of the queue abstraction as an ADT includes the following

operations:

addBack (newElement) Insert a value into the queue

Front() Return the front (first) element in queue

removeFront() Remove the front (first) element in queue

isEmpty() Determine whether the queue has any elements

The deque will add the following:

addFront(newElement) Insert a value at front of deque

Back() Return the last element in the queue

removeBack() Return the last element in the queue

Notice that the queue is really just a special case of the deque. Any implementation of the

deque will also work as an implementation of the queue. (A deque can also be used to

implement a stack, a topic we will explore in the exercises).

As with the stack, it is the FIFO (first in, first out) property that is, in the end, the

fundamental defining characteristic of the queue, and not the names of the operations.

Some designers choose to use names such as “add”, “push” or “insert”, leaving the

location unspecified. Similarly some implementations elect to make a single operation

that will both return and remove an element, while other implementations separate these

two tasks, as well do here. And finally, as in the stack, there is the question of what the

effect should be if an attempt is made to access or remove an element from an empty

collection. The most common solutions are to throw and exception (which is what we

will do), or to return a special value, such as Null.

For a deque the defining property is that elements can only be added or removed from the

end points. It is not possible to add or remove values from the middle of the collection.

Applications of Queues

to be written.

Queue Implementation Techniques

As with the stack, the two most common implementation techniques for a queue are to

use a linked list or to use an array. In the following lessons we will explore both of these.

Worksheet Q1: Building a Linked List Queue

A stack only needed to maintain a link to one end of the chain of values, since both

insertions and removals occurred on the same side. A queue, on the other hand, performs

insertions on one side and removals from the other. Therefore it is necessary to maintain

a links to both the front and the back of the collection.

We will add another variation to our container. A sentinel is a special link, one that does

not contain a value. The sentinel is used to mark either the beginning or end of a chain of

links. In our case we will use a sentinel at the front. This is sometimes termed a list

header. The presence of the sentinel makes it easier to address special cases. For

example, the list of links is never actually empty, even when it is logically empty, since

there is always at least one link. A new value is inserted after the end.

Values are removed from the front, as with the stack. But because of the sentinel, these

will be the element right after the sentinel.

You should complete the following skeleton code for the ListQueue. The structures have

been written for you, as well as the initialization routine. The function isEmpty must

Sentinel 3 7 4 2

6. How difficult would it be to write the method addFront(newValue) that insertes a

new element into the front of the collection? A container that supports adding

values at either and, but removal from only one side, is sometimes termed a scroll.

7. Explain why removing the value from the back would be difficult for this container.

What would be the algorithmic complexity of the removeLast operation?

Worksheet Q2: A Linked List Deque – using Double Links

If you answered the questions in the previous worksheet you will have noticed that

removal from the end of a ListQueue is difficult because with only a single link it is

difficult to “back up”. That is, while you have a pointer to the end sentinel, you do not

have an easy way to back up and find the link immediately preceding the sentinel.

One solution to this problem is to use a doubly-linked list. In a doubly-linked list, each

link maintains two pointers. One link, the forward link, points forward toward the next

link in the chain. The other link, the prev link, points backwards towards the previous

element.

With this picture, it is now easy to move either forward or backwards from any link.

We will use this new ability to create a linked list deque. In order to simplify the

implementation, we will this time include sentinels at both the beginning and the end of

the chain of links. Because of the sentinels, both adding to the front and adding to the

end of a collection can be viewed as special cases of a more general “add to the middle

of a list” operation. That is, perform an insertion such as the following:

picture

Similarly, removing a value (from other the front or the back) is a special case of a

more general remove operation:

picture

Complete the implementation of the ListDeque based on these ideas. The structures

have been written for you, as well as the body for some of the methods. You need to

complete the internal “addLink” and “removeLink” methods, as well as the methods

front, back, and isEmpty.

struct dlink { EleType value; struct dlink * next;

struct dlink * prev; }; struct listDeque { struct dlink * frontSentinel; struct dlink * backSentinel; }; void ListDequeInit (struct listDeque *q) { q->frontSentinel = (struct dlink *) malloc(sizeof(struct dlink)); assert(q->frontSentinel != 0); q->backSentinel = (struct dlink *) malloc(sizeof(struct dlink)); assert(q->backSentinel); q->frontSentinel->next = q->backSentinel; q->backSentinel->prev = q->frontSentinal; } void ListDequeAddFront (struct listDeque *q, EleType e) { _addLink(q->frontSentinel, e); } void ListDequeAddback (struct listDeque *q, EleType e) { _addLink(q->backSentinel->prev, e); } void _addLink (struct dlink *lnk, EleType e) { } void listDequeRemoveFront (struct listDeque *q) { assert(! listDequeIsEmpty(q)); _removeLink (q->frontSentinal->next); } void ListDequeRemoveBack (struct listDeque *q) { assert(! listDequeIsEmpty(q)); _removeLink (q->backSentinel->prev); } void _removeLink (struct dlink *lnk) { } EleType ListDequeFront (struct listDeque *q) { } EleType ListDequeBack (struct listDeque *q) {

But adding a value to the front is also simple. Simply decrement the starting point by

one, then add the new element to the front:

picture

Removing elements undo these operations. As before, if an insertion is performed when

the size is equal to the capacity, the array must be doubled in capacity, and the values

copied into the new array.

There is just one problem. Nothing prevents the data values from wrapping around

from the upper part of the array to the lower:

To accommodate index values must be computed carefully. When an index wraps

around the end of the array it must be altered to point to the start of the array. This is

easily done by subtracting the capacity of the array. That is, suppose we try to index the

fifth element in the picture above. We start by adding the index, 5, to the starting

location, 7. The resulting sum is 12. But there are only eleven values in the collection.

Subtracting 11 from 12 yields 1. This is the index for the value we seek.

Using these ideas, complete the following skeleton class for the ArrayDeque:

struct ArrayDeque { EleType * data; int count; int capacity; int start; }; void ArrayDequeInit (struct ArrayDeque *q) { q->start = q->count = 0; q->capacity = 5; q->data = (EleType *) malloc(q->capacity * sizeof(EleType)); assert(q->data); } void ArrayDequeAddFront (struct ArrayDeque *q, EleType e) { } void ArrayDequeAddBack (struct ArrayDeque *q, EleType e) { }

void ArrayDequeRemoveFront (struct ArrayDeque *q) { } void ArrayDequeRemoveBack (struct ArrayDeque *q) { } EleType ArrayDequeFront (struct ArrayDeque *q) { } EleType ArrayDequeBack (struct ArrayDeque *q) { } int ArrayDequeIsEmpty (struct ArrayDeque *q) { }

A deque implemented in this fashion is sometimes termed a circular buffer , since the

right hand side circles around to begin again on the left.

As with the Vector data type, the array in the ArrayDeque must be declared as Object,

rather than as E. This is because Java generics do not allow the creation of an array of

generic types. The methods front and back will also generate a warning because the

validity of the cast to E cannot be checked at compile time. This warning can be safely

ignored.

Self Study Questions

1. What are the defining characteristics of the queue ADT?

2. What do the letters in FIFO represent? How does this describe the queue?

3. What does the term deque stand for?

4. How is the deque ADT different from the queue abstraction?

5. What will happen if an attempt is made to remove a value from an empty queue?

Short Exercises Analysis Exercises