This series of posts is designed to not only explain how some algorithms work but also how you can think about problems to come up with your own algorithms.
the Fibonacci series is used in teaching Computer Science as it translates well in to a recursive solution - even if the solution is not ideal. The Fibonacci series starts with a 0 and 1, then each following number is worked out by taking the sum of the two previous numbers. This gives us the formula of:
Fib(n) = Fib(n-1) + Fib(n-2)
The reason it's used is because that's already a recursive function and looks something like this:
To look at performance of below solutions you can check out: http://jsperf.com/fibonacci-y-combinator/5
the Fibonacci series is used in teaching Computer Science as it translates well in to a recursive solution - even if the solution is not ideal. The Fibonacci series starts with a 0 and 1, then each following number is worked out by taking the sum of the two previous numbers. This gives us the formula of:
Fib(n) = Fib(n-1) + Fib(n-2)
The reason it's used is because that's already a recursive function and looks something like this:
To look at performance of below solutions you can check out: http://jsperf.com/fibonacci-y-combinator/5
The Recursive Solution:
var Fib = function(n) {
if (n <= 1)
return n;
return Fib(n-1) + Fib(n-2);
}
We can see that if given a formula we can write it out almost as we were give - with the exception of adding in the base conditions. We do have to be careful though as this will duplicate our work, we can see this by putting Fib(n-1) = Fib(n-2) + Fib(n-3) in to our original formula:
Fib(n) = Fib(n-1) + Fib(n-2)
Fib(n) = Fib(n-2) + Fib(n-3) + Fib(n-2)
As you can see we are doing Fib(n-2) more than once, and as we recurse through we will have a lot more duplication of effort. To fix this we instead will memoize our answers so instead of calling back through the recursive chain we will cache answers and return them when we get them back. We can make a general memoize function like so:
So if we do this:
We should see a large performance gain. But this basically only puts us on a par with a linear algorithm and we are keeping all of the answers in memory. The great thing about it though is that if we want to work out different numbers several times, at least some of the work will be done for us. However if we want to print out the series of Fibonacci numbers instead of the nth one, then a linear equation should be better.
And this is a good first attempt. For each loop we move along one until we get our answer. This is a pretty easy one to get so I'm going to move on to an optimization that I haven't seen a lot:
or perhaps in a more readable form:
Now first off passing in 0 will give you the wrong number, but we'll ignore that for now. What we've done is change the loop to go two numbers at a time rather than one. Because we do this we can eliminate the need for a temporary variable and we're only doing two assignments to go up two spots rather than three assignments to go up one. If we used destructuring assignment we could change our original algorithm to work without a temporary variable, but because it's not in all versions of javascript it's unlikely you would consider using it as yet (though for those interested it would be [a, b] = [b, a+b]).
The trick with this is to see that once we did a = a + b then we could just do b = b + a to return our a and b to be the smaller and larger numbers respectively. So when you are looping through a series looks for shortcuts, can you calculate more than one variable at a time? if you're going through numbers can you use arithmetic instead of saving a temporary variable?
There is something called the Fibonacci matrix. this matrix will give us the Fibonacci number for N if we multiply itself N times.:
[1, 1] ^ N
[1, 0]
Then it's just a simple matter of using the divide and conquer for powers. The complexity for this algorithm comes in the matrix multiplication and the algorithm goes a little something like this:
Which is far more complicated but will get faster compared to the other algorithms the higher the number.
Fib(n) = Fib(n-1) + Fib(n-2)
Fib(n) = Fib(n-2) + Fib(n-3) + Fib(n-2)
As you can see we are doing Fib(n-2) more than once, and as we recurse through we will have a lot more duplication of effort. To fix this we instead will memoize our answers so instead of calling back through the recursive chain we will cache answers and return them when we get them back. We can make a general memoize function like so:
var memoize = function(fn) {
var cache = {};
return function(arg) {
if (arg in cache) return cache[arg];
cache[arg] = fn(arg);
return cache[arg];
};
}
So if we do this:
FibMem = memoize(Fib);
We should see a large performance gain. But this basically only puts us on a par with a linear algorithm and we are keeping all of the answers in memory. The great thing about it though is that if we want to work out different numbers several times, at least some of the work will be done for us. However if we want to print out the series of Fibonacci numbers instead of the nth one, then a linear equation should be better.
The Linear Equation
var Fib = function(n) {
var a = 0;
var b = 1;
for(var i = 0; i < n; i++) {
var temp = b;
b = a + b;
a = temp;
}
return a;
}
And this is a good first attempt. For each loop we move along one until we get our answer. This is a pretty easy one to get so I'm going to move on to an optimization that I haven't seen a lot:
The Fast Loop
var Fib = function(n) {
var half = Math.floor((n-1)/2);
for(var i = 0, a = 0, b = 1; i < half; i++,b+=(a+=b)) {}
return n%2==0 ? b : a;
}
or perhaps in a more readable form:
var Fib = function(n) {
var a = 0;
var b = 1;
for(var i = 0; i < n - 2; i += 2) {
a = a + b;
b = b + a;
}
if (n % 2)
return a;
return b;
}
Now first off passing in 0 will give you the wrong number, but we'll ignore that for now. What we've done is change the loop to go two numbers at a time rather than one. Because we do this we can eliminate the need for a temporary variable and we're only doing two assignments to go up two spots rather than three assignments to go up one. If we used destructuring assignment we could change our original algorithm to work without a temporary variable, but because it's not in all versions of javascript it's unlikely you would consider using it as yet (though for those interested it would be [a, b] = [b, a+b]).
The trick with this is to see that once we did a = a + b then we could just do b = b + a to return our a and b to be the smaller and larger numbers respectively. So when you are looping through a series looks for shortcuts, can you calculate more than one variable at a time? if you're going through numbers can you use arithmetic instead of saving a temporary variable?
Divide and Conquer
This is interesting as we've been taught that divide and conquer methods are really good, and that's usually true. The one problem with what I'll show you is that the individual steps are so heavy that for most numbers you'll be calculating up to, it's just not worth it but the higher the number you're calculating the faster it will be and should eventually be faster than the other methods. To do this method we first of all have to know some math.There is something called the Fibonacci matrix. this matrix will give us the Fibonacci number for N if we multiply itself N times.:
[1, 1] ^ N
[1, 0]
Then it's just a simple matter of using the divide and conquer for powers. The complexity for this algorithm comes in the matrix multiplication and the algorithm goes a little something like this:
//to find the nth number
//fibonacci identity
Fib = [1,1,1,0]
//multiply a 2x2 Matrix
function mult2x2(a,b){
return(
[ a[0]*b[0]+a[1]*b[2], a[0]*b[1]+a[1]*b[3],
a[2]*b[0]+a[3]*b[2], a[2]*b[1]+a[3]*b[3]
]
);
}
function fibRecurse(M,n){
//if n is one return the identity
if(n==1)
return M;
//get the value of fib^n/2 (divide)
var fibSoFar = fibRecurse(M,Math.floor(n/2));
//multiply the two halves (and multiply by 1 more if not an even number) (conquer)
fibSoFar = mult2x2(fibSoFar,fibSoFar);
if(n%2==1)
fibSoFar = mult2x2(fibSoFar,Fib);
return fibSoFar;
}
Which is far more complicated but will get faster compared to the other algorithms the higher the number.
Hey, very good explanaition man, tnx, I was looking for that. I, as a web developer with some knowledge in javascript was looking for some good explanaitions on classic algorithms using the language. Javascript is growing faster than expected and it is a fun and powerful language to work with.
ReplyDeletetnx