-1

最近、アルゴリズムに興味を持っていて、その単純さからフィボナッチ数列に興味を持っていました。

Web で多くの情報を読んだ後、フィボナッチ数列の n 番目の項を 15 ミリ秒未満で計算する JavaScript で何かをまとめることができました。それは 1476 まで上がります...1477 は無限大で、1478 は NaN です (javascript によると!)

私はコード自体を非常に誇りに思っていますが、それは完全な怪物です。

だからここに私の質問があります: A) シーケンスを計算するより速い方法はありますか? B) 2 つの行列を乗算するためのより高速で小さい方法はありますか?

コードは次のとおりです。

//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 の 2 つの引数を取り、a*b を返します。ここで、a と b は 2x2 配列です。ああ、余談ですが、魔法のようなことが起こりました... Strassen アルゴリズムを JS 配列表記に変換していたところ、最初の試みでうまくいきました! 素晴らしいですね。:P

これを行うためのより簡単な方法を見つけることができれば、事前に感謝します。

4

10 に答える 10

11

推測しないでください、ベンチマーク:

編集:他の回答で述べた最適化された乗算関数を使用して、独自の行列実装を追加しました。これにより大幅な高速化が実現しましたが、ループを使用した行列乗算の通常の 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]

あなたのアルゴリズムは、キャッシュされていないループとほぼ同じくらい悪いです。キャッシングが最善の策であり、それに続いて丸めアルゴリズムが続きます。これにより、big に対して不正確な結果が得られ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]
于 2009-01-09T11:53:01.037 に答える
6

n 番目のフィボナッチ数には、閉じた形式 (ループなし) の解があります。

ウィキペディアを参照してください。

于 2009-01-09T00:13:00.210 に答える
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] として格納されたので、ルックアップ、乗算、およびビット シフトを実行して目的の値を取得できました。

于 2009-01-09T00:32:27.033 に答える
2

JavaScript でのクローズド フォーム ソリューション: O(1)、n=75 まで正確

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 として解析します。

于 2013-07-25T16:55:19.010 に答える
2

以前の回答が少し混み合っていたので、新しい回答を投稿します。

通常の 2x2 行列乗算を使用してアルゴリズムを高速化できます。つまり、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]]
    ];
}

精度と速度が重要な場合は、キャッシュ ソリューションを使用してください。精度は問題にならないが、メモリ消費が問題になる場合は、丸めソリューションを使用します。nマトリックス ソリューションは、高速な結果が必要で、精度を気にせず、関数を繰り返し呼び出したくない場合にのみ意味があります。

編集:特殊な乗算関数を使用し、一般的な部分式を削除し、新しい配列を作成する代わりに既存の配列の値を置き換えると、計算をさらに高速化できます。

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はグローバル マトリックス変数の名前です。

于 2009-01-09T17:55:56.763 に答える
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 + ')');
于 2009-01-09T00:57:42.803 に答える
1

既に計算された結果を格納するオブジェクトを使用して、独自の小さな実装を作成しました。Node.JS で記述しましたが、1476 のフィボナッチを計算するのに 2 ミリ秒 (私のタイマーによると) が必要でした。

以下は、純粋な 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 から 2 ミリ秒の間で変化します

于 2014-07-02T18:17:52.123 に答える
1

これは、フィボナッチ数列を計算する非常に高速なソリューションです

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)
于 2012-05-15T23:22:08.953 に答える
1

次のように、すでに計算されている結果をメモ化するのはどうですか。

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();

追加する 1 つの注意点は、技術的な (ブラウザーのセキュリティ) 制約により、メモ化された関数の引数は配列またはスカラー値のみにすることができるということです。オブジェクトはありません。

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

于 2009-01-09T00:13:32.267 に答える
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
于 2017-08-21T09:27:08.897 に答える