IKI 10100: Data Structures & Algorithms Ruli Manurung (acknowledgments to Denny & Ade Azurat) 1 Fasilkom UI Ruli Manurung (Fasilkom UI)IKI10100: Lecture6 th Mar 2007 Implementing Stacks & Queues
2 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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 Stack Applications Balanced Symbol Checker Postfix Machines Summary Outline
3 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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.
4 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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();
5 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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 top to -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].
6 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Example top(-1) Atop(0) A Btop(1) Push A Push B MyStack myStack = new MyStack(); myStack.push(A); myStack.push(B);
7 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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;
8 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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/2 push() non-doubling calls. Effectively, still constant time Amortization.
9 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Stack Implementation: Array public class MyArrayStack { 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() … }
10 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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 dcba topOfStack
11 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Stack Implementation: Linked List public class MyLinkedListStack { private ListNode topOfStack; public MyLinkedListStack() … public boolean isEmpty() … public void makeEmpty() … public T top() … public void pop() … public T topAndPop() … public void push(T x) … }
12 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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();
13 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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) X back XY XYZ YZ enqueue(X)enqueue(Y)enqueue(Z)dequeue()
14 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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). But what happens if enqueue and dequeue array.length-1 items? XYZ back YZ enqueue(X) enqueue(Y) enqueue(Z) dequeue() front Z back dequeue() front
15 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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.
16 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Circular Example Both front and back indexes “wraparound” the array. Think of the array as a circle… PQR frontback PQRS frontback PQRS front T back QRS front T back RS front T back S front T back T frontback
17 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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?
18 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Queue Implementation: Array public class MyArrayQueue { 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) … }
19 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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 abcd frontback
20 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Queue Implementation: Linked List public class MyLinkedListQueue { private ListNode front; private ListNode back; public MyLinkedListQueue() … public boolean isEmpty() … public void makeEmpty() … public T getFront() … public void dequeue() … public T getFrontAndDequeue() … public void enqueue(T x) … }
21 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Stack Application: Balanced Symbols Java uses pairs of symbols to denote blocks: ( and ) { and } [ and ] An opening symbol must be matched with a closing symbol: { [ ( ) ] } is OK { ( [ ) ] } is NOT OK A closing symbol, e.g. ‘)’, must match the most recently seen opening symbol, e.g. ‘(‘. This can be done using a stack.
22 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Balanced Symbol Checker Algorithm Make an empty stack Read symbols: If symbol is an opening symbol, push onto stack. If symbol is a closing symbol: If stack is empty, report error! Else, (top and) pop stack. If popped symbol is not a matching opening symbol, report error! At the end, if the stack is not empty, report error!
23 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Say we are given an arithmetic expression, and we are asked to evaluate it. Typically, we use infix expressions, of the form operand1 OPERATOR operand2 Example: * 3 To process infix expressions we need precedence and associativity: * ^ 3 ^ ^ 5 * 3 * 6 / 7 ^ 2 ^ 2 We use brackets: (1-2)-((((4^5)*3)*6)/(7^(2^2))) Stack Application: Simple Calculator
24 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Postfix Notation Typically, we use postfix expressions, of the form operand1 operand2 OPERATOR Example: * With postfix expressions, precedence is unambiguous: ^ 5 * 3 * 6 / 7 ^ 2 ^ 2, or (1-2)-((((4^5)*3)*6)/(7^(2^2))), becomes ^ 3 * 6 * ^ ^ / -
25 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 Postfix Machines A postfix machine evaluates a postfix expression: If an operand is seen, push it onto stack If an operator is seen, pop appropriate number of operands, evaluate operator, push result onto stack When the complete postfix expression is evaluated, the stack should contain exactly one item: the result.
26 Ruli Manurung (Fasilkom UI)IKI10100: Lecture 6 th Mar 2007 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. Summary