I've never heard of the identities mentioned by @vnp. Here are different ways to use them.
Naive, recursive implementation
def recursive_fibonacci(n):
if n < 3:
return [0, 1, 1][n]
elif n % 2 == 0:
m = n // 2
return (recursive_fibonacci(m - 1) + recursive_fibonacci(m + 1)) * recursive_fibonacci(m)
else:
m = (n + 1) // 2
return recursive_fibonacci(m) ** 2 + recursive_fibonacci(m - 1) ** 2
It seems to return the correct result:
>>> recursive_fibonacci(100000) == fibonacci(100000)
True
Note that its performance is horrible compared to the basic iterative approach, though. The goal would be to achieve O(log(n))
complexity by calculating f(n)
from f(n//2)
but it fails because it uses 2 or 3 recursive calls at each step.
By adding a print(' ' * l + str(n))
line, it's possible to see the steps for f(10)
:
10
4
1
3
2
1
2
6
2
4
1
3
2
1
2
3
2
1
5
3
2
1
2
With caching
To avoid calculating the same values again and again, caching can be used:
from functools import lru_cache
@lru_cache(maxsize=1024)
def recursive_fibonacci(n):
if n < 3:
return [0, 1, 1][n]
elif n % 2 == 0:
m = n // 2
return (recursive_fibonacci(m - 1) + recursive_fibonacci(m + 1)) * recursive_fibonacci(m)
else:
m = (n + 1) // 2
return recursive_fibonacci(m) ** 2 + recursive_fibonacci(m - 1) ** 2
Here are the steps for fibonacci(1000)
:
1000
499
250
124
61
31
16
7
4
1
3
2
9
5
8
15
30
14
6
63
32
17
62
126
64
33
125
249
501
251
500
Iterative O(log N)
@JamesKPolk mentioned a related question. The linked article describes a recursive O(log N)
solution, which calculates two following Fibonacci values at each step.
It's possible to rewrite this method in an iterative way by converting the integer to a list of bits:
def fibonacci(n):
f_n, f_n_plus_1 = 0, 1
for i in range(n.bit_length() - 1, -1, -1):
f_n_squared = f_n * f_n
f_n_plus_1_squared = f_n_plus_1 * f_n_plus_1
f_2n = 2 * f_n * f_n_plus_1 - f_n_squared
f_2n_plus_1 = f_n_squared + f_n_plus_1_squared
if n >> i & 1:
f_n, f_n_plus_1 = f_2n_plus_1, f_2n + f_2n_plus_1
else:
f_n, f_n_plus_1 = f_2n, f_2n_plus_1
return f_n
No caching needed, no recursion and a O(log N)
complexity. For n = 1E6
, this function requires around 100 ms
while the basic approach requires almost 20s
.