Ejemplos 4
Sucesión de Fibonacci
ALGORÍTMICA Y COMPLEJIDAD
Grados en Ing. Informática y Matemáticas
Universidad de Cantabria
Camilo Palazuelos Calderón
Definimos la función Fibonacci1
, que devuelve el $n$-ésimo término de la sucesión de Fibonacci, recibido el entero no negativo $n$ como parámetro.
def Fibonacci1(n):
if n <= 1:
return n
else:
return Fibonacci1(n - 1) + Fibonacci1(n - 2)
[Fibonacci1(n) for n in range(10)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Evaluamos el coste temporal empírico de Fibonacci1
con $n$ desde $4$ hasta $40$ (de $4$ en $4$).
import time
m = 40
T = [0]
for n in range(m // 10, m + 1, m // 10):
t = time.time()
Fibonacci1(n)
T.append(time.time() - t)
#print(f'Iteración {n // (m // 10)} de 10')
Dibujamos una gráfica con el tiempo de ejecución $T(n)$ de Fibonacci1
en función de $n$.
import matplotlib.pyplot as plt
import numpy as np
with plt.style.context('ggplot'):
plt.plot(np.array(T), label = '$T(n) \in O(\phi^n)$')
plt.xticks(
[x for x in range(0, 11, 2)],
[x * 4 for x in range(0, 11, 2)])
plt.xlabel( '$n$', fontsize = 12)
plt.ylabel('$T(n)$ en s', fontsize = 12)
plt.title('Complejidad temporal')
plt.legend()
plt.show()
Definimos las funciones Fibonacci2
y Fibonacci3
, las versiones memoizadas recursiva e iterativa, respectivamente, de Fibonacci1
.
def Fibonacci2(n):
return TopDown(n, [None for _ in range(n + 1)])
def TopDown(n, A):
if n <= 1:
return n
else:
if A[n] is None:
A[n] = TopDown(n - 1, A) + TopDown(n - 2, A)
return A[n]
[Fibonacci2(n) for n in range(10)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
def Fibonacci3(n):
return BottomUp(n, [None for _ in range(n + 1)])
def BottomUp(n, A):
for i in range(min(2, n + 1)):
A[i] = i
for i in range(2, n + 1):
A[i] = A[i - 1] + A[i - 2]
return A[n]
[Fibonacci3(n) for n in range(10)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Vamos un paso más allá y definimos la función Fibonacci4
memoizando solo lo necesario para calcular el $i$-ésimo término de la sucesión (para todo $i$ tal que $0 \le i \le n$): los términos $(i - 1)$-ésimo (act
ual) y $(i - 2)$-ésimo (pre
vio).
def Fibonacci4(n):
act, pre = 0, 1
for _ in range(1, n + 1):
act, pre = act + pre, act
return act
[Fibonacci4(n) for n in range(10)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Evaluamos el coste temporal empírico de Fibonacci4
con $n$ desde $1 \cdot 10^5$ hasta $10 \cdot 10^5$ (de $10^5$ en $10^5$).
m = 1000000
T = [0]
for n in range(m // 10, m + 1, m // 10):
t = time.time()
Fibonacci4(n)
T.append(time.time() - t)
#print(f'Iteración {n // (m // 10)} de 10')
Dibujamos una gráfica con el tiempo de ejecución $T(n)$ de Fibonacci4
en función de $n$.
with plt.style.context('ggplot'):
plt.plot(np.array(T), label = '$T(n) \in O(n^2)$')
plt.xticks(
[ x for x in range(0, 11, 2)],
[f'${x} \cdot 10^5$' for x in range(0, 11, 2)])
plt.xlabel( '$n$', fontsize = 12)
plt.ylabel('$T(n)$ en s', fontsize = 12)
plt.title('Complejidad temporal')
plt.legend()
plt.show()
Nota: Fibonacci4
realiza $n$ sumas de enteros no negativos, y el coste temporal de cada una de estas es $O(m)$, donde $m$ es el número de dígitos de los enteros no negativos que sumar. El número de dígitos del $n$-ésimo número de Fibonacci es asintótico a $n \log_{10} \varphi$, donde $\varphi$ es el número áureo, por lo que el coste temporal de Fibonacci4
es $n \cdot O(n \log_{10} \varphi) = O(n^2)$.