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 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; ff(10); toc >> tic; ff(20); toc >> tic; ff(21); toc >> tic; ff(22); toc >> tic; ff(23); toc You see the times increasing exponentially, the time for ff(21) is much higher (almost double) of that for ff(20) and so one Why? Conside the simple example ******** Figure 1 ******** ff(6) requires ff(5) ff(4) ff(4) ff(3) ff(3) ff(2) ff(3) ff(2) ff(2) ff(1) ff(2) ff(1) ff(2) ff(1) You can see that the left-most branch looks like ff(6) --> ff(5) --> ff(4) --> ff(3) --> ff(2) i.e. it is n-1 long (n=6) Whereas the right most branch is ff(6) --> ff(4) --> ff(2) i.e. it is n/2 long (n=6) To compute ff(6) we need to compute ff(1) 3 times ff(2) 5 times ff(3) 3 times ff(4) 2 times ff(5) 1 once ff(6) 1 once The sequence of times 1 1 2 3 5 is a Fibonacci sequence as well... We repeat too much work for ff(20) we will compute ff(2) about ff(18) times! One would suffice 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 ff(21) requires twice as much work as ff(20) 2. Consider fff.m (pruning the tree) Consider the Figure 1 tree. - In order to compute ff(6) we need ff(5) and we do not need the computations on the right subtree of ff(6). Why ? Because ff(4) was computed in the computation of ff(5)! And ff(3) in the computation of ff(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 ff(t) would compute ff(t) but also would return the ff(t-1) value. Those two values would be used to compute ff(t+1) as the sum ff(t)+ff(t-1) ff(6) receives ff(5) and ff(4) adds them up to compute ff(6) / ff(5) send up itself and ff(4) receives ff(4) and ff(3) adds them up to compute ff(5) / ff(4) send up itself and ff(3) receives ff(3) and ff(2) adds them up to compute ff(4) / ff(3) send up itself and ff(2) receives ff(2) and ff(1) adds them up to compute ff(3) / ff(2) send up itself and ff(1) This is what fff.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!