0% found this document useful (0 votes)
21 views

05 Stack

The document discusses different implementations of a stack abstract data type (ADT). It describes array-based and pointer-based implementations of a stack, including the operations, header files, and code for each. The array implementation uses a fixed-size array while the pointer version allows dynamic resizing. Both support the standard stack operations like push, pop, and checking if empty.

Uploaded by

nttqn203
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views

05 Stack

The document discusses different implementations of a stack abstract data type (ADT). It describes array-based and pointer-based implementations of a stack, including the operations, header files, and code for each. The array implementation uses a fixed-size array while the pointer version allows dynamic resizing. Both support the standard stack operations like push, pop, and checking if empty.

Uploaded by

nttqn203
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 51

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

• The Stack ADT stores arbitrary objects.


• Insertions and deletions follow the Last-In
First-Out (LIFO) principle
• The last item placed on the stack will
be the first item removed.

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)

• All three implementations use a StackException class to


handle possible exceptions

class StackException {
public:
StackException(const string& err) : error(err) {}
string error;
};

7
Implementations of the ADT Stack (cont.)

(reuse existing List.h)

(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

template <class T>


bool Stack<T>::isEmpty() const {
return top < 0;
}

11
An Array-Based Implementation
template <class T>
void Stack<T>::push(const T& newItem) {

if (top >= MAX_STACK-1)


throw StackException("StackException: stack full on push");
else
items[++top] = 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

• top is a reference to the head of a linked


list of items

• A copy constructor, assignment operator,


and destructor must be supplied

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

template <class T>


bool Stack<T>::isEmpty() const
{
return topPtr == NULL;
}

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->item = newItem; // insert the data

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

template <class T>


Stack<T>::Stack(const Stack& rhs) {
*this = rhs; // reuse assignment operator
}

27
Testing the Stack Class
int main() {
Stack<int> s;
for (int i = 0; i < 10; i++)
s.push(i);

Stack<int> s2 = s; // test copy constructor (also tests assignment)

std::cout << "Printing s:" << std::endl;


while (!s.isEmpty()) {
int value;
s.topAndPop(value);
std::cout << value << std::endl;
}

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"

template <class T>


class Stack{
public:

bool isEmpty() const;


void push(const T& newItem);
void pop();
void topAndPop(T& stackTop);
void getTop(T& stackTop) const;

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

• isEmpty(): return list.isEmpty()


• push(x): list.insert(x, list.zeroth())
• pop(): list.remove(list.first()->element)
• topAndPop(&x) and getTop(&x) are similar

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

Infix Expression Postfix Expression Prefix Expression


5+2*3 523*+ +5*23
5*2+3 52*3+ +*523
5*(2+3)-4 523+*4- -*5+234

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

• When an operator is entered, the calculator


– Applies it to the top two operands of the stack
– Pops the operands from the stack
– Pushes the result of the operation on the 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

• Move the operators in the stack to the end of postfix expression

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

• Typically, stacks are used by compilers to implement recursive


methods
– During execution, each recursive call generates an activation
record that is pushed onto a stack
– We can get stack overflow error if a function makes makes
too many recursive calls

• Stacks can be used to implement a non recursive version of a


recursive algorithm

45
C++ Run-time Stack

• The C++ run-time system main() {


keeps track of the chain of int i = 5; bar
active functions with a stack foo(i); m=6
• When a function is called, }
the run-time system pushes foo(int j) {
on the stack a frame int k; foo
containing k = j+1; j=5
k=6
– Local variables and return bar(k);
}
value
• When a function returns, its bar(int m) { main
frame is popped from the … i=5
}
stack and control is passed to
the method on top of the Run-time Stack
stack
46
Example: Factorial function
int fact(int n)
{
if (n ==0)
return (1);
else
return (n * fact(n-1));
}

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

You might also like