The fibonacci function is a great (or horrible) way to demonstrate the pros and cons of iterative and recursive approaches

Approaches

Iterative

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
unsigned long int fib(unsigned long int n) {
  int prev1 = 0;
  int prev2 = 1;

  for (int i = 0; i < n; i++) {
    int tmp = prev1;
    prev1 = prev2;
    prev2 += tmp;
  }
  return prev1;
}

Recursive

1
2
3
4
5
6
unsigned long int fib(unsigned long int n) {
  switch (n) {
    case 0:  return 0;
    case 1:  return 1;
    default: return fib(n-1) + fib(n-2);
	}

Spot an issue?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fib(5) = fib(4) + fib(3)
fib(4) = fib(3) + fib(2)
fib(3) = fib(2) + fib(1)
fib(2) = fib(1) + fib(0)
fib(1) = 1
fib(0) = 0

fib(5) = [ fib(4)          ] + fib(3)
       = [ fib(3) + fib(2) ] + fib(3)

Let’s call fib(5)

1
2
3
4
5
6
7
8
fib(5) = fib(4)                       + fib(3)
       = fib(3)            + fib(2)   + fib(3)
//       ^                   ^                     
// See an issue? We're going to call `fib(3)` twice
       = fib(2) + fib(1)    + fib(2)  + fib(2) + fib(1)
//       ^                    ^         ^               `
// And `fib(2)` is going to be called three times!

fib(5) will eventually end up doing something like:

1
2
fib(5) = fib(1) + fib(0) + fib(1) + fib(1) + fib(0) + fib(1) + fib(0) + fib(1)

(more than) 8 operations for something that should only need 5…

Let’s blow up this execution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fib(5) = fib(4)                                                                                             + fib(3)
         fib(4) = fib(3) +                                               + fib(2)                             fib(3) = fib(2)                           + fib(1)
                  fib(3) = fib(2)                           + fib(1)       fib(2) = fib(1)     + fib(0)                fib(2) = fib(1)     + fib(0)       fib(1) = 1
                           fib(2) = fib(1)     + fib(0)       fib(1) = 1            fib(1) = 1   fib(0) = 0                     fib(1) = 1   fib(0) = 0
                                    fib(1) = 1   fib(0) = 0                       = 1                                         = 1
                                  = 1                                                                                = 2
                         = 2
                = 3
       = 5

15 executions of fib(), to what only needed 5?

Performance

Let’s compare the two approaches

nfibOperations (Iterative)Operations (Recursive)
1111
2123
3235
4349
55515
68625
713741
821867
9349109
105510177
118911287
1214412465
1323313753
14377141219
15610151973
16987163193
171597175167
182584188361
1941811913529
2067652021891
21109462135421
22177112257313
23286572392735
244636824150049
257502525242785
2612139326392835
2719641827635621
28317811281028457
29514229291664079
30832040302692537
311346269314356617
322178309327049155
3335245783311405773
3457028873418454929
3592274653529860703
36149303523648315633
37241578173778176337
383908816938126491971
396324598639204668309
4010233415540331160281
4116558014141535828591
4226791429642866988873
43433494437431402817465
44701408733442269806339
451134903170453672623805


As the Fibonacci sequence gets larger, the number of iterative operations increase constantly, whilst the number ofrecursive operations increase exponentially.. that’s bad..
Well, ops(n) = ops(n-1) + ops(n-2) + 1
So the 46th Fibonacci number will take 2269806339 + 3672623805 + 1 =5942430145alot of operations

So, the iterative approach is clearly the better algorithm for Fibonacci numbers..

Optimisation - Multiple Calls

If we need to get several Fibonacci numbers, it might be worthwhile to cache all of the results.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdlib.h>

struct fibonacci {
  unsigned long int size;
  unsigned long int *items;
};

struct fibonacci fibonacci = {
  .size = 0,
  .items = NULL
};

unsigned long int ulint_max(unsigned long int a, unsigned long int b) {
  return a > b ? a : b;
}

unsigned long int fib(unsigned long int n) {
  if (n == 0) {
    return 0;
  }

  if (n > fibonacci.size) {
    int init = fibonacci.items == NULL;

    unsigned long int oldSize = ulint_max(fibonacci.size, 2);

    fibonacci.items = realloc(fibonacci.items, sizeof(unsigned long int) * ulint_max(n, 2));

    if (init) {
      fibonacci.items[0] = 1;
      fibonacci.items[1] = 1;
      fibonacci.size = 2;
    }

    for (unsigned long int i = oldSize; i < n; i++) {
      fibonacci.items[i] = fibonacci.items[i - 1] + fibonacci.items[i - 2];
    }

    fibonacci.size = n;
  }

  return fibonacci.items[n - 1];
}

// Don't forget to call `free(fibonacci.items);`

Generating 100000 Fibonacci numbers…

With the caching approach, we get incredibly fast times!

typetime
real0m0.006s
user0m0.006s
sys0m0.000s

As opposed to the standard iterative (non-caching) approach

typetime
real0m7.464s
user0m7.454s
sys0m0.000s

As for the recursive approach, we get no way close the performance of the iterative approaches
Generating just 50 Fibonacci numbers (0.05%) takes forever.

typetime
real6m39.595s
user6m38.480s
sys0m00.001s

So, yes - the iterative approach is MUCH better.
Although we do use way more memory - these days, it’s not that bad…

… as long as it’s not a memory leak.