Pergunta

Eu me tornei interessado em algoritmos ultimamente, e a seqüência de Fibonacci agarrou a minha atenção devido à sua simplicidade.

Eu consegui colocar algo juntos em javascript que calcula o enésimo termo na seqüência de Fibonacci em menos de 15 milissegundos depois de ler muita informação na web. Ele vai até 1476 ... 1477 é o infinito e 1478 é NaN (de acordo com javascript!)

Estou muito orgulhoso do próprio código, exceto que é um monstro absoluto.

Então aqui está a minha pergunta: A) há uma maneira mais rápida para calcular a seqüência? B) há uma maneira mais rápida / menor para multiplicar duas matrizes?

Aqui está o código:

//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 função de matriz leva dois argumentos: A e B, e retorna a * b *, onde a e b são 2x2 matrizes. Ah, e em uma nota lateral, uma coisa mágica aconteceu ... Eu estava convertendo o algoritmo de Strassen em notação de matriz JS e funcionou na minha primeira tentativa! Fantástico, não é? : P

Agradecemos antecipadamente se você conseguir encontrar uma maneira mais fácil de fazer isso.

Foi útil?

Solução

Não especule, referência:

Editar: eu adicionei a minha própria implementação matriz, utilizando as otimizados multiplicação funções mencionadas na minha outra resposta. Isto resultou num grande aumento de velocidade, mas até mesmo a baunilha O (n ^ 3) aplicação de multiplicação de matrizes com laçadas foi mais rápido do que o algoritmo de 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>

rendimentos

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]

Seu algoritmo é quase tão ruim quanto looping uncached. Caching é a sua melhor aposta, seguido de perto pelo algoritmo de arredondamento -. Que produz resultados incorretos para grande n (como faz o seu algoritmo de matriz)

Para n menor, seus executa algoritmo ainda pior do que tudo o resto:

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]

Outras dicas

Há uma forma fechada (sem voltas) solução para o enésimo número de Fibonacci.

Veja Wikipedia.

Pode muito bem haver uma maneira mais rápida para calcular os valores, mas eu não acredito que é necessário.

calculá-los uma vez e, em seu programa, a saída dos resultados como a linha fibdata abaixo:

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];
}

Então, isso é o código que você enviar para seus clientes. Isso é um O (1) solução para você.

As pessoas muitas vezes ignoram a solução 'cache'. Uma vez tive a rotinas de trigonometria gravação para um sistema embarcado e, ao invés de usar séries infinitas para calculá-los na mosca, eu só tinha algumas tabelas de pesquisa, 360 entradas em cada um para cada um dos graus de entrada.

Não é preciso dizer, ele gritou, juntamente, com o custo de apenas cerca de 1K de RAM. Os valores foram armazenados como entradas de 1 byte, [valor real (0-1) * 16] para que pudéssemos fazer uma pesquisa, se multiplicam e mudança pouco para conseguir o valor desejado.

A minha resposta anterior ficou um pouco lotado, então eu vou postar um novo:

Você pode acelerar o seu algoritmo usando baunilha 2x2 matriz de multiplicação - ou seja, substituir a sua função matrix() com este:

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]]
    ];
}

Se você se importa com precisão e velocidade, usar a solução de cache. Se a precisão não é uma preocupação, mas o consumo de memória é, usar a solução de arredondamento. A solução matriz só faz sentido se você quer resultados para grande n rápido, não se importam com precisão e não quer chamar a função repetidamente.

Editar: Você pode até mesmo acelerar ainda mais a computação se você usar especializados multiplicação funções, eliminar subexpressions comuns e substituir os valores na matriz existente em vez de criar uma nova matriz:

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;
}

Nota:. a é o nome da variável de matriz mundial

solução de forma fechada no JavaScript: O (1), se preciso para 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;
}

Com certeza, até mesmo de multiplicação começa a tomar sua despesa ao lidar com grandes números, mas isso vai lhe dar a resposta. Tanto quanto eu sei, porque de JavaScript arredondamento dos valores, é só se precisa para n = 75. Passado isso, você vai ter uma boa estimativa, mas não vai ser totalmente precisos menos que queira fazer algo complicado como loja os valores como uma corda, em seguida, analisar os como BigIntegers.

Como cerca memoizing os resultados que onde já calculado, como tal:

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

Ou se você quiser uma função memoization mais genérico, estender o protótipo 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();

Uma nota para adicionar é que, devido a limitações técnicas (segurança do navegador), os argumentos para funções memoized só pode ser matrizes ou valores escalares . Nenhum objeto.

Referência: http://talideon.com/weblog/ 2005/07 / javascript-memoization.cfm

Para expandir um pouco sobre a resposta Dreas 's:

1) cache deve começar o mais [0, 1]
2) o que você faz com 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)

Se você não gosta erros silenciosas:

// 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 + ')');

Aqui está uma solução muito rápida de calcular a seqüência de Fibonacci

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)

Acabo de escrever a minha própria implementação pouco usando um objeto para armazenar resultados já apurados. Eu escrevi-o em Node.js, que precisava de 2ms (de acordo com o meu temporizador) para calcular o fibonacci para 1476.

Aqui está o código tirou a pura 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)

EDIT: Eu não tente executar este em um navegador

EDIT novamente: agora eu fiz. tente o jsFiddle: jsFiddle fibonacci Tempo varia entre 0 e 2 ms

Muito mais rápido algoritmo:

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top