2009-01-09 60 views
0

我近来对算法很感兴趣,斐波那契数列由于其简单性而引起了我的注意。Javascript Fibonacci第n项优化

我已经设法把一些东西放在一起,在阅读了大量网络信息后,它在不到15毫秒的时间内计算斐波那契数列中的第n项。它高达1476 ... 1477是无限的,1478是NaN(根据javascript!)

我为代码本身感到非常自豪,除了它是一个完全的怪物。

所以这里是我的问题: A)有更快的方法来计算序列? B)有两种矩阵乘法的更快/更小的方法吗?

下面的代码:

//Fibonacci sequence generator in JS 
//Cobbled together by Salty 
m = [[1,0],[0,1]]; 
odd = [[1,1],[1,0]]; 
function matrix(a,b) { 
    /* 
     Matrix multiplication 
     Strassen Algorithm 
     Only works with 2x2 matrices. 
    */ 
    c=[[0,0],[0,0]]; 
    c[0][0]=(a[0][0]*b[0][0])+(a[0][1]*b[1][0]); 
    c[0][1]=(a[0][0]*b[0][1])+(a[0][1]*b[1][1]); 
    c[1][0]=(a[1][0]*b[0][0])+(a[1][1]*b[1][0]); 
    c[1][1]=(a[1][0]*b[0][1])+(a[1][1]*b[1][1]); 
    m1=(a[0][0]+a[1][1])*(b[0][0]+b[1][1]); 
    m2=(a[1][0]+a[1][1])*b[0][0]; 
    m3=a[0][0]*(b[0][1]-b[1][1]); 
    m4=a[1][1]*(b[1][0]-b[0][0]); 
    m5=(a[0][0]+a[0][1])*b[1][1]; 
    m6=(a[1][0]-a[0][0])*(b[0][0]+b[0][1]); 
    m7=(a[0][1]-a[1][1])*(b[1][0]+b[1][1]); 
    c[0][0]=m1+m4-m5+m7; 
    c[0][1]=m3+m5; 
    c[1][0]=m2+m4; 
    c[1][1]=m1-m2+m3+m6; 
    return c; 
} 
function fib(n) { 
    mat(n-1); 
    return m[0][0]; 
} 
function mat(n) { 
    if(n > 1) { 
     mat(n/2); 
     m = matrix(m,m); 
    } 
    m = (n%2<1) ? m : matrix(m,odd); 
} 
alert(fib(1476)); //Alerts 1.3069892237633993e+308 

矩阵函数有两个参数:a和b,并返回一个* b,其中a和b是2×2阵列。 哦,并在一个侧面说明,发生了一件神奇的事情......我正在将Strassen算法转换成JS数组符号,它在我的第一次尝试中就起作用了!太棒了,对吧? :P

如果您设法找到更简单的方法来提前致谢。

+0

的功能停止工作,如果重复调用 - 它会在第二次调用返回的NaN ...... – Christoph 2009-01-09 11:46:45

+0

加入'M = [ [1,0],[0,1]];`作为第一行到`fib()`修复这个... – Christoph 2009-01-09 11:48:41

+0

btw:你有没有注意到你计算c两次 - m1之前的代码已经是2x2矩阵操作 - 你在下面的步骤中覆盖... – Christoph 2009-01-09 20:43:21

回答

11

不要猜测,风向标:

编辑:我说我自己的矩阵实现使用我的其他答案中提到的优化乘法函数。这导致了一个主要的加速,但即使是循环矩阵乘法的vanilla O(n^3)实现也比Strassen算法更快。

<pre><script> 

var fib = {}; 

(function() { 
    var sqrt_5 = Math.sqrt(5), 
     phi  = (1 + sqrt_5)/2; 

    fib.round = function(n) { 
     return Math.floor(Math.pow(phi, n)/sqrt_5 + 0.5); 
    }; 
})(); 

(function() { 
    fib.loop = function(n) { 
     var i = 0, 
      j = 1; 

     while(n--) { 
      var tmp = i; 
      i = j; 
      j += tmp; 
     } 

     return i; 
    }; 
})(); 

(function() { 
    var cache = [0, 1]; 

    fib.loop_cached = function(n) { 
     if(n >= cache.length) { 
      for(var i = cache.length; i <= n; ++i) 
       cache[i] = cache[i - 1] + cache[i - 2]; 
     } 

     return cache[n]; 
    }; 
})(); 

