0% found this document useful (0 votes)
20 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
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 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
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