Week 12: More on recursion

More examples of recursion

Sums, again

Let’s move away from simple numerical examples and start looking at recursion over data structures. In particular, let’s look at operations over arrays and over linked lists.

Suppose you want to sum the elements of an array iteratively. Easy-peasy:

int sum(int[] a) {
    int s = 0;
    for (int i = 0; i < a.length; i++) {
        s += a[i];
    }
    return s;
}

OK, now suppose you want to sum the integers contained in a linked list. Remember, a linked list is a set of Node<E> objects (each with a next reference and a E data variable). Lists are identified by their head, which is just a node. The iterative way to sum up a list of integers in an linked list would look like:

int sum(Node<Integer> head) {
    int s = 0;
    for (Node<Integer> current = head; current != null; current = current.next) {
        s += current.data;
    }
    return s;
}

Recursion on linked lists

How might we write this recursively? Well, how would we define it recursively?

The sum of the elements in an empty list is zero.

The sum of the elements in a list of one element is just that element’s value.

For two (or more) elements, it’s the two elements values…or put another way, it’s the sum of the current value and the sum of the rest of the list’s values.

This is similar to how we might think of sumToN, where we say the sum of the first n is equal to n + the sum of (n-1). But in this case, we say the sum of the values is equal to the current node’s value plus the sum of the remaining nodes.

In other words:

sum(node) =

It’s an exact parallel to our sumToN code. We’d write it the same way:

int sum(Node<Integer> node) {
    if (node == null) {
        return 0;
    }
    else {
        return node.data + sum(node.next);
    }
}

Code-wise, this approach is not horribly worse than an iterative solution, even if it feels unfamiliar to the typical Java programmer. Linked structures (which are defined partially recursively) tend to be pretty OK to write recursive algorithms for. In some languages, you get more syntax support, and writing recursive algorithms is the preferred way to do many things. And on some data structures and problems, recursive solutions, even in a language that favors iteration, like Java, “feel” more natural. But more about that later.

Recursion on arrays

OK. What about the array case for summing entries, could you do it recursively?

You need to keep track of both the array and the index to the array. But the sum method only takes an array as a parameter, and doesn’t know anything about an index. For reasons I’m not going to bore you with, the Land of Recursion frowns upon the use of state that exists outside of the method calls themselves (it’s not disallowed, but used injudiciously it can lead to recursive methods that are impossible to reason about). Instead, all state gets passed into methods as arguments, and out of methods as return values. In that sense, the methods are “functions” in the mathematical sense.

So anyway, you would need to write what’s called a “helper method.” The purpose of the helper method is to give you access, through a new argument, to some state you wish to propagate through the recursion. What state do we need? The index of the array currently being considered. What does this look like?

int sumRecursive(int[] a) {
    return sumRecursive(a, 0);
}

int sumRecursive(int[] a, int i) {
    if (i == a.length) {
        return 0;
    }
    else {
        return a[i] + sumRecursive(a, i + 1);
    }

}

What’s going on here? sum(a) calls sum(a, i); only the latter is recursive. What’s it saying?

sum(a, i) =

Here, we’re passing some state – the index in question – along on the recursive calls. And we need to do so because we need to keep track of “where we are” in the recursion. In linked lists, this happens naturally (because the node reference tracks where we are and the structure itself), but arrays require us to track it explicitly in a variable.

Again: adding this helper method lets us pass state through the recursion.

The utility of writing out your recursion

It’s a good idea to get into the habit of writing out your recursion in text before coding it. Remember this?

int sum(Node<Integer> node) {
    if (node == null) {
        return 0;
    }
    else {
        return node.data + sum(node.next);
    }
}

translates to

The sum of a list :

Or think of factorial, for example. The factorial of a number is:

And so on.

Suppose we want to make a copy of a linked list. Iteratively what do we do? Something like:

static <E> Node<E> copyIterative(Node<E> head) {
    if (head == null) {
        return null;
    }

    Node<E> newHead = new Node<>();
    newHead.data = head.data;

    Node<E> oldRef = head.next;
    Node<E> newRef = newHead;
    while (oldRef != null) {
        Node<E> n = new Node<>();
        n.data = oldRef.data;
        newRef.next = n;

        newRef = newRef.next;
        oldRef = oldRef.next;
    }
    return newHead;
}

In the code above, we maintain two references: one (oldRef) points to the next node in the old list to be copied; the other (newRef) points to the most recently copied node in the new list (that is, the new list’s current tail). We traverse the old list until we reach the end, copying each node into a new node and attaching it to the end of the new list. (on board)

What about doing it recursively? Let’s write out the definition first.

