Stacks Stack A stack is a data structure

  • Slides: 28
Download presentation
Stacks

Stacks

Stack • A stack is a data structure that stores data in such a

Stack • A stack is a data structure that stores data in such a way that the last piece of data stored, is the first one retrieved – also called last-in, first-out • Only access to the stack is the top element – consider trays in a cafeteria • to get the bottom tray out, you must first remove all of the elements above

Stack • Push – the operation to place a new item at the top

Stack • Push – the operation to place a new item at the top of the stack • Pop – the operation to remove the next item from the top of the stack

Stack M C R C push(M) R C item = pop() item = M

Stack M C R C push(M) R C item = pop() item = M R X X X A A A

Implementing a Stack • At least three different ways to implement a stack –

Implementing a Stack • At least three different ways to implement a stack – array – vector – linked list • Which method to use depends on the application – what advantages and disadvantages does each implementation have?

Implementing Stacks: Array • Advantages – best performance • Disadvantage – fixed size •

Implementing Stacks: Array • Advantages – best performance • Disadvantage – fixed size • Basic implementation – initially empty array – field to record where the next data gets placed into – if array is full, push() returns false • otherwise adds it into the correct spot – if array is empty, pop() returns null • otherwise removes the next item in the stack

Stack Class (array based) class Stack. Array { private Object[ ] stack; private int

Stack Class (array based) class Stack. Array { private Object[ ] stack; private int next. In; public Stack. Array(int size) { stack = new Object[size]; next. In = 0; } public boolean push(Object data); public Object pop(); public void clear(); public boolean is. Empty(); public boolean is. Full(); }

push() Method (array based) public boolean push(Object data) { if(next. In == stack. length)

push() Method (array based) public boolean push(Object data) { if(next. In == stack. length) { return false; } // stack is full // add the element and then increment next. In stack[next. In] = data; next. In++; return true; }

pop() Method (array based) public Object pop() { if(next. In == 0) { return

pop() Method (array based) public Object pop() { if(next. In == 0) { return null; } // stack is empty // decrement next. In and return the data next. In--; Object data = stack[next. In]; return data; }

Notes on push() and pop() • Other ways to do this even if using

Notes on push() and pop() • Other ways to do this even if using arrays – may want to keep a size variable that tracks how many items in the list – may want to keep a max. Size variable that stores the maximum number of elements the stack can hold (size of the array) • you would have to do this in a language like C++ – could add things in the opposite direction • keep track of next. Out and decrement it on every push; increment it on every pop

Remaining Methods (array based) public void clear() { next. In = 0; } public

Remaining Methods (array based) public void clear() { next. In = 0; } public boolean is. Empty() { return next. In == 0; } public boolean is. Full() { return next. In == stack. length; }

Additional Notes • Notice that the array is considered empty if next. In equals

Additional Notes • Notice that the array is considered empty if next. In equals zero – doesn’t matter if there is more data stored in the array – it will never be retrieved • pop() method will automatically return • For a truly robust implementation – should set array elements equal to null if they are not being used • why? how?

Implementing a Stack: Vector • Advantages – grows to accommodate any amount of data

Implementing a Stack: Vector • Advantages – grows to accommodate any amount of data – second fastest implementation when data size is less than vector size • Disadvantage – slowest method if data size exceeds current vector size • have to copy everything over and then add data – wasted space if anomalous growth • vectors only grow in size – they don’t shrink – can grow to an unlimited size • I thought this was an advantage? • Basic implementation – virtually identical to array based version

Stack Class (vector based) class Stack. Vector { private Object[ ] stack; private int

Stack Class (vector based) class Stack. Vector { private Object[ ] stack; private int next. In; public Stack. Vector(int initial. Size) { stack = new Object[initial. Size]; next. In = 0; } public void push(Object data); public Object pop(); public void clear(); public boolean is. Empty(); }

push() Method (vector based) public void push(Object data) { // see if we need

push() Method (vector based) public void push(Object data) { // see if we need to grow this stack if(next. In == stack. length) { Object [ ] tmp = new Object[stack. length * 2]; for(int i=0; i<stack. length; i++) tmp[i] = stack[i]; stack = tmp; } // now add the element and increment next. In stack[next. In] = data; next. In++; }

pop() Method (vector based) public Object pop() { if(next. In == 0) { return

pop() Method (vector based) public Object pop() { if(next. In == 0) { return null; } // stack empty // decrement next. In, get the data, and return it next. In--; Object data = stack[next. In]; return data; }

Notes on push() and pop() • Notice that the pop() method is identical to

Notes on push() and pop() • Notice that the pop() method is identical to that for an array based version • Only difference is in push() method – doesn’t return a boolean because it cannot fail • unless we run out of memory – first checks if the push will exceed the current array • if so, create a new array that’s 2 x as big, copy data, and make that the new stack • this is the case that’s very slow

Remaining Methods (vector based) • The clear() and is. Empty() methods are identical to

Remaining Methods (vector based) • The clear() and is. Empty() methods are identical to those in an array based stack implementation • There is no need for an is. Full() method – why?

Implementing a Stack: Linked List • Advantages: – always constant time to push or

Implementing a Stack: Linked List • Advantages: – always constant time to push or pop an element – can grow to an infinite size • Disadvantages – the common case is the slowest of all the implementations – can grow to an infinite size • Basic implementation – list is initially empty – push() method adds a new item to the head of the list – pop() method removes the head of the list

Stack Class (list based) class Stack. List { private Linked. List list; public Stack.

Stack Class (list based) class Stack. List { private Linked. List list; public Stack. List() { list = new Linked. List(); } public void push(Object data) { list. add. Head(data); } public Object pop() { return list. delete. Head(); } public void clear() { list. clear(); } public boolean is. Empty() { return list. is. Empty(); } }

Additional Notes • It should appear obvious that linked lists are very well suited

Additional Notes • It should appear obvious that linked lists are very well suited for stacks – add. Head() and delete. Head() are basically the push() and pop() methods • Our original list implementation did not have a clear() method – it’s very simple to do – how would you do it? • Again, no need for the is. Full() method – list can grow to an infinite size

Stack Applications • Stacks are a very common data structure – compilers • parsing

Stack Applications • Stacks are a very common data structure – compilers • parsing data between delimiters (brackets) – operating systems • program stack – virtual machines • manipulating numbers – pop 2 numbers off stack, do work (such as add) – push result back on stack and repeat – artificial intelligence • finding a path

Reverse Polish Notation • Way of inputting numbers to a calculator – (5 +

Reverse Polish Notation • Way of inputting numbers to a calculator – (5 + 3) * 6 becomes 5 3 + 6 * – 5 + 3 * 6 becomes 5 3 6 * + • We can use a stack to implement this – consider 5 3 + 6 * + 6 3 5 * 6 8 – try doing 5 3 6 * + 8 48

public int rpn(String equation) { Stack. List stack = new Stack. List(); String. Tokenizer

public int rpn(String equation) { Stack. List stack = new Stack. List(); String. Tokenizer tok = new String. Tokenizer(equation); while(tok. has. More. Tokens()) { String element = tok. next. Token(); if(is. Operator(element)) { char op = element. char. At(0); if(op == ‘=‘) { int result = ((Integer)stack. pop()). int. Value(); if(!stack. is. Empty() || tok. has. More. Tokens()) { return Integer. MAX_VALUE; } // error else { return result; } } else { Integer op 1 = (Integer)stack. pop() Integer op 2 = (Integer)stack. pop(); if((op 1 == null) || (op 2 == null)) { return Integer. MAX_VALUE; } stack. push(do. Operation(op, op 1, op 2)); } } else { Integer operand = new Integer(Integer. parse. Int(element)); stack. push(operand); } } return Integer. MAX_VALUE; }

Finding a Path • Consider the following graph of flights Z Key : city

Finding a Path • Consider the following graph of flights Z Key : city (represented as C) Y W R C 1 S P T Example W X C 2 : flight from city C to city C 1 2 Q S flight goes from W to S

Finding a Path • If it exists, we can find a path from any

Finding a Path • If it exists, we can find a path from any city C 1 to another city C 2 using a stack – place the starting city on the bottom of the stack • mark it as visited • pick any arbitrary arrow out of the city – city cannot be marked as visited • place that city on the stack – also mark it as visited • if that’s the destination, we’re done • otherwise, pick an arrow out of the city currently at – next city must not have been visited before – if there are no legitimate arrows out, pop it off the stack and go back to the previous city • repeat this process until the destination is found or all the cities have been visited

Example • Want to go from P to Y – push P on the

Example • Want to go from P to Y – push P on the stack and mark it as visited – pick R as the next city to visit (random select) • push it on the stack and mark it as visited – pick X as the next city to visit (only choice) • push it on the stack and mark it as visited – no available arrows out of X – pop it – no more available arrows from R – pop it – pick W as next city to visit (only choice left) • push it on the stack and mark it as visited – pick Y as next city to visit (random select) • this is the destination – all done

Psuedo-Code for the Example public boolean find. Path(City origin, City destination) { Stack. Array

Psuedo-Code for the Example public boolean find. Path(City origin, City destination) { Stack. Array stack = new Stack(num. Cities); clear. All. City. Marks(); stack. push(origin); origin. mark(); while(!stack. is. Empty()) { City next = pick. City(); if(next == destination) { return true; } if(next != null) { stack. push(next); } else { stack. pop(); } // no valid arrows out of city } return false; }