The first step to understanding recursion is to understand how it works. The tool we'll use for observing recursion is, primarily, the activation tree.
The activation tree is grown in depth first order in a way that reflects the pushing and popping of stack frames as a result of function calls and returns. The activation tree can be used to follow along any collection of function invocations, not just recursive ones.
To build an activation tree, start with the program code. The program code is thought of as the executable, the activation tree can keep track of local variables, parameters, return values and where you are in the execution. Usually, you only need to track parameters, return value, and return address.
Constructing an activation tree is done by following these rules.
fun3()
, you return to the point where the executable
called fun3()
. If there is a return value, conceptually replace
fun3()
with the return value.
Try this on a non-recursive set of functions.
Code | Suppose x = 10; | Suppose x = 5; |
---|---|---|
int A(int m) { if (m < 10) return B(m+1); else return C(m-1); } int B (int m) { if (m == 10) return 3; else return C(m-2); } int C(int m) { return m * 2; } void main () { int x = ... ; print ( A (x) ); } |
|
|
Q: When returning from
A(10) with value 18 , where am I? Ans: In main . I've just calculated the parameter to print()
| Q: Where am I in the code after I return from B(6) with value 8 ?Ans: In A() . I've just calculated the value that will be used in the
first return statement in A() . Q: Where am I after I return from C(4) with value 8 ? Ans: In B() . I've just calculate the value that will be used in the
second return statement in B() .
|
In thinking about the basic principles of recursion , get in your mind:
Example: Compute factorial, n!
0! = 1 n! = n * (n-1)!The base case is
n==0
. n - 1
*
int fact(n) { if (n==0) return 1; else return n * fact (n - 1); }
Now use the tree to track what happens when computing the Fibonacci number.
Code | fib(3) |
---|---|
int fib(n) { if (n==0) return 1; if (n==1) return 1; return fib1(n-1) + fib2(n-2); } |
n==0
and n==1
.n-1
and n-2
. +
.
Here's the tree for invoking fib(5)
Note:
fib(3) appears 2 timesfib(2) appears 3 timesfib(1) appears 6 times!Altogether, there are 15 invocations of fib for fib(5)
|
The next program will count the number of elements in a linked list.
Here's a generic linked list |
---|
(head==null)
.head.next
1+count() )
Let's use this list, where the addresses are explicitly given. |
Code | count(α) |
---|---|
int count ( head ) { if (head == null) return 0; return 1 + count (head.next); } |
Let's use this list, where the addresses are explicitly given. |
Code | printList(α) |
---|---|
void printList(α) { if (head==null) return; // base case else { print (head.value); // do before call printList(head.next); // note reduced set return; // could have combined with previous } } |
Code | printList(α) |
---|---|
void printList(α) { if (head==null) return; // base case else { printList(head.next); // note reduced set print (head.value); // do after call return; // could have combined with previous; } } |
Use a recursive program to insert a new element at the end of a singly linked list.
What is the base case? p == null
For the recursive call, what is the reduced set? head.next
What is the construction? None, instead the "work" is done via assignment (=
).
Let's use this list, where the addresses are explicitly given. |
Code | Node * insertAtEnd(Node * head, Node * n) |
---|---|
void Node * insertAtEnd(Node * head, Node * n) { if (head==null) return n; // base case else { head.next = insertAtEnd (head.next, n); return head; } } void main () { Node * head = null; . . . Node * n = new Node (data, null); . . . head = insertAtEnd(head, n); // A . . . }Note at the A comment, I need to make the head assignment, because each head.next is being modified all the way back out of the recursion. |
// compute inner product, sentinel is -1 c = 0; for (int i = 0; i < n; i++) c += a[i] * b[i];