Algoritmos increíblemente rápidos

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:











an+k+1=i=1kcian+i







El primero k los elementos de la secuencia se consideran dados. Número k se llama cardinalidad de la secuencia, y ci coeficientes de la secuencia. Ejemplo típico: números de Fibonacci, donde a1=0 , a2=1 , c1=1 , c2=1 ... 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? an como erección en n - - ? , ? , an+2 ? an an+1 . , , . - "" . ? : ! , , ?







, ! :







A=(1110)







, an+1=A1,1n . , !







, ? ? , :







(fn+1fnfnfn1)×(1110)=(fn+2fn+1fn+1fn)







, " " . . A . "":











t1=1t2=1t3=1...tn+3=tn+2+tn+1+tn







A :







(110101100)







? , . , . :







(tn+2tn+1tntn+1tntn1tntn1tn2)×(110101100)=(tn+3tn+2tn+1tn+2tn+1tntn+1tntn1)







, T :







(t5t4t3t4t3t2t3t2t1)=(311111110)







tn+2=(T×An1)1,1 . , A n1 , T A . T A .







, k :







(c11000c20100c30010ck0001)







, , 2k1 , "" .







, O(k3logn) : O(k3) , O(logn) . . , , n44 , n208 . \ , . , n118 .







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. .







T A , 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)
      
      





n — , . , , , - . , , .









: A[1..n]



( ). A[i..j]



. i



j



. , O(n3) , O(n2) , O(nlogn) O(n) .







:







  1. . , . . , .
  2. . , .
  3. O(nlogn) . , : , , .
  4. 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:







  1. k < 0



    ... Entonces 0 superará k



    en el primero max



    .
  2. k = 0



    ... En el primero max



    , simplemente puede eliminar el segundo argumento.
  3. k > 0



    ... Entonces max{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?








All Articles