copy(node) =

How’s it look? Base case seems fine. Let’s do some examples on the board.

I find this version a little easier to read than the iterative case, but maybe that’s just me.

Passing values through the recursion

In all of the examples we’ve seen so far, the result has been implicitly accumulated – it’s been computed from and returned as the result of a chain of recursive calls. Sometimes doing so is awkward, and we want to explicitly “accumulate” a result into a variable as we go. Then, we return that variable (which contains the accumulated result) at the end of the recursion, in the base case.

We’ve already seen a mechanism to do this – when we used recurison to iterate over an array, we passed the index down through the recursion. We can do the same thing here, and pass the result through the recursion (building it up step-by-step).

Reversing a linked list, recursively

The classic example of this is making a copy of a list in reverse order.

reversed(node) =

But how do we append to the end? We don’t have a tail pointer. We’d have to traverse the list (recursively?) each time. That seems (and is) crap, that is, quadratic.

Let’s take a different approach. Again, remember the array-summing?

	int sum(int[] a) {
		return sum(a, 0);
	}
	
	int sum(int[] a, int i) {
		if (i == a.length) {
			return 0;
		} else {
			return a[i] + sum(a, i + 1);
		}

Here, we had to pass the value i into the “helper method” in order to “keep track” of where we were in the array – it was an extra parameter. We can also pass around a partial result into the recursion. Each step of the recursion builds a little more of the answer, and when we reach the “end” (base case), we return the built-up result.

reversed(node, rev_so_far) =

In other words, we’ll build up the reversed list as we go, passing it in as a parameter, and returning it when we’re done:

static <E> Node<E> reversed(Node<E> node) {
    return reversed(node, null);
}

static <E> Node<E> reversed(Node<E> node, Node<E> rev) {
    if (node == null) {
        return rev;
    }
    else {
        Node<E> n = new Node<>();
        n.data = node.data;
        n.next = rev;
        return reversed(node.next, n);
    }
}

(show example on board)

Passing state, either to control the recursion (like array summing) or to build a result (like reversing the list) is common in recursive programming.

Recursion on binary trees

Remember earlier in the semester when we talked about binary trees? They’re like lists: structures composed of linked nodes. But in a binary tree, each node has a left and right “child” rather than a single next node:

class Node<E> {
    public E data;
    public Node<E> left;
    public Node<E> right;
}

Some types of binary trees have additional constraints. For example, binary search trees might require that the values stored in the left subtree always be less than or equal to the current node’s value, and the values stored in the right subtree always be greater.

As you’ll see in 187, you can operate on these trees either recursively or iteratively. Some operations are about as hard to write iteratively or recursively (not unlike writing contains in a linked list). Others are more clear one way or the other. Let’s do a few examples. The basic binary search tree looks like this:

// note we need the Comparable here if it's a BST, so we can order the Nodes by E
// but a regular (non-search_ tree doesn't require it.
public class BinarySearchTree<E extends Comparable<E>> { 
    private Node<E> root;
}

Suppose we have an existing tree. How do we find an element in the tree? Let’s write a contains() method both ways (iterative and recursive). This method bears a strong resemblance to the contains method of a linked list, but has to check the correct child of a node (notably, not both children!).

Iterative first: (1) We start at the root. then, we (2) repeat the following: If the node contains the data, (3) return true; otherwise, (4) descend to the appropriate child. If we reach the end without finding the value, (5) return false.

public boolean containsIterative(E e) {
    //1
    Node<E> node = root;

    //2
    while (node != null) {

        //3 
        int cmp = e.compareTo(node.data);

        if (cmp == 0) {
            return true;
        } else if (cmp < 0) {
            node = node.left;
        } else {
            node = node.right;
        }
    }

    return false;
}

What about recursively? We’ll need a helper method to be able to pass the “current” node in, but otherwise it’s much the same.

Our textual description is something like:

contains(node, e) =

Two more quick binary tree examples, this time of methods that recursion makes easier. Suppose your binary tree contains Integers. How would you find the sum of the values? If this were a list, you would traverse the list and sum them up as you went. But if you “iterate” through a tree, you must go either left or right. What about if you recurse? Then you can define sum() as follows:

The sum of the values of the tree rooted at a given node is equal to:

(on board)

That’s a clear recursion, the code would look something like:

int sum(Node<Integer> node) {
    if (node == null) {
        return 0;
    }
    else {
        return node.data + sum(node.left) + sum(node.right);
    }
}

Suppose we wanted to just count the number of nodes in the tree?

int count(Node<Integer> node) {
    if (node == null) {
        return 0;
    }
    else {
        return 1 + count(node.left) + count(node.right);
    }
}

Here’s another example. What if we want to calculate the height of a tree? Remember, the height is the distance from the root to the node furthest from the root (on board).

Again, we don’t know in advance which child of a given node leads toward the “deeper” subtree, so we have to explore both. We can define the height of the subtree rooted at a node as:

Again, in Java, though this one’s slightly messier due to the null check:

int height(Node<E> node) {
    if (node.left == null && node.right == null) {
        return 0;
    }
    if (node.left == null) {
        return 1 + height(node.right);
    }
    if (node.right == null) {
        return 1 + height(node.left);
    }
    return 1 + Math.max(height(node.left), height(node.right));
}

Can you write these (sum, count, and height) iteratively? Sure, but not without (at least) one of two things:

Recursion is equivalent to the latter – the program’s call stack is your auxiliary storage, and if you use a stack to track the frontier, your iterative program can be written to visit the nodes in exactly the same order as the recursive version.

Hanoi

Tower of Hanoi, a simple puzzle where you move disks from one peg to another, the goal being to move all the disks from the first peg to the last. The constraints are that you may only move one disk at a time, and you must place a disk on either an empty peg, or atop a disk larger than itself.

A recursive procedure to solve the puzzle is as follows. Label the three pegs the source (that is, the starting) peg, the target (the place we want the disks all to go), and the spare (the other peg). To move m disks from the source to the target, use the following solve(m, source, target, spare) procedure:

  1. If we are being asked to move zero pegs, do nothing (base case).
  2. Move m − 1 disks from the source to the spare peg using solve(m - 1, source, spare, target) This leaves the disk m as a top disk on the source peg and all the other disks on the spare peg.
  3. Move the disk m from the source to the target (which is empty).
  4. Move the m - 1 disks from the spare to the target, using the now-empty source peg as the spare: solve(m - 1, spare, target, source)

What does this look like in code? Let’s declare a Hanoi class to handle this:

public class Hanoi {
	int n;
	private List<Character> a;
	private List<Character> b;
	private List<Character> c;

	public Hanoi(int n) {
		this.n = n;
		a = new ArrayList<>();
		b = new ArrayList<>();
		c = new ArrayList<>();

		for (char i = (char)('1' + n - 1); i >= '1'; i--) {
			a.add(i);
		}
	}

	public String toString() {
		return a.toString() + "\n" + b.toString() + "\n" + c.toString();
	}

    	public static void main(String[] args) {
		int n = 3;
		Hanoi h = new Hanoi(n);
		System.out.println(h);
	}

And translate our recursive solver into code, including printouts each time it moves a disk:

private void solve(int m, List<Character> source, List<Character> target, List<Character> spare) {
    if (m == 0) {
        return;
    }
    solve(m - 1, source, spare, target);
    target.add(source.remove(source.size() - 1));
    System.out.println("----");
    System.out.println(this);
    solve(m - 1, spare, target, source);
}

And finally, set up to call it:

public void solve() {
    solve(n, a, c, b);
}

Et voilà:

	public static void main(String[] args) {
		int n = 3;
		Hanoi h = new Hanoi(n);
		System.out.println(h);
		h.solve();
	}

How does it work? It works well. 😎

You’ll get the tools you need to prove it’s correct in COMPSCI 250.

Other minor notes on recursion

Some other languages make use of recursion and recursive definitions much more natural than Java. For example, here’s a snippet of OCaml to computes the factorial:

let rec factorial = function 
  | 0 -> 1
  | x -> x * factorial (x - 1)
;;

print_int (factorial 10)

It’s like a direct transcription of the mathematical description! We might talk more about OCaml (and a couple of other languages) later as part of the course wrap-up.

Notes on War

A12 (War) does not require the use of recursion. And in fact I recommend against it! It does, however, require a careful reading of the expected behavior, and almost certainly some writing of your own tests.

Do what you like, of course, but I suggest something like the following pseudocode:

findWinner():
    instantiate a War object

War: 
constructor:
	initialize decks, other values

simulateGame():
	while !gameOver:
	  battle()
	return outcome

battle():
  if game over (winner or draw or technical draw):
	  set outcome
		return
	increment battle count
	take card from each player, add to spoils
	if a player wins:
	  allocate spoils
	else war()

war():
  if game over (not enough cards for war):
	  set outcome
		return
	remove three cards from each player, add to to spoils

Notice that we did not use recursion in War, even though we’re learning about it in class! You can certainly implement aspects of it recursively, but I don’t recommend it, nor will I help you debug a crazy three-way recursion among different methods.