Algoritmos incrivelmente rápidos

Enquanto estudo programação, encontro exemplos de algoritmos impossíveis. A intuição diz que não pode ser, mas o computador refuta simplesmente executando o código. Como esse problema, que requer um mínimo de custos cúbicos no tempo, pode ser resolvido em apenas um quadrado? E esse eu definitivamente decidirei para a linha. O que? Existe um algoritmo de logaritmo muito mais eficiente e elegante? Surpreendente!







Neste artigo, apresentarei vários desses algoritmos de "quebra de padrões", mostrando que a intuição pode superestimar muito a complexidade de tempo de um problema.







Interessante? Bem-vindo ao corte!







Cálculo do enésimo elemento da sequência recorrente no logaritmo



Por "recorrente", quero dizer uma sequência que satisfaça a seguinte equação:











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







O primeiro k elementos da sequência são considerados dados. Número k é chamado decardinalidade dasequência, e ci coeficientes i da sequência. Exemplo típico: números de Fibonacci, onde a1=0 , a2=1 , c1=1 , c2=1 . Obtemos os números conhecidos: 0, 1, 1, 2, 3, 5, 8, 13, ... Parece que não há dificuldade em calcular o enésimo elemento por linha, mas acontece que pode ser feito para um logaritmo!







Idéia: e se você imaginar um cálculo an como ereção em n enésima potência de algum objeto? Você pode elevar a uma potência para o logaritmo, mas que tipo de objeto será? Em geral, o que você precisa saber para calcular an+2 ? Somente 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}
      
      





. , T[i] >= 0



i



. k = A[i + 1]



. :







  1. k < 0



    . 0 k



    max



    .
  2. k = 0



    . max



    .
  3. k > 0



    . max{T[i] + k, k, 0} = T[i] + k = max{T[i] + k, 0}



    .


, :







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
      
      







Em ambas as tarefas, a técnica de programação dinâmica nos ajudou a melhorar qualitativamente o desempenho. Isso não é coincidência, a dinâmica geralmente fornece algoritmos assintoticamente ideais graças à economia embutida: contamos tudo apenas uma vez.







Que algoritmos incríveis você conhece?








All Articles