Para compreendemos bem a ineficiência desta implementação, vamos calcular
alguns valores interessantes.
Em primeiro lugar, uma revelação: a sucessão de Fibonacci pode ser expressa
de forma fechada. «Estamos aqui com tudo isto e a coisa resume-se a uma
fórmula fechada?», podem perguntar. Sim. Mas pode haver boas razões para não
recorrer a essa fórmula: ela obriga-nos a trabalhar com valores de vírgula
flutuante e, por isso, a lidar com as correspondentes limitações de precisão.
As nossas implementações, recorrendo a long, são exactas... Podem encontrar
informação sobre a sucessão de Fibonacci, e sobre a sua forma fechada, em
vários locais. Recomendamos os seguintes:
A forma fechada da sucessão de Fibonacci é
,
onde
, que é chamado número de ouro,
e
.
Outra forma fechada é
,
onde

é a função que resulta no inteiro mais próximo ou arredondamento de
, sendo que em caso de empate entre dois inteiros
mais próximos se escolhe o inteiro que for par.
Ambas as formas mostram bem o que já tínhamos observado: o crescimento da
sucessão de Fibonacci é extremamente rápido. Mais precisamente, o crescimento
da sucessão de Fibonacci é exponencial, sendo a base da exponencial o
número de ouro e o expoente o número do termo da sucessão.
Vamos agora calcular o número de invocações da função recursive_fibonacci()
que ocorrem durante a cálculo de um dado termo
da
sucessão de Fibonacci, ou seja, durante a execução da função que acabámos de
definir quando se lhe passa como argumento esse valor
. Chamemos a esse número de invocações
.
Em primeiro lugar, é evidente que invocar recursive_fibonacci() com
como argumento leva a uma invocação inicial,
nomeadamente a que acabámos de referir, com
como
argumento. Se o valor de
for 0 ou 1, então não é
realizada qualquer outra invocação. Ou seja,
quando
ou
. Se o
valor de
for maior que 1, então a função será
invocada recursivamente com os valores
e
como argumento, o que resultará num total de
invocações. Ou seja, o número de
invocações recursivas da função pode ser expresso também de forma recursiva:

Esta definição é muito parecida com a definição recursiva da própria sucessão
de Fibonacci. De facto, é fácil demonstrar que
. Ou seja, o número de invocações cresce
exponencialmente, tal como a própria sucessão de Fibonacci! Não admira que
esta implementação seja absolutamente inaceitável...
Já agora, podemos também calcular o número de somas realizadas. Seja
o número de somas realizadas quando se invoca
recursive_fibonacci() com o argumento
. É fácil
ver que, quando
é 0 ou 1, a invocação de
recursive_fibonacci() resulta em 0 somas. Quando se invoca com
maior que 1, realiza-se uma soma dos resultados
das invocações recursivas com os valores
e
como argumento, o que resultará num total de
somas. Ou seja, o número de somas
realizadas durante a invocação da função pode ser expresso também de forma
recursiva:

Esta definição é também muito parecida com a definição recursiva da própria
sucessão de Fibonacci. De facto, é fácil demonstrar que
. Ou seja, o número de somas cresce
exponencialmente, tal como a própria sucessão de Fibonacci! Isto quando uma
implementação iterativa trivial precisa de 0 somas para calcular o termo 0 da
sucessão e de
somas para calcular o termo
quando
. Mais uma vez,
não admira que esta implementação seja absolutamente inaceitável.
A moral desta estória não é que a recursividade seja naturalmente perversa.
Não o é. As soluções recursivas podem ser tão eficientes quanto as soluções
iterativas. O problema aqui não é a recursividade em si, mas o algoritmo
usado.
Implementação recursiva com lookup
Documentação