Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Lesson 2 - Stack

Download as pdf or txt
Download as pdf or txt
You are on page 1of 18

J.E.D.

I
2 Stacks
2.1 Objectives
At the end of the lesson, the student should be able to:
Explain the basic concepts and operations on the ADT stack
Implement the ADT stack using sequential and linked representation
Discuss applications of stack: the pattern recognition problem and conversion
from infix to postfix
Explain how multiple stacks can be stored using one-dimensional array
Reallocate memory during stack overflow in multiple-stack array using unit-shift
policy and Garwick's algorithm
2.2 Introduction
Stack is a linearly ordered set of elements having the the discipline of last-in, first out,
hence it is also known as LIFO list. It is similar to a stack of boxes in a warehouse,
where only the top box could be retrieved and there is no access to the other boxes.
Also, adding a box means putting it at the top of the stack.
Stacks are used in pattern recognition, lists and tree traversals, evaluation of
expressions, resolving recursions and a lot more. The two basic operations for data
manipulation are push and pop, which are insertion into and deletion from the top of
stack respectively.
Just like what was mentioned in Chapter 1, interface (Application Program Interface or
API) is used to implement ADT in Java. The following is the Java interface for stack:
public interface Stack{
public int size(); /* returns the size of the stack */
public boolean isEmpty(); /* checks if empty */
public Object top() throws StackException;
public Object pop() throws StackException;
public void push(Object item) throws StackException;
}
StackException is an extension of RuntimeException:
class StackException extends RuntimeException{
public StackException(String err){
super(err);
}
}
Stacks has two possible implementations - a sequentially allocated one-dimensional
Data Structures 22
J.E.D.I
array (vector) or a linked linear list. However, regardless of implementation, the interface
Stack will be used.
2.3 Operations
The following are the operations on a stack:
Getting the size
Checking if empty
Getting the top element without deleting it from the stack
Insertion of new element onto the stack (push)
Deletion of the top element from the stack (pop)
Figure 1.6 PUSH Operation
Figure 1.7
Figure 1.8 POP Operation
Data Structures 23
J.E.D.I
2.4 Sequential Representation
Sequential allocation of stack makes use of arrays, hence the size is static. The stack is
empty if the top=-1 and full if top=n-1. Deletion from an empty stack causes an
underflow while insertion onto a full stack causes an overflow. The following figure
shows an example of the ADT stack:
Figure 1.9 Deletion and Insertion
The following is the Java implementation of stack using sequential representation:
public class ArrayStack implements Stack{
/* Default length of the array */
public static final int CAPACITY = 1000;
/* Length of the array used to implement the stack */
public int capacity;
/* Array used to implement the stack*/
Object S[];
/* Initializes the stack to empty */
int top = -1;
/* Initialize the stack to default CAPACITY */
public ArrayStack(){
this(CAPACITY);
}
/* Initialize the stack to be of the given length */
public ArrayStack(int c){
capacity = c;
S = new Object[capacity];
}
/* Implementation of size() */
public int size(){
return (top+1);
}
/* Implementation of isEmpty() */
public boolean isEmpty(){
return (top < 0);
Data Structures 24
J.E.D.I
}
/* Implementation of top() */
public Object top(){
if (isEmpty()) throw new
StackException("Stack empty.");
return S[top];
}
/* Implementation of pop() */
public Object pop(){
Object item;
if (isEmpty())
throw new StackException("Stack underflow.");
item = S[top];
S[top--] = null;
return item;
}
/* Implementation of push() */
public void push(Object item){
if (size()==capacity)
throw new StackException("Stack overflow.");
S[++top]=item;
}
}
2.5 Linked Representation
A linked list of stack nodes could be used to implement a stack. In linked representation,
a node with the structure defined in lesson 1 will be used:
class Node{
Object info;
Node link;
}
The following figure shows a stack represented as a linked linear list:
Figure 1.10 Linked Representation
The following Java code implements the ADT stack using linked representation:
public class LinkedStack implements Stack{
private Node top;
Data Structures 25
J.E.D.I
/* The number of elements in the stack */
private int numElements = 0;
/* Implementation of size() */
public int size(){
return (numElements);
}
/* Implementation of isEmpty() */
public boolean isEmpty(){
return (top == null);
}
/* Implementation of top() */
public Object top(){
if (isEmpty()) throw new
StackException("Stack empty.");
return top.info;
}
/* Implementation of pop() */
public Object pop(){
Node temp;
if (isEmpty())
throw new StackException("Stack underflow.");
temp = top;
top = top.link;
return temp.info;
}
/* Implementation of push() */
public void push(Object item){
Node newNode = new Node();
newNode.info = item;
newNode.link = top;
top = newNode;
}
}
2.6 Sample Application: Pattern Recognition
Problem
Given is the set L = { wcw
R
| w { a, b }+ }, where w
R
is the reverse of w, defines a
language which contains an infinite set of palindrome strings. w may not be the empty
string. Examples are aca, abacaba, bacab, bcb and aacaa.
The following is the algorithm that can be used to solve the problem:
1. Get next character a or b from input string and push onto the stack; repeat until the
symbol c is encountered.
2. Get next character a or b from input string, pop stack and compare. If the two
symbols match, continue; otherwise, stop - the string is not in L.
The following are the additional states in which the input string is said to be not in L:
Data Structures 26
J.E.D.I
1. The end of the string is reached but no c is encountered.
2. The end of the string is reached but the stack is not empty.
3. The stack is empty but the end of the string is not yet reached.
The following examples illustrate how the algorithm works:
Input Action Stack
abbabcbabba ------ (bottom) --> (top)
abbabcbabba Push a a
bbabcbabba Push b ab
babcbabba Push b abb
abcbabba Push a abba
bcbabba Push b abbab
cbabba Discard c abbab
babba Pop, compare b and b --> ok abba
abba Pop, compare a and a --> ok abb
bba Pop, compare b and b --> ok ab
ba Pop, compare b and b --> ok a
a Pop, compare a and a --> ok -
- Success
Input Action Stack
abacbab ------ (bottom) --> (top)
abacbab Push a a
bacbab Push b ab
acbab Push a aba
cbab Discard c aba
bab Pop, compare a and b
--> no match, not in the string
ba
In the first example, the string is accepted while in the second it is not.
The following is the Java program used to implement the pattern recognizer:
public class PatternRecognizer{
ArrayStack S = new ArrayStack(100);
public static void main(String[] args){
PatternRecognizer pr = new PatternRecognizer();
if (args.length < 1) System.out.println(
"Usage: PatternRecognizer <input string>");
Data Structures 27
J.E.D.I
else {
boolean inL = pr.recognize(args[0]);
if (inL) System.out.println(args[0] +
" is in the language.");
else System.out.println(args[0] +
" is not in the language.");
}
}
boolean recognize(String input){
int i=0; /* Current character indicator */
/* While c is not encountered, push the character
onto the stack */
while ((i < input.length()) &&
(input.charAt(i) != 'c')){
S.push(input.substring(i, i+1));
i++;
}
/* The end of the string is reached but
no c is encountered */
if (i == input.length()) return false;
/* Discard c, move to the next character */
i++;
/* The last character is c */
if (i == input.length()) return false;
while (!S.isEmpty()){
/* If the input character and the one on top
of the stack do not match */
if ( !(input.substring(i,i+1)).equals(S.pop()))
return false;
i++;
}
/* The stack is empty but the end of the string
is not yet reached */
if ( i < input.length() ) return false;
/* The end of the string is reached but the stack
is not empty */
else if ( (i == input.length()) && (!S.isEmpty()) )
return false;
else return true;
}
}
Application: Infix to Postfix
An expression is in infix form if every subexpression to be evaluated is of the form
operand-operator-operand. On the other hand, it is in postfix form if every
subexpression to be evaluated is of the form operand-operand-operator. We are
accustomed to evaluating infix expression but it is more appropriate for computers to
evaluate expressions in postfix form.
Data Structures 28
J.E.D.I
There are some properties that we need to note in this problem:
The degree of an operator is the number of operands it has.
The rank of an operand is 1. the rank of an operator is 1 minus its degree. the rank of
an arbitrary sequence of operands and operators is the sum of the ranks of the
individual operands and operators.
if z = x | y is a string, then x is the head of z. x is a proper head if y is not the
null string.
Theorem: A postfix expression is well-formed iff the rank of every proper head is
greater than or equal to 1 and the rank of the expression is 1.
The following table shows the order of precedence of operators:
Operator Priority Property Example
^ 3 right associative a^b^c = a^(b^c)
* / 2 left associative a*b*c = (a*b)*c
+ - 1 left associative a+b+c = (a+b)+c
Examples:
Infix Expression Postfix Expression
a * b + c / d a b * c d / -
a ^ b ^ c - d a b c ^ ^ d -
a * ( b + ( c + d ) / e ) - f a b c d + e /+* f -
a * b / c + f * ( g + d ) / ( f - h ) ^ i a b * c / f g d + * f h - i ^ / +
In converting from infix to postfix, the following are the rules:
1. The order of the operands in both forms is the same whether or not parentheses are
present in the infix expression.
2. If the infix expression contains no parentheses, then the order of the operators in
the postfix expression is according to their priority .
3. If the infix expression contains parenthesized subexpressions, rule 2 applies for such
subexpression.
And the following are the priority numbers:
icp(x) - priority number when token x is an incoming symbol (incoming priority)
isp(x) - priority number when token x is in the stack (in-stack priority)
Token, x icp(x) isp(x) Rank
Operand 0 - +1
+ - 1 2 -1
* / 3 4 -1
^ 6 5 -1
Data Structures 29
J.E.D.I
Token, x icp(x) isp(x) Rank
( 7 0 -
Now the algorithm:
1. Get the next token x.
2. If x is an operand, then output x
3. If x is the ( , then push x onto the stack.
4. If x is the ), then output stack elements until an ( is encountered. Pop stack
once more to delete the (. If top = 0, the algorithm terminates.
5. If x is an operator, then while icp(x) < isp(stack(top)), output stack elements;
else, if icp(x) > isp(stack(top)), then push x onto the stack.
6. Go to step 1.
As an example, let's do the conversion of a + ( b * c + d ) - f / g ^ h into its
postfix form:
Incoming Symbol Stack Output Remarks
a a Output a
+ + a Push +
( +( a Push (
b +( ab Output b
* +(* ab icp(*) > isp(()
c +(* abc Output c
+ +(+ abc* icp(+) < isp(*), pop *
icp(+) > isp((), push +
d +(+ abc*d Output d
) + abc*d+ Pop +, pop (
- - abc*d++ icp(-) < isp(+), pop +, push -
f - abc*d++f Output f
/ -/ abc*d++f icp(/)>isp(-), push /
g -/ abc*d++fg Output g
^ -/^ abc*d++fg icp(^)>isp(/), push ^
h -/^ abc*d++fgh Output h
- abc*d++fgh^/- Pop ^, pop /, pop -
Data Structures 30
J.E.D.I
2.7 Advanced Topics on Stacks
2.7.1 Multiple Stacks using One-Dimensional Array
Two or more stacks may coexist in a common vector S of size n. This approach boasts of
better memory utilization.
If two stacks share the same vector S, they grow toward each other with their bottoms
anchored at opposite ends of S. The following figure shows the behavior of two stacks
coexisting in a vector S:
Figure 1.11 Two Stacks Coexisting in a Vector
At initialization, stack 1's top is set to -1, that is, top1=-1 and for stack2 it is top2=n.
2.7.1.1 Three or More Stacks in a Vector S
If three or more stacks share the same vector, there is a need to keep track of several
tops and base addresses. Base pointers defines the start of m stacks in a vector S with
size n. Notation of which is B(i):
B[i] = n/m * i - 1 0 i < m
B[m] = n-1
B[i] points to the space one cell below the stack's first actual cell. To initialize the stack,
tops are set to point to base addresses, i.e.,
T[i] = B[i] , 0 i m
For example:
Three Stacks in a Vector
Data Structures 31
J.E.D.I
2.7.1.2 Three Possible States of a Stack
The following diagram shows the three possible states of a stack: empty, not full (but
not empty) and full. Stack i is full if T[i] = B[i+1]. It is not full if T[i] < B[i+1] and
empty if T[i] = B[i].
Figure 1.12 Three States of Stack (empty, non-empty but not full, full)
The following Java code snippets show the implementation of the operations push and
pop for multiple stacks:
/* Pushes element on top of stack i */
public void push(int i, Object item) {
if (T[i]==B[i+1]) MStackFull(i);
S[++T[i]]=item;
}
/* Pops the top of stack i */
public Object pop(int i) throws StackException{
Object item;
if (isEmpty(i))
throw new StackException("Stack underflow.");
item = S[T[i]];
S[T[i]--] = null;
return item;
}
The method MStackFull handles the overflow condition.
2.7.2 Reallocating Memory at Stack Overflow
When stacks coexist in an array, it is possible for a stack, say stack i, to be full while the
adjacent stacks are not. In such a scenario, there is a need to reallocate memory to
make space for insertion at the currently full stack. To do this, we scan the stacks above
stack i (address-wise) for the nearest stack with available cells, say stack k, and then
shift the stack i+1 through stack k one cell upwards until a cell is available to stack i. If
all stacks above stack i are full, then we scan the stacks below for the nearest stack with
free space, say stack k, and then we shift the cells one place downward. This is known as
the unit-shift method. If k is initially set to -1, the following code implements the unit-
shift method for the MStackFull:
/* Handles stack overflow at stack i using Unit-Shift Policy */
Data Structures 32
J.E.D.I
/* Returns true if successful, otherwise false */
void unitShift(int i) throws StackException{
int k=-1; /* Points to the 'nearest' stack with free space*/
/*Scan the stacks above(address-wise) the overflowed stack*/
for (int j=i+1; j<m; j++)
if (T[j] < B[j+1]) {
k = j;
break;
}
/* Shift the items of stack k to make room at stack i */
if (k > i){
for (int j=T[k]; j>T[i]; j--)
S[j+1] = S[j];
/* Adjust top and base pointers */
for (int j=i+1; j<=k; j++) {
T[j]++;
B[j]++;
}
}
/*Scan the stacks below if none is found above */
else if (k > 0){
for (int j=i-1; j>=0; j--)
if (T[j] < B[j+1]) {
k = j+1;
break;
}
for (int j=B[k]; j<=T[i]; j++)
S[j-1] = S[j];
/* Adjust top and base pointers */
for (int j=i; j>k; j--) {
T[j]--;
B[j]--;
}
}
else /* Unsuccessful, every stack is full */
throw new StackException("Stack overflow.");
}
2.7.2.1 Memory Reallocation using Garwick's Algorithm
Garwick's algorithm is a better way than unit-shift method to reallocate space when a
stack becomes full. It reallocates memory in two steps: first, a fixed amount of space is
divided among all the stacks; and second, the rest of the space is distributed to the
stacks based on the current need. The following is the algorithm:
1. Strip all the stacks of unused cells and consider all of the unused cells as comprising
the available or free space.
2. Reallocate one to ten percent of the available space equally among the stacks.
3. Reallocate the remaining available space among the stacks in proportion to recent
growth, where recent growth is measured as the difference T[j] - oldT[j], where
oldT[j] is the value of T[j] at the end of last reallocation. A negative(positive)
difference means that stack j actually decreased(increased) in size since last
reallocation.
Data Structures 33
J.E.D.I
Knuth's Implementation of Garwick's Algorithm
Knuth's implementation fixes the portion to be distributed equally among the stacks at
10%, and the remaining 90% are partitioned according to recent growth. The stack size
(cumulative growth) is also used as a measure of the need in distributing the
remaining 90%.The bigger the stack, the more space it will be allocated.
The following is the algorithm:
1. Gather statistics on stack usage
stack sizes = T[j] - B[j]
Note: +1 if the stack that overflowed
differences = T[j] - oldT[j] if T[j] - oldT[j] >0
else 0 [Negative diff is replaced with 0]
Note: +1 if the stack that overflowed
freecells = total size - (sum of sizes)
incr = (sum of diff)
Note: +1 accounts for the cell that the overflowed stack is in need of.
2. Calculate the allocation factors
= 10% * freecells / m
= 90%* freecells / incr
where
m = number of stacks
is the number of cells that each stack gets from 10% of available
space allotted
is number of cells that the stack will get per unit increase in stack
usage from the remaining 90% of free space
3. Compute the new base addresses
- free space theoretically allocated to stacks 0, 1, 2, ..., j - 1
- free space theoretically allocated to stacks 0, 1, 2, ..., j
actual number of whole free cells allocated to stack j =
Initially, (new)B[0] = -1 and = 0
for j = 1 to m-1:
= + + diff[j-1]*
B[j] = B[j-1] + size[j-1] +
=
4. Shift stacks to their new boundaries
5. Set oldT = T
Data Structures 34
J.E.D.I
Consider the following example. Five stacks coexist in a vector of size 500. The state of
the stacks are shown in the figure below:
Figure 1.13 State of the Stacks Before Reallocation
1. Gather statistics on stack usage
stack sizes = T[j] - B[j]
Note: +1 if the stack that overflowed
differences = T[j] - OLDT[j] if T[j] - OLDT[j] >0
else 0 [Negative diff is replaced with 0]
Note: +1 if the stack that overflowed
freecells = total size - (sum of sizes)
incr = (sum of diff)
Factor Value
stack sizes size = (80, 120+1, 60, 35, 23)
differences diff = (0, 15+1, 16, 0, 8)
freecells 500 - (80 + 120+1 + 60 + 35 + 23) = 181
incr 0 + 15+1 + 16 + 0 + 8 = 40
2. Calculate the allocation factors
= 10% * freecells / m = 0.10 * 181 / 5 = 3.62
= 90%* freecells / incr = 0.90 * 181 / 40 = 4.0725
3. Compute the new base addresses
B[0] = -1 and = 0
for j = 1 to m:
= + + diff(j-1)*
B[j] = B[j-1] + size[j-1] +
=
j = 1: = 0 + 3.62 + (0*4.0725) = 3.62
B[1] = B[0] + size[0] +
Data Structures 35
J.E.D.I
= -1 + 80 + 3.62 - 0 = 82
= 3.62
j = 2: = 3.62 + 3.62 + (16*4.0725) = 72.4
B[2] = B[1] + size[1] +
= 82 + 121 + 72.4 - 3.62 = 272
= 72.4
j = 3: = 72.4 + 3.62 + (16*4.0725) = 141.18
B[3] = B[2] + size[2] +
= 272 + 60 + 141.18 - 72.4 = 401
= 141.18
j = 4: = 141.18 + 3.62 + (0*4.0725) = 144.8
B[4] = B[3] + size[3] +
= 401 + 35 + 144.8 - 141.18 = 439
= 144.8
To check, NEWB(5) must be equal to 499:
j = 5: = 144.8 + 3.62 + (8*4.0725) = 181
B[5] = B[4] + size[4] +
= 439 + 23 + 181 - 144.8 = 499 [OK]
4. Shift stacks to their new boundaries.
B = (-1, 82, 272, 401, 439, 499)
T[i] = B[i] + size [i] ==> T = (0+80, 83+121, 273+60, 402+35, 440+23)
T = (80, 204, 333, 437, 463)
oldT = T = (80, 204, 333, 437, 463)
Figure 1.14 State of the Stacks After Reallocation
There are some techniques to make the utilization of multiple stacks better. First, if
known beforehand which stack will be the largest, make it the first. Second, the
algorithm can issue a stop command when the free space becomes less than a specified
minimum value that is not 0, say minfree, which the user can specify.
Aside from stacks, the algorithm can be adopted to reallocate space for other
sequentially allocated data structures (e.g. queues, sequential tables, or combination).
The following Java program implements the Garwick's algorithm for MStackFull.
/* Garwick's method for MStackFull */
void garwicks(int i) throws StackException{
int diff[] = new int[m];
int size[] = new int[m];
Data Structures 36
J.E.D.I
int totalSize = 0;
double freecells, incr = 0;
double alpha, beta, sigma=0, tau=0;
/* Compute for the allocation factors */
for (int j=0; j<m; j++){
size[j] = T[j]-B[j];
if ( (T[j]-oldT[j]) > 0 ) diff[j] = T[j]-oldT[j];
else diff[j] = 0;
totalSize += size[j];
incr += diff[j];
}
diff[i]++;
size[i]++;
totalSize++;
incr++;
freecells = n - totalSize;
alpha = 0.10 * freecells / m;
beta = 0.90 * freecells / incr;
/* If every stack is full */
if (freecells < 1)
throw new StackException("Stack overflow.");
/* Compute for the new bases */
for (int j=1; j<m; j++){
tau = sigma + alpha + diff[j-1] * beta;
B[j] = B[j-1] + size[j-1] + (int) Math.floor(tau)
- (int) Math.floor(sigma);
sigma = tau;
}
/* Restore size of the overflowed stack to its old value */
size[i]--;
/* Compute for the new top addresses */
for (int j=0; j<m; j++) T[j] = B[j] + size[j];
oldT = T;
}
2.8 Summary
A stack is a linearly ordered set of elements obeying the last-in, first-out (LIFO)
principle
Two basic stack operations are push and pop
Stacks have two possible implementations - a sequentially allocated one-
dimensional array (vector) or a linked linear list
Stacks are used in various applications such as pattern recognition, lists and tree
traversals, and evaluation of expressions
Two or more stacks coexisting in a common vector results in better memory
utilization
Memory reallocation techniques include the unit-shift method and Garwick's
algorithm
Data Structures 37
J.E.D.I
2.9 Lecture Exercises
1. Infix to Postfix. Convert the following expressions into their infix form. Show the
stack.
a) a+(b*c+d)-f/g^h
b) 1/2-5*7^3*(8+11)/4+2
2. Convert the following expressions into their postfix form
a) a+b/c*d*(e+f)-g/h
b) (a-b)*c/d^e*f^(g+h)-i
c) 4^(2+1)/5*6-(3+7/4)*8-2
d) (m+n)/o*p^q^r*(s/t+u)-v
Reallocation strategies for stack overflow. For numbers 3 and 4,
a) draw a diagram showing the current state of the stacks.
b) draw a diagram showing the state of the stacks after unit-shift policy is
implemented.
c) draw a diagram showing the state of the stacks after the Garwick's algorithm is
used. Show how the new base addresses are computed.
3. Five stacks coexist in a vector of size 500. An insertion is attempted at stack 2. The
state of computation is defined by:
OLDT(0:4) = (89, 178, 249, 365, 425)
T(0:4) = (80, 220, 285, 334, 433)
B(0:5) = (-1, 99, 220, 315, 410, 499)
4. Three stacks coexist in a vector of size 300. An insertion is attempted at stack 3. The
state of computation is defined by:
OLDT(0:2) = (65, 180, 245)
T(0:2) = (80, 140, 299)
B(0:3) = (-1, 101, 215, 299)
2.10 Programming Exercises
1. Write a Java program that checks if parentheses and brackets are balanced in an
arithmetic expression.
2. Create a Java class that implements the conversion of well-formed infix expression
into its postfix equivalent.
3. Implement the conversion from infix to postfix expression using linked
implementation of stack. The program shall ask input from the user and checks if the
input is correct. Show the output and contents of the stack at every iteration.
4. Create a Java class definition of a multiple-stack in one dimensional vector.
Data Structures 38
J.E.D.I
Implement the basic operations on stack (push, pop, etc) to make them applicable on
multiple-stack. Name the class MStack.
5. A book shop has bookshelves with adjustable dividers. When one divider becomes full,
the divider could be adjusted to make space. Create a Java program that will
reallocate bookshelf space using Garwick's Algorithm.
Data Structures 39

You might also like