05 Stack
05 Stack
Outline
• Abstract Data Type
• Stack ADT
• Array-based Implementation
• Pointer-based Implementation
• ADT List-based Implementation
• Applications
Abstract Data Types (ADTs)
• An abstract data type (ADT) is an abstraction of a data structure
• An ADT specifies:
– Data stored
– Operations on the data
– Error conditions associated with operations
3
The Stack ADT
Stack of dishes 4
ADT Stack Operations
• Create an empty stack
• Destroy a stack
• Determine whether a stack is empty
• Add a new item -- push
• Remove the item that was added most recently -- pop
• Retrieve the item that was added most recently
ADT Stack Operations (cont.)
• Stack()
– creates a an empty stack
• ~Stack()
– destroys a stack
• isEmpty():boolean
– determines whether a stack is empty or not
• push(in newItem:StackItemType)
– Adds newItem to the top of a stack
• pop() throw StackException
• topAndPop(out stackTop:StackItemType)
– Removes the top of a stack (ie. removes the item that was added most recently
• getTop(out stackTop:StackItemType)
– Retrieves the top of stack into stackTop
6
Implementations of the ADT Stack
• The ADT stack can be implemented using
– An array
– A linked list
– The ADT list (linked list of the previous lecture)
class StackException {
public:
StackException(const string& err) : error(err) {}
string error;
};
7
Implementations of the ADT Stack (cont.)
(pointer-based)
8
An Array-Based Implementation
• Private data fields
– An array of items of type StackItemType
– The index top
9
An Array-Based Implementation –Header File
#include "StackException.h"
const int MAX_STACK = maximum-size-of-stack;
template <class T>
class Stack {
public:
Stack(); // default constructor; copy constructor and destructor
are supplied by the compiler
// stack operations:
bool isEmpty() const; // Determines whether a
stack is empty.
void push(const T& newItem); // Adds an item to the top of a
stack.
void pop(); // Removes the top of a stack.
void topAndPop(T& stackTop);
void getTop(T& stackTop) const; // Retrieves top of stack.
private:
T items[MAX_STACK]; // array of stack items
int top; // index to top of stack
};
10
An Array-Based Implementation
template <class T>
Stack<T>::Stack(): top(-1) {} // default
constructor
11
An Array-Based Implementation
template <class T>
void Stack<T>::push(const T& newItem) {
12
An Array-Based Implementation – pop
template <class T>
void Stack<T>::pop() {
if (isEmpty())
throw StackException("StackException:
stack empty on pop");
else
--top;
// stack is not empty; pop top
}
13
An Array-Based Implementation – pop
template <class T>
void Stack<T>::topAndPop(T& stackTop) {
if (isEmpty())
throw StackException("StackException: stack empty on
pop");
else // stack is not empty; retrieve top
stackTop = items[top--];
}
14
An Array-Based Implementation – getTop
template <class T>
void Stack<T>::getTop(T& stackTop) const {
if (isEmpty())
throw StackException("StackException: stack
empty on getTop");
else
stackTop = items[top];
}
15
An Array-Based Implementation
l Disadvantages of the array based implementation is similar the
disadvantages of arrays
l Furthermore, it forces all stack objects to have MAX_STACK
elements
l We can fix this limitation by using a pointer instead of an array
template <class T>
class Stack {
public: “Need to implement
Stack(int size) : items(new T [size]) { }; copy constructor,
... // other parts not shown destructor and
private: assignment operator in
T* items; // pointer to the stack this case”
elements
int top; // index to top of stack
};
16
A Pointer-Based Implementation
• A pointer-based implementation
– Required when the stack needs to grow
and shrink dynamically
– Very similar to linked lists
17
A Pointer-Based Implementation – Header File
template <class Object>
class StackNode
{
public:
StackNode(const Object& e = Object(), StackNode* n = NULL)
: element(e), next(n) {}
Object item;
StackNode* next;
};
18
A Pointer-Based Implementation – Header File
#include "StackException.h"
template <class T>
class Stack{
public:
Stack(); // default constructor
Stack(const Stack& rhs); // copy constructor
~Stack(); // destructor
Stack& operator=(const Stack& rhs); // assignment operator
bool isEmpty() const;
void push(const T& newItem);
void pop();
void topAndPop(T& stackTop);
void getTop(T& stackTop) const;
private:
StackNode<T> *topPtr; // pointer to the first node in the stack
};
19
A Pointer-Based Implementation – constructor
and isEmpty
template <class T>
Stack<T>::Stack() : topPtr(NULL) {}
// default constructor
20
A Pointer-Based Implementation – push
template <class T>
void Stack<T>::push(const T& newItem) {
// create a new node
StackNode *newPtr = new StackNode;
newPtr->next = topPtr;
// link this node to the stack
topPtr = newPtr;
// update the stack top
}
21
A Pointer-Based Implementation – pop
template <class T>
void Stack<T>::pop() {
if (isEmpty())
throw StackException("StackException: stack empty on
pop");
else {
StackNode<T> *tmp = topPtr;
topPtr = topPtr->next; // update the stack top
delete tmp;
}
}
22
A Pointer-Based Implementation – topAndPop
template <class T>
void Stack<T>::topAndPop(T& stackTop) {
if (isEmpty())
throw StackException("StackException: stack empty on
topAndPop");
else {
stackTop = topPtr->item;
StackNode<T> *tmp = topPtr;
topPtr = topPtr->next; // update the stack top
delete tmp;
}
}
23
A Pointer-Based Implementation – getTop
template <class T>
void Stack<T>::getTop(T& stackTop) const {
if (isEmpty())
throw StackException("StackException: stack
empty on getTop");
else
stackTop = topPtr->item;
}
24
A Pointer-Based Implementation –
destructor
template <class T>
Stack<T>::~Stack() {
// pop until stack is empty
while (!isEmpty())
pop();
}
25
A Pointer-Based Implementation – assignment
template <class T>
Stack<T>& Stack<T>::operator=(const Stack& rhs) {
if (this != &rhs) {
if (!rhs.topPtr) topPtr = NULL;
else {
topPtr = new StackNode<T>;
topPtr->item = rhs.topPtr->item;
StackNode<T>* q = rhs.topPtr->next;
StackNode<T>* p = topPtr;
while (q) {
p->next = new StackNode<T>;
p->next->item = q->item;
p = p->next;
q = q->next;
}
p->next = NULL;
}
}
return *this;
26
}
A Pointer-Based Implementation – copy
constructor
27
Testing the Stack Class
int main() {
Stack<int> s;
for (int i = 0; i < 10; i++)
s.push(i);
28
Testing the Stack Class
std::cout << "Printing s2:" << std::endl;
while (!s2.isEmpty()) {
int value;
s2.topAndPop(value);
std::cout << value << std::endl;
}
return 0;
}
29
An Implementation That Uses the ADT List
#include "StackException.h"
#include "List.h"
private:
List<T> list;
}
30
An Implementation That Uses the ADT List
• No need to implement constructor, copy constructor, destructor,
and assignment operator
– The list's functions will be called when needed
31
Comparing Implementations
• Array-based
– Fixed size (cannot grow and shrink dynamically)
• Pointer-based
– May need to perform realloc calls when the currently allocated
size is exceeded //Recall the line Stack(int size) : items(new T [size]) { };
– But push and pop operations can be very fast
• Using the previously defined linked-list
– Reuses existing implementation
– Reduces the coding effort but may be a bit less efficient
32
Checking for Balanced Braces
• A stack can be used to verify whether a program contains
balanced braces
• An example of balanced braces
abc{defg{ijk}{l{mn}}op}qr
• An example of unbalanced braces
abc{def}}{ghij{kl}m
• Requirements for balanced braces
– Each time we encounter a “}”, it matches an already encountered “{”
– When we reach the end of the string, we have matched each “{”
33
Checking for Balanced Braces -- Traces
34
Checking for Balanced Braces -- Algorithm
aStack.createStack(); balancedSoFar = true; i=0;
while (balancedSoFar and i < length of aString) {
ch = character at position i in aString; i++;
if (ch is ‘{‘) // push an open brace
aStack.push(‘{‘);
else if (ch is ‘}’) // close brace
if (!aStack.isEmpty())
aStack.pop(); // pop a matching open brace
else // no matching open brace
balancedSoFar = false;
// ignore all characters other than braces
}
if (balancedSoFar and aStack.isEmpty())
aString has balanced braces
else
aString does not have balanced braces
35
Application: Algebraic Expressions
• To evaluate an infix expression //infix: operator in b/w operands
1. Convert the infix expression to postfix form
2. Evaluate the postfix expression //postfix: operator after
operands; similarly we have prefix: operator before
operands
36
Application: Algebraic Expressions
• Infix notation is easy to read for humans
• Pre-/postfix notation is easier to parse for a machine
• The big advantage in pre-/postfix notation is that there never arise
any questions like operator precedence
37
Evaluating Postfix Expressions
• When an operand is entered, the calculator
– Pushes it onto a stack
38
Evaluating Postfix Expressions: 2 3 4 + *
39
Converting Infix Expressions to Postfix
Expressions
• Read the infix expression
– When an operand is entered, append it to the end of postfix
expression
– When an ’(‘ is entered, push it into the stack
– When an ’)‘ is entered, move operators from the stack to the
end of postfix expression until ’(‘
– When an operator is entered, push it into the stack
40
Converting Infix Expressions to Postfix
Expressions
a - (b + c * d)/ e è a b c d * + e / -
41
Converting Infix Expressions to Postfix
Expressions
• Benefits about converting from infix to postfix
– Operands always stay in the same order with respect to one
another
– An operator will move only “to the right” with respect to the
operands
– All parentheses are removed
42
Converting Infix Expr. to Postfix Expr. --
Algorithm
for (each character ch in the infix expression) {
switch (ch) {
case operand: // append operand to end of postfixExpr
postfixExpr=postfixExpr+ch; break;
case ‘(‘: // save ‘(‘ on stack
aStack.push(ch); break;
case ‘)’: // pop stack until matching ‘(‘, and remove ‘(‘
while (top of stack is not ‘(‘) {
postfixExpr=postfixExpr+(top of stack);
aStack.pop();
}
aStack.pop(); break;
43
Converting Infix Expr. to Postfix Expr. --
Algorithm
case operator:
aStack.push(); break; // save new operator
} } // end of switch and for
// append the operators in the stack to postfixExpr
while (!isStack.isEmpty()) {
postfixExpr=postfixExpr+(top of stack);
aStack(pop);
}
44
The Relationship Between Stacks and Recursion
• A strong relationship exists between recursion and stacks
45
C++ Run-time Stack
47
3/31/20
Tracing the call fact (3)
N=0
if (N==0) true
return (1)
N=1 N=1
if (N==0) false if (N==0) false
return (1*fact(0)) return (1*fact(0))
N=2 N=2 N=2
if (N==0) false if (N==0) false if (N==0) false
return (2*fact(1)) return (2*fact(1)) return (2*fact(1))
N=3 N=3 N=3 N=3
if (N==0) false if (N==0) false if (N==0) false if (N==0) false
return (3*fact(2)) return (3*fact(2)) return (3*fact(2)) return (3*fact(2))
After original After 1st call After 2nd call After 3rd call
call 48
3/31/20
Tracing the call fact (3)
N=1
if (N==0) false
return (1* 1)
N=2 N=2
if (N==0) false if (N==0) false
return (2*fact(1)) return (2* 1)
N=3 N=3 N=3
if (N==0) false if (N==0) false if (N==0) false
return (3*fact(2)) return (3*fact(2)) return (3* 2)
After return After return After return return 6
from 3rd call from 2nd call from 1st call 49
3/31/20
Example: Reversing a string
void printReverse(const char* str)
{
if (*str) {
printReverse(str + 1)
cout << *str << endl;
}
}
50
Example: Reversing a string
void printReverseStack(const char* str)
{
Stack<char> s;
for (int i = 0; str[i] != ’\0’; ++i)
s.push(str[i]);
while(!s.isEmpty()) {
char c;
s.topAndPop(c);
cout << c;
}
}
51