Estou tentando me ensinar como calcular a notação BigO para uma função arbitrária. Encontrei essa função em um livro didático. O livro afirma que a função é O (n 2 ). Dá uma explicação do porquê disso, mas estou lutando para segui-lo. Gostaria de saber se alguém pode me mostrar a matemática por trás disso. Fundamentalmente, entendo que é algo menor que O (n 3 ), mas não consegui pousar independentemente em O (n 2 )
Suponha que recebamos três seqüências de números, A, B e C. Vamos assumir que nenhuma sequência individual contém valores duplicados, mas que pode haver alguns números que estão em duas ou três das seqüências. O problema da disjunção de três vias é determinar se a interseção das três seqüências está vazia, a saber, que não há elemento x tal que x ∈ A, x ∈ B e x ∈ C.
Aliás, isso não é um problema de lição de casa para mim - esse navio navegou anos atrás:), apenas eu tentando ficar mais esperto.
def disjoint(A, B, C):
"""Return True if there is no element common to all three lists."""
for a in A:
for b in B:
if a == b: # only check C if we found match from A and B
for c in C:
if a == c # (and thus a == b == c)
return False # we found a common value
return True # if we reach this, sets are disjoint
[Editar] De acordo com o livro:
Na versão aprimorada, não é apenas uma questão de economizar tempo se tivermos sorte. Afirmamos que o pior caso de interrupção é O (n 2 ).
A explicação do livro, que luto para seguir, é a seguinte:
Para explicar o tempo de execução geral, examinamos o tempo gasto na execução de cada linha de código. O gerenciamento do loop for sobre A requer tempo O (n). O gerenciamento do loop for sobre B representa um total de tempo de O (n 2 ), uma vez que esse loop é executado n momentos diferentes. O teste a == b é avaliado O (n 2 ) vezes. O restante do tempo gasto depende de quantos pares correspondentes (a, b) existem. Como observamos, existem no máximo n pares desse tipo; portanto, o gerenciamento do loop sobre C e os comandos dentro do corpo desse loop usam no máximo o tempo O (n 2 ). O tempo total gasto é O (n 2 ).
(E para dar o devido crédito ...) O livro é: Estruturas de dados e algoritmos em Python por Michael T. Goodrich et. todos, Wiley Publishing, pág. 135
[Editar] Uma justificativa; Abaixo está o código antes da otimização:
def disjoint1(A, B, C):
"""Return True if there is no element common to all three lists."""
for a in A:
for b in B:
for c in C:
if a == b == c:
return False # we found a common value
return True # if we reach this, sets are disjoint
No acima, você pode ver claramente que esse é O (n 3 ), porque cada loop deve ser executado ao máximo. O livro afirmaria que, no exemplo simplificado (dado primeiro), o terceiro loop é apenas uma complexidade de O (n 2 ); portanto, a equação de complexidade é como k + O (n 2 ) + O (n 2 ), que finalmente produz O (n 2 ).
Embora eu não possa provar que esse é o caso (portanto, a questão), o leitor pode concordar que a complexidade do algoritmo simplificado é pelo menos menor que o original.
[Edit] E para provar que a versão simplificada é quadrática:
if __name__ == '__main__':
for c in [100, 200, 300, 400, 500]:
l1, l2, l3 = get_random(c), get_random(c), get_random(c)
start = time.time()
disjoint1(l1, l2, l3)
print(time.time() - start)
start = time.time()
disjoint2(l1, l2, l3)
print(time.time() - start)
Rendimentos:
0.02684807777404785
0.00019478797912597656
0.19134306907653809
0.0007600784301757812
0.6405444145202637
0.0018095970153808594
1.4873297214508057
0.003167390823364258
2.953308343887329
0.004908084869384766
Como a segunda diferença é igual, a função simplificada é de fato quadrática:
[Editar] E ainda mais uma prova:
Se eu assumir o pior caso (A = B! = C),
if __name__ == '__main__':
for c in [10, 20, 30, 40, 50]:
l1, l2, l3 = range(0, c), range(0,c), range(5*c, 6*c)
its1 = disjoint1(l1, l2, l3)
its2 = disjoint2(l1, l2, l3)
print(f"iterations1 = {its1}")
print(f"iterations2 = {its2}")
disjoint2(l1, l2, l3)
rendimentos:
iterations1 = 1000
iterations2 = 100
iterations1 = 8000
iterations2 = 400
iterations1 = 27000
iterations2 = 900
iterations1 = 64000
iterations2 = 1600
iterations1 = 125000
iterations2 = 2500
Usando o segundo teste de diferença, o pior resultado é exatamente quadrático.