Mientras estudiaba programación, me encontré con ejemplos de algoritmos imposibles. La intuición dice que esto no puede ser, pero la computadora lo refuta simplemente ejecutando el código. ¿Cómo se puede resolver un problema así, que requiere un mínimo de costos cúbicos en el tiempo, en solo un cuadrado? Y ese definitivamente lo decidiré por la línea. ¿Qué? ¿Existe un algoritmo de logaritmo mucho más eficiente y elegante? ¡Asombroso!
En este artículo presentaré varios de estos algoritmos de "ruptura de patrones", mostrando que la intuición puede sobrestimar en gran medida la complejidad temporal de un problema.
¿Interesante? ¡Bienvenido bajo el corte!
Cálculo del enésimo elemento de la secuencia recurrente en el logaritmo
Por "recurrente" me refiero a una secuencia que satisface la siguiente ecuación:
El primero los elementos de la secuencia se consideran dados. Número se llama cardinalidad de la secuencia, y coeficientes de la secuencia. Ejemplo típico: números de Fibonacci, donde , , , ... Obtenemos los números conocidos: 0, 1, 1, 2, 3, 5, 8, 13, ... Parece que no hay dificultad para calcular el enésimo elemento por línea, pero resulta que se puede hacer para un logaritmo!
Idea: ¿y si imaginas un cálculo? como erección en - - ? , ? , ? . , , . - "" . ? : ! , , ?
, ! :
,
, ? ? , :
, " " . .
? , . , . :
,
,
, ,
,
Matrix :
class Matrix:
def __init__(self, n):
self.n = n
self.rows = [[0 for col in range(n)] for row in range(n)]
def set(self, row, col, value):
self.rows[row][col] = value
def get(self, row, col):
return self.rows[row][col]
def __str__(self):
result = ''
for row in self.rows:
result += ' '.join([str(col) for col in row])
result += '\n'
return result
def __mul__(self, other):
result = Matrix(self.n)
for row in range(self.n):
for col in range(self.n):
s = sum([self.get(row, k) * other.get(k, col) for k in range(self.n)])
result.set(row, col, s)
return result
def __len__(self):
return self.n
def __pow__(self, k):
if k == 0:
result = Matrix(len(self))
for i in range(len(self)):
result.set(i, i, 1)
elif k == 1:
result = self
elif k == 2:
result = self * self
else:
rem = k % 3
prev = self.__pow__((k - rem) // 3)
result = prev * prev * prev
if rem:
result *= self.__pow__(rem)
return result
__pow__
: M ** k
, M
Matrix
. , 3. .
Matrix
:
A = Matrix(3)
A.set(0, 0, 1)
A.set(0, 1, 1)
A.set(1, 0, 1)
A.set(1, 2, 1)
A.set(2, 0, 1)
T = Matrix(3)
T.set(0, 0, 3)
T.set(0, 1, 1)
T.set(0, 2, 1)
T.set(1, 0, 1)
T.set(1, 1, 1)
T.set(1, 2, 1)
T.set(2, 0, 1)
T.set(2, 1, 1)
T.set(2, 2, 0)
n = int(sys.argv[1])
if n:
print(T * A ** (n - 1))
else:
print(T ** 0)
: A[1..n]
( ). A[i..j]
. i
j
. ,
:
- . , . . , .
- . , .
-
. , : , , .O ( n log n ) -
.O ( n ) T[1..n]
,i
- ,i
. , ,T .T
. , T[i + 1]
, T[i]
? , i
, , . , T[i + 1]
T[i] + A[i + 1]
, A[i + 1]
, 0, A[i + 1] < 0
. :
T[0] = 0, T[i + 1] = max{T[i] + A[i + 1], A[i + 1], 0} = max{T[i] + A[i + 1], 0}
Demostremos la última igualdad. Eso está claro T[i] >= 0
para cualquiera i
. Deja k = A[i + 1]
. Considere tres casos:
k < 0
... Entonces 0 superarák
en el primeromax
.k = 0
... En el primeromax
, simplemente puede eliminar el segundo argumento.k > 0
... Entoncesmax{T[i] + k, k, 0} = T[i] + k = max{T[i] + k, 0}
.
Debido a la linealidad y simplicidad de la ecuación, el algoritmo es bastante corto:
def kadane(ints):
prev_sum = 0
answer = -1
for n in ints:
prev_sum = max(prev_sum + n, 0)
if prev_sum >= answer:
answer = prev_sum
return answer
Conclusión
En ambas tareas, la técnica de programación dinámica nos ayudó a mejorar cualitativamente el rendimiento. Esto no es una coincidencia, la dinámica a menudo proporciona algoritmos óptimos asintóticamente gracias a la economía incorporada: solo contamos todo una vez.
¿Qué algoritmos asombrosos conoces?