22: More Recursion

Announcements

Quiz Monday!

Recursion for control flow

Last class we talked about using recursion to solve problems. As a reminder, recursion is a mechanism for control flow, similar to loops in its behavior but quite dissimilar in terms of its appearance.

We talked about how, if you can define the solution to some problem recursively, you can generally rewrite that recursive definition into a recursive method in Java.

For example, we might define the “bipower of n” (that is to say, 2^n) in one of two ways. One way looks like this:

bipower(n) is 1, multiplied by 2 n times.

This leads pretty cleanly to an iterative definition of bipower:

int bipower(int n) {
    int p = 1;
    for (int i = 0; i < n; i++) {
        p *= 2;
    }
    return p;
}

Or, we might define it recursively:

bipower(n) is 1 if n is 0, and is 2 * bipower(n - 1)

Generally, any time we can write a recursive definition for a problem, we can then write the recursive method pretty easily. But how do we write these recursive definitions? It’s pretty easy if you’re a Math major and you’re used to this style of thinking, or if you’re a CS major who has passed through the purifying flame of COMPSCI 250, or you’re just a nerdy-nerd.

Translating from iterative to recursive

But what if you’re more comfortable thinking about things iteratively? And why wouldn’t you be, since you’ve been conditioned by AP CS and/or 121 and/or this class to always think in terms of for (and sometimes, while) loops?

That’s OK. Today we’re going to spend some time practicing translating loops from their iterative to their recursive form, so that you can see (some of) the symmetry between these two forms of control flow.

So, first things first: it turns out that while for is generally very useful for the iteratively-minded, it obscures some of the connection between iteration and recursion in a way that while does not. But good news: we can trivially rewrite any (non-enhanced) for loop as a while loop. To refresh your memory, here’s an example:

int sumToNFor(int n) {
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        sum += i;
    }
    return sum;
}

int sumToNWhile(int n) {
    int sum = 0;

    int i = 1;
    while (true) {
        if (i > n) {
            break;
        }
        else {
            sum += i;
        }
        i++;
    }
    return sum;
}

Notice I separated out the break to make the cases clear.

More generally:

for (init; condition; increment)
    doStuff

is equivalent to

init
while (true):
    if !condition:
        break
    else:
        doStuff
    increment

There’s still not an exact correspondence to the recursive form, though:

int sumToN(int n) {
    if (n == 0) {
        return 0;
    }
    else {
        return n + (sumToN(n - 1));
    }
}

Why not? Because our loop control variable (i) is still being tested against an input to the method (a variable), rather than an end point that’s independent of the input. If we rewrite our for loop to terminate based upon a constant rather than a variable:

for (int i = n ; i >= 0; i--)

and rewrite our while loop the same way:

int sumToNWhile(int n) {
    int sum = 0;

    int i = 1;
    while (true) {
        if (i == 0) {
            break;
        }
        else {
            sum += i;
        }
        i--;
    }
    return sum;
}

It’s maybe now a little easier to see how the base case and the recursive case of the recursive solution relate to the branches within the while loop of the iterative solution.

There’s a general observation here about translating iterative solutions to recursive ones to make: Recursive methods must “make progress toward the base case.” For methods where “the thing that’s making progress” is a counter, that means the most natural way to write them is usually counting down rather than up, even though that’s the opposite way we typically write our for loops.

Why? Because we need to know “when to stop” our recursion. It’s easy to know when you’ve hit a constant (in other words, when n == 0), since there’s no external state to keep track of. But it’s less clear “how to stop” if your recursion starts at 0 and counts up to n. It’s certainly possible (we’ll get there).

More examples

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;
}

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 elements, it’s the two elements 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) = 0 if node is null, or the current node’s value + the sum of the remaining nodes’ values (node.data + sum(node.next)) otherwise.

In-class exercise

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

Is this implementation correct?

Corrected, 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 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, are just more natural. But more about that later.

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,” though not for sure (since Java values are mutable).

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?

In-class exercise

    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);
        }

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

sum(a, i) = 0 if the index i is past the end of the array, and the current element’s value plus the sum of the values of the elements after it (a[i] + sum(a, i+1)) otherwise

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.

Accumulators

In all of the examples we’ve seen so far, the result has been implicitly accumulated – it’s been compute from and returned as the result of a chain of recursive calls. Sometimes doing so is awkward. For example, suppose we want to make a copy of a linked list. Iteratively what do we do? Something like:

static <E> Node<E> copy(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;
}

What about recursively?

copy(node) = null if the list is empty, otherwise it’s a copy of the current node, whose next pointer points to a copy of the rest of the list:

    static <E> Node<E> copy(Node<E> head) {
        if (head == null) {
            return null;
        }
        else {
            Node<E> n = new Node<>();
            n.data = head.data;
            n.next = copy(head.next);
            return n;
        }
    }

How’s it look? Base case seems fine. What about for a single-node list (on board). That seems OK. What about a two-node list? OK, now we’re cooking. I actually find this a little easier to read than the iterative case, but maybe that’s just me.

What about if we want to return a reversed copy of the list? Hmm.

reversed(node) = null if the list is empty, otherwise it’s a reversed copy of the rest of the list, with the current node at its end.

But how do we append to the end? We’d have to traverse the list (recursively?) each time. That seems (and is) crap (quadratic). Instead, we’ll build up the reversed list as we go, passing it in as a parameter.

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

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

More next class.