CS302 Lecture Notes - Dynamic Programming
Example Program #1: Fibonacci Numbers


This is one of the simplest and cleanest dynamic programming problems.

Fibonacci numbers have a recursive definition:

Fib(0) = 1
Fib(1) = 1
If n > 1, Fib(n) = Fib(n-1) + Fib(n-2)

This definition maps itself to a simple recursive function, which you've seen before in CS140. However, we'll go through it again. This is Step 1: writing a recursive answer to a problem. I bundle this into a class because it makes steps 2 and 3 easier. It's in fib1.cpp:

#include <iostream>
#include <vector>
using namespace std;

class Fib {
  public:
    int find_fib(int n);
};

int Fib::find_fib(int n)
{
  if (n == 0 || n == 1) return 1;
  return find_fib(n-1) + find_fib(n-2);
}

main(int argc, char **argv)
{
  Fib f;

  if (argc != 2) {
    cerr << "usage: fib n\n";
    exit(1);
  }

  cout << f.find_fib(atoi(argv[1])) << endl;
}

The problem with this is that its performance blows up exponentially, so that, for example, calculating Fib(45) takes quite a long period of time. When we teach this in CS140, we turn it into a for() loop that starts with Fib(0) and Fib(1) and builds up to Fib(n).

However, with dynamic programming, we proceed to step two: memoization. We accept the recursive definition, but simply create a cache for the answers to that after the first time Fib(i) is called for some i, it returns its answer from the cache. We implement it in fib2.cpp below. We initialize the cache with -1's to denote empty values. fib2.cpp

#include <iostream>
#include <vector>
using namespace std;

class Fib {
  public:
    int find_fib(int n);
    vector <int> cache;
};

int Fib::find_fib(int n)
{
  if (cache.size() == 0) cache.resize(n+1, -1);
  if (cache[n] != -1) return cache[n];
  if (n == 0 || n == 1) {
    cache[n] = 1;
  } else {
    cache[n] = find_fib(n-1) + find_fib(n-2);
  }
  return cache[n];
}

Next, we perform Step 3, which makes the observation that whenever we call find_fib(n), it only makes recursive calls to values less than n. That means that we can build the cache from zero up to n with a for loop (fib3.cpp):

#include <iostream>
#include <vector>
using namespace std;

class Fib {
  public:
    int find_fib(int n);
    vector <int> cache;
};

int Fib::find_fib(int n)
{
  int i;
  if (n == 0 || n == 1)  return 1;

  cache.resize(n+1, -1);

  cache[0] = 1;
  cache[1] = 1;
  for (i = 2; i <= n; i++) cache[i] = cache[i-1] + cache[i-2];

  return cache[n];
}

Finally, when we reflect that we only ever look at the last two values in the cache, we can omit the cache completely. This is Step 4 (in fib4.cpp):

int Fib::find_fib(int n)
{
  int v[3];
  int i;

  if (n == 0 || n == 1) return 1;

  v[0] = 1;
  v[1] = 1;
  for (i = 2; i <= n; i++) {
    v[2] = v[0] + v[1];
    v[0] = v[1];
    v[1] = v[2];
  }

  return v[2];
}