J'essaie de m'apprendre à calculer la notation BigO pour une fonction arbitraire. J'ai trouvé cette fonction dans un manuel. Le livre affirme que la fonction est O (n 2 ). Cela explique pourquoi, mais j'ai du mal à suivre. Je me demande si quelqu'un pourrait peut-être me montrer le calcul derrière la raison. Fondamentalement, je comprends que c'est quelque chose de moins que O (n 3 ), mais je ne pouvais pas atterrir indépendamment sur O (n 2 )
Supposons qu'on nous donne trois séquences de nombres, A, B et C. Nous supposerons qu'aucune séquence individuelle ne contient de valeurs en double, mais qu'il peut y avoir des nombres dans deux ou trois séquences. Le problème de la disjonction des ensembles à trois voies est de déterminer si l'intersection des trois séquences est vide, à savoir qu'il n'y a pas d'élément x tel que x ∈ A, x B et x ∈ C.
D'ailleurs, ce n'est pas un problème de devoirs pour moi - ce navire a navigué il y a des années:), juste moi essayant d'être plus intelligent.
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
[Edit] Selon le manuel:
Dans la version améliorée, nous ne gagnons pas simplement du temps si nous avons de la chance. Nous affirmons que le temps d'exécution le plus défavorable pour disjoint est O (n 2 ).
L'explication du livre, que j'ai du mal à suivre, est la suivante:
Pour prendre en compte le temps d'exécution total, nous examinons le temps d'exécution de chaque ligne de code. La gestion de la boucle for sur A nécessite O (n) temps. La gestion de la boucle for sur B représente un total de O (n 2 ) fois, puisque cette boucle est exécutée n fois. Le test a == b est évalué O (n 2 ) fois. Le reste du temps passé dépend du nombre de paires correspondantes (a, b). Comme nous l'avons noté, il y a au plus n paires de ce type, et la gestion de la boucle sur C ainsi que les commandes contenues dans le corps de cette boucle utilisent au plus O (n 2 ) fois. Le temps total passé est O (n 2 ).
(Et pour donner le crédit qui convient ...) Le livre est: Structures de données et algorithmes en Python par Michael T. Goodrich et. tous, Wiley Publishing, p. 135
[Edit] Une justification; Ci-dessous le code avant optimisation:
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
Dans ce qui précède, vous pouvez clairement voir qu'il s'agit de O (n 3 ), car chaque boucle doit fonctionner au maximum. Le livre affirmerait que dans l'exemple simplifié (donné en premier), la troisième boucle n'est qu'une complexité de O (n 2 ), de sorte que l'équation de la complexité va comme k + O (n 2 ) + O (n 2 ) qui donne finalement O (n 2 ).
Bien que je ne puisse pas prouver que tel est le cas (d'où la question), le lecteur peut convenir que la complexité de l'algorithme simplifié est au moins inférieure à celle de l'original.
[Edit] Et pour prouver que la version simplifiée est quadratique:
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)
Rendements:
0.02684807777404785
0.00019478797912597656
0.19134306907653809
0.0007600784301757812
0.6405444145202637
0.0018095970153808594
1.4873297214508057
0.003167390823364258
2.953308343887329
0.004908084869384766
La seconde différence étant égale, la fonction simplifiée est bien quadratique:
[Edit] Et encore une preuve supplémentaire:
Si je suppose le pire des cas (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)
rendements:
iterations1 = 1000
iterations2 = 100
iterations1 = 8000
iterations2 = 400
iterations1 = 27000
iterations2 = 900
iterations1 = 64000
iterations2 = 1600
iterations1 = 125000
iterations2 = 2500
En utilisant le second test de différence, le résultat le plus défavorable est exactement quadratique.