CSE143 Notes 4/24/06

Recursion

To understand recursion
You must understand recursion

Eric Roberts, Stanford University

Recursion has a reputation for being one of those things that are "difficult". In some ways that is true - it is a different way of approaching problems and it can sometimes take a bit of practice to wrap your head around it. But in other ways there's nothing new - we're still dealing with methods, statements, and variables, and what we already know about those things hasn't changed. So we'll approach recursion by using it to solve several problems - some of them simple toy examples at first while we're getting the hang of things, but later more substantial problems whose solution benefits greatly from being able to think recursively.

But first - a simple example: what row are you sitting in?

How did you figure that out?

 

You probably used a fairly simple iterative algorithm - count the rows.

Now let's look at the problem another way. Suppose you're sitting way back towards the rear of the room. How could we break the problem down so we don't have to count all of those rows? Let's try it.

 

 

 

 

 

This is an example of recursion - we broke a more complex problem down by solving a simpler version of itself, then used that simpler solution to the subproblem to help answer the original question.

Another example: write a method to print a line of output containing n "*"s. First, an iterative solution with a loop:

 

 

 

Now how could we break the problem of writing a line containing n "*"s into a simpler problem? What is the simplest version of the problem that we could solve?

 

 

 

Given that we know how to solve a very simple version of the problem, how can we solve the more complex original problem? We need to figure out some way to break the original problem down into a small, fixed amount of work, plus a smaller version of the original problem.

 

 

 

 

 

These are examples of recursive algorithms or solutions to problems. While the particulars differ, all recursive algorithms have the same basic structure. A recursive algorithm contains

A key part in designing a recursive algorithm or solution to a problem is that each recursive case must make progress towards solving the problem by solving a "smaller" version of the original problem. This can involve solving the original problem on a subset of the original data, or solving the same problem with a smaller parameter or something, but if the recursive cases do not solve "smaller" problems, then we won't make progress towards eventually reaching a final answer and stopping.

While recursion in programming may be new to you, you've probably already seen recursive definitions in mathematics. For example, exponentiation with positive integer exponents is defined as follows: x0 = 1, and xn = x * xn-1, if n > 0. This definition is recursive because xn is defined in terms of xn-1. We can use that definition directly to write a Java method to compute xn.

   public int powr(int x, int n) {







   }

But how does this work? One way to look at the evaluation of an expression like pow(2,3) is to expand the body of the method and see how the result is built up.

   powr(2,3) =

 

 

 

 


That is conceptually correct and is one good way to think about recursive algorithms, particularly when you are trying to design or understand them.

At another level we are dealing with Java methods, which can have parameters and local variables. It's worth a closer look to be sure we understand exactly how a recursive method works in Java.

As we said earlier, there's really nothing new here. A Java method call executes just like it always does:

  1. Evaluate the argument expressions
  2. Allocate space for the parameters and local variables of the method
  3. Use the argument values to initialize the method parameters
  4. Begin execution of the statements in the method body.

The only twist here is that the method we are calling might well be the same method - or might be a method that has been previously called but has not yet completed its work because it called another method. But that doesn't change how things work. However one thing that might be a bit subtle is that each method call gets its own new set of parameters and local variables. We don't reuse the ones that "belong" to an earlier activation of the same method. With that in mind, let's trace execution of the method call powr(2,3) carefully.