Sto cercando di insegnare a me stesso come calcolare la notazione BigO per una funzione arbitraria. Ho trovato questa funzione in un libro di testo. Il libro afferma che la funzione è O (n 2 ). Fornisce una spiegazione del perché, ma faccio fatica a seguirlo. Mi chiedo se qualcuno potrebbe essere in grado di mostrarmi la matematica dietro perché è così. Fondamentalmente, capisco che è qualcosa di meno di O (n 3 ), ma non potrei atterrare indipendentemente su O (n 2 )
Supponiamo che ci vengano fornite tre sequenze di numeri, A, B e C. Assumeremo che nessuna singola sequenza contenga valori duplicati, ma che ci possano essere alcuni numeri che sono in due o tre delle sequenze. Il problema della disgiunzione dell'insieme a tre vie consiste nel determinare se l'intersezione delle tre sequenze è vuota, vale a dire che non esiste alcun elemento x tale che x ∈ A, x ∈ B e x ∈ C.
Per inciso, questo non è un problema per me - quella nave ha navigato anni fa:), solo io sto cercando di diventare più intelligente.
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
[Modifica] Secondo il libro di testo:
Nella versione migliorata, non siamo semplicemente noi a risparmiare tempo se siamo fortunati. Sosteniamo che il tempo di esecuzione nel caso peggiore per disgiunto è O (n 2 ).
La spiegazione del libro, che faccio fatica a seguire, è questa:
Per tenere conto del tempo di esecuzione complessivo, esaminiamo il tempo impiegato per eseguire ciascuna riga di codice. La gestione del ciclo for over A richiede tempo O (n). La gestione del ciclo for over B tiene conto per un totale di O (n 2 ) tempi, poiché quel loop viene eseguito in tempi diversi. Il test a == b viene valutato O (n 2 ) volte. Il resto del tempo impiegato dipende da quante coppie corrispondenti (a, b) esistono. Come abbiamo notato, ci sono al massimo n coppie del genere, quindi la gestione del loop su C e i comandi all'interno del corpo di quel loop, usano al massimo O (n 2 ). Il tempo totale trascorso è O (n 2 ).
(E per dare il giusto credito ...) Il libro è: Strutture di dati e algoritmi in Python di Michael T. Goodrich et. tutti, Wiley Publishing, pag. 135
[Modifica] Una giustificazione; Di seguito è riportato il codice prima dell'ottimizzazione:
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
In quanto sopra, puoi vedere chiaramente che questo è O (n 3 ), perché ogni ciclo deve essere eseguito al massimo. Il libro affermerebbe che nell'esempio semplificato (dato prima), il terzo ciclo è solo una complessità di O (n 2 ), quindi l'equazione della complessità va come k + O (n 2 ) + O (n 2 ) che alla fine produce O (n 2 ).
Anche se non posso provare questo (quindi la domanda), il lettore può concordare sul fatto che la complessità dell'algoritmo semplificato è almeno inferiore a quella originale.
[Modifica] E per dimostrare che la versione semplificata è quadratica:
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)
I rendimenti:
0.02684807777404785
0.00019478797912597656
0.19134306907653809
0.0007600784301757812
0.6405444145202637
0.0018095970153808594
1.4873297214508057
0.003167390823364258
2.953308343887329
0.004908084869384766
Poiché la seconda differenza è uguale, la funzione semplificata è effettivamente quadratica:
[Modifica] E ancora ulteriori prove:
Se presumo il caso peggiore (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)
rendimenti:
iterations1 = 1000
iterations2 = 100
iterations1 = 8000
iterations2 = 400
iterations1 = 27000
iterations2 = 900
iterations1 = 64000
iterations2 = 1600
iterations1 = 125000
iterations2 = 2500
Utilizzando il secondo test della differenza, il risultato peggiore è esattamente quadratico.