1. A short story
  2. - RECURSION
  3. Recursion - Chap 8 of Williams College manuscript, Chap 17 of Big Java
  4. A simple example: raising a number to a power
  5. A faster algorithm for calculating powers
  6. Writing and understanding recursive programs
  7. Another example: path finding

A short story

A friend of mine took his niece and nephew to the Statue of Liberty. There were many people there that day, and as a result, they found themselves standing on the stairs leading up to the crown for a very long time. (They were stuck in a traffic jam of sorts.) As they waited, the kids began to wonder how many more stairs there were to the top. They couldn't see the top, and the stairs were packed with people, so they couldn't run up ahead to count. So my friend (who happens to be a computer scientist) suggested this: He pointed out to them that if they knew the remaining distance for the person ahead of them in line, they would know their remaining distance as well. (They would just have to add one!) The person ahead of them in line could figure out their remaining distance the same way. So all they had to do was ask the person ahead of them, who'd ask the person ahead of them, who'd ask the person ahead of them... and so on.


Recursion

The story above is an example of recursion. Recursion provides a way to solve problems that might otherwise be thought of as involving iteration. (In the example above, that would be the repetitive process of counting stairs on one's own.) The idea behind recursion is to describe how you would complete the solution to a problem if someone were to provide you with a partial solution.


A simple example: raising a number to a power

Let's now consider an example of the use of recursion in Java. The following method raises a number (base) to a power (exponent):

    /**
     *  @param exponent >= 0
     *  @returns base raised to exponent power
     **/ 
    public int simpleRecPower(int base, int exponent)
    {
       if (exponent == 0)
          return 1;
       else
          return base * simpleRecPower(base,exponent-1);
    }

How does this work? Clearly, if we evaluate simpleRecPower(3,0), the condition exponent==0 is true, so the method should return 1. Suppose instead we evaluate simpleRecPower(3,1). According to the method definition and the fact that 1!=0, we get that simpleRecPower(3,1) = 3*simpleRecPower(3,0), and we know the value of simpleRecPower(3,0) is 1. Thus the final answer is 3*1 or 3. The key is that we are using the facts that b0 = 1 and be+1 = b*be to calculate powers. Because we are calculating complex powers using simpler powers we eventually get to our base case.

A handy way of thinking about recursive programs is to think about having someone else handle the recursive call. So if I want to calculate simpleRecPower(3,5), I ask someone else to calculate simpleRecPower(3,4) and then, when they give me the answer, 81, multiply that answer by 3 to get the final answer of 243.

Of course we could also write this method with a while loop:

    /**
     *  @param exponent >= 0
     *  @returns base raised to exponent power
     **/ 
    public int simpleWhilePower(int base, int exponent)
    {
       int answer = 1;
       int counter = 1;
       while (counter <= exponent)
       {
          answer = answer * base;
          counter++;
       }
       return answer;
    }

If this is just as easily written using a while, why do we care about recursion? In some cases it provides us with a solution that is more elegant. That elegance can sometimes give us insights that help us to solve a problem more efficiently.


A faster algorithm for calculating powers

Using a simple modification of the above recursive method we can get a very efficient algorithm for calculating powers. In particular, if we use either of the above methods it will take 1024 multiplications to calculate 31024. Using a slightly more clever algorithm we can cut this down to only 11 multiplications!

    /**
	@param exponent >= 0
	@returns base raised to exponent power
    **/ 
    public int fastPower(int base, int exponent) {
	if (exponent == 0) 
	    return 1
	else if (exponent%2 == 1)       // exponent is odd
	    return base * fastPower(base, exponent-1)
	else                            // exponent is even 
	    return fastPower(base * base, exponent / 2)
    }

In both of the programs given earlier, the number of multiplications necessary is equal to the value of the exponent. That is not the case here.

    fastPower(3,16) = fastPower(9,8)                 // mult
		    = fastPower(81,4)                // mult
		    = fastPower(6561,2)              // mult
		    = fastPower(43046721,1)          // mult
		    = 43046721 * fastPower(43046721,0)
		    = 43046721 * 1                   // mult
		    = 43046721

Thus it only took 5 multiplications (and 4 divisions by 2) using fastPower, whereas it would have taken 16 multiplications the other way (and divisions by two can be done very efficiently in binary).

In general it takes somewhere between log2(exponent) and 2 * log2(exponent) multiplications to compute a power this way. While this doesn't make a difference for small values of exponent, it does make a difference when exponent is large. For example, computing fastPower(3,1024) would only take 11 multiplications, while computing it the other way would take 1024 multiplications.

Why does this algorithm work? It works because it is based on the following simple rules of exponents:

The key is that by rearranging the order of doing things in a clever way, we can cut down the amount of work considerably! (Again it is possible to write the above algorithm with a while loop, but the above formulation is arguably easier to understand!)


Writing and understanding recursive programs

We can both write and understand recursive programs as follows:

  1. Write the base case. Convince yourself that this works correctly.
  2. Write the "recursive" case.


Another example: path finding

Every week my daughter gets a homework packet. Each packet contains a variety of assignments, but one is always labeled as the "Challenge Problem" for the week. A few weeks ago the challenge problem was to count all the paths from A to D in the following grid. You can only move to the right or up (no moving along diagonals).

This problem (and path-finding problems in general) can be solved quite easily using recursion. We notice that from point A, the starting point, we must either pass through the point to our right or we must pass through the point above us (since we can't move diagonally). If we knew the number of paths from our right neighbor and the number of paths from our neighbor above, then all we have to do is add those together!

In general:

The following method does exactly this:

public int allPaths(int xStart, int yStart, int xEnd, int yEnd) {
   // the number of paths from our neighbor to the right
   int rightPaths = 0;
   // the number of paths from our neighbor above
   int upPaths = 0;
		
   // if the start point is the same as the end point, there's
   // only one path.
   if (xStart == xEnd && yStart == yEnd)
      return 1;
		
   // find out how many paths there are from the neighbor to the
   // right of the start point
   if (xStart < xEnd)
      rightPaths = allPaths(xStart+1, yStart, xEnd, yEnd);
			
   // find out how many paths there are from the neighbor 
   // above of the start point
   if (yStart > yEnd)
      upPaths = allPaths(xStart, yStart-1, xEnd, yEnd);
			
   // add up the paths from each neighbor and return
   return rightPaths + upPaths;		
}

Try out this Demo, which solves the problem above. It begins with a display of the 5x5 grid. When you click the mouse, it will tell you the number of paths found. In this version, the starting point is the dot in the fourth row and second column. The end point is the upper right corner. The demo highlights the grid positions searched. You'll see that it always searches up and to the right.