Stacks and Recursion Elimination, and Queues

Stacks in Computer Science

Stacks are commonly found in computer science. The reason why is that computing is full of cases where you need to return to a previous position, and in fact keep a whole stack of previous positions. We have seen this in the way that calling methods can be thought of as building a stack of environments. When Java makes a method call, it needs to save its current position in the code, move to the code for the new method, execute that code in its own environment, and then return to the previous position where the method was called from. Executing a method may involve executing another method, and so on, hence building up a complete stack of positions to return to.

Suppose I have two tasks to perform, a then b. Suppose I know the way to perform a is to perform subtasks c then d, and the way to perform b is to perform subtasks e then f. Now suppose I know the way to perform c is to perform g then h, and I know how to do those directly, I don't have to break them further into subtasks. And suppose also the way to perform d is to do i then j.

I could keep a list of the tasks I have to do in the order I have to do them, and that list would start off as [a,b]. Since doing a involves doing c then d, then I could change my lists of tasks to [c,d,b]. And since I know doing c involves doing g then h, I can change my lists of tasks to [g,h,d,b]. I can now do g directly, since I know how to do that without breaking it into subtasks, so I do it and my list of tasks becomes [h,d,b]. Similarly, I can do h, leaving me with [d,b]. Now to do d, I must do i then j, so my list of tasks becomes [i,j,b], and so on.

My list of tasks works like a stack, and to do all my tasks I obey a simple loop. I take the task off the top of the stack (or the front of the list). If it is a task I can do directly, I do it, otherwise if it is a task I can break down into subtasks, I push the subtasks on top of the stack (put them at the front of the list). I follow this simple loop until my stack or list is empty, then I have finished my work.

I could write what I know about the tasks I have to perform as a set of methods:

myWork()
{
a();
b();
}

a()
{
c();
d();
}

b()
{
e();
f();
}

c()
{
g();
h();
}

d()
{
i();
j();
}
I could also represent the tasks as a tree structure, showing how tasks break into subtasks:

The work that is done in executing these tasks is equivalent to a traversal of this tree. Previously we suggested any such traversal would require recursion, but with an explicit stack of tasks we were able to do it purely with iteration.

Printing a tree using a stack and iteration

The task of printing a tree can be broken into three subtasks, printing its two branches and printing its node value. The order in which these subtasks are handled makes the difference as to whether the tree is printed with pre-order, in-order or post-order traversal. For ordered trees, printing in-order results in the components of the tree being printed completed in ascending order.

Rather than using recursion, following what is written above, we can use a stack to keep a list of printing tasks to be done. The stack will consist both of trees and of node values. As we have considered throughout, rather than have a method which actually prints a tree, we will have a method that returns a string that can be printed. This gives us the following code for the task of printing a tree of integers representing a set which could be put in the place of the toString methods in set classes given previously:

 public String toString()
{
if(tree.isEmpty())
return "{}";
else
{
Stack stack = new Stack();
stack.push(tree);
return "{"+toString(stack)+"}";
}
}

private static String toString(Stack stack)
{
String str="";
while(!stack.isEmpty())
{
Object next = stack.top();
stack.pop();
if(next instanceof Integer)
{
str+=next;
if(!stack.isEmpty())
str+=',';
}
else
{
Tree t = (Tree)next;
if(!t.right().isEmpty())
stack.push(t.right());
stack.push(new Integer(t.item()));
if(!t.left().isEmpty())
stack.push(t.left());
}
}
return str;
}
We create a new stack and push the whole tree onto it. Then we call an iterative method which loops through the stack until it is empty, taking items off the front of it, adding them to the string which is finally returned if they represent integers, breaking them down into their subtrees and node value if they represent trees.

The stack is declared as previously with its component items of type Object. They do not all have to be of the same actual type, and in this case they are not. The stack contains a mixture of items, some of type Tree and some of type Integer. The type Integer is the wrapper class for int, so new Integer(t.item()) creates a new Integer object representing the int given by t.item(). We can tell whether the item that is taken from the top of the stack is of type Integer rather than of type Tree by using instanceof. In Java, if exp is an expression and T is the name of a type, exp instanceof T is a boolean expression which evaluates to true if exp refers to an object of type T and false if exp refers to an object which is not of type T. Obviously, it is only of use if exp is of a type more general than T, which it is in our code, since exp is the variable next of the most general type, Object.

