Chapter 7 Queues
We have already studied the Stack
data structure. We saw that it was a last-in-first-out (LIFO) type of data
structure. In other words when you remove something from a stack, you will
remove the most recent item that you placed on the stack (i.e thje one you
placed on the stack last).
A
queue is almost the opposite. It is a first-in-first-out (FIFO) data structure.
Here when you remove an item from a queue, you will remove the oldest item. It
is very similar to a check-out line in a grocery store. The first customer to
get into the line will be the first customer to get checked out and leave.
A
stack only had one data that pointed us into the stack, the “top”, because all
we were interested in was placing something on the top, or removing something
from the top.
A
queue must have two pieces of data, a “front” and a “rear” or “back”. For
example, if we are to place items in an array in a queue fashion, then we need
to know where the front of the queue is in order to remove an item and we need
to know where the back of the queue is to add an item.
|
|
|
|
|
|
0 1 2 3 4
Front Back
A
stack had a few simple operations, the primary two being “push” and “pop”. A queue
also has only a few simple operations, the primary ones being “enqueue” (place
something into the queue, and “dequeue” (remove something from the queue).
As
before we can build a “queue interface”:
public
interface QueueInterface {
public boolean isEmpty();
// Determines whether a queue is empty.
// Precondition: None.
// Postcondition: Returns true if the queue
is empty;
// otherwise returns false.
public void enqueue(Object newItem) throws
QueueException;
// Adds an item at the back of a queue.
// Precondition: newItem is the item to be
inserted.
// Postcondition: If the operation was
successful, newItem
// is at the back of the queue. Some
implementations
// may throw QueueException if newItem
cannot be added
// to the queue.
public Object dequeue() throws
QueueException;
// Retrieves and removes the front of a
queue.
// Precondition: None.
// Postcondition: If the queue is not empty,
the item
// that was added to the queue earliest is
returned and
// the item is removed. If the queue is
empty, the
// operation is impossible and
QueueException is thrown.
public void dequeueAll();
// Removes all items of a queue.
// Precondition: None.
// Postcondition: The queue is empty.
public Object peek() throws QueueException;
// Retrieves the item at the front of a
queue.
// Precondition: None.
// Postcondition: If the queue is not empty,
the item
// that was added to the queue earliest is
returned.
// If the queue is empty, the operation is
impossible
// and QueueException is thrown.
} // end QueueInterface
Here is some code that may be used if you had a queue named “myQ”:
myQ.createQueue();
myQ.enqueue(task1);
myQ.enqueue(task2);
myQ.enqueue(task3);
//computer
is ready
execute(myQ.dequeue());
execute(myQ.dequeue());
//
oops another request
myQ.enqueue(task4);
//computer
is ready
execute(myQ.dequeue());
execute(myQ.dequeue());
Assuming that the above code
is handling the execution of tasks inside of an operating system, in what order
will the tasks get executed?
One of the nice things about queues is that they can
be easily implemented with a linked list structure.
Remember that we said that we need two data items,
front and back. Well, with a linked list and a little thought, we can actually
do it with only one.
Consider the figure in your textbook:
![]()
![]()
![]()
firstNode lastNode
If we make the list circular where the “7” node
points back to the “2” node, then we only need the “lastNode” pointer. The
“firstNode” can always be gotten to by referencing “lastNode.getNext( )”.
Your textbook goes through the processes of a)
inserting an item into a nonempty queue, b) inserting an item into an empty
queue, and deleting an item from a queue that has more than one node. There is
also code that goes along with these operations. We will go over this in class,
step-by-step.
Now we are ready to see the complete implementation of the queue interface using these lists. We will go over all of the methods very carefully:
public
class QueueReferenceBased
implements
QueueInterface {
private Node lastNode;
public QueueReferenceBased() {
lastNode = null;
} //
end default constructor
// queue operations:
public boolean isEmpty() {
return lastNode == null;
} //
end isEmpty
public void dequeueAll() {
lastNode = null;
} //
end dequeueAll
public void enqueue(Object newItem) {
Node newNode = new Node(newItem);
// insert the new node
if (isEmpty()) {
// insertion into empty queue
newNode.setNext(newNode);
}
else {
// insertion into nonempty queue
newNode.setNext(lastNode.getNext());
lastNode.setNext(newNode);
}
// end if
lastNode = newNode; // new node is at back
} //
end enqueue
public Object dequeue() throws
QueueException {
if (!isEmpty()) {
// queue is not empty; remove front
Node firstNode = lastNode.getNext();
if (firstNode == lastNode) { // special
case?
lastNode = null; // yes, one node in queue
}
else {
lastNode.setNext(firstNode.getNext());
}
// end if
return firstNode.getItem();
}
else {
throw new
QueueException("QueueException on dequeue:"
+ "queue
empty");
}
// end if
} //
end dequeue
public Object peek() throws QueueException {
if (!isEmpty()) {
// queue is not empty; retrieve front
Node firstNode = lastNode.getNext();
return firstNode.getItem();
}
else {
throw new
QueueException("QueueException on peek:"
+ "queue empty");
}
// end if
} //
end peek
}
// end QueueReferenceBased
Here is a cute little program to test to see if we can enqueue some items into a queue.
public
class QueueTest {
public static void main(String[] args) {
QueueReferenceBased aQueue =
new
QueueReferenceBased();
for (int i = 0; i < 9; i++) {
aQueue.enqueue(new Integer(i));
}
// end for
} //
end main
} // QueueTest
An in class exercise: How would you write a program, similar to the above program that would remove each item from the queue, one at a time and print them?
I will include our old friend, the Node class, for
reference, just in case we need it:
public class Node {
private Object item;
private Node next;
public Node(Object newItem) {
item = newItem;
next = null;
} // end constructor
public Node(Object newItem, Node nextNode) {
item = newItem;
next = nextNode;
} // end constructor
public void setItem(Object newItem) {
item = newItem;
} // end setItem
public Object getItem() {
return item;
} // end getItem
As you may remember, when we
studied stacks, we started by using an array to implement them. It seemed the
easiest way. But with queues, arrays are not the easy way to go; this is the
reason we chose to implement them as a list structure first. In the next
lecture we will consider how to make an array look like a queue and the special
problems that go with it.
If you have some time, you may want to start to look at problems 3 and 4 on page 326 to get some practice, but I will not assign them just yet. I want you to complete all of the other pending assignments first!!
As we said before, the
linked list approach is probably the best way to implement a queue. It requires
only one pointer and there is virtually no limit to how many items can be
placed in the queue. However, many computer scientists do not like the idea of
dynamic memory allocation, especially when memory is scarce in some systems,
and they prefer an array-based implementation of a queue. We saw that
implementing a stack by using an array was fairly straightforward. Is it the
same for a queue? Let’s see.
Consider a simple array of 10 elements. It is to be
a queue. It will hold the names of patients that need treatment at a medical
center. The patients arrive one by one and are processed as whenever help is
available. The first patient to arrive is “Joe”. It is natural to place Joe in
the first location of the array. At that point he is both in the front and in
the back of the queue. Then “Sue” comes along. She is placed in the next
position and becomes the new “back” of the queue.
|
Joe |
Sue |
|
|
|
|
|
|
|
|
0 1 2
3 4 5 6 7 8 9
Front = 0
Back = 1
Other patients arrive and depart in a random fashion
and we keep moving them down the “track” in the array. Eventually we may have
something that looks like this
|
|
|
|
|
|
|
Tim |
Mary |
Sam |
|
Front = 6
Back = 8
Now Deb arrives, Tim gets processed and then Carl arrives. What happens? Deb and Tim are no problem, but when Carl arrives there is seemingly no place to put him in the queue. The queue seems full (we have reached the end) even though there are perhaps some empty spaces in the beginning indices (where Joe and Sue used to be).
What is the answer? The answer is to treat the array
as a circular array so that when Carl arrives, he is placed in the first
location of the array.
|
Carl |
|
|
|
|
|
|
Mary |
Sam |
Deb |
Front = 7
Back = 0
The problem now is to keep track of the Front and Back item pointers and to be able to determine when the queue is full and when the queue is empty.
If we begin properly, then inserting and deleting an
item from the queue is fairly easy. We simply have to use “modulo” arithmetic.
Suppose that Deb is in the queue at position 9. Carl comes in. His position is
(back + 1) % queue_size = ( 9 + 1 ) %
10 = 0.
In general insertion is done as follows:
back
= (back + 1) % MAX-QUEUE;
items[back] = newItem;
++count;
Notice that we are also using a count.
Deletion from the queue is done as follows:
front = (front + 1) % MAX_QUEUE;
--count;
Now the only question is how to the variables “front” and “back” correctly so that everything works out:
front = 0;
back = MAX_QUEUE –1;
count = 0;
Here is the complete implementation for the array-based queue:
public
class QueueArrayBased implements QueueInterface {
private final int MAX_QUEUE = 50; // maximum
size of queue
private Object[] items;
private int front, back, count;
public QueueArrayBased() {
items = new Object[MAX_QUEUE];
front = 0;
back = MAX_QUEUE-1;
count = 0;
} //
end default constructor
// queue operations:
public boolean isEmpty() {
return count == 0;
} //
end isEmpty
public boolean isFull() {
return count == MAX_QUEUE;
} //
end isFull
public void enqueue(Object newItem) {
if (!isFull()) {
back = (back+1) % (MAX_QUEUE);
items[back] = newItem;
++count;
}
else {
throw new QueueException("QueueException
on enqueue: "
+ "Queue
full");
}
// end if
} //
end enqueue
public Object dequeue() throws
QueueException {
if (!isEmpty()) {
// queue is not empty; remove front
Object queueFront = items[front];
front = (front+1) % (MAX_QUEUE);
--count;
return queueFront;
}
else {
throw new
QueueException("QueueException on dequeue: "+
"Queue
empty");
}
// end if
} //
end dequeue
public void dequeueAll() {
items = new Object[MAX_QUEUE];
front = 0;
back = MAX_QUEUE-1;
count = 0;
} //
end dequeueAll
public Object peek() throws QueueException {
if (!isEmpty()) {
// queue is not empty; retrieve front
return items[front];
}
else {
throw new QueueException("Queue
exception on peek: " +
"Queue
empty");
}
// end if
} //
end peek
}
// end QueueArrayBased
public class
QueueException extends RuntimeException {
public QueueException(String s) {
super(s);
} // end constructor
} // end QueueException
Your assignment is
to do problems 2, 3 and 4 on page 326 under Self-Test Exercises and problems 4
and 5 on page 327 under Exercises.