(function() { 
    //Fibonacci sequence generator in JS 
    //Cobbled together by Salty 
    var m; 
    var odd = [[1,1],[1,0]]; 

    function matrix(a,b) { 
     /* 
      Matrix multiplication 
      Strassen Algorithm 
      Only works with 2x2 matrices. 
     */ 
     var c=[[0,0],[0,0]]; 
     var m1=(a[0][0]+a[1][1])*(b[0][0]+b[1][1]); 
     var m2=(a[1][0]+a[1][1])*b[0][0]; 
     var m3=a[0][0]*(b[0][1]-b[1][1]); 
     var m4=a[1][1]*(b[1][0]-b[0][0]); 
     var m5=(a[0][0]+a[0][1])*b[1][1]; 
     var m6=(a[1][0]-a[0][0])*(b[0][0]+b[0][1]); 
     var m7=(a[0][1]-a[1][1])*(b[1][0]+b[1][1]); 
     c[0][0]=m1+m4-m5+m7; 
     c[0][1]=m3+m5; 
     c[1][0]=m2+m4; 
     c[1][1]=m1-m2+m3+m6; 
     return c; 
    } 

    function mat(n) { 
     if(n > 1) { 
      mat(n/2); 
      m = matrix(m,m); 
     } 
     m = (n%2<1) ? m : matrix(m,odd); 
    } 

    fib.matrix = function(n) { 
     m = [[1,0],[0,1]]; 
     mat(n-1); 
     return m[0][0]; 
    }; 
})(); 

(function() { 
    var a; 

    function square() { 
     var a00 = a[0][0], 
      a01 = a[0][1], 
      a10 = a[1][0], 
      a11 = a[1][1]; 

     var a10_x_a01 = a10 * a01, 
      a00_p_a11 = a00 + a11; 

     a[0][0] = a10_x_a01 + a00 * a00; 
     a[0][1] = a00_p_a11 * a01; 
     a[1][0] = a00_p_a11 * a10; 
     a[1][1] = a10_x_a01 + a11 * a11; 
    } 

    function powPlusPlus() { 
     var a01 = a[0][1], 
      a11 = a[1][1]; 

     a[0][1] = a[0][0]; 
     a[1][1] = a[1][0]; 
     a[0][0] += a01; 
     a[1][0] += a11; 
    } 

    function compute(n) { 
     if(n > 1) { 
      compute(n >> 1); 
      square(); 
      if(n & 1) 
       powPlusPlus(); 
     } 
    } 

    fib.matrix_optimised = function(n) { 
     if(n == 0) 
      return 0; 

     a = [[1, 1], [1, 0]]; 
     compute(n - 1); 

     return a[0][0]; 
    }; 
})(); 

(function() { 
    var cache = {}; 
    cache[0] = [[1, 0], [0, 1]]; 
    cache[1] = [[1, 1], [1, 0]]; 

    function mult(a, b) { 
     return [ 
      [a[0][0] * b[0][0] + a[0][1] * b[1][0], 
       a[0][0] * b[0][1] + a[0][1] * b[1][1]], 
      [a[1][0] * b[0][0] + a[1][1] * b[1][0], 
       a[1][0] * b[0][1] + a[1][1] * b[1][1]] 
     ]; 
    } 

    function compute(n) { 
     if(!cache[n]) { 
      var n_2 = n >> 1; 
      compute(n_2); 
      cache[n] = mult(cache[n_2], cache[n_2]); 
      if(n & 1) 
       cache[n] = mult(cache[1], cache[n]); 
     } 
    } 

    fib.matrix_cached = function(n) { 
     if(n == 0) 
      return 0; 

     compute(--n); 

     return cache[n][0][0]; 
    }; 
})(); 

function test(name, func, n, count) { 
    var value; 

    var start = Number(new Date); 
    while(count--) 
     value = func(n); 
    var end = Number(new Date); 

    return 'fib.' + name + '(' + n + ') = ' + value + ' [' + 
     (end - start) + 'ms]'; 
} 

for(var func in fib) 
    document.writeln(test(func, fib[func], 1450, 10000)); 

</script></pre> 

产生

fib.round(1450) = 4.8149675025003456e+302 [20ms] 
fib.loop(1450) = 4.81496750250011e+302 [4035ms] 
fib.loop_cached(1450) = 4.81496750250011e+302 [8ms] 
fib.matrix(1450) = 4.814967502500118e+302 [2201ms] 
fib.matrix_optimised(1450) = 4.814967502500113e+302 [585ms] 
fib.matrix_cached(1450) = 4.814967502500113e+302 [12ms] 