If the top of the stack is of type Integer, that is just joined onto the string giving the overall representation of the tree. The statement str+=next will automatically use the toString method from the object referred to by next, which in the case of Integer is built in as part of the Java library, and just gives the string corresponding to the value of the integer.

If the object from the top of the stack is not an Integer, it must be a Tree. We need to view it as a Tree in order to be able to apply the Tree methods to it. The statement Tree t = (Tree)next sets t to refer to the same object as next refers to, but treating it as a Tree object rather than an Object object. It is then broken into its three parts which are put onto the stack, except that empty trees are ignored.

Queues

Stacks are sometimes called LIFO data structures, standing for "last in, first out", meaning that the last object that was added to a stack (using push) will be the first that is taken (using top and pop) when one is removed. Another sort of data structure is referred to as FIFO, meaning "first in, first out", meaning that when you take an object out of the structure you will get the first one that was put into it of all those that are still in it. In other words, it is a queue: items join a queue at the back and leave it at the front.

A queue could be represented just by a linked list, but this makes adding new items at the end inefficient, since you have to send a pointer down the list to reach the last cell in order to add a new one. A more efficient representation represents a queue as a linked list with an extra pointer in the queue object pointing to the last element of the linked list. So the following structure represents a queue storing the elements [7,3,8,2]:

Then the code for class Queue is:

class Queue
{
private Cell front,back;

public Object first()
{
return front.first;
}

public void leave()
{
front=front.next;
if(front==null)
back=null;
}

public void join(Object obj)
{
if(front==null)
{
front = new Cell(obj,null);
back=front;
}
else
{
back.next = new Cell(obj,null);
back=back.next;
}
}

public boolean isEmpty()
{
return front==null;
}

private static class Cell
{
Object first;
Cell next;

Cell(Object f,Cell n)
{
first=f;
next=n;
}
}
}
For simplicity, as with our other data structures, there is no error reporting if an attempt is made to access an item in a queue which is actually empty (it will cause a NullPointerException to be thrown). If the queue is empty both the front and back pointer are set to null and account needs to be taken of this possibility. Otherwise, the pointer manipulations needed to remove an item from the front of the queue (in method leave) and add an item to the back of the queue (in method join) are simple.

The operations here are destructive. The representation suggested here cannot be easily adapted to give queues that operate on a constructive basis, but in practice there is rarely a need for queues which operate with constructive methods.

Queues are used in computer science for the same sort of uses where queues are used in real life - where there are several entities wanting to use a resource which can only be used by one entity at a time, and the entities are given use of the resource in the order at which they made a request for it. Queue data structures are often used in computer simulations to simulate real life queues.

Queues are also used if we want to traverse a tree in breadth first order. This means looking at the node first, then the items which are the node values of the branches of the tree, then the items which are the node values of their branches and so on. So in the tree representing my work above, after dealing with the node value of the tree, I would deal with it content in alphabetical order.

Breadth-first traversal of a tree cannot be done using simple recursion because it involves looking at part of one branch, then part of another, then going back to the first branch and so on. However, it can be done if we use a queue in a similar way to our use of a stack above to traverse a tree without using recursion. A new queue of trees is created, with the tree to be traversed its only member. Then a loop is set up in which the front item of the queue is taken from it, its node value processed and any non-empty branches added to the queue. This is continued until the queue is empty. If "proessing a node value" means "joining a string equivalent of the node value to a cumulative string", the result will be a string reprsenting the items in the tree in breadth-first order.

The code below could be added to our class which represented sets as ordered trees with field tree of type Tree as the representation, in order to give a method breadthFirst which when called on a set prints the set, but with the items in the set in the order they are in the tree if traversed breadthfirst:

 public String breadthFirst()
{
if(tree.isEmpty())
return "{}";
else
{
Queue q = new Queue();
q.join(tree);
return "{"+breadthFirst(q)+"}";
}
}

private static String breadthFirst(Queue q)
{
String str="";
while(!q.isEmpty())
{
Tree t = (Tree)q.first();
q.leave();
if(!t.left().isEmpty())
q.join(t.left());
if(!t.right().isEmpty())
q.join(t.right());
str+=t.item();
if(!q.isEmpty())
str+=",";
}
return str;
}

Matthew Huntbach

Last modified: 5 April 2005