Stacks and Queues
Stacks and Queues
Stacks and queues are among the most frequently used data structures in computer science. Both are examples of restricted access containers . Like lists, they are containers which hold collections of objects. They are one-dimensional structures. Unlike lists, however, the access to those objects is limited to the first or last elements only.
Question: What is the advantage of restricting access? Why not just use a List?
Applications
Stacks:
- parsing of context-free languages (compilers)
- implementation of method call and return at the machine language level (run-time stack)
- implementation of recursion
- backtracking algorithms
- operand stack in the Java Virtual Machine
Queues:
- simulation
- operating systems
Recall the lists you used in implementing the radix sort. What type of access did you use?
Interfaces
Access to a stack is on a last in, first out basis. This is accomplished by allowing insertions and deletions to be performed only at one end of the stack.. Access to a queue is on a first in, first out basis. This is accomplished by allowing insertions only at one end of the queue and deletions only at the other end of the queue. A deque (doubly-ended queue) is a generalization which allows both insertions and deletions to be performed at either end of the deque.Stack interface
boolean isEmpty();
void push(Object obj); // insert an object into the stack. Items are pushed onto the "top" of the stack.
Object pop(); // remove an item from the stack. The item which is removed is the one on the top of the stack.
Object top(); // returns the top element from the stack without removing it.
Queue interface
boolean isEmpty();
void enqueue(Object obj); // insert an object into the queue. Items are added to the rear of the queue.
Object dequeue(); // remove an object from the queue. Items are removed from the front of the queue.
example Write a method which counts the number of elements in a given stack.
int count(Stack s);
example Write a method which creates a copy of a given stack.
Stack copy(Stack s);
Stack Implementation
A stack can be implemented using either a linked list or an array. In fact, it can be implemented with any version of the List interface. However, it may be more efficient to implement it directly.
We'll look at a few different versions:
1. Using a generic SimpleList.
Question: We can choose to use either the beginning or end of the list to perform push and pop operations. Which is the better choice? Does it matter?
version 1: push and pop at the end of the list
void push(Object obj)
{
add(obj);
}
Object pop()
{
return remove(size()-1);
}
Object top()
{
return get(size()-1);
}
version 2: push and pop at the beginning of the list
void push(Object obj)
{
add(0,obj);
}
Object pop()
{
return remove(0);
}
Object top()
{
return get(0);
}
2. Using the low-level methods of the RecursiveList.
void push(Object obj)
{
prepend(obj);
}
Object pop()
{
return removeFirst();
}
Object top()
{
return getFirst();
}
3. Using an array.
4. Use a linked list.
Queue Implementation
A queue can also be implemented using either a linked list or an array. We'll consider several implementations:1. Using a LinkedList with head and tail pointers.
Enqueueing can be done at the tail and dequeueing at the head, both in time O(1). If enqueuing is done at the head and dequeuing at the tail, the dequeuing time becomes O(n).
If a doubly-linked list is used, access to both head and tail are O(1), so both enqueue and dequeue will have time O(1).
2. Using a recursively-defined linked list.
A recursively-defined list has a first and a rest. This gives immediate access to the first element, but not the last. If it is used to implement a queue, one could let the first element be the front of the queue or the rear of the queue. If the first element is the front, then dequeue is O(1), but enqueue is O(n). On the other hand, if the first element is the rear, then enqueue is O(1), but dequeue is O(n).
Conclusion: A recursively-defined linked list is probably not a good choice for a queue implementation.
3. Using an array.
The traditional way of using an array to implement a queue is called a circular queue. Two array indices are maintained: one to the front element of the queue and the other to the rear element. As items are enqueued, the rear index is incremented; as items are dequeued, the front index is incremented. When the rear index reaches the end of the array, it "wraps around" to slot 0 and continues. Both enqueue and dequeue are O(1) (not considering resize operations).