As with a stack, one could instead specify a queue in terms of the allowable sequences of enqueue and dequeue operations (using some kind of "algebra").
For instance, consider the following sequences:
Other operations that you sometimes want to provide:
Queues are also used in simulation programs. A simulation program is a program that mimics, or simulates, the behavior of some complicated real-world situation, such as
Some of these situations are particularly well described using queues -- they are characterized by "things waiting in line". For instance,
create an empty queue Q to hold the postfix expression create an empty stack S to hold the operators that have not yet been added to the postfix expression while there are more tokens do get the next token t if t is a number then enqueue t on Q else if t is an operator then push t on S else if t is ( then skip else if t is ) then pop S and enqueue the result on Q endwhile the final answer is in QFor example:
However, it's more convenient not to have to put parentheses around everything. We have precedence conventions, that tell which operations to do first, in the absence of parentheses. For instance, 4 * 3 + 2 equals 12 + 2 = 14, not 4 * 5 = 20.
We need to modify the above algorithm to handle precedence.
create an empty queue Q to hold the postfix expression create an empty stack S to hold the operators that have not yet been added to the postfix expression while there are more tokens do get the next token t if t is a number then enqueue t on Q else if S is empty then push t on S else if t is ( (even if S is not empty) then push t on S else if t is ) then while top of S is not ( do pop S and enqueue the result on Q endwhile pop S (to get rid of the ( that ended the while) else (t is a real operator and S is not empty) while precedence of t is <= precedence of top of S do pop S and enqueue the result on Q endwhile push t on S endif endwhile while S is not empty do pop S and enqueue the result on Q endwhile the final answer is in QFor example:
Representation:
* enqueue(x): (can experience overflow) tail++ A[tail]:= x * dequeue(x): (can also check for empty queue) head++ return A[head-1] * isEmpty: return (tail < head) * peek: return A[head] * size: return tail - head + 1The problem is you will march off the end of the array after very many operations, even if the size of the queue is small compared to the size of A.
A better approach is to wrap around to reuse the vacated space at the beginning of the array, in a circular fashion. We can do this using the operator % (or, mod). Here's a first cut:
* enqueue(x): tail := (tail + 1) % A.length A[tail]:= x * dequeue(x): temp = A[head] head := (head + 1) % A.length return temp * isEmpty: return (tail < head) ???The problem is that tail can wrap around and be in front of head, when the queue is not empty. To get around this problem, add one more state component:
You can also get around the limitations of the size of the array using the same idea as for the array implementation of a stack: Whenever the array is discovered to be full during an enqueue, allocate a new array that is twice the size of the old one, copy the old array to the new one and then enqueue.
One complication with the queue, though, is that the contents of the queue might be in two sections: from head to the end of the array, and then from the beginning of the array to tail. When copying to the new array, you'll need to take this into account.
Given the above discussion, you should be able to understand Program 6.22 in Standish. The only difference is that I've used slightly different terminology:
Performance of the circular array implementation of a queue: