Stacks and Queues
4.5 Queues
4.5.2 Implementing a Queue Using a Linked List
The following is a sample run of Program P4.7:
Enter a positive integer: 192837465 Digits in reverse order: 564738291
We can create an empty queue with the following statement:
Queue Q = new Queue();
This will create the structure shown in Figure 4-10.
Q
head tail
Figure 4-10. An empty queue (linked list representation)
To add an item to the queue, we must add it at the tail of the list. Here is enqueue:
public void enqueue(NodeData nd) { Node p = new Node(nd);
if (this.empty()) { head = p;
tail = p;
} else {
tail.next = p;
tail = p;
}
} //end enqueue
If the queue is empty, the new item becomes the only one in the queue; head and tail are set to point to it. If the queue is not empty, the item at the tail is set to point to the new one, and tail is updated to point to the new one.
To take an item off the queue, we first check whether the queue is empty. If it is, we print a message and end the program. If not, the item at the head of the queue is returned, and the node containing the item is deleted.
If, by removing an item, head becomes null, it means that the queue is empty. In this case, tail is also set to null. Here is dequeue:
public NodeData dequeue() { if (this.empty()) {
System.out.printf("\nAttempt to remove from an empty queue\n");
System.exit(1);
}
NodeData hold = head.data;
head = head.next;
if (head == null) tail = null;
return hold;
} //end dequeue
To use the Queue class, a user needs to declare only what he wants NodeData to be. To illustrate, suppose he wants a queue of integers. He can define NodeData as follows:
public class NodeData { int num;
public NodeData(int n) { num = n;
}
public int getIntData() {return num;}
} //end class NodeData
Previously, we wrote Program P4.7 which reads an integer and prints its digits in reverse order. We now rewrite it as Program P4.8 using our new Node, Queue, and NodeData classes.
Program P4.8
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(new NodeData(n % 10));
n = n / 10;
}
System.out.printf("\nDigits in reverse order: ");
while (!Q.empty())
System.out.printf("%d", Q.dequeue().getIntData());
System.out.printf("\n");
} //end main } //end QueueTest class NodeData { int num;
public NodeData(int n) { num = n;
}
public int getIntData() {return num;}
} //end class NodeData class Node {
NodeData data;
Node next;
public Node(NodeData d) { data = d;
next = null;
}
} //end class Node class Queue {
Node head = null, tail = null;
public boolean empty() { return head == null;
}
public void enqueue(NodeData nd) { Node p = new Node(nd);
if (this.empty()) { head = p;
tail = p;
} else {
tail.next = p;
tail = p;
}
} //end enqueue
public NodeData dequeue() { if (this.empty()) {
System.out.printf("\nAttempt to remove from an empty queue\n");
System.exit(1);
}
NodeData hold = head.data;
head = head.next;
if (head == null) tail = null;
return hold;
} //end dequeue } //end class Queue
The following is a sample run of Program P4.8:
Enter a positive integer: 192837465 Digits in reverse order: 564738291
Stacks and queues are important to systems programmers and compiler writers. We have seen how stacks are used in the evaluation of arithmetic expressions. They are also used to implement the “calling” and “return”
mechanism for functions. Consider the situation where function A calls function C, which calls function B, which calls function D. When a function returns, how does the computer figure out where to return to? We show how a stack can be used to do this.
Assume we have the following situation, where a number, like 100, represents the return address, which is the address of the next instruction to be executed when the function returns:
function A function B function C function D . . . . C; D; B; . 100: 200: 300:
. . . .
When A calls C, the address 100 is pushed onto a stack, S. When C calls B, 300 is pushed onto S. When B calls D, 200 is pushed onto S. At this stage, the stack looks like the following, and control is in D:
(bottom of stack) 100 300 200 (top of stack)
When D finishes and is ready to return, the address at the top of the stack (200) is popped, and execution continues at this address. Note that this is the address immediately following the call to D.
Next, when B finishes and is ready to return, the address at the top of the stack (300) is popped, and execution continues at this address. Note that this is the address immediately following the call to B.
Finally, when C finishes and is ready to return, the address at the top of the stack (100) is popped, and execution continues at this address. Note that this is the address immediately following the call to C.
Naturally, queue data structures are used in simulating real-life queues. They are also used to implement queues in the computer. In a multiprogramming environment, several jobs may have to be queued while waiting on a particular resource such as processor time or a printer.
Stacks and queues are also used extensively in working with more advanced data structures such as trees and graphs. We will discuss trees in Chapter 8.
EXERCISES 4
1. What is an abstract data type?
2. What is a stack? What are the basic operations that can be performed on a stack?
3. What is a queue? What are the basic operations that can be performed on a queue?
4. Modify Program P4.5 to recognize infix expressions with mismatched brackets.
5. Program P4.5 works with single-digit operands. Modify it to handle any integer operands.
6. Modify Program P4.5 to handle expressions with operations such as %
, square root, sine, cosine, tangent, logarithm, and exponential.
7. Write declarations/functions to implement a stack of double