210 likes | 350 Views
Implementing Stacks & Queues. Outline. ADT: Stacks Basic operations Examples of use Implementations Array-based and linked list-based ADT: Queues Basic operations Examples of use Implementations Array-based and linked list-based. Linear Data Structures.
E N D
Outline • ADT: Stacks • Basic operations • Examples of use • Implementations • Array-based and linked list-based • ADT: Queues • Basic operations • Examples of use • Implementations • Array-based and linked list-based
Linear Data Structures • A collection of components that are arranged along one dimension, i.e in a straight line, or linearly. • Stack: a linear data structure where access is restricted to the most recently inserted item. • Queue: a linear data structure where access is restricted to the least recently inserted item. • Both of these abstract data types can be implemented at a lower level using a list: either an array or a linked list.
Stack • The last item added is pushed (added) to the stack. • The last item added can be popped (removed) from the stack. • The last item added can be topped (accessed) from the stack. • These operations all take constant time: O(1). A typical stack interface: void push(Thing newThing); void pop(); Thing top();
Stack Implementation: Array • A stack can be implemented as an array A and an integer top that records the index of the top of the stack. • For an empty stack, set topto -1. • When push(X) is called, increment top, and write X to A[top]. • When pop() is called, decrement top. • When top() is called, return A[top].
Push A Push B B top(1) A top(0) A top(-1) Example MyStack myStack = new MyStack(); myStack.push(A); myStack.push(B);
Array Doubling • When array-based stack is constructed, instantiate an array with a “default” size. • When the array underlying the stack is full (not the stack itself!), we can increase the array through array doubling. • Allocate a new array twice the size, and copy the old array to the first half of the new array: Thing[] newA = new Thing[oldA.length*2]; for(int ii=0; ii<oldA.length; ii++) newA[ii] = oldA[ii]; oldA = newA;
Running Time • Without array doubling, all stack operations take constant time – O(1). • With array doubling, push() may be O(N), but this happens quite rarely: array doubling due to data size N must be preceded by N/2push()non-doubling calls. • Effectively, still constant time Amortization.
Stack Implementation: Array public class MyArrayStack<T> { private T[] array; private int topOfStack; private static final int DEFAULT_CAPACITY = 10; public MyArrayStack() … public boolean isEmpty() … public void makeEmpty() … public T top() … public void pop() … public T topAndPop() … public void push(T x) … private void doubleArray() … }
d c b a Stack Implementation: Linked List • First item in list = top of stack (if empty: null) • push(Thing x): • Create a new node containing x • Insert it as the first element • pop(): • Delete first item (i.e. move “top” to the second item) • top(): • Return the data of the first element topOfStack
Stack Implementation: Linked List public class MyLinkedListStack<T> { private ListNode<T> topOfStack; public MyLinkedListStack() … public boolean isEmpty() … public void makeEmpty() … public T top() … public void pop() … public T topAndPop() … public void push(T x) … }
Queue • Last item added is enqueued (added) to the back. • First item added is dequeued (removed) from the front. • First item added can be accessed: getFront. • These operations all take constant time – O(1). A typical queue interface: void enqueue(Thing newThing); void dequeue(); Thing getFront();
X X Y X Y Z Z Y back back back back Queue Implementation: Simple Idea • Store items in an array A • Maintain index: back • Front of queue = A[0] • Back of queue = A[back] • Enqueue is easy & fast: store at A[back], back++ • Dequeue is inefficient: A[1] to A[back] needs to be shifted (and back--) O(N) enqueue(X) enqueue(Y) enqueue(Z) dequeue()
Y X Y Z Z Z back back front front front back Queue Implementation: Better Idea • Add another index: front, which records the front of the queue • Dequeue is now done by incrementing front • Both enqueue and dequeue are now O(1). enqueue(X) enqueue(Y) enqueue(Z) dequeue() dequeue() Question: what happens if we enqueue then dequeuearray.length-1 items?
Queue Implementation: “Circular” Array • After the array.length-1-th item is enqueued, the underlying array is full, even though the queue is not logically, it should be (almost?) empty. • Solution: wraparound • Re-use cells at beginning of array that are ‘empty’ due to dequeue. • When either front or back is incremented and points “outside array” (≥array.length), reset to 0.
front back back front back back front back front back front front Q T R Q P P Q R S T R S T R R Q T P S T S S front back Circular Example • Both front and back indexes “wraparound” the array. Think of the array as a circle…
Java Implementation • Fairly straightforward. Basically, maintain • Front • Back • Number of items in queue • When is the underlying array really full? • How do we do array doubling?
Queue Implementation: Array public class MyArrayQueue<T> { private T[] array; private int front,back,currentSize; private static final int DEFAULT_CAPACITY = 10; public MyArrayQueue() … public boolean isEmpty() … public void makeEmpty() … public T getFront() … public void dequeue() … public T getFrontAndDequeue() … public void enqueue(T x) … private void doubleQueue() … private int increment(int x) … }
a b c d Queue Implementation: Linked List • Maintain 2 node references: front & back • An empty queue: front = back = null. • enqueue(Thing X): • Create a new node N containing X • If queue empty: front = back = N • Else append N and update back • dequeue(): • Delete first item (referenced by front) • getFront(): • Return data of first element front back
Queue Implementation: Linked List public class MyLinkedListQueue<T> { private ListNode<T> front; private ListNode<T> back; public MyLinkedListQueue() … public boolean isEmpty() … public void makeEmpty() … public T getFront() … public void dequeue() … public T getFrontAndDequeue() … public void enqueue(T x) … }
Summary • Both versions, array and linked-list, run in O(1) • Linked-list implementation requires extra overhead due to next reference at each node • (Circular) array implementation of queues can be quite tricky • Array space doubling needs memory at least 3x size of actual data.