Stacks and Queues
4.5 Queues
4.5.1 Implementing a Queue Using an Array
public NodeData(char c, int n) { ch = c;
num = n;
}
public char getCharData() {return ch;}
public int getIntData() {return num;}
public static NodeData getRogueValue() { return new NodeData('$', -999999);
}
} //end class NodeData
The following is a sample run of Program P4.6:
Type an infix expression and press Enter (7 – 8 / 2 / 2) * ((7 – 2) * 3 – 6) The postfix form is
7 8 2 / 2 / - 7 2 – 3 * 6 - * Its value is 45
Since we are using an array, we will need to know its size in order to declare it. We will need to have some information about the problem to determine a reasonable size for the array. We will use the symbolic constant MaxQ. In our implementation, the queue will be declared full if there are MaxQ-1 elements in it and we attempt to add another.
We begin to define the class Queue as follows:
public class Queue {
final static int MaxQ = 100;
int head = 0, tail = 0;
int[] QA = new int[MaxQ];
...
Valid values for head and tail will range from 0 to MaxQ-1. When we initialize a queue, we will set head and tail to 0; later, we will see why this is a good value.
As usual, we can create an empty queue, Q, with this:
Queue Q = new Queue();
When this statement is executed, the situation in memory can be represented as shown in Figure 4-5.
This represents the empty queue. In working with queues, we will need a function that tells us whether a queue is empty. We can use the following:
public boolean empty() { return head == tail;
}
Shortly, we will see that given the way we will implement the enqueue and dequeue operations, the queue will be empty whenever head and tail have the same value. This value will not necessarily be 0. In fact, it may be any of the values from 0 to MaxQ-1, the valid subscripts of QA.
Consider how we might add an item to the queue. In a real queue, a person joins at the tail. We will do the same here by incrementing tail and storing the item at the location indicated by tail.
For example, to add 36, say, to the queue, we increment tail to 1 and store 36 in QA[1]; head remains at 0.
If we then add 15 to the queue, it will be stored in QA[2] and tail will be 2.
If we now add 52 to the queue, it will be stored in QA[3] and tail will be 3.
Our picture in memory will look like Figure 4-6.
Q
0 1 2 3 4 5
Q
head 0 tail 0
Figure 4-5. Array representation of a queue
Note that head points “just in front of” the item, which is actually at the head of the queue, and tail points at the last item in the queue.
Now consider taking something off the queue. The item to be taken off is the one at the head. To remove it, we must first increment head and then return the value pointed to by head.
For example, if we remove 36, head will become 1, and it points “just in front of” 15, the item now at the head.
Note that 36 still remains in the array, but, to all intents and purposes, it is not in the queue.
Suppose we now add 23 to the queue. It will be placed in location 4 with tail being 4 and head being 1.
The picture now looks like Figure 4-7.
There are three items in the queue; 15 is at the head, and 23 is at the tail.
Consider what happens if we continuously add items to the queue without taking off any. The value of tail will keep increasing until it reaches MaxQ-1, the last valid subscript of QA. What do we do if another item needs to be added?
We could say that the queue is full and stop the program. However, there are two free locations, 0 and 1. It would be better to try to use one of these. This leads us to the idea of a circular queue. Here, we think of the locations in the array as arranged in a circle: location MaxQ-1 is followed by location 0.
So, if tail has the value MaxQ-1, incrementing it will set it to 0.
Suppose we had not taken off any item from the queue. The value of head will still be 0. Now, what if, in
attempting to add an item, tail is incremented from MaxQ-1 to 0? It now has the same value as head. In this situation, we declare that the queue is full.
We do this even though nothing is stored in location 0, which is, therefore, available to hold another item. The reason for taking this approach is that it simplifies our code for detecting when the queue is empty and when it is full.
This is also the reason why we set both head and tail to 0 initially. It enables us to easily detect when the queue is full if items are inserted continuously.
To emphasize, when the queue is declared full, it contains MaxQ-1 items.
Q
0 1
36 15 52
2 3 4 5
QA
head 0 tail 3
Figure 4-6. State of the queue after adding 36, 15, and 52
Q
0 1
36 15 52 23
2 3 4 5
QA
head 1 tail 3
Figure 4-7. State of the queue after removing 36 and adding 23
We can now write enqueue, an instance method to add an item to the queue.
public void enqueue(int n) {
tail = (tail + 1) % MaxQ; //increment tail circularly if (tail == head) {
System.out.printf("\nQueue is full\n");
System.exit(1);
}
QA[tail] = n;
} //end enqueue
We first increment tail. If, by doing so, it has the same value as head, we declare that the queue is full. If not, we store the new item in position tail.
Consider Figure 4-7. If we delete 15 and 52, it changes to Figure 4-8.
Q
0 1
36 15 52 23
2 3 4 5
QA
head 3 tail 4
Figure 4-8. Queue after removing 15, 52
Now, head has the value 3, tail has the value 4, and there is one item, 23, in the queue at location 4. If we delete this last item, head and tail would both have the value 4 and the queue would be empty. This suggests that we have an empty queue when head has the same value as tail.
But wait! Didn’t we just say that the queue is full when head and tail have the same value? True, but there’s a difference. At any time, if head == tail, the queue is empty. However, if after incrementing tail to add an item it becomes the same as head, then the queue is full.
We can now write dequeue, a method that removes an item from the queue.
public int dequeue() { if (this.empty()) {
System.out.printf("\nAttempt to remove from an empty queue\n");
System.exit(2);
}
head = (head + 1) % MaxQ; //increment head circularly return QA[head];
} //end dequeue
If the queue is empty, an error is reported, and the program is halted. If not, we increment head and return the value in location head. Note, again, that if head has the value MaxQ -1, incrementing it sets it to 0.
To test our queue operations, we write Program P4.7, which reads an integer and prints its digits in reverse order.
For example, if 12345 is read, the program prints 54321. The digits are extracted, from the right, and stored in a queue.
The items in the queue are taken off, one at a time, and printed.
Program P4.7
import java.util.*;
public class QueueTest {
public static void main(String[] args) { Scanner in = new Scanner(System.in);
Queue Q = new Queue();
System.out.printf("Enter a positive integer: ");
int n = in.nextInt();
while (n > 0) { Q.enqueue(n % 10);
n = n / 10;
}
System.out.printf("\nDigits in reverse order: ");
while (!Q.empty())
System.out.printf("%d", Q.dequeue());
System.out.printf("\n");
} //end main } //end QueueTest class Queue {
final static int MaxQ = 100;
int head = 0, tail = 0;
int[] QA = new int[MaxQ];
public boolean empty() { return head == tail;
}
public void enqueue(int n) {
tail = (tail + 1) % MaxQ; //increment tail circularly if (tail == head) {
System.out.printf("\nQueue is full\n");
System.exit(1);
}
QA[tail] = n;
} //end enqueue public int dequeue() { if (this.empty()) {
System.out.printf("\nAttempt to remove from an empty queue\n");
System.exit(2);
}
head = (head + 1) % MaxQ; //increment head circularly return QA[head];
} //end dequeue } //end class Queue
The following is a sample run of Program P4.7:
Enter a positive integer: 192837465 Digits in reverse order: 564738291