In the examples of recursion that we have seen so far, the shape of the recursive computation has been guided and controlled either by the shape of the data structure on which it operates (flat list recursion, deep list recursion, pair recursion) or by a natural-number counter that steps down to zero or up to some fixed limit.
In an indefinite recursion, the computation, in a sense, determines its own shape: There is still a base case, but it may be extremely difficult to determine when that base case will be reached, or even whether it will be reached at all. In this reading we examine three mathematical functions that use indefinite recursion.
For the first mathematical function, we go back to Ancient Greece! The GCD (Greatest Common Divisor) algorithm created by the Ancient Greek mathematician Euclid, is still the most efficent know algorithm for computing the largest positive which divides two given positive integers. Euclid noticed that the greatest common divisor of positive integers a and b also divides the remainder that results when a is divided by b. More specifically, we can write a as q*b + r, where r is less than b. From this equation it is clear that the GCD of a and b is the same as the GCD of b and r. From this fact we can write the following procedure.
(define Euclid-GCD
(lambda (a b)
(if (= b 0)
a
(Euclid-GCD b (remainder a b)))))
First we note that this procedure always terminates eventually when its a and b are both positive integers. In the case when b is larger than a the arguments are switched. If a is larger than b both arguments are reduced in the next call to Euclid-GCD. Finally in the case where a is equal to b, Euclid-GCD makes only one more call to itself.
Once we are convinced that the procedure always terminates eventually when its a and b are both positive integers, we then investigate how many calls to itself Euclid-GCD makes. Notice that when a is larger than b, a is more than twice as large as (remainder a b) thus the number calls to Euclid-GCD is at most 2*log2(a). Thus even for very large a and b, Euclid-GCD runs very quickly.
In the second example, we present a procedure that finds the prime factors of a given positive integer:
(define prime-factors
(lambda (number)
(prime-factors-kernel number 2)))
(define prime-factors-kernel
(lambda (number divisor)
(cond ((= number 1) null)
((zero? (remainder number divisor))
(cons divisor
(prime-factors-kernel (quotient number divisor)
divisor)))
(else (prime-factors-kernel number (+ divisor 1))))))
For instance, here is a summary of the steps in the evaluation of
(prime-factors 60):
(prime-factors 60)
--> (prime-factors-kernel 60 2)
--> (cons 2 (prime-factors-kernel 30 2))
--> (cons 2 (cons 2 (prime-factors-kernel 15 2)))
--> (cons 2 (cons 2 (prime-factors-kernel 15 3)))
--> (cons 2 (cons 2 (cons 3 (prime-factors-kernel 5 3))))
--> (cons 2 (cons 2 (cons 3 (prime-factors-kernel 5 4))))
--> (cons 2 (cons 2 (cons 3 (prime-factors-kernel 5 5))))
--> (cons 2 (cons 2 (cons 3 (cons 5 (prime-factors-kernel 1 5)))))
--> (cons 2 (cons 2 (cons 3 (cons 5 null))))
It happens to be the case that the prime-factors procedure
terminates and returns a value eventually, no matter what positive integer
you give it, but this is not exactly obvious. I'm sure some of you are
interested in knowing why prime-factors terminates, so I'll
give you a link to the proof; the
rest of you may, if you like, take my word for it.
Even after you study the proof of termination, however, it is not easy to
determine in advance how much computation the procedure will do before the
base case is reached and the answer can actually be constructed and
returned. The number of recursive calls depends less on the magnitude of
the number than on what its factors are. For instance, finding the prime
factors of 1007 requires fifty-three calls to
prime-factors-kernel; finding the prime factors of 1008
requires only thirteen; finding the prime factors of 1009 requires more
than a thousand. There is a pattern, but it can't be anticipated: To
know
how much computation will be required, you actually have to perform the
computation and identify the prime factors.
Here is an even subtler example. The following procedure takes any positive integer as an argument and returns a list of positive integers -- if it returns at all ...
(define Collatz-sequence
(lambda (number)
(cons number
(cond ((= number 1) null)
((even? number)
(Collatz-sequence (quotient number 2)))
(else
(Collatz-sequence (+ (* 3 number) 1)))))))
In other words: The Collatz sequence of number begins with
number. If number is 1, the sequence ends
there.
Otherwise, if number is even, its sequence continues with the
Collatz sequence for half of number; if number
is
odd, its sequence continues with the Collatz sequence for three times
number plus one.
The Collatz sequences for many positive integers are known, and all those
that are known eventually reach 1 and terminate. However, it is not known
whether every Collatz sequence includes 1 -- it is possible that there is
a
number whose Collatz sequence goes on forever. Applying
Collatz-sequence to such a number would cause a runaway
recursion.
Worse yet, no general rule is known for determining the length of a
number's Collatz sequence, or even putting an upper bound on it, so if we
apply Collatz-sequence to a particular number and then wait
and wait and wait for the result to appear, in general we won't know
whether we have discovered an infinite Collatz sequence or just one that
takes a long time for DrScheme to assemble. So we can't tell whether we
have a runaway recursion or not!
Because some indefinite recursions have such inconvenient computational properties, it's good practice to regard them with suspicion and to avoid them whenever some more conventional form of recursion is a reasonable alternative. In some cases, you'll have to be a skillful mathematician to prove that a given indefinite recursion terminates -- whereas if you use list recursion or recursion with natural numbers, the structure of the list or the definition of `natural number' guarantees that the recursion eventually reaches its base case and gives you some idea of how long the process takes.
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~gum/courses/151/readings/math-functions.xhtml
created March 2, 1997
last revised February 24, 2002
John David Stone (stone@cs.grinnell.edu) and Ben Gum (gum@cs.grinnell.edu)