你的算法是几乎未缓存循环的那样糟糕。高速缓存是您的最佳选择,紧随其后的是舍入算法 - 对于大型的n(与您的矩阵算法一样)会产生不正确的结果。

对于较小的n,你的算法的性能比一切更糟糕:

fib.round(100) = 354224848179263100000 [20ms] 
fib.loop(100) = 354224848179262000000 [248ms] 
fib.loop_cached(100) = 354224848179262000000 [6ms] 
fib.matrix(100) = 354224848179261900000 [1911ms] 
fib.matrix_optimised(100) = 354224848179261900000 [380ms] 
fib.matrix_cached(100) = 354224848179261900000 [12ms] 
6

对于第n个斐波那契数有一个封闭形式(无环)解。

See Wikipedia.

+0

你认为它是O(1)的理由是什么?据我所知,封闭形式使用f^n,n的幂只能用O(n)算法计算。 – paxdiablo 2009-01-09 00:21:16

+2

封闭的形式意味着不是一个无限的系列。它并不意味着O(1)的任何想象力。 – paxdiablo 2009-01-09 00:23:18

+1

好的,对于具有指数运算指令的硬件,它是O(1)。在任何情况下,通过给O(1)乘法计算O(log n),可以通过缓存2个指数的幂来代替天真循环来计算f^n。 – recursive 2009-01-09 00:25:32

1

如何memoizing的结果,其中已计算,像这样的:

var IterMemoFib = function() { 
    var cache = [1, 1]; 
    var fib = function(n) { 
     if (n >= cache.length) { 
      for (var i = cache.length; i <= n; i++) { 
       cache[i] = cache[i - 2] + cache[i - 1]; 
      } 
     } 
     return cache[n]; 
    } 

    return fib; 
}(); 

或者,如果你想要一个更通用的记忆化功能,延长Function原型:

Function.prototype.memoize = function() { 
    var pad = {}; 
    var self = this; 
    var obj = arguments.length > 0 ? arguments[i] : null; 

    var memoizedFn = function() { 
     // Copy the arguments object into an array: allows it to be used as 
     // a cache key. 
     var args = []; 
     for (var i = 0; i < arguments.length; i++) { 
      args[i] = arguments[i]; 
     } 

     // Evaluate the memoized function if it hasn't been evaluated with 
     // these arguments before. 
     if (!(args in pad)) { 
      pad[args] = self.apply(obj, arguments); 
     } 

     return pad[args]; 
    } 

    memoizedFn.unmemoize = function() { 
     return self; 
    } 

    return memoizedFn; 
} 

//Now, you can apply the memoized function to a normal fibonacci function like such: 
Fib = fib.memoize(); 

需要注意的一点是,由于技术(浏览器安全)的限制,的论点对于memoized函数只能是数组或标量值。没有物体。

参考:http://talideon.com/weblog/2005/07/javascript-memoization.cfm

4

有可能是计算值,更快的方式,但我不认为这是必要的。

一次计算它们,并在你的程序,输出结果如下fibdata行:

fibdata = [1,1,2,3,5,8,13, ... , 1.3069892237633993e+308]; // 1476 entries. 
function fib(n) { 
    if ((n < 0) || (n > 1476)) { 
     ** Do something exception-like or return INF; 
    } 
    return fibdata[n]; 
} 

那么,这就是你运送到客户端的代码。这是一个O(1)解决方案。

人们经常忽视'缓存'解决方案。我曾经为嵌入式系统写过三角函数,而不是使用无限级数来快速计算它们,我只是有几个查找表,每个输入的每个度数有360个条目。

不用说,它只需要大约1K的RAM就可以尖叫起来。这些值被存储为1字节的条目,[实际值(0-1)* 16],所以我们可以做一个查找,乘法和位移来获得所需的值。

1

要扩大Dreas的回答有点:

1)cache应该开始为[0, 1]
2)你怎么用IterMemoFib(5.5)呢? (cache[5.5] == undefined

fibonacci = (function() { 
    var FIB = [0, 1]; 

    return function (x) { 
    if ((typeof(x) !== 'number') || (x < 0)) return; 
    x = Math.floor(x); 

    if (x >= FIB.length) 
     for (var i = FIB.length; i <= x; i += 1) 
     FIB[i] = FIB[i-1] + FIB[i-2]; 

    return FIB[x]; 
    } 
})(); 

alert(fibonacci(17)); // 1597 (FIB => [0, 1, ..., 1597]) (length = 17) 
alert(fibonacci(400)); // 1.760236806450138e+83 (finds 18 to 400) 
alert(fibonacci(1476)); // 1.3069892237633987e+308 (length = 1476) 

如果你不喜欢沉默的错误:

// replace... 
if ((typeof(x) !== 'number') || (x < 0)) return; 

// with... 
if (typeof(x) !== 'number') throw new TypeError('Not a Number.'); 
if (x < 0) throw new RangeError('Not a possible fibonacci index. (' + x + ')'); 
1

我以前的答案有一点拥挤,所以我会发布一个新问题:

可以加快你的算法采用2×2香草矩阵乘法 - 即本替换你matrix()功能:

function matrix(a, b) { 
    return [ 
     [a[0][0] * b[0][0] + a[0][1] * b[1][0], 
      a[0][0] * b[0][1] + a[0][1] * b[1][1]], 
     [a[1][0] * b[0][0] + a[1][1] * b[1][0], 
      a[1][0] * b[0][1] + a[1][1] * b[1][1]] 
    ]; 
} 

如果你关心ACCU活泼和速度,使用缓存解决方案。如果精度不是问题,但内存消耗是使用舍入解决方案。矩阵解决方案只有在你想要快速的结果时才有意义,不需要关心精度,也不想重复调用函数。

编辑:,你甚至可以进一步加快计算,如果你使用专门的乘法功能,消除公共子和现有的阵列中替换的值,而不是创建一个新的数组:

function square() { 
    var a00 = a[0][0], 
     a01 = a[0][1], 
     a10 = a[1][0], 
     a11 = a[1][1]; 

    var a10_x_a01 = a10 * a01, 
     a00_p_a11 = a00 + a11; 

    a[0][0] = a10_x_a01 + a00 * a00; 
    a[0][1] = a00_p_a11 * a01; 
    a[1][0] = a00_p_a11 * a10; 
    a[1][1] = a10_x_a01 + a11 * a11; 
} 

function powPlusPlus() { 
    var a01 = a[0][1], 
     a11 = a[1][1]; 

    a[0][1] = a[0][0]; 
    a[1][1] = a[1][0]; 
    a[0][0] += a01; 
    a[1][0] += a11; 
} 

注意: a是全局矩阵变量的名称。

2

这里是计算斐波那契序列中的JavaScript

function fib(n){ 
    var start = Number(new Date); 
    var field = new Array(); 
    field[0] = 0; 
    field[1] = 1; 
    for(var i=2; i<=n; i++) 
     field[i] = field[i-2] + field[i-1] 
    var end = Number(new Date); 
    return 'fib' + '(' + n + ') = ' + field[n] + ' [' + 
     (end - start) + 'ms]'; 

} 

var f = fib(1450) 
console.log(f) 
1

闭合解的一个非常快的解决方案:

function fib(n){ 
    var sqrt5 = Math.sqrt(5); 
    var a = (1 + sqrt5)/2; 
    var b = (1 - sqrt5)/2; 
    var ans = Math.round((Math.pow(a, n) - Math.pow(b, n))/sqrt5); 
    return ans; 
} 

诚然,甚至乘法开始与庞大的数字打交道时采取的费用,但是这会给你答案。据我所知,由于JavaScript对值进行四舍五入,因此只有n = 75时才是准确的。过去,你会得到一个很好的估计,但它不会是完全准确的,除非你想要做一些棘手的事情,比如存储作为字符串的值然后将它们解析为BigIntegers。

1

我刚刚写了一个使用Object的小实现来存储已计算结果。我(根据我的定时器)在Node.js的,这需要2ms的写它来计算1476

斐波纳契这里的剥离下来纯JavaScript代码:

var nums = {}; // Object that stores already computed fibonacci results 
function fib(n) { //Function 
    var ret; //Variable that holds the return Value 
    if (n < 3) return 1; //Fib of 1 and 2 equal 1 => filtered here 
    else if (nums.hasOwnProperty(n)) ret = nums[n]; /*if the requested number is 
    already in the object nums, return it from the object, instead of computing */ 
    else ret = fib(n - 2) + fib(n - 1); /* if requested number has not 
    yet been calculated, do so here */ 
    nums[n] = ret; // add calculated number to nums objecti 
    return ret; //return the value 
} 

//and finally the function call: 
fib(1476) 

编辑:我做了不要尝试在浏览器中运行此操作!

再次编辑:现在我做了。尝试的jsfiddle:jsfiddle fibonacci时间变化介于0和2ms的

0

快得多的算法:

const fib = n => fib[n] || (fib[n-1] = fib(n-1)) + fib[n-2]; 
fib[0] = 0; // Any number you like 
fib[1] = 1; // Any number you like