The Fibonacci sequence is defined as:
Lets make a recursive implementation:
import timeit
# Recursive Fibonacci function
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Timing the Fibonacci function
n = 10 # Example Fibonacci number
execution_time = timeit.timeit(lambda: fibonacci(n), number=1)
print(f"Fibonacci({n}) = {fibonacci(n)}")
print(f"Execution time: {execution_time:.6f} seconds")
Output:
Fibonacci(10) = 55
Execution time: 0.000023 seconds
Fibonacci(10) = 55
Execution time: 0.000023 seconds
which is not much but if we choose n like 36 it takes
Output:
Fibonacci(36) = 14930352
Execution time: 6.045001 seconds
it is very inefficient it has Time Complexity Fibonacci(36) = 14930352
Execution time: 6.045001 seconds
In this post I will show how we can do better using Linear Algebra
Matrix Representation of the Fibonacci Sequence
We can represent the Fibonacci recurrence relation using matrices. Consider the vector x(n) :
The recurrence relation can be rewritten as:
we can express it by matrix multiplication:
Let's denote the matrix:
if we start with
then
we can Diagonalize Matrix A in form of:
where D is a Diagonal Matrix containing eigenvalues of A
now
we see we can cancel out
so
Lets do Diagonalization of A
import numpy as np
A = np.array([[1, 1],
[1, 0]])
eigenvalues, eigenvectors = np.linalg.eig(A)
D = np.diag(eigenvalues)
M = eigenvectors
M_inv = np.linalg.inv(M)
D,M,M_inv
Output:
(array([[ 1.61803399, 0. ], [ 0. , -0.61803399]]),
array([[ 0.85065081, -0.52573111], [ 0.52573111, 0.85065081]]),
array([[ 0.85065081, 0.52573111], [-0.52573111, 0.85065081]]))
(array([[ 1.61803399, 0. ], [ 0. , -0.61803399]]),
array([[ 0.85065081, -0.52573111], [ 0.52573111, 0.85065081]]),
array([[ 0.85065081, 0.52573111], [-0.52573111, 0.85065081]]))
New Fibonacci Implementation
import timeit
import numpy as np
def fibonacci(n):
D = np.array(np.array([[1.61803399,0],[0,-0.61803399]]))
M = np.array([[ 0.85065081, -0.52573111],
[ 0.52573111, 0.85065081]])
M_inv = np.array([[ 0.85065081, 0.52573111],
[-0.52573111, 0.85065081]])
x1 = np.array([1,0])
res = [email protected]_power(D,n-1)@M_inv@x1
return int(res[0])
# Timing the Fibonacci function
n = 36 # Example Fibonacci number
execution_time = timeit.timeit(lambda: fibonacci(n), number=1)
print(f"Fibonacci({n}) = {fibonacci(n)}")
print(f"Execution time: {execution_time:.6f} seconds")
Output:
Fibonacci(36) = 14930352
Execution time: 0.000199 seconds
we can go even higher like n= 100
Fibonacci(36) = 14930352
Execution time: 0.000199 seconds
Output:
Fibonacci(100) = 354224876645738086400
Execution time: 0.000243 seconds
Our new implementation have Time Complexity Fibonacci(100) = 354224876645738086400
Execution time: 0.000243 seconds