




Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
The design and implementation of a queue class using an array in java. It covers the need for instance variables such as an array, an integer for the front of the queue, and an integer for the total number of elements in the queue. The importance of tracking the index of the front of the queue and the efficiency of dequeuing and enqueuing operations. It also addresses the issue of resizing the array when necessary and the amortized analysis of queue operations.
Typology: Study notes
1 / 8
This page cannot be seen from the preview
Don't miss anything!





Before I get to the code, let's consider some design issues. First off, what instance variables are needed to maintain a Queue object? As the title indicates, the main instance variable we are going to use is an array. BUT, we need others. Since we need immediate access to the front of the queue, it may make sense to have an integer front that stores the index into the array that stores the value at the front of the queue.
Some would argue that this is unnecessary. Why not just store the value at the front of the queue in array index 0 ALWAYS? This idea can work, but there is an inefficiency issue. What is it?
If we always keep the front of the queue in array index 0, then every time we do a dequeue, what do we have to do?
We have to move EVERY element still in the queue to the previous index. This takes O(n) time, where n is the number of elements in the queue. If this is a large number, then this could take a while.
On the otherhand, if we kept track of the array index storing the front of the queue, when we do a dequeue, ALL we have to do is increment this array index.
BUT, it isn't that simple either...What happens when you run out of room at the end of the array?
You can just wrap around to the beginning of the array. But another issue to worry about is copying over the front of the array, once you allow wrapping around.
One easy way to prevent this is by storing the number of elements in the queue.
So, we've got our instance variables set:
With this setup, dequeuing is quite easy. All we have to do is the following:
Enqueuing isn't so bad either:
But, the case we HAVEN'T considered is enqueuing into the Queue when the array is full!
Then, we have no choice but to resize the array. As it turns out, a good way to do this is to double the size of the array. Now, keep in mind that when we do this, we'll have to copy over everything from the original array so that everything is consistent with how we store all the values. Thus, we CAN'T just copy each value from the items array into the new larger array into the same exact indexes.
public void Enqueue(int n) {
// Takes care of the case where the array is full. if (size == items.length) {
printA(); // Checking contents of items for debugging.
int i,j; int[] temp; // Expanded temporary array to store queue elements. temp = new int[2*items.length];
// Copy all the elements from the queue into the temporary array, // resetting the front of the queue to be 0. for (i=front,j=0; j < size; j++,i=(i+1)%size) temp[j] = items[i];
// Add in the new element. temp[size] = n;
// Make other necessary adjustments. front = 0; items = temp;
printA(); // Checking new contents for debugging. }
// Take care of the normal case. else items[(front+size)%items.length] = n;
// Increment size since an element was added. size++; }
// Dequeues the front integer from the queue.
public int Dequeue() {
// Take care of the normal non-empty queue case. if (size > 0) {
int val = items[front]; // Store item to return.
// Make necessary adjustments to instance variables. front=(front+1)%items.length; size--;
return val; // Return front element. } return -1; // Signals an empty queue. }
// Returns true if the queue is empty, false otherwise. public boolean Empty() { return (size == 0); }
// Returns the front element w/o dequeuing it. public int Front() {
// Return the appropriate value. if (size > 0) return items[front]; return -1; // Signals empty queue. }
// Method for testing only. Prints the entire contents of items.
Clearly, Dequeue(), Empty(), and Front() are O(1) operations each since they always do a constant amount of work. But, how much work is done by Enqueue(). In the typical case, an enqueue also takes constant time. BUT, when the array grows, an enqueue is quite expensive. So, we might say that in the worst case, a single enqueue operation takes O(n) when there are n elements in the queue.
But we also know that this case can not occur all that frequently. There is a type of analysis called amortized algorithm analysis that "helps" us analyze operations like the enqueue operation that have vastly different running times on separate executions.
The idea is as follows: Compute the worst possible running time over a stretch of any n enqueue operations. Take this value, divide it by n, and that will give your amortized worst case running time. Basically, this is saying that although one operation could give you a particular running time, if you take the average of any consecutive n operations, the running time will be quicker. Let's consider this with the enqueue.
Given a full array of size n, consider n enqueues. Each of these, except for one, will take O(1) time. That one "bad" enqueue will take O(n) time. Adding this up, we essentially get nO(1) + O(n) = O(n) for the total work done in the n operations. But, the average work will then be O(n)/n which is some constant, or O(1). Another way to think of this is as follows:
Let's say you have 2n cents to spend. Each cent is spent for a constant time operation. Your n-1 enqueues cost you 1 cent each. You are left with n+1 cents for your the worst case enqueue. All in all, you spend O(n) cents. Note: If we didn't double the size of the array every time, this analysis would not work. Consider the case of adding 5 to the size of the array each time...
Luckily, with a stack, since you access the same "end" of the linear data structure, we only need two instance variables: an array, and an integer to the top of the stack. I won't show you the code for this because you will have to implement it for your next assignment, but here are some issues to think about:
When pushing, you simply add the element to the "top" of the array, and then increment "top." The special case is if your array is full. Then you need to resize the array. Here you must double the size of the array. Copy the contents over to the new array, and then add the new element to the appropriate location. Finally, you must move the new array into the old one. Luckily arrays are references in Java and this is an inexpensive operation. (e.g. the dynamic growth of the array in the queue implementation shown today.)
A pop is quite simple. If the top isn't 0, you've got an element to pop, save it, decrement top, and then return the element.
For the method top, you do what is prescribed above, except for decrementing top.
Empty just checks the value of top.