Insight 21.9.1.
Tail recursion focuses on doing work on the way down the recursion stack. Non-tail recursive algorithms do work on the way back out of the recursion calls.
countdown
function was tail recursive. No work happens in it after the recursive call to countdown
:
void countdown(int n) {
if (n == 0) {
cout << "Blastoff!" << endl;
} else {
cout << n << endl;
countdown(n - 1); // recursive call
}
}
int factorial(int n) {
if (n == 0) {
return 1;
} else {
// get answer, then multiply by n before returning
return n * factorial(n - 1);
}
}
factorial(5)
does not need to do anything once factorial(4)
is done producing an answer, there is no need to keep a stack frame around for it. We can just overwrite it with the data for factorial(4)
. Then we can wipe that out and reuse the space for factorial(3)
. And so on...
factorial(4)
counts on being able to ask factorial(3)
for its answer and then factorial(4)
produces the answer including 4. To make it tail-recursive, we need factorial(4)
to give all the information factorial(3)
needs to get the final answer.
accumulator
as it will accumulate the βanswer so farβ.
int factorial(int n, int accumulator);
factorial(3, 4)
to say βHey, you need to calculate 3! and multiply that by 4.β. factorial(3)
, will take the 4, multiply it by 3, and then call factorial(2, 12)
to say βHey, you need to calculate 2! and multiply that by 12.β. Eventually we will call factorial(0, 12)
to say βHey, you need to calculate 0! and multiply that by 12.β. Factorial of 0 is still our base case. But when we reach n == 0
, we already should have a final result to return.
newAccumulator
(βthe answer so farβ) before the recursive call and then hands that value off to the next call. We also have a non-recursive function to kick start the recursive process with an accumulator of 1.
countZeros
. We will add a parameter to keep track of the current count of zeros so that all the work can be done on the way down the recursion stack.