0. Consider function f.m It is a function than calls itself. It is called a recursive function. f(n) a. returns immediately an 1 for the value of z if n is 1, [This is called the base case; it returns a value rathen than it calls itself recursively] otherwise b. returns n* the value f(n-1) [this is the recursive formulation] Thus f(1) =1 f(2) = 2 * f(1) but f(1) =1 i.e. f(2)=2 f(3) = 3 * f(2) but since f(2)=2 then f(3)=6 Thus f computes the factorial function f(n) = n! = n x (n-1) x (n-2 )... 2 x 1 When f(20) is called, it calls f(19) that calls f(18) that calls f(17) ... that calls f(2) that calls f(1) which returns 1. Thus when f(20) is called at some point 20 copies of function n are active for 20 different values of the parameter n. Each copy has its own space, and consumes resources (memory and CPU cycles). You can only imagine what happens when f(100) is called of f(10000). 1. Function ff.m or f1.m This is our Fibonacci function for n=1 or n=2 the base case (return value) is 1. for all other cases the Fibonacci formula is used. Consider timing the computation involving the 10th, 20th, 21st, 22nd Fibonacci number as in >> tic; f1(10); toc >> tic; f1(20); toc >> tic; f1(21); toc >> tic; f1(22); toc >> tic; f1(23); toc You see the times increasing exponentially, the time for f1(21) is much higher (almost double) of that for f1(20) and so one Why? Conside the simple example ******** Figure 1 ******** f1(6) requires f1(5) f1(4) f1(4) f1(3) f1(3) f1(2) f1(3) f1(2) f1(2) f1(1) f1(2) f1(1) f1(2) f1(1) You can see that the left-most branch looks like f1(6) --> f1(5) --> f1(4) --> f1(3) --> f1(2) i.e. it is n-1 long (n=6) Whereas the right most branch is f1(6) --> f1(4) --> f1(2) i.e. it is n/2 long (n=6) To compute f1(6) we need to compute f1(1) 3 times f1(2) 5 times f1(3) 3 times f1(4) 2 times f1(5) 1 once f1(6) 1 once The sequence of times 1 1 2 3 5 is a Fibonacci sequence as well... We repeat too much work for f1(20) we will compute f1(2) about f1(18) times! One would suf1ice but this recursive method consumes too many resources!!! In general the recursion 'graph' or tree looks like this one that is a binary tree. The tree below is a binary tree of 4 levels 0 through 3. Level i has 2^i nodes and total number of nodes is 2^(i+1) -1 = 2^4-1 = 15 for this tree x 1 level 0 x x 2 level 1 x x x x 4 level 2 x x x x x x x x 8 level 3 Thus the Fibonacci tree looks like x x x x x x x x x x x x x x x This tree is fuller than x x x that has 7 = 2^n/2 -1 = 2^3 -1 x x x x but emptier than x x x x x x x x x x x x x x x that has 2^(n-1)-1 = 2^(6-1)-1 = 31 nodes x x xx xx xx xx xx xx xx Thus in general f(n) requires at least 2^n/2 and at most 2^(n-1) calculations an exponential number. When we go from 2^20 to 2^21 the exponent increases by one but the amount doubles! That's why f1(21) requires twice as much work as ff(20) 2. Consider f2.m (pruning the tree) Consider the Figure 1 tree. - In order to compute f1(6) we need f1(5) and we do not need the computations on the right subtree of f1(6). Why ? Because f1(4) was computed in the computation of f1(5)! And f1(3) in the computation of f1(4) and so on. So we can thus eliminate all but the left-most branch computations if we make every node (call) return not only its value but the value of the Previous Fibonacci number. Thus the call to f1(t) would compute f1(t) but also would return the f1(t-1) value. Those two values would be used to compute f1(t+1) as the sum f1(t)+f1(t-1) f1(6) receives f1(5) and f1(4) adds them up to compute f1(6) / f1(5) send up itself and f1(4) receives f1(4) and f1(3) adds them up to compute f1(5) / f1(4) send up itself and f1(3) receives f1(3) and f1(2) adds them up to compute f1(4) / f1(3) send up itself and f1(2) receives f1(2) and f1(1) adds them up to compute f1(3) / f1(2) send up itself and f1(1) This is what f2.m does ! 3. A previous final exam problem: convert.m Find the binary representation of integer m by calling convert(m) convert(5) is [ convert(5/2) convert(mod(5,2)) ] mod(x,2) is 0 or 1 and the binary representation for 0 or 1 is 0 or 1 respectively! Thus convert(mod(5,2)) = 1 What is convert(5/2)=convert(2). Well the Binary represent. of 2 is 10 Thus convert(5) is [ convert(5/2) convert(5%2) ] = [ '10' '1'] = '101' Note that the '1' and '10' are strings of UNICODE characters and ['10' '1'] is the concatenation of a two-character array '10' with '1' i.e. '101' which is indeed 5 in binary. Note that convert(2) = [convert(2/2) convert(mod(2,2)) ] = [ '1' '0'] = '10' 4. MATLAB profiler profile on profile of profile clear profile report profile on; tic; test1(10000); toc; profile off ; profile report profile on; tic; test2(10000); toc; profile off ; profile report profile on; tic; test3(10000); toc; profile off ; profile report profile on; tic; test4(10000); toc; profile off ; profile report On my machine the first function test1 runs in 3 seconds. test2 runs in 0.3 seconds i.e. it 10 times faster And test3 and test4 can run even faster by a factor of 10 or more over test2 ! Why ? Because test1 dynamically augments the array . Its execution time is 1 2 3 4 5 ... 10000 = 1 +2 +3 +4 + ... + 10000 = 10000 x 10001 /2 ~ 50,000,000 as opposed to 10,000 calculations i.e. 5,000 slower than thought of. And array operations are faster than for loops Thus in MATLAB for efficiency - PREALLOCATE arrays - USE array operations over loops - Minimize use of variables!