Good Ol Fibonacci
Starting to get back into studying basic algorithms again and thought I’d start with good ol Fibonacci. Most programmers already know how to the naive approach. This got me thinking, what are some other ways to solve this.
If you’ve forgotten the Fibonacci sequence can be defined as follows:
F(n) = {
0 if n = 0
1 if n = 1
F(n-1) + F(n-2) if n >= 2
}
Below are several implementations for generating the nth Fibonacci number. I’ve also include some timing characteristics to show how efficient the algorithm(s) is.
The first version (FibNaive) is the simplistic implementation and what many would probably call the “naive” approach. This is simply coding the recurrence formula above. This implementation runs in exponential time. This version performs the same calculation numerous times and is very inefficient.
The second version (FibCache) uses a cache to fix the problems in FibNaive. This version still uses the the problem with the recurrence above to calculate the nth fibonacci number. However, once a fibonacci number has been calculated it is stored in a cache so no re-calculation is done. The version now runs in O(n).
The third version (FibBottomUp) is similar to FibCache. It calculates the sequence from 2..n instead of n..2 like the previous two version. Each fibonacci number found is stored into a lookup table for subsequent searches. It also doesn’t use recursion to solve the problem. This version runs in O(n)
The fourth version (FibMatrix) is an attempt to see if the runtime can be better than O(n). This version does have a runtime of O(lg n). This version
uses the matrix form of the fibonacci sequence and its properties to allow the runtime to be better than O(n).
Because the implementations below use longs to store Fib(n) we can only calculate a max of Fib(92) before long overflows. Here are the timings
on a 2Ghz dual core.
Timings:
FibCache(92) = 7540113804746346429 took [0.333632]ms.
FibBottomUp(92) = 7540113804746346429 took [0.006355]ms.
FibMatrix(92) = 7540113804746346429 took [0.052101]ms.
(got tired of waiting)
FibNaive(53) = 53316291173 took [805.088976739]secs.
/*
* Fib1.java
*
* Copyright (c) 2009 skaoth. All rights reserved.
*
* This software/library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or version 3 of the
* License.
*
* This software/library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE
*
* See the GNU Lesser General Public License for more details.
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* http://www.gnu.org/licenses/lgpl.html
*/
package fib;
public class FibNaive {
public long calculateFib(long n) {
StopWatch sw = new StopWatch();
sw.start();
long answer = fib(n);
sw.stop();
System.out.println("FibNaive(" + n +") = " + answer + " took [" + sw.timeInSeconds() + "]secs.");
return answer;
}
private long fib(long n) {
if(n == 0 || n == 1) { return n; }
return fib(n - 1) + fib(n - 2);
}
}
/*
* FibCache.java
*
* Copyright (c) 2009 skaoth. All rights reserved.
*
* This software/library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or version 3 of the
* License.
*
* This software/library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE
*
* See the GNU Lesser General Public License for more details.
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* http://www.gnu.org/licenses/lgpl.html
*/
package fib;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author skaoth
*/
public class FibCache {
private Map cache = new HashMap();
public long calculateFib(long n) {
StopWatch sw = new StopWatch();
sw.start();
long answer = fib(n);
sw.stop();
System.out.println("FibCache(" + n +") = " + answer + " took [" + sw.timeInMilliseconds() + "]ms.");
return answer;
}
private long fib(long n) {
if(n == 0 || n == 1) { return n; }
// check cache;
if(cache.containsKey(n)) {
return cache.get(n);
}
long fib = fib(n - 1) + fib(n - 2);
cache.put(n, fib);
return fib;
}
}
/*
* BottomUpFib.java
*
* Copyright (c) 2009 skaoth. All rights reserved.
*
* This software/library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or version 3 of the
* License.
*
* This software/library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE
*
* See the GNU Lesser General Public License for more details.
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* http://www.gnu.org/licenses/lgpl.html
*/
package fib;
public class FibBottomUp {
private long[] lookup = null;
public long calculateFib(int n) {
if(n > Integer.MAX_VALUE || n < 2) {
throw new IllegalArgumentException("Invalid value of n");
}
lookup = new long[n + 1];
lookup[0] = 0;
lookup[1] = 1;
StopWatch sw = new StopWatch();
sw.start();
long answer = fib(n);
sw.stop();
System.out.println("FibBottomUp(" + n +") = " + answer + " took [" + sw.timeInMilliseconds() + "]ms.");
return answer;
}
private long fib(long n) {
for(int i = 2; i <= n; i++) {
lookup[i] = lookup[i-1] + lookup[i-2];
}
return lookup[(int)n];
}
}
/*
* MatrixFib.java
*
* Copyright (c) 2009 skaoth. All rights reserved.
*
* This software/library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2.1 of the License, or version 3 of the
* License.
*
* This software/library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE
*
* See the GNU Lesser General Public License for more details.
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* http://www.gnu.org/licenses/lgpl.html
*/
package fib;
/**
* Description: The idea behind using the matrix form of the Fibonacci sequence
* is to see if it can help in getting better than O(n) runtime. Indeed this is
* true. The matrix form as shown on wikipedia allows the fibonacci sequence
* to be solved in O(lg n).
* Let A be the the matrix [1 1] ^n
* [1 0]
* With this representation the problem can be stated as:
* Fib(n) =
* {
* A^(n/2) * A(n/2) if n is even
* A^((n-1/2) * (A(n-1/2) * A if n is odd
* }
*
* @link http://en.wikipedia.org/wiki/Fibonacci_number#Matrix_form
* @author skaoth
*/
public class FibMatrix {
private static long fibMatrix[][] = {{ 1, 1}, {1, 0}};
private boolean even = false;
public long calculateFib(int n) {
if(n > Integer.MAX_VALUE || n < 2) {
throw new IllegalArgumentException("Invalid value of n");
}
int m = n;
even = isEven(n);
if(even) {
n /= 2;
} else {
n = (n - 1) / 2;
}
StopWatch sw = new StopWatch();
sw.start();
long matrix[][] = fib(n);
sw.stop();
System.out.println("FibMatrix(" + m +") = " + matrix[0][1] + " took [" + sw.timeInMilliseconds() + "]ms.");
return matrix[0][1];
}
private long[][] fib(int n) {
long matrix[][] = {{1, 1}, {1, 0}}; // starting matrix
for(int i = n; i > 1; i--) {
matrix = multiply(matrix, fibMatrix);
}
// Square
matrix = multiply(matrix, matrix);
if(!even) {
matrix = multiply(matrix, fibMatrix);
}
return matrix;
}
private boolean isEven(int n) {
return ((n & 0x1) == 0);
}
private long[][] multiply(long[][] m1, long[][] m2) {
long[][] newMatrix = new long[2][2];
newMatrix[0][0] = m1[0][0] * m2[0][0] + m1[0][1] * m2[1][0];
newMatrix[0][1] = m1[0][0] * m2[0][1] + m1[0][1] * m2[1][1];
newMatrix[1][0] = m1[1][0] * m2[0][0] + m1[1][1] * m2[1][0];
newMatrix[1][1] = m1[1][0] * m2[0][1] + m1[1][1] * m2[1][1];
return newMatrix;
}
}