18C. Duplicated Recursive Calls

Algorithm designers use recursion because they know that, in terms of algorithm performance, recursion is an amplifier. If you have an idea that is a little bit good, recursion can amplify that to make it very good.

But the flip side is that, if you do something a little bad, recursion amplifies that too and makes it very bad.

To illustrate, suppose that you have an array A of n integers (where n > 0), and you want to find the largest value in A, as we did previously. Here is a definition of largest for your consideration.

  // largest(A,n) yields the largest of
  // A[0], A[1], ..., A[n-1].

  int largest(const int A[], const int n)
  {
    if(n == 1)
    {
      return A[0];
    }
    else if(A[n-1] > largest(A, n-1))
    {
      return A[n-1];
    }
    else
    {
      return largest(A, n-1);
    }
  }

That computes the correct answer, but it is very slow. Suppose that A[0] is larger than all of the other values in A. Evaluating condition A[n-1] > largest(A, n−1) requires evaluation of largest(A, n−1), and its value is always false (because we are assuming that A[0] is the largest). So our function needs to evaluate the answer, largest(A, n−1), again.

It initially seems that computing largest(A, n−1) twice should double the amount of time used. But that forgets about the amplification that recursion introduces. The following diagram shows a small part of computation of largest(A, 100), under the assumption that A[0] is larger than all of the other values in A[1, …, 99]. The diagram abbreviates largest as L. The two recursive calls to largest(A, 99) are shown in the diagram.

Each level in the tree has twice as many nodes as the level above it. There are 299 nodes in the level whose nodes say L(A, 1).

299 is, very roughly, 1,000,000,000,000,000,000,000,000,000,000.

It is easy to fix the problem. Don't compute largest(A, n−1) twice. It would suffice to compute it and store its result in a variable, as in the following, or simply use the max function as we did previously.

  int largest(const int A[], const int n)
  {
    if(n == 1)
    {
      return A[0];
    }
    else 
    {
      int m = largest(A, n-1);
      if(A[n-1] > m)
      {
        return A[n-1];
      }
      else
      {
        return m;
      }
    }
  }

Exercises

  1. Why do duplicated recursive calls lead to slow algorithms? Answer

  2. If a recursive algorithm calls itself twice on the same parameter, does that make it take twice as long as it would if it only did one recursive call? Answer