Qual è una semplice spiegazione inglese della notazione "Big O"?


4935

Preferirei la minor definizione formale possibile e la matematica semplice.


57
Riepilogo: limite superiore della complessità di un algoritmo. Vedi anche la domanda simile Big O, come si calcola / approssimazione? per una buona spiegazione.
Kosi2801,

6
Le altre risposte sono abbastanza buone, solo un dettaglio per capirlo: O (log n) o mezzi simili, che dipende dalla "lunghezza" o "dimensione" dell'input, non dal valore stesso. Questo potrebbe essere difficile da capire, ma è molto importante. Ad esempio, ciò accade quando l'algoritmo divide le cose in due in ogni iterazione.
Harald Schilly,

15
C'è una lezione dedicata alla complessità degli algoritmi nella lezione 8 del corso "Introduzione all'informatica e alla programmazione" del MIT youtube.com/watch?v=ewd7Lf2dr5Q Non è un inglese completamente semplice, ma fornisce una bella spiegazione con esempi che sono facilmente comprensibile.
Ivanjovanovic,

17
Big O è una stima delle prestazioni nel caso peggiore di una funzione supponendo che l'algoritmo eseguirà il numero massimo di iterazioni.
Paul Sweatte,

Risposte:


6627

Nota veloce, questo quasi sicuramente confonde la notazione Big O (che è un limite superiore) con la notazione Theta "Θ" (che è un limite a due lati). Nella mia esperienza, questo è in realtà tipico delle discussioni in contesti non accademici. Ci scusiamo per l'eventuale confusione causata.


La grande complessità O può essere visualizzata con questo grafico:

Analisi O grande

La definizione più semplice che posso dare per la notazione Big-O è questa:

La notazione Big-O è una rappresentazione relativa della complessità di un algoritmo.

Ci sono alcune parole importanti e scelte deliberatamente in quella frase:

  • parente: puoi confrontare solo le mele con le mele. Non è possibile confrontare un algoritmo per eseguire la moltiplicazione aritmetica con un algoritmo che ordina un elenco di numeri interi. Ma un confronto tra due algoritmi per eseguire operazioni aritmetiche (una moltiplicazione, un'aggiunta) ti dirà qualcosa di significativo;
  • rappresentazione: Big-O (nella sua forma più semplice) riduce il confronto tra algoritmi a una singola variabile. Tale variabile è scelta in base a osservazioni o ipotesi. Ad esempio, gli algoritmi di ordinamento vengono in genere confrontati in base alle operazioni di confronto (confrontando due nodi per determinarne l'ordinamento relativo). Ciò presuppone che il confronto sia costoso. Ma cosa succede se il confronto è economico ma lo scambio è costoso? Cambia il confronto; e
  • complessità: se mi ci vorrà un secondo per ordinare 10.000 elementi, quanto tempo impiegherò a ordinare un milione? La complessità in questo caso è una misura relativa di qualcos'altro.

Torna indietro e rileggi quanto sopra quando hai letto il resto.

Il miglior esempio di Big-O che mi viene in mente è fare l'aritmetica. Prendi due numeri (123456 e 789012). Le operazioni aritmetiche di base che abbiamo imparato a scuola erano:

  • Inoltre;
  • sottrazione;
  • moltiplicazione; e
  • divisione.

Ognuna di queste è un'operazione o un problema. Un metodo per risolverli si chiama algoritmo .

L'aggiunta è la più semplice. Allini i numeri in alto (a destra) e aggiungi le cifre in una colonna scrivendo l'ultimo numero di quell'aggiunta nel risultato. La parte "decine" di quel numero viene riportata alla colonna successiva.

Supponiamo che l'aggiunta di questi numeri sia l'operazione più costosa in questo algoritmo. È ovvio che per aggiungere questi due numeri insieme dobbiamo sommare 6 cifre (e possibilmente portare un 7). Se sommiamo due numeri di 100 cifre, dobbiamo fare 100 aggiunte. Se aggiungiamo due numeri di 10.000 cifre dobbiamo fare 10.000 aggiunte.

Vedi lo schema? La complessità (essendo il numero di operazioni) è direttamente proporzionale al numero di cifre n nel numero maggiore. Chiamiamo questa O (n) o complessità lineare .

La sottrazione è simile (tranne che potrebbe essere necessario prendere in prestito anziché trasportare).

La moltiplicazione è diversa. Si allineano i numeri, si prende la prima cifra nel numero in basso e si moltiplica a sua volta per ogni cifra nel numero in alto e così via per ogni cifra. Quindi per moltiplicare i nostri due numeri a 6 cifre dobbiamo fare 36 moltiplicazioni. Potremmo aver bisogno di aggiungere fino a 10 o 11 colonne per ottenere anche il risultato finale.

Se abbiamo due numeri di 100 cifre, dobbiamo fare 10.000 moltiplicazioni e 200 aggiunte. Per due numeri di un milione di cifre dobbiamo fare moltiplicazioni di un trilione (10 12 ) e due milioni di aggiunte.

Quando l'algoritmo si ridimensiona con n al quadrato , questo è O (n 2 ) o complessità quadratica . Questo è un buon momento per introdurre un altro concetto importante:

Ci preoccupiamo solo della parte più significativa della complessità.

L'astuto potrebbe aver capito che potremmo esprimere il numero di operazioni come: n 2 + 2n. Ma come hai visto dal nostro esempio con due numeri di un milione di cifre ciascuno, il secondo termine (2n) diventa insignificante (rappresentando lo 0,0002% delle operazioni totali per quella fase).

Si può notare che qui abbiamo ipotizzato lo scenario peggiore. Mentre moltiplichi i numeri di 6 cifre, se uno di loro ha 4 cifre e l'altro ha 6 cifre, allora abbiamo solo 24 moltiplicazioni. Tuttavia, calcoliamo lo scenario peggiore per quella 'n', cioè quando entrambi sono numeri a 6 cifre. Quindi la notazione Big-O riguarda lo scenario peggiore di un algoritmo.

La rubrica telefonica

Il prossimo miglior esempio che mi viene in mente è la rubrica telefonica, normalmente chiamata White Pages o simili, ma varia da paese a paese. Ma sto parlando di quello che elenca le persone per cognome e quindi iniziali o nome, possibilmente indirizzo e quindi numeri di telefono.

Ora, se stessi istruendo un computer a cercare il numero di telefono di "John Smith" in un elenco telefonico che contiene 1.000.000 di nomi, cosa faresti? Ignorando il fatto che potresti indovinare quanto è iniziata la S (supponiamo che tu non possa), cosa faresti?

Una tipica applicazione potrebbe essere quella di aprire fino a metà, prendere il 500.000 ° e confrontarlo con "Smith". Se capita di essere "Smith, John", siamo davvero fortunati. Molto più probabile è che "John Smith" sarà prima o dopo quel nome. Se è dopo dividiamo quindi la metà dell'ultima rubrica a metà e ripetiamo. Se è prima, allora dividiamo la prima metà della rubrica a metà e ripetiamo. E così via.

Questa si chiama ricerca binaria e viene utilizzata ogni giorno nella programmazione, indipendentemente dal fatto che la realizzi o meno.

Quindi, se vuoi trovare un nome in una rubrica di un milione di nomi, puoi effettivamente trovare qualsiasi nome facendolo al massimo 20 volte. Nel confrontare gli algoritmi di ricerca decidiamo che questo confronto è la nostra 'n'.

  • Per una rubrica di 3 nomi sono necessari 2 confronti (al massimo).
  • Per 7 ci vogliono al massimo 3.
  • Per 15 ci vogliono 4.
  • ...
  • Per 1.000.000 servono 20.

È incredibilmente buono, vero?

In termini di Big-O questo è O (log n) o complessità logaritmica . Ora il logaritmo in questione potrebbe essere ln (base e), log 10 , log 2 o qualche altra base. Non importa che sia ancora O (log n) proprio come O (2n 2 ) e O (100n 2 ) sono ancora entrambi O (n 2 ).

Vale la pena a questo punto spiegare che Big O può essere usato per determinare tre casi con un algoritmo:

  • Caso migliore: nella ricerca nella rubrica, il caso migliore è che troviamo il nome in un confronto. Questa è O (1) o complessità costante ;
  • Caso previsto: come discusso sopra questo è O (log n); e
  • Caso peggiore: anche questo è O (log n).

Normalmente non ci interessa il caso migliore. Siamo interessati al caso previsto e al peggio. A volte l'uno o l'altro di questi sarà più importante.

Torna all'elenco telefonico.

Cosa succede se si dispone di un numero di telefono e si desidera trovare un nome? La polizia ha una rubrica telefonica inversa, ma tali ricerche vengono negate al grande pubblico. O lo sono? Tecnicamente è possibile invertire la ricerca di un numero in una normale rubrica. Come?

Si inizia dal nome e si confronta il numero. Se è una partita, ottimo, in caso contrario, passa alla successiva. Devi farlo in questo modo perché la rubrica non è ordinata (comunque per numero di telefono).

Quindi, per trovare un nome dato il numero di telefono (ricerca inversa):

  • Caso migliore: O (1);
  • Caso previsto: O (n) (per 500.000); e
  • Caso peggiore: O (n) (per 1.000.000).

Il commesso viaggiatore

Questo è un problema abbastanza famoso in informatica e merita una menzione. In questo problema, hai N città. Ognuna di queste città è collegata a 1 o più altre città da una strada di una certa distanza. Il problema del commesso viaggiatore è trovare il tour più breve che visita ogni città.

Sembra semplice? Pensa di nuovo.

Se hai 3 città A, B e C con strade tra tutte le coppie, puoi andare:

  • A → B → C
  • A → C → B
  • B → C → A
  • B → A → C
  • C → A → B
  • C → B → A

Bene, in realtà c'è di meno perché alcuni di questi sono equivalenti (A → B → C e C → B → A sono equivalenti, ad esempio perché usano le stesse strade, proprio al contrario).

In realtà, ci sono 3 possibilità.

  • Porta questo in 4 città e hai (iirc) 12 possibilità.
  • Con 5 sono 60.
  • 6 diventa 360.

Questa è una funzione di un'operazione matematica chiamata fattoriale . Fondamentalmente:

  • 5! = 5 × 4 × 3 × 2 × 1 = 120
  • 6! = 6 × 5 × 4 × 3 × 2 × 1 = 720
  • 7! = 7 × 6 × 5 × 4 × 3 × 2 × 1 = 5040
  • ...
  • 25! = 25 × 24 ×… × 2 × 1 = 15.511.210.043.330.985.984.000.000
  • ...
  • 50! = 50 × 49 ×… × 2 × 1 = 3.04140932 × 10 64

Quindi il Big-O del problema del commesso viaggiatore è O (n!) O complessità fattoriale o combinatoria .

Quando arrivi in ​​200 città non c'è abbastanza tempo nell'universo per risolvere il problema con i computer tradizionali.

Qualcosa a cui pensare.

Tempo polinomiale

Un altro punto di cui ho voluto fare una breve menzione è che qualsiasi algoritmo che ha una complessità di O (n a ) si dice che abbia una complessità polinomiale o sia risolvibile nel tempo polinomiale .

O (n), O (n 2 ) ecc. Sono tutti tempi polinomiali. Alcuni problemi non possono essere risolti in tempi polinomiali. Alcune cose sono usate nel mondo per questo. La crittografia a chiave pubblica è un ottimo esempio. È computazionalmente difficile trovare due fattori primi di un numero molto grande. In caso contrario, non potremmo utilizzare i sistemi di chiave pubblica che utilizziamo.

Ad ogni modo, questo è tutto per la mia (eventualmente semplice inglese) spiegazione di Big O (rivista).


578
Mentre le altre risposte si concentrano sulla spiegazione delle differenze tra O (1), O (n ^ 2) et al .... il tuo è quello che descrive in dettaglio come gli algoritmi possono essere classificati in n ^ 2, nlog (n) ecc. + 1 per una buona risposta che mi ha aiutato a capire anche la notazione Big O
Yew Long,

22
si potrebbe voler aggiungere che big-O rappresenta un limite superiore (dato da un algoritmo), big-Omega fornisce un limite inferiore (solitamente dato come una prova indipendente da un algoritmo specifico) e big-Theta significa che un algoritmo "ottimale" raggiungere quel limite inferiore è noto.
mdm,

21
Questo è utile se stai cercando la risposta più lunga, ma non la risposta che spiega meglio Big-O in modo semplice.
kirk.burleson,

169
-1: Questo è palesemente sbagliato: _ "BigOh è la rappresentazione relativa della complessità dell'algoritmo". No. BigOh è un limite superiore asintotico ed esiste abbastanza bene indipendentemente dall'informatica. O (n) è lineare. No, stai confondendo BigOh con theta. il registro n è O (n). 1 è O (n). Il numero di voti a questa risposta (e i commenti), che fa l'errore di base di confondere Theta con BigOh è abbastanza imbarazzante ...

74
"Quando arrivi in ​​200 città non c'è abbastanza tempo nell'universo per risolvere il problema con i computer tradizionali." Quando l'universo finirà?
Isaac,

729

Mostra come un algoritmo si ridimensiona in base alla dimensione dell'input.

O (n 2 ) : noto come complessità quadratica

  • 1 oggetto: 1 secondo
  • 10 articoli: 100 secondi
  • 100 articoli: 10000 secondi

Si noti che il numero di articoli aumenta di un fattore 10, ma il tempo aumenta di un fattore 10 2 . Fondamentalmente, n = 10 e quindi O (n 2 ) ci dà il fattore di ridimensionamento n 2 che è 10 2 .

O (n) : noto come complessità lineare

  • 1 oggetto: 1 secondo
  • 10 articoli: 10 secondi
  • 100 articoli: 100 secondi

Questa volta il numero di oggetti aumenta di un fattore 10, così come il tempo. n = 10 e quindi il fattore di ridimensionamento di O (n) è 10.

O (1) : noto come complessità costante

  • 1 oggetto: 1 secondo
  • 10 articoli: 1 secondo
  • 100 articoli: 1 secondo

Il numero di articoli è ancora in aumento di un fattore 10, ma il fattore di ridimensionamento di O (1) è sempre 1.

O (log n) : noto come complessità logaritmica

  • 1 oggetto: 1 secondo
  • 10 articoli: 2 secondi
  • 100 articoli: 3 secondi
  • 1000 articoli: 4 secondi
  • 10000 articoli: 5 secondi

Il numero di calcoli viene aumentato solo da un registro del valore di input. Quindi, in questo caso, supponendo che ogni calcolo richieda 1 secondo, il registro dell'input nè il tempo richiesto, quindi log n.

Questo è il senso. Riducono la matematica in modo che potrebbe non essere esattamente n 2 o qualunque cosa dicano, ma questo sarà il fattore dominante nel ridimensionamento.


5
cosa significa esattamente questa definizione? (Il numero di articoli è ancora in aumento di un fattore 10, ma il fattore di ridimensionamento di O (1) è sempre 1.)
Zach Smith

105
Non secondi, operazioni. Inoltre, ti sei perso il tempo fattoriale e logaritmico.
Chris Charabaruk,

7
Questo non spiega molto bene che O (n ^ 2) potrebbe descrivere un algoritmo che funziona esattamente in .01 * n ^ 2 + 999999 * n + 999999. È importante sapere che gli algoritmi vengono confrontati usando questa scala, e che il confronto funziona quando n è "sufficientemente grande". Il timsort di Python utilizza effettivamente l'ordinamento per inserzione (caso peggiore / medio O (n ^ 2)) per array di piccole dimensioni poiché ha un piccolo overhead.
Casey Kuball,

6
Questa risposta confonde anche la notazione O grande e la notazione Theta. La funzione di n che restituisce 1 per tutti i suoi input (di solito semplicemente scritta come 1) è in realtà in O (n ^ 2) (anche se è anche in O (1)). Allo stesso modo, un algoritmo che deve fare solo un passo che richiede una quantità costante di tempo è anche considerato un algoritmo O (1), ma anche un algoritmo O (n) e O (n ^ 2). Ma forse matematici e informatici non sono d'accordo sulla definizione: - /.
Jacob Akkerboom,

1
La complessità logaritmica O (log n) considerata in questa risposta è della base 10. Generalmente lo standard è calcolare con la base 2. Si dovrebbe tenere presente questo fatto e non bisogna confondersi. Inoltre, come menzionato da @ChrisCharabaruk, la complessità indica il numero di operazioni e non i secondi.
Aksh1801,

405

La notazione Big-O (chiamata anche notazione "crescita asintotica") è ciò che "assomiglia" alle funzioni quando si ignorano fattori costanti e cose vicine all'origine . Lo usiamo per parlare di come scala le cose .


Nozioni di base

per ingressi "sufficientemente" grandi ...

  • f(x) ∈ O(upperbound)significa f"non cresce più velocemente di"upperbound
  • f(x) ∈ Ɵ(justlikethis)significa f"cresce esattamente come"justlikethis
  • f(x) ∈ Ω(lowerbound)significa f"non cresce più lentamente di"lowerbound

alla notazione big-O non interessano i fattori costanti: 9x²si dice che la funzione "cresca esattamente come" 10x². Né la notazione asintotica big-O si occupa di cose non asintotiche ("cose ​​vicino all'origine" o "cosa succede quando la dimensione del problema è piccola"): 10x²si dice che la funzione "cresce esattamente come" 10x² - x + 2.

Perché dovresti ignorare le parti più piccole dell'equazione? Perché diventano completamente sminuiti dalle grandi parti dell'equazione mentre considerate scale sempre più grandi; il loro contributo diventa nano e irrilevante. (Vedi la sezione di esempio.)

Detto in altro modo, tutto dipende dal rapporto che vai all'infinito. Se si divide il tempo effettivo impiegato per O(...), si otterrà un fattore costante nel limite di input di grandi dimensioni. Intuitivamente questo ha un senso: le funzioni "si scalano" l'una con l'altra se puoi moltiplicare l'una per ottenere l'altra. Questo è quando diciamo ...

actualAlgorithmTime(N) ∈ O(bound(N))
                                       e.g. "time to mergesort N elements 
                                             is O(N log(N))"

... questo significa che per problemi di dimensioni "abbastanza grandi" N (se ignoriamo le cose vicino all'origine), esiste una costante (ad es. 2.5, completamente inventata) tale che:

actualAlgorithmTime(N)                 e.g. "mergesort_duration(N)       "
────────────────────── < constant            ───────────────────── < 2.5 
       bound(N)                                    N log(N)         

Ci sono molte scelte di costante; spesso la scelta "migliore" è conosciuta come il "fattore costante" dell'algoritmo ... ma spesso la ignoriamo come se ignorassimo termini non più grandi (vedi la sezione Fattori costanti per il motivo per cui di solito non contano). Puoi anche pensare all'equazione di cui sopra come un limite, dicendo " Nello scenario peggiore, il tempo impiegato non sarà mai peggio che approssimativamente N*log(N), entro un fattore 2,5 (un fattore costante a cui non ci importa molto) " .

In generale, O(...)è il più utile perché spesso ci preoccupiamo del comportamento nel caso peggiore. Se f(x)rappresenta qualcosa di "cattivo" come il processore o l'utilizzo della memoria, allora " f(x) ∈ O(upperbound)" significa " upperboundè lo scenario peggiore di utilizzo del processore / memoria".


applicazioni

In quanto costrutto puramente matematico, la notazione big-O non si limita a parlare del tempo di elaborazione e della memoria. Puoi usarlo per discutere gli asintotici di qualsiasi cosa in cui il ridimensionamento sia significativo, come ad esempio:

  • il numero di possibili strette di mano tra le Npersone a una festa ( Ɵ(N²), in particolare N(N-1)/2, ma ciò che conta è che "si ridimensiona" )
  • numero probabilistico di persone che hanno visto il marketing virale in funzione del tempo
  • come la latenza del sito Web si ridimensiona con il numero di unità di elaborazione in una CPU, GPU o cluster di computer
  • come muore la bilancia della potenza termica sulla CPU in funzione del conteggio dei transistor, della tensione, ecc.
  • quanto tempo deve essere eseguito un algoritmo, in funzione della dimensione dell'input
  • quanto spazio deve essere eseguito un algoritmo, in funzione della dimensione dell'input

Esempio

Per l'esempio della stretta di mano sopra, tutti in una stanza stringono la mano di tutti gli altri. In questo esempio #handshakes ∈ Ɵ(N²),. Perché?

Esegui un po 'di backup: il numero di strette di mano è esattamente n-scegli-2 o N*(N-1)/2(ognuna di N persone stringe la mano di N-1 altre persone, ma questa stretta di mano conta due volte quindi dividi per 2):

tutti stringono la mano a tutti gli altri.  Credito di immagine e licenza secondo l'articolo "grafico completo" dei beni comuni Wikipedia / Wikimedia. matrice di adiacenza

Tuttavia, per un numero molto elevato di persone, il termine lineare Nè sminuito e contribuisce effettivamente a 0 al rapporto (nel grafico: la frazione di caselle vuote sulla diagonale rispetto alle caselle totali si riduce man mano che il numero di partecipanti aumenta). Pertanto il comportamento di ridimensionamento è order N²o il numero di strette di mano "cresce come N²".

#handshakes(N)
────────────── ≈ 1/2
     N²

È come se le caselle vuote sulla diagonale del grafico (segni di spunta N * (N-1) / 2) non fossero nemmeno presenti ( segni di spunta N 2 asintoticamente).

(digressione temporanea da "inglese semplice" :) Se volessi dimostrarlo a te stesso, potresti eseguire una semplice algebra sul rapporto per dividerlo in più termini ( limsignifica "considerato nel limite di", ignoralo se non l'ho visto, è solo notazione per "e N è davvero molto grande"):

    N²/2 - N/2         (N²)/2   N/2         1/2
lim ────────── = lim ( ────── - ─── ) = lim ─── = 1/2
N→∞     N²       N→∞     N²     N²      N→∞  1
                               ┕━━━┙
             this is 0 in the limit of N→∞:
             graph it, or plug in a really large number for N

tl; dr: il numero di handshake 'sembra' x² così tanto per valori di grandi dimensioni, che se dovessimo scrivere il rapporto # handshakes / x², il fatto che non avremmo bisogno esattamente di handshake x² non verrebbe nemmeno mostrato in decimale per un tempo arbitrariamente grande.

ad es. per x = 1 milione, rapporto # handshakes / x²: 0.499999 ...


Building Intuition

Questo ci permette di fare dichiarazioni come ...

"Per input abbastanza grande = N, indipendentemente dal fattore costante, se raddoppio la dimensione dell'ingresso ...

  • ... raddoppio il tempo impiegato da un algoritmo O (N) ("tempo lineare"). "

    N → (2N) = 2 ( N )

  • ... Raddoppio al quadrato (quadruplo) il tempo impiegato da un algoritmo O (N²) ("tempo quadratico"). " (Es. Un problema 100x tanto grande prende 100² = 10000x il tempo ... probabilmente insostenibile)

    → (2N) ² = 4 ( )

  • ... Doppio cubo (ottuplo) il tempo impiegato da un algoritmo O (N³) ("tempo cubico"). " (Es. Un problema 100x tanto grande richiede 100³ = 1000000x il tempo ... molto insostenibile)

    cN³ → c (2N) ³ = 8 ( cN³ )

  • ... Aggiungo un importo fisso al tempo impiegato da un algoritmo O (log (N)) ("tempo logaritmico"). " (Economico!)

    c log (N) → c log (2N) = (c log (2)) + ( c log (N) ) = (importo fisso) + ( c log (N) )

  • ... non cambio il tempo impiegato da un algoritmo O (1) ("tempo costante"). " (Il più economico!)

    c * 1c * 1

  • ... I "(in pratica) raddoppia" il tempo impiegato da un algoritmo O (N log (N)). " (Abbastanza comune)

    è inferiore a O (N 1.000001 ), che potresti essere disposto a chiamare sostanzialmente lineare

  • ... Aumento ridicolmente il tempo impiegato da un algoritmo O (2 N ) ("tempo esponenziale"). " (Raddoppieresti (o triplicherai, ecc.) Il tempo semplicemente aumentando il problema di una singola unità)

    2 N → 2 2N = (4 N ) ............ metti un altro modo ...... 2 N → 2 N + 1 = 2 N 2 1 = 2 2 N

[per i matematicamente inclini, puoi spostare il mouse sugli spoiler per i sidenotes minori]

(con credito a https://stackoverflow.com/a/487292/711085 )

(tecnicamente il fattore costante potrebbe forse importare in alcuni esempi più esoterici, ma ho scritto le cose sopra (ad esempio nel registro (N)) in modo tale che non lo faccia)

Questi sono gli ordini di crescita del pane e burro che i programmatori e gli informatici applicati usano come punti di riferimento. Li vedono sempre. (Quindi, mentre potresti tecnicamente pensare "Raddoppiare l'input rende un algoritmo O (√N) 1.414 volte più lento," è meglio pensarlo come "questo è peggio del logaritmico ma migliore del lineare".)


Fattori costanti

Di solito, non ci importa quali siano i fattori costanti specifici, perché non influenzano il modo in cui la funzione cresce. Ad esempio, il completamento di due algoritmi potrebbe richiedere del O(N)tempo, ma uno potrebbe essere due volte più lento dell'altro. Di solito non ci interessa troppo a meno che il fattore non sia molto grande poiché l'ottimizzazione è un affare complicato ( Quando l'ottimizzazione è prematura? ); anche il semplice atto di scegliere un algoritmo con un big-O migliore spesso migliorerà le prestazioni di ordini di grandezza.

Alcuni algoritmi asintoticamente superiori (ad es. Un O(N log(log(N)))ordinamento senza confronto ) possono avere un fattore costante così grande (ad es. 100000*N log(log(N))), O un sovraccarico relativamente grande come O(N log(log(N)))con un nascosto + 100*N, che raramente vale la pena utilizzare anche su "big data".


Perché O (N) è a volte il meglio che puoi fare, ovvero perché abbiamo bisogno di strutture dati

O(N)gli algoritmi sono in qualche modo gli algoritmi "migliori" se devi leggere tutti i tuoi dati. L' atto stesso di leggere un mucchio di dati è O(N)un'operazione. Il caricamento in memoria è in genere O(N)(o più veloce se si dispone di supporto hardware o non c'è tempo se hai già letto i dati). Tuttavia, se tocchi o guardi ogni dato (o anche ogni altro dato), il tuo algoritmo impiegherà del O(N)tempo per eseguire questo aspetto. Non importa quanto tempo impiega il tuo algoritmo reale, lo sarà almeno O(N)perché ha trascorso quel tempo a guardare tutti i dati.

Lo stesso si può dire per l' atto stesso di scrivere . Tutti gli algoritmi che stampano N cose impiegheranno N tempo perché l'output è almeno così lungo (es. Stampare tutte le permutazioni (modi per riorganizzare) un insieme di N carte da gioco è fattoriale:) O(N!).

Ciò motiva l'uso delle strutture di dati : una struttura di dati richiede la lettura dei dati una sola volta (di solito il O(N)tempo), oltre a una quantità arbitraria di pre-elaborazione (ad esempio O(N)o O(N log(N))o O(N²)) che cerchiamo di mantenere di piccole dimensioni. Successivamente, la modifica della struttura dei dati (inserzioni / eliminazioni / ecc.) E l'esecuzione di query sui dati richiedono pochissimo tempo, come O(1)o O(log(N)). Procedi quindi a fare un gran numero di domande! In generale, più lavoro sei disposto a fare in anticipo, meno lavoro dovrai fare in seguito.

Ad esempio, supponi di avere le coordinate di latitudine e longitudine di milioni di segmenti stradali e di voler trovare tutti gli incroci stradali.

  • Metodo ingenuo: se avessi le coordinate di un incrocio stradale e volessi esaminare le strade vicine, dovresti passare ogni volta attraverso i milioni di segmenti e controllare ciascuno di adiacenza.
  • Se avessi bisogno di farlo solo una volta, non sarebbe un problema dover fare il metodo ingenuo di O(N)lavoro solo una volta, ma se vuoi farlo molte volte (in questo caso, Nvolte, una volta per ogni segmento), noi avrebbe dovuto fare un O(N²)lavoro, o 1000000² = 1000000000000 operazioni. Non va bene (un computer moderno può eseguire circa un miliardo di operazioni al secondo).
  • Se utilizziamo una struttura semplice chiamata tabella hash (una tabella di ricerca a velocità istantanea, nota anche come hashmap o dizionario), paghiamo un piccolo costo preelaborando tutto in O(N)tempo. Successivamente, in media ci vuole solo tempo costante per cercare qualcosa con la sua chiave (in questo caso, la nostra chiave sono le coordinate di latitudine e longitudine, arrotondate in una griglia; cerchiamo gli spazi di griglia adiacenti di cui ci sono solo 9, che è un costante).
  • Il nostro compito è passato da non fattibile O(N²)a gestibile O(N), e tutto ciò che dovevamo fare era pagare un costo minore per creare una tabella di hash.
  • analogia : l'analogia in questo caso particolare è un puzzle: abbiamo creato una struttura di dati che sfrutta alcune proprietà dei dati. Se i nostri segmenti di strada sono come pezzi di un puzzle, li raggruppiamo abbinando colore e modello. Quindi sfruttiamo questo per evitare di fare un lavoro extra in seguito (confrontando i pezzi del puzzle di colore simile tra loro, non con ogni altro pezzo di puzzle).

La morale della storia: una struttura di dati ci consente di velocizzare le operazioni. Ancora di più, le strutture dati avanzate possono farti combinare, ritardare o addirittura ignorare le operazioni in modi incredibilmente intelligenti. Problemi diversi avrebbero analogie diverse, ma implicherebbero tutti l'organizzazione dei dati in un modo che sfrutti una struttura a cui teniamo o che ci siamo imposti artificialmente per la contabilità. Lavoriamo in anticipo (fondamentalmente pianificando e organizzando), e ora i compiti ripetuti sono molto più facili!


Esempio pratico: visualizzazione degli ordini di crescita durante la codifica

La notazione asintotica è, al suo interno, abbastanza separata dalla programmazione. La notazione asintotica è una struttura matematica per pensare a come le cose si ridimensionano e possono essere utilizzate in molti campi diversi. Detto questo ... ecco come applicare la notazione asintotica alla codifica.

Nozioni di base: ogni volta che interagiamo con ogni elemento in una raccolta di dimensioni A (come un array, un set, tutte le chiavi di una mappa, ecc.) O eseguiamo iterazioni di un ciclo, questo è un fattore moltiplicativo di dimensione A Perché dico "un fattore moltiplicativo"? - perché i cicli e le funzioni (quasi per definizione) hanno un tempo di esecuzione moltiplicativo: il numero di iterazioni, i tempi di lavoro svolto nel ciclo (o per le funzioni: il numero di volte in cui chiamate il funzione, tempi di lavoro svolto nella funzione). (Ciò vale se non facciamo nulla di speciale, come saltare i loop o uscire dal loop in anticipo, o cambiare il flusso di controllo nella funzione in base agli argomenti, che è molto comune.) Ecco alcuni esempi di tecniche di visualizzazione, con pseudocodice di accompagnamento.

(qui, le xs rappresentano unità di lavoro a tempo costante, istruzioni per il processore, codici operativi dell'interprete, qualunque cosa)

for(i=0; i<A; i++)        // A * ...
    some O(1) operation     // 1

--> A*1 --> O(A) time

visualization:

|<------ A ------->|
1 2 3 4 5 x x ... x

other languages, multiplying orders of growth:
  javascript, O(A) time and space
    someListOfSizeA.map((x,i) => [x,i])               
  python, O(rows*cols) time and space
    [[r*c for c in range(cols)] for r in range(rows)]

Esempio 2:

for every x in listOfSizeA:   // A * (...
    some O(1) operation         // 1
    some O(B) operation         // B
    for every y in listOfSizeC: // C * (...
        some O(1) operation       // 1))

--> O(A*(1 + B + C))
    O(A*(B+C))        (1 is dwarfed)

visualization:

|<------ A ------->|
1 x x x x x x ... x

2 x x x x x x ... x ^
3 x x x x x x ... x |
4 x x x x x x ... x |
5 x x x x x x ... x B  <-- A*B
x x x x x x x ... x |
................... |
x x x x x x x ... x v

x x x x x x x ... x ^
x x x x x x x ... x |
x x x x x x x ... x |
x x x x x x x ... x C  <-- A*C
x x x x x x x ... x |
................... |
x x x x x x x ... x v

Esempio 3:

function nSquaredFunction(n) {
    total = 0
    for i in 1..n:        // N *
        for j in 1..n:      // N *
            total += i*k      // 1
    return total
}
// O(n^2)

function nCubedFunction(a) {
    for i in 1..n:                // A *
        print(nSquaredFunction(a))  // A^2
}
// O(a^3)

Se facciamo qualcosa di leggermente complicato, potresti comunque essere in grado di immaginare visivamente cosa sta succedendo:

for x in range(A):
    for y in range(1..x):
        simpleOperation(x*y)

x x x x x x x x x x |
x x x x x x x x x   |
x x x x x x x x     |
x x x x x x x       |
x x x x x x         |
x x x x x           |
x x x x             |
x x x               |
x x                 |
x___________________|

Qui, il più piccolo profilo riconoscibile che puoi disegnare è ciò che conta; un triangolo è una forma bidimensionale (0,5 A ^ 2), proprio come un quadrato è una forma bidimensionale (A ^ 2); il fattore costante di due qui rimane nel rapporto asintotico tra i due, tuttavia, lo ignoriamo come tutti i fattori ... (Ci sono alcune sfumature sfavorevoli a questa tecnica che non inserisco qui; può fuorviarti.)

Naturalmente questo non significa che i loop e le funzioni siano cattivi; al contrario, sono i mattoni dei moderni linguaggi di programmazione e li adoriamo. Tuttavia, possiamo vedere che il modo in cui intrecciamo loop, funzioni e condizionali insieme ai nostri dati (flusso di controllo, ecc.) Imita il tempo e lo spazio utilizzati dal nostro programma! Se l'utilizzo del tempo e dello spazio diventa un problema, è quando ricorre all'intelligenza e troviamo un algoritmo o una struttura dati semplice che non avevamo considerato, per ridurre in qualche modo l'ordine di crescita. Tuttavia, queste tecniche di visualizzazione (anche se non sempre funzionano) possono darti un'ipotesi ingenua nel caso peggiore.

Ecco un'altra cosa che possiamo riconoscere visivamente:

<----------------------------- N ----------------------------->
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x
x x x x x x x x
x x x x
x x
x

Possiamo semplicemente riorganizzarlo e vedere che è O (N):

<----------------------------- N ----------------------------->
x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x|x x x x x x x x|x x x x|x x|x

O forse fai i passaggi di log (N) dei dati, per O (N * log (N)) tempo totale:

   <----------------------------- N ----------------------------->
 ^  x x x x x x x x x x x x x x x x|x x x x x x x x x x x x x x x x
 |  x x x x x x x x|x x x x x x x x|x x x x x x x x|x x x x x x x x
lgN x x x x|x x x x|x x x x|x x x x|x x x x|x x x x|x x x x|x x x x
 |  x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x|x x
 v  x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x|x

Non correlato ma vale la pena ricordare di nuovo: se eseguiamo un hash (ad es. Un dizionario / ricerca hashtable), questo è un fattore di O (1). È abbastanza veloce.

[myDictionary.has(x) for x in listOfSizeA]
 \----- O(1) ------/    

--> A*1 --> O(A)

Se facciamo qualcosa di molto complicato, come ad esempio con una funzione ricorsiva o algoritmo divide et impera, è possibile utilizzare il teorema (di solito funziona), o in casi ridicole l'Akra-Bazzi Teorema (funziona quasi sempre) si guarda il tempo di esecuzione del tuo algoritmo su Wikipedia.

Ma i programmatori non la pensano così perché alla fine l'intuizione dell'algoritmo diventa una seconda natura. Inizierai a programmare qualcosa di inefficiente e penserai immediatamente "Sto facendo qualcosa di gravemente inefficiente? ". Se la risposta è "sì" E prevedi che sia davvero importante, puoi fare un passo indietro e pensare a vari trucchi per far funzionare le cose più velocemente (la risposta è quasi sempre "usa un hashtable", raramente "usa un albero", e molto raramente qualcosa di un po 'più complicato).


Complessità ammortizzata e nel caso medio

C'è anche il concetto di "ammortizzato" e / o "caso medio" (si noti che questi sono diversi).

Caso medio : questo non è altro che usare la notazione big-O per il valore atteso di una funzione, piuttosto che la funzione stessa. Nel solito caso in cui tutti gli input sono considerati ugualmente probabili, il caso medio è solo la media del tempo di esecuzione. Ad esempio con quicksort, anche se il caso peggiore è O(N^2)per alcuni input davvero cattivi, il caso medio è il solito O(N log(N))(gli input davvero cattivi sono molto piccoli in numero, così pochi che non li notiamo nel caso medio).

Caso peggiore ammortizzato : alcune strutture di dati possono presentare una complessità nel caso peggiore che è grande, ma garantiscono che se si eseguono molte di queste operazioni, la quantità media di lavoro eseguita sarà migliore del caso peggiore. Ad esempio, potresti avere una struttura di dati che normalmente richiede O(1)tempo costante . Tuttavia, di tanto in tanto 'singhiozzerà' e impiegherà del O(N)tempo per un'operazione casuale, perché forse ha bisogno di fare un po 'di contabilità o raccolta dei rifiuti o qualcosa del genere ... ma ti promette che se fa il singhiozzo, non farà di nuovo il singhiozzo per N più operazioni. Il costo nel caso peggiore è ancora O(N)per operazione, ma il costo ammortizzato su molte corse è O(N)/N=O(1)per operazione. Poiché le grandi operazioni sono sufficientemente rare, si può considerare che l'enorme quantità di lavoro occasionale si fonde con il resto del lavoro come un fattore costante. Diciamo che il lavoro è "ammortizzato" su un numero sufficientemente elevato di chiamate da scomparire in modo asintotico.

L'analogia per l'analisi ammortizzata:

Guidi una macchina. Occasionalmente, devi passare 10 minuti alla stazione di rifornimento e poi 1 minuto a rifornire il serbatoio di benzina. Se lo facessi ogni volta che vai in qualsiasi posto con la tua auto (trascorri 10 minuti guidando fino alla stazione di benzina, impiegando qualche secondo a riempire una frazione di gallone), sarebbe molto inefficiente. Ma se riempi il serbatoio una volta ogni pochi giorni, gli 11 minuti trascorsi guidando fino alla stazione di servizio vengono "ammortizzati" in un numero sufficientemente ampio di viaggi, che puoi ignorarlo e far finta che tutti i tuoi viaggi siano forse del 5% più lunghi.

Confronto tra il caso medio e il caso peggiore ammortizzato:

  • Caso medio: facciamo alcune ipotesi sui nostri input; cioè se i nostri input hanno probabilità diverse, allora i nostri output / runtime avranno probabilità diverse (di cui prendiamo la media). Di solito, supponiamo che i nostri input siano tutti ugualmente probabili (probabilità uniforme), ma se gli input del mondo reale non si adattano alle nostre assunzioni di "input medio", i calcoli di output / runtime medi potrebbero essere privi di significato. Se anticipi input uniformemente casuali, questo è utile a cui pensare!
  • Caso peggiore ammortizzato: se si utilizza una struttura di dati peggiori ammortizzati, le prestazioni sono garantite all'interno del caso peggiore ammortizzato ... eventualmente (anche se gli input sono scelti da un demone malvagio che sa tutto e sta provando a fottiti). Di solito, lo usiamo per analizzare algoritmi che possono essere molto "instabili" in termini di prestazioni con imprevisti singhiozzi di grandi dimensioni, ma con il passare del tempo funzionano esattamente come altri algoritmi. (Tuttavia, a meno che la struttura dei dati non abbia limiti massimi per il lavoro eccezionale su cui è disposta a procrastinare, un malvagio aggressore potrebbe forse costringerti a recuperare il massimo del lavoro procrastinato tutto in una volta.

Tuttavia, se sei ragionevolmente preoccupato per un attaccante, ci sono molti altri vettori di attacco algoritmico di cui preoccuparsi oltre all'ammortamento e al caso medio.)

Sia il caso medio che l'ammortamento sono strumenti incredibilmente utili per pensare e progettare pensando al ridimensionamento.

(Vedi Differenza tra caso medio e analisi ammortizzata se interessati a questo argomento secondario.)


Big-O multidimensionale

Il più delle volte, le persone non si rendono conto che c'è più di una variabile al lavoro. Ad esempio, in un algoritmo di ricerca di stringhe, l'algoritmo potrebbe richiedere del tempo O([length of text] + [length of query]), ovvero è lineare in due variabili come O(N+M). Altri algoritmi più ingenui possono essere O([length of text]*[length of query])o O(N*M). Ignorare più variabili è una delle sviste più comuni che vedo nell'analisi dell'algoritmo e può ostacolarti durante la progettazione di un algoritmo.


L'intera storia

Tieni presente che big-O non è l'intera storia. Puoi velocizzare drasticamente alcuni algoritmi utilizzando la memorizzazione nella cache, rendendoli ignari della cache, evitando colli di bottiglia lavorando con la RAM anziché il disco, utilizzando la parallelizzazione o facendo un lavoro in anticipo: queste tecniche sono spesso indipendenti dall'ordine di crescita Notazione "big-O", sebbene vedrai spesso il numero di core nella notazione big-O degli algoritmi paralleli.

Inoltre, tieni presente che a causa di vincoli nascosti del tuo programma, potresti non preoccuparti davvero del comportamento asintotico. È possibile che si stia lavorando con un numero limitato di valori, ad esempio:

  • Se stai ordinando qualcosa come 5 elementi, non vuoi usare lo O(N log(N))speedsort veloce ; si desidera utilizzare l'ordinamento per inserzione, che si comporta bene su input di piccole dimensioni. Queste situazioni si presentano spesso in algoritmi di divisione e conquista, in cui si suddivide il problema in sottoproblemi sempre più piccoli, come l'ordinamento ricorsivo, le trasformazioni veloci di Fourier o la moltiplicazione della matrice.
  • Se alcuni valori sono effettivamente delimitati a causa di alcuni fatti nascosti (ad esempio, il nome umano medio è delimitato dolcemente a forse 40 lettere e l'età umana è dolcemente limitata a circa 150). Puoi anche imporre limiti ai tuoi input per rendere effettivamente i termini costanti.

In pratica, anche tra algoritmi che hanno prestazioni asintotiche uguali o simili, il loro merito relativo può in realtà essere guidato da altre cose, come: altri fattori di prestazione (quicksort e mergesort sono entrambi O(N log(N)), ma quicksort sfrutta le cache della CPU); considerazioni di non prestazione, come facilità di implementazione; se una libreria è disponibile e quanto è stimabile e mantenuta la libreria.

I programmi funzioneranno anche più lentamente su un computer da 500 MHz contro un computer da 2 GHz. Non lo consideriamo davvero come parte dei limiti delle risorse, perché pensiamo al ridimensionamento in termini di risorse della macchina (ad esempio per ciclo di clock), non al secondo reale. Tuttavia, ci sono cose simili che possono influenzare "segretamente" le prestazioni, ad esempio se si sta eseguendo in emulazione o se il codice del compilatore è ottimizzato o meno. Ciò potrebbe rendere più lunghe alcune operazioni di base (anche relativamente l'una rispetto all'altra) o addirittura accelerare o rallentare alcune operazioni in modo asintotico (anche relativamente l'una rispetto all'altra). L'effetto può essere piccolo o grande tra diverse implementazioni e / o ambienti. Passa da una lingua all'altra per cambiare quel lavoro extra? Dipende da altre cento ragioni (necessità, abilità, colleghi, produttività del programmatore,

I problemi di cui sopra, come l'effetto della scelta del linguaggio di programmazione utilizzato, non sono quasi mai considerati come parte del fattore costante (né dovrebbero esserlo); tuttavia si dovrebbe essere consapevoli di loro perché a volte (anche se raramente) possono influenzare le cose. Ad esempio in cpython, l'implementazione della coda di priorità nativa è asintoticamente non ottimale ( O(log(N))piuttosto che O(1)per la vostra scelta di inserimento o find-min); usi un'altra implementazione? Probabilmente no, poiché l'implementazione in C è probabilmente più veloce e probabilmente ci sono altri problemi simili altrove. Ci sono compromessi; a volte contano ea volte no.


( modifica : la spiegazione del "semplice inglese" termina qui.)

Appendici matematiche

Per completezza, la definizione precisa della notazione big-O è la seguente: f(x) ∈ O(g(x))significa che "f è asintoticamente limite superiore di const * g": ignorando tutto sotto un valore finito di x, esiste una costante tale che |f(x)| ≤ const * |g(x)|. (Gli altri simboli sono i seguenti: proprio come Osignifica ≤, Ωsignifica ≥. Esistono varianti minuscole: osignifica <, e ωsignifica>.) f(x) ∈ Ɵ(g(x))Significa sia f(x) ∈ O(g(x))e f(x) ∈ Ω(g(x))(limite superiore e inferiore di g): esistono alcune costanti tali che f risiederà sempre nella "banda" tra const1*g(x)e const2*g(x). È la più forte affermazione asintotica che puoi fare ed approssimativamente equivalente==. (Mi dispiace, ho scelto di rimandare la menzione dei simboli di valore assoluto fino ad ora, per motivi di chiarezza; soprattutto perché non ho mai visto valori negativi emergere in un contesto informatico.)

Le persone useranno spesso = O(...), che è forse la notazione "comp-sci" più corretta, e del tutto legittima da usare; "f = O (...)" viene letto "f è ordine ... / f è delimitato da xxx da ..." e viene considerato come "f è un'espressione i cui asintotici sono ...". Mi è stato insegnato a usare il più rigoroso ∈ O(...). significa "è un elemento di" (ancora letto come prima). In questo caso particolare, O(N²)contiene elementi come { 2 N², 3 N², 1/2 N², 2 N² + log(N), - N² + N^1.9, ...} ed è infinitamente grande, ma è ancora un set.

O e Ω non sono simmetrici (n = O (n²), ma n² non è O (n)), ma Ɵ è simmetrico e (poiché queste relazioni sono tutte transitive e riflessive) Ɵ, quindi, è simmetrico e transitivo e riflessivo e quindi suddivide l'insieme di tutte le funzioni in classi di equivalenza . Una classe di equivalenza è un insieme di cose che consideriamo essere le stesse. Vale a dire, data qualsiasi funzione tu possa pensare, puoi trovare un "rappresentante asintotico" canonico / unico della classe (prendendo generalmente il limite ... penso ); proprio come puoi raggruppare tutti i numeri interi in pari o dispari, puoi raggruppare tutte le funzioni con Ɵ in x-ish, log (x) ^ 2-ish, ecc ... praticamente ignorando termini più piccoli (ma a volte potresti essere bloccato con funzioni più complicate che sono classi separate per se stesse).

La =notazione potrebbe essere la più comune ed è persino usata nelle carte da scienziati informatici di fama mondiale. Inoltre, spesso accade che in un ambiente informale, le persone diranno O(...)quando intendono Ɵ(...); questo è tecnicamente vero poiché l'insieme di cose Ɵ(exactlyThis)è un sottoinsieme di O(noGreaterThanThis)... ed è più facile da scrivere. ;-)


28
Un'eccellente risposta matematica, ma l'OP ha chiesto una semplice risposta inglese. Questo livello di descrizione matematica non è necessario per comprendere la risposta, anche se per le persone particolarmente matematiche potrebbe essere molto più semplice da comprendere del "semplice inglese". Tuttavia, l'OP ha richiesto quest'ultima.
El Zorko,

42
Presumibilmente persone diverse dall'OP potrebbero essere interessate alle risposte a questa domanda. Non è questo il principio guida del sito?
Casey,

6
Anche se forse vedo perché le persone potrebbero sfogliare la mia risposta e pensare che sia troppo math (in particolare le osservazioni di "math is the new plain english", da quando sono state rimosse), la domanda originale chiede di big-O che riguarda le funzioni, quindi io cerca di essere esplicito e di parlare delle funzioni in modo da integrare l'intuizione inglese. La matematica qui può essere spesso rivista, o capita con un background matematico di scuola superiore. Sento che alla fine le persone potrebbero guardare l'Addenda matematica e presumere che faccia parte della risposta, quando è semplicemente lì per vedere come appare la vera matematica.
ninjagecko,

5
Questa è una risposta fantastica; IMO molto migliore di quello con il maggior numero di voti. La "matematica" richiesta non va al di là di ciò che è necessario per comprendere le espressioni tra parentesi dopo "O", che nessuna spiegazione ragionevole che utilizza esempi può evitare.
Dave Abrahams,

1
"f (x) ∈ O (upperbound) significa f" non cresce più velocemente di "upperbound" queste tre semplici parole, ma le spiegazioni matematicamente appropriate del grande Oh, Theta e Omega sono d'oro. Mi ha descritto in parole povere il punto che 5 diverse fonti non sembrano tradurmi senza scrivere espressioni matematiche complesse. Grazie uomo! :)
timbram,

243

EDIT: Nota veloce, questo quasi sicuramente confonde la notazione Big O (che è un limite superiore) con la notazione Theta (che è sia un limite superiore che inferiore). Nella mia esperienza questo è in realtà tipico delle discussioni in contesti non accademici. Ci scusiamo per l'eventuale confusione causata.

In una frase: con l'aumentare delle dimensioni del tuo lavoro, quanto tempo ci vuole per completarlo?

Ovviamente sta usando solo "dimensione" come input e "tempo impiegato" come output - la stessa idea si applica se si desidera parlare dell'utilizzo della memoria, ecc.

Ecco un esempio in cui abbiamo T-shirt N che vogliamo asciugare. Ci assumiamo è incredibilmente veloce per farli nella posizione di asciugatura (cioè l'interazione umana è trascurabile). Questo non è il caso nella vita reale, ovviamente ...

  • Usando una linea di lavaggio all'esterno: supponendo che tu abbia un cortile infinitamente grande, il lavaggio si asciuga in O (1) tempo. Per quanto ne possiedi, otterrà lo stesso sole e aria fresca, quindi le dimensioni non influiscono sul tempo di asciugatura.

  • Usando un'asciugatrice: metti 10 camicie per ogni carico e poi vengono fatte un'ora dopo. (Ignora i numeri reali qui - sono irrilevanti.) Quindi asciugare 50 camicie richiede circa 5 volte di più che asciugare 10 camicie.

  • Mettere tutto in un armadio arieggiato: se mettiamo tutto in un grande mucchio e lasciamo che il calore generale lo faccia, ci vorrà molto tempo affinché le camicie centrali si asciughino. Non vorrei indovinare i dettagli, ma sospetto che questo sia almeno O (N ^ 2) - aumentando il carico di lavaggio, il tempo di asciugatura aumenta più rapidamente.

Un aspetto importante della notazione "big O" è che non dice quale algoritmo sarà più veloce per una data dimensione. Prendi una tabella hash (chiave stringa, valore intero) vs una matrice di coppie (stringa, numero intero). È più veloce trovare una chiave nell'hashtable o un elemento nell'array, basato su una stringa? (vale a dire per l'array, "trova il primo elemento in cui la parte di stringa corrisponde alla chiave fornita.") Gli hashtable sono generalmente ammortizzati (~ = "in media") O (1) - una volta impostati, dovrebbe richiedere circa allo stesso tempo per trovare una voce in una tabella di 100 voci come in una tabella di 1.000.000 di voci. Trovare un elemento in un array (basato sul contenuto piuttosto che sull'indice) è lineare, ovvero O (N) - in media, dovrai guardare la metà delle voci.

Questo rende un hashtable più veloce di un array per le ricerche? Non necessariamente. Se hai una raccolta molto piccola di voci, un array potrebbe essere più veloce - potresti essere in grado di controllare tutte le stringhe nel tempo necessario per calcolare semplicemente l'hashcode di quella che stai guardando. Man mano che il set di dati diventa più grande, tuttavia, l'hashtable alla fine batterà l'array.


6
Una tabella hash richiede l'esecuzione di un algoritmo per calcolare l'indice dell'array effettivo (a seconda dell'implementazione). E un array ha solo O (1) perché è solo un indirizzo. Ma questo non ha nulla a che fare con la domanda, solo un'osservazione :)
Filip Ekberg,

7
La spiegazione di Jon ha molto a che fare con la domanda che penso. è esattamente come uno potrebbe spiegarlo a qualche mamma, e alla fine lo capirà, penso :) Mi piace l'esempio dei vestiti (in particolare l'ultimo, dove spiega la crescita esponenziale della complessità)
Johannes Schaub - litb,

4
Filip: Non sto parlando di indirizzare un array per indice, sto parlando di trovare una voce corrispondente in un array. Potresti rileggere la risposta e vedere se non è ancora chiaro?
Jon Skeet,

3
@Filip Ekberg Penso che tu stia pensando a una tabella di indirizzi diretti in cui ogni indice è mappato direttamente a una chiave, quindi è O (1), tuttavia credo che Jon stia parlando di una matrice non ordinata di coppie chiave / val che devi cercare attraverso linearmente.
ljs,

2
@RBT: No, non è una ricerca binaria. Può arrivare immediatamente al bucket hash giusto , basandosi semplicemente su una trasformazione dal codice hash all'indice bucket. Dopodiché, trovare il giusto codice hash nel bucket può essere lineare o può essere una ricerca binaria ... ma a quel punto sei arrivato a una proporzione molto piccola della dimensione totale del dizionario.
Jon Skeet,

126

Big O descrive un limite superiore al comportamento di crescita di una funzione, ad esempio il tempo di esecuzione di un programma, quando gli input diventano grandi.

Esempi:

  • O (n): se raddoppio la dimensione dell'ingresso il tempo di esecuzione raddoppia

  • O (n 2 ): se la dimensione dell'ingresso raddoppia i quadrupli di runtime

  • O (registro n): se la dimensione dell'ingresso raddoppia il tempo di esecuzione aumenta di uno

  • O (2 n ): se la dimensione dell'ingresso aumenta di uno, il tempo di esecuzione raddoppia

La dimensione dell'input è generalmente lo spazio in bit necessario per rappresentare l'input.


7
! errato per esempio O (n): se raddoppio la dimensione dell'ingresso il tempo di esecuzione si moltiplica fino a una costante non zero finita. Intendo O (n) = O (n + n)
arena-ru,

7
Sto parlando della f in f (n) = O (g (n)), non della g come sembri capire.
Starblue

Ho votato a favore, ma l'ultima frase non contribuisce molto che sento. Spesso non parliamo di "bit" quando discutiamo o misuriamo Big (O).
cdiggins,

5
È necessario aggiungere un esempio per O (n registro n).
Christoffer Hammarström,

Non è così chiaro, essenzialmente si comporta un po 'peggio di O (n). Quindi, se n raddoppia, il tempo di esecuzione viene moltiplicato per un fattore leggermente maggiore di 2.
Starblue

106

La notazione O grande è più comunemente usata dai programmatori come misura approssimativa del tempo necessario per completare un calcolo (algoritmo) espresso in funzione della dimensione del set di input.

Big O è utile per confrontare il modo in cui due algoritmi si ingrandiranno all'aumentare del numero di input.

Più precisamente, la notazione Big O viene utilizzata per esprimere il comportamento asintotico di una funzione. Ciò significa che la funzione si comporta quando si avvicina all'infinito.

In molti casi la "O" di un algoritmo rientrerà in uno dei seguenti casi:

  • O (1) - Il tempo per il completamento è lo stesso indipendentemente dalle dimensioni del set di input. Un esempio è l'accesso a un elemento array per indice.
  • O (Registro N) - Il tempo per il completamento aumenta all'incirca in linea con il registro2 (n). Ad esempio, 1024 elementi impiegano all'incirca il doppio di 32 elementi, poiché Log2 (1024) = 10 e Log2 (32) = 5. Un esempio è trovare un elemento in un albero di ricerca binario (BST).
  • O (N) - Tempo di completamento che si ridimensiona linearmente con la dimensione del set di input. In altre parole, se si raddoppia il numero di elementi nel set di input, l'algoritmo impiega circa il doppio del tempo. Un esempio è il conteggio del numero di elementi in un elenco collegato.
  • O (N Log N) : il tempo necessario per completare aumenta del numero di elementi per il risultato di Log2 (N). Un esempio di questo è l' heap sort e l' ordinamento rapido .
  • O (N ^ 2) - Il tempo per il completamento è approssimativamente uguale al quadrato del numero di elementi. Un esempio è l' ordinamento a bolle .
  • O (N!) - Il tempo per il completamento è il fattoriale del set di input. Un esempio di ciò è la soluzione di forza bruta del problema del venditore ambulante .

Big O ignora i fattori che non contribuiscono in modo significativo alla curva di crescita di una funzione quando la dimensione dell'input aumenta verso l'infinito. Ciò significa che le costanti aggiunte o moltiplicate per la funzione vengono semplicemente ignorate.


cdiggins, e se avessi la complessità O (N / 2), dovrebbe essere O (N) o O (N / 2), per esempio quale complessità se eseguirò il loop su mezza stringa.
Melad Basilius,

1
@Melad Questo è un esempio di una costante (0,5) che viene moltiplicata per la funzione. Ciò viene ignorato poiché si ritiene che abbia un effetto significativo per valori molto grandi di N.
cdiggins,

82

Big O è solo un modo per "esprimerti" in modo comune, "Quanto tempo / spazio ci vuole per eseguire il mio codice?".

Potresti vedere spesso O (n), O (n 2 ), O (nlogn) e così via, tutti questi sono solo modi di mostrare; Come cambia un algoritmo?

O (n) significa che Big O è n, e ora potresti pensare: "Che cos'è n !?" Bene "n" è la quantità di elementi. Imaging che si desidera cercare un elemento in un array. Dovresti guardare ogni elemento e come "Sei l'elemento / elemento corretto?" nel peggiore dei casi, l'elemento si trova all'ultimo indice, il che significa che ci è voluto tanto tempo quanto ci sono elementi nell'elenco, quindi per essere generico, diciamo "oh hey, n è un dato numero di valori!" .

Quindi potresti capire cosa significa "n 2 ", ma per essere ancora più specifici, gioca con il pensiero di avere un algoritmo di ordinamento semplice, il più semplice; bubblesort. Questo algoritmo deve esaminare l'intero elenco, per ciascun elemento.

La mia lista

  1. 1
  2. 6
  3. 3

Il flusso qui sarebbe:

  • Confronta 1 e 6, qual è il più grande? Ok 6 è nella posizione giusta, andando avanti!
  • Confronta 6 e 3, oh, 3 è meno! Spostiamolo, Ok, l'elenco è cambiato, ora dobbiamo iniziare dall'inizio!

Questo è O n 2 perché, devi guardare tutti gli elementi nell'elenco ci sono "n" elementi. Per ogni oggetto, guardi ancora una volta tutti gli oggetti, per il confronto, anche questo è "n", quindi per ogni oggetto, guardi "n" volte significando n * n = n 2

Spero che sia semplice come lo vuoi tu.

Ma ricorda, Big O è solo un modo per espanderti nel modo di tempo e spazio.


per logN consideriamo per loop in esecuzione da 0 a N / 2 il che dire di O (log log N)? Voglio dire come appare il programma?
scusami

55

Big O descrive la natura di ridimensionamento fondamentale di un algoritmo.

Ci sono molte informazioni che Big O non ti dice su un determinato algoritmo. Taglia all'osso e fornisce solo informazioni sulla natura di ridimensionamento di un algoritmo, in particolare su come la risorsa utilizza (pensa al tempo o alla memoria) di un algoritmo ridimensiona in risposta alla "dimensione di input".

Considera la differenza tra un motore a vapore e un razzo. Non sono semplicemente diverse varietà della stessa cosa (come, diciamo, un motore Prius contro un motore Lamborghini) ma sono fondamentalmente diversi tipi di sistemi di propulsione. Un motore a vapore può essere più veloce di un razzo giocattolo, ma nessun motore a pistoni a vapore sarà in grado di raggiungere le velocità di un veicolo di lancio orbitale. Questo perché questi sistemi hanno caratteristiche di ridimensionamento diverse per quanto riguarda la relazione del carburante richiesto ("utilizzo delle risorse") per raggiungere una determinata velocità ("dimensioni di input").

Perché è così importante? Perché il software si occupa di problemi che possono differire nelle dimensioni per fattori fino a un trilione. Consideralo per un momento. Il rapporto tra la velocità necessaria per viaggiare sulla Luna e la velocità di camminata umana è inferiore a 10.000: 1, e questo è assolutamente minuscolo rispetto alla gamma di dimensioni di input che il software può affrontare. E poiché il software può affrontare una gamma astronomica di dimensioni di input, esiste il potenziale per la complessità di Big O di un algoritmo, la sua natura di ridimensionamento fondamentale, per superare qualsiasi dettaglio di implementazione.

Considera l'esempio di ordinamento canonico. Bubble-sort è O (n 2 ) mentre merge-sort è O (n log n). Supponiamo che tu abbia due applicazioni di ordinamento, l'applicazione A che utilizza l'ordinamento a bolle e l'applicazione B che utilizza l'unione-ordinamento, e diciamo che per dimensioni di input di circa 30 elementi l'applicazione A è 1.000 volte più veloce dell'applicazione B all'ordinamento. Se non devi mai ordinare più di 30 elementi, è ovvio che dovresti preferire l'applicazione A, poiché è molto più veloce con queste dimensioni di input. Tuttavia, se scopri che potresti dover ordinare dieci milioni di elementi, ciò che ti aspetteresti è che l'applicazione B finisca per essere migliaia di volte più veloce dell'applicazione A in questo caso, interamente a causa del modo in cui ciascun algoritmo si ridimensiona.


40

Ecco il semplice bestiario inglese che tendo a usare per spiegare le varietà comuni di Big-O

In tutti i casi, preferisci gli algoritmi più in alto nell'elenco a quelli più in basso nell'elenco. Tuttavia, il costo per passare a una classe di complessità più costosa varia in modo significativo.

O (1):

Nessuna crescita. Indipendentemente da quanto sia grande il problema, puoi risolverlo nello stesso lasso di tempo. Ciò è in qualche modo analogo alla trasmissione in cui è necessaria la stessa quantità di energia per trasmettere a una determinata distanza, indipendentemente dal numero di persone che si trovano all'interno del raggio di trasmissione.

O (registro n ):

Questa complessità è la stessa di O (1) tranne per il fatto che è solo un po 'peggio. Per tutti gli scopi pratici, puoi considerare questo come un ridimensionamento costante molto grande. La differenza di lavoro tra l'elaborazione di 1 mila e 1 miliardo di articoli è solo un fattore sei.

O ( n ):

Il costo per risolvere il problema è proporzionale alla dimensione del problema. Se il problema raddoppia, il costo della soluzione raddoppia. Poiché la maggior parte dei problemi deve essere scansionata nel computer in qualche modo, come l'inserimento di dati, la lettura del disco o il traffico di rete, questo è generalmente un fattore di ridimensionamento conveniente.

O ( n registro n ):

Questa complessità è molto simile a O ( n ) . Ai fini pratici, i due sono equivalenti. Questo livello di complessità sarebbe generalmente considerato scalabile. Modificando le ipotesi, alcuni algoritmi O ( n log n ) possono essere trasformati in algoritmi O ( n ) . Ad esempio, limitare la dimensione delle chiavi riduce l'ordinamento da O ( n log n ) a O ( n ) .

O ( n 2 ):

Cresce come un quadrato, dove n è la lunghezza del lato di un quadrato. Questo è lo stesso tasso di crescita dell '"effetto di rete", in cui tutti in una rete potrebbero conoscere tutti gli altri nella rete. La crescita è costosa. La maggior parte delle soluzioni scalabili non può usare algoritmi con questo livello di complessità senza fare ginnastica significativa. Ciò si applica generalmente anche a tutte le altre complessità polinomiali - O ( n k ) -.

O (2 n ):

Non si ridimensiona. Non hai speranze di risolvere qualsiasi problema di dimensioni non banali. Utile per sapere cosa evitare, e per gli esperti per trovare algoritmi approssimati che sono in O ( n k ) .


2
Potresti considerare un'analogia diversa per O (1)? L'ingegnere in me vuole tirare fuori una discussione sull'impedenza RF dovuta a ostacoli.
johnwbyrd,

36

Big O è una misura di quanto tempo / spazio utilizza un algoritmo rispetto alla dimensione del suo input.

Se un algoritmo è O (n), il tempo / spazio aumenterà alla stessa velocità del suo input.

Se un algoritmo è O (n 2 ), il tempo / spazio aumenta alla velocità del suo input al quadrato.

e così via.


2
Non si tratta di spazio. Riguarda la complessità che significa tempo.
S. Lott,

13
Ho sempre creduto che potesse riguardare il tempo o lo spazio. ma non per entrambi allo stesso tempo.
Rocco,

9
La complessità sicuramente può riguardare lo spazio. Dai un'occhiata a questo: en.wikipedia.org/wiki/PSPACE
Tom Crockett

4
Questa risposta è la più "semplice" qui. I precedenti in realtà presumono che i lettori ne sappiano abbastanza per capirli, ma gli scrittori non ne sono consapevoli. Pensano che i loro siano semplici e chiari, che assolutamente non lo sono. Scrivere un sacco di testo con un bel formato e fare fantasiosi esempi artificiali che sono difficili per le persone non CS non è chiaro e semplice, è solo attraente per gli stackoverflowers che sono principalmente persone CS per votare. Spiegare il termine CS in un inglese semplice non ha assolutamente bisogno di codice e matematica. +1 per questa risposta sebbene non sia ancora abbastanza buono.
W.Sun

Questa risposta rende l'errore (comune) di assumere che f = O (g) significa che f e g sono proporzionali.
Paul Hankin,

33

Qual è una semplice spiegazione inglese di Big O? Con la minima definizione formale possibile e matematica semplice.

Una semplice spiegazione inglese della necessità di una notazione Big-O:

Quando programmiamo, stiamo cercando di risolvere un problema. Quello che codifichiamo si chiama algoritmo. La notazione O grande ci consente di confrontare le prestazioni peggiori dei nostri algoritmi in modo standardizzato. Le specifiche hardware variano nel tempo e i miglioramenti dell'hardware possono ridurre il tempo necessario per l'esecuzione di un algoritmo. Ma sostituire l'hardware non significa che il nostro algoritmo sia migliore o migliorato nel tempo, poiché il nostro algoritmo è sempre lo stesso. Quindi, al fine di permetterci di confrontare diversi algoritmi, per determinare se uno è migliore o no, usiamo la notazione Big O.

Una semplice spiegazione inglese di cosa è la Big O Notation:

Non tutti gli algoritmi vengono eseguiti nello stesso intervallo di tempo e possono variare in base al numero di elementi nell'input, che chiameremo n . Sulla base di ciò, consideriamo l'analisi del caso peggiore o un limite superiore del tempo di esecuzione man mano che n si ingrandiscono. Dobbiamo essere consapevoli di ciò che n è, perché molte delle notazioni Big O lo fanno riferimento.


32

È molto difficile misurare la velocità dei programmi software e, quando ci proviamo, le risposte possono essere molto complesse e piene di eccezioni e casi speciali. Questo è un grosso problema, perché tutte quelle eccezioni e casi speciali sono fonte di distrazione e inutili quando vogliamo confrontare due diversi programmi tra loro per scoprire quale è "il più veloce".

Come risultato di tutta questa inutile complessità, le persone cercano di descrivere la velocità dei programmi software usando le espressioni più piccole e meno complesse (matematiche) possibili. Queste espressioni sono approssimazioni molto grossolane: sebbene, con un po 'di fortuna, cattureranno l' "essenza" del fatto che un software sia veloce o lento.

Poiché sono approssimazioni, usiamo la lettera "O" (Big Oh) nell'espressione, come una convenzione per segnalare al lettore che stiamo facendo una grossolana semplificazione. (E per assicurarsi che nessuno pensi erroneamente che l'espressione sia in qualche modo accurata).

Se leggi "Oh" come "nell'ordine di" o "approssimativamente" non andrai troppo lontano. (Penso che la scelta del Big-Oh potrebbe essere stata un tentativo di umorismo).

L'unica cosa che queste espressioni "Big-Oh" cercano di fare è descrivere quanto rallenta il software man mano che aumentiamo la quantità di dati che il software deve elaborare. Se raddoppiamo la quantità di dati che devono essere elaborati, il software ha bisogno del doppio del tempo necessario per terminare il suo funzionamento? Dieci volte più a lungo? In pratica, c'è un numero molto limitato di espressioni big-Oh che incontrerai e di cui dovrai preoccuparti:

Il bene:

  • O(1) Costante : l'esecuzione del programma richiede lo stesso tempo, indipendentemente dall'entità dell'input.
  • O(log n) Logaritmico : il tempo di esecuzione del programma aumenta solo lentamente, anche con grandi aumenti delle dimensioni dell'input.

Il cattivo:

  • O(n) Lineare : il tempo di esecuzione del programma aumenta proporzionalmente alla dimensione dell'input.
  • O(n^k) Polinomio : - Il tempo di elaborazione cresce sempre più velocemente - come una funzione polinomiale - all'aumentare della dimensione dell'input.

... e il brutto:

  • O(k^n) Esponenziale Il tempo di esecuzione del programma aumenta molto rapidamente con aumenti anche moderati delle dimensioni del problema: è solo pratico elaborare piccoli set di dati con algoritmi esponenziali.
  • O(n!) Fattoriale Il tempo di esecuzione del programma sarà più lungo di quanto tu possa permetterti di aspettare qualsiasi cosa tranne i set di dati più piccoli e più banali.

4
Ho anche sentito il termine Linearithmic - O(n log n)che sarebbe considerato buono.
Jason Down,

28

Una risposta semplice e diretta può essere:

Big O rappresenta il peggior tempo / spazio possibile per quell'algoritmo. L'algoritmo non richiederà mai più spazio / tempo oltre tale limite. Big O rappresenta la complessità tempo / spazio nel caso estremo.


28

Ok, i miei 2 centesimi.

Big-O, è il tasso di aumento delle risorse consumate dal programma, rispetto alle dimensioni dell'istanza del problema

Risorsa: potrebbe essere il tempo totale della CPU, potrebbe essere lo spazio RAM massimo. Per impostazione predefinita si riferisce al tempo della CPU.

Supponiamo che il problema sia "Trova la somma",

int Sum(int*arr,int size){
      int sum=0;
      while(size-->0) 
         sum+=arr[size]; 

      return sum;
}

istanza problema = {5,10,15} ==> dimensione istanza problema = 3, iterations-in-loop = 3

istanza problema = {5,10,15,20,25} ==> dimensione istanza problema = 5 iterazioni in-loop = 5

Per input di dimensione "n" il programma sta crescendo alla velocità di "n" iterazioni in array. Quindi Big-O è N espresso come O (n)

Supponiamo che il problema sia "Trova la combinazione",

    void Combination(int*arr,int size)
    { int outer=size,inner=size;
      while(outer -->0) {
        inner=size;
        while(inner -->0)
          cout<<arr[outer]<<"-"<<arr[inner]<<endl;
      }
    }

istanza problema = {5,10,15} ==> dimensione istanza problema = 3, iterazioni totali = 3 * 3 = 9

istanza problema = {5,10,15,20,25} ==> dimensione istanza problema = 5, iterazioni totali = 5 * 5 = 25

Per input di dimensione "n" il programma sta crescendo alla velocità di iterazioni "n * n" in array. Quindi Big-O è N 2 espresso come O (n 2 )


3
while (size-->0)Spero che questo non lo chieda di nuovo.
sig. 5

26

La notazione O grande è un modo per descrivere il limite superiore di un algoritmo in termini di spazio o tempo di esecuzione. Il n è il numero di elementi nel problema (cioè dimensione di un array, numero di nodi in un albero, ecc.) Siamo interessati a descrivere il tempo di esecuzione man mano che n diventa grande.

Quando diciamo che un algoritmo è O (f (n)), stiamo dicendo che il tempo di esecuzione (o spazio richiesto) da quell'algoritmo è sempre inferiore ad alcuni tempi costanti f (n).

Dire che la ricerca binaria ha un tempo di esecuzione di O (logn) significa che esiste una costante c che è possibile moltiplicare per il log (n) che sarà sempre maggiore del tempo di esecuzione della ricerca binaria. In questo caso avrai sempre un fattore costante di confronto log (n).

In altre parole, dove g (n) è il tempo di esecuzione del tuo algoritmo, diciamo che g (n) = O (f (n)) quando g (n) <= c * f (n) quando n> k, dove c e k sono alcune costanti.


Possiamo usare la notazione BigO per misurare anche il caso peggiore e il caso medio. en.wikipedia.org/wiki/Big_O_notation
cdiggins

25

" Qual è una chiara spiegazione inglese di Big O? Con la minor definizione formale possibile e matematica semplice. "

Una domanda così meravigliosamente semplice e breve sembra almeno meritare una risposta altrettanto breve, come uno studente potrebbe ricevere durante il tutoraggio.

La notazione O grande indica semplicemente quanto tempo * può essere eseguito un algoritmo, in termini di solo la quantità di dati di input **.

(* in un senso del tempo meraviglioso e senza unità !)
(** che è ciò che conta, perché le persone vorranno sempre di più , sia che vivano oggi o domani)

Bene, cosa c'è di così meraviglioso nella notazione Big O se è quello che fa?

  • In pratica, l'analisi Big O è così utile e importante perché Big O pone l'accento esattamente sulla dell'algoritmo propria complessità e completamente ignora tutto ciò che è semplicemente una costante di proporzionalità, come un motore JavaScript, la velocità di una CPU, la connessione a Internet, e tutte quelle cose che diventano diventano rapidamente ridicolmente obsoleto come un modello T . Big O si concentra sulle prestazioni solo nel modo che conta altrettanto per le persone che vivono nel presente o nel futuro.

  • La notazione Big O mette anche in luce direttamente il principio più importante della programmazione / ingegneria informatica, il fatto che ispira tutti i buoni programmatori a continuare a pensare e sognare: l'unico modo per ottenere risultati oltre la lenta marcia della tecnologia è inventare un migliore algoritmo .


5
Mi viene chiesto di spiegare qualcosa di matematico senza matematica è sempre una sfida personale per me, come dottorando in buona fede. matematico e insegnante che ritiene che una cosa del genere sia effettivamente possibile. Ed essendo anche un programmatore, spero che nessuno si preoccupi del fatto che ho trovato rispondere a questa particolare domanda, senza matematica, come una sfida che era completamente irresistibile.
Joseph Myers,

22

Esempio di algoritmo (Java):

// Given a list of integers L, and an integer K
public boolean simple_search(List<Integer> L, Integer K)
{
    // for each integer i in list L
    for (Integer i : L)
    {
        // if i is equal to K
        if (i == K)
        {
            return true;
        }
    }

    return false;
}

Descrizione dell'algoritmo:

  • Questo algoritmo cerca un elenco, elemento per elemento, cercando una chiave,

  • Scorrendo su ogni elemento dell'elenco, se è la chiave, restituisce True,

  • Se il ciclo è terminato senza trovare la chiave, restituisce False.

La notazione Big-O rappresenta il limite superiore della complessità (Tempo, Spazio, ..)

Per trovare The Big-O on Time Complexity:

  • Calcola quanto tempo impiega (per quanto riguarda le dimensioni dell'input) il caso peggiore:

  • Caso peggiore: la chiave non esiste nell'elenco.

  • Time (Worst-Case) = 4n + 1

  • Tempo: O (4n + 1) = O (n) | in Big-O, le costanti vengono trascurate

  • O (n) ~ Lineare

C'è anche Big-Omega, che rappresenta la complessità del Best-Case:

  • Best-case: la chiave è il primo elemento.

  • Tempo (migliore) = 4

  • Tempo: Ω (4) = O (1) ~ Instant \ Constant


2
Da dove viene la tua costante 4?
Rod

1
@Rod iteratore init, confronto iteratore, lettura iteratore, confronto chiave .. Penso che Csarebbe meglio
Khaled.K

18

Big O

f (x) = O ( g (x)) quando x va a (ad esempio a = + ∞) significa che esiste una funzione k tale che:

  1. f (x) = k (x) g (x)

  2. k è limitato in alcune vicinanze di a (se a = + ∞, significa che ci sono numeri N e M tali che per ogni x> N, | k (x) | <M).

In altre parole, in parole povere: f (x) = O ( g (x)), x → a, significa che in una vicinanza di a, f si decompone nel prodotto di ge una funzione limitata.

Piccolo o

A proposito, ecco per confronto la definizione di piccola o.

f (x) = o ( g (x)) quando x indica che esiste una funzione k tale che:

  1. f (x) = k (x) g (x)

  2. k (x) va a 0 quando x va a.

Esempi

  • sin x = O (x) quando x → 0.

  • sin x = O (1) quando x → + ∞,

  • x 2 + x = O (x) quando x → 0,

  • x 2 + x = O (x 2 ) quando x → + ∞,

  • ln (x) = o (x) = O (x) quando x → + ∞.

Attenzione! La notazione con il segno uguale "=" usa una "uguaglianza falsa": è vero che o (g (x)) = O (g (x)), ma falso che O (g (x)) = o (g (X)). Allo stesso modo, è ok scrivere "ln (x) = o (x) quando x → + ∞", ma la formula "o (x) = ln (x)" non avrebbe alcun senso.

Più esempi

  • O (1) = O (n) = O (n 2 ) quando n → + ∞ (ma non viceversa, l'uguaglianza è "falsa"),

  • O (n) + O (n 2 ) = O (n 2 ) quando n → + ∞

  • O (O (n 2 )) = O (n 2 ) quando n → + ∞

  • O (n 2 ) O (n 3 ) = O (n 5 ) quando n → + ∞


Ecco l'articolo di Wikipedia: https://en.wikipedia.org/wiki/Big_O_notation


3
Stai affermando "Big O" e "Small o" senza spiegare cosa sono, introducendo molti concetti matematici senza dire perché sono importanti e il link a Wikipedia potrebbe essere in questo caso troppo ovvio per questo tipo di domanda.
Aggiungi Saxena l'

@AditSaxena Cosa intendi con "senza spiegare cosa sono"? Ho spiegato esattamente cosa sono. Cioè, "grande O" e "piccola o" non sono nulla da soli, solo una formula come "f (x) = O (g (x))" ha un significato, che ho spiegato (in un inglese semplice, ma senza definire ovviamente tutte le cose necessarie da un corso di calcolo). A volte "O (f (x))" è visto come la classe (in realtà l'insieme) di tutte le funzioni "g (x)" tale che "g (x) = O (f (x))", ma questo è un passo in più, che non è necessario per comprendere le basi.
Alexey,

Bene, ok, ci sono parole che non sono un semplice inglese, ma è inevitabile, a meno che non dovrei includere tutte le definizioni necessarie da Analisi matematica.
Alexey,

2
Ciao #Alexey, per favore dai un'occhiata alla risposta accettata: è lunga ma è ben costruita e ben formattata. Inizia con una definizione semplice senza bisogno di background matematici. Nel fare ciò introduce tre parole "tecniche" che spiega immediatamente (relative, rappresentazione, complessità). Questo procede passo dopo passo mentre si scava in questo campo.
Adit Saxena,

2
Big O viene utilizzato per comprendere il comportamento asintotico degli algoritmi per lo stesso motivo per cui viene utilizzato per comprendere il comportamento asintotico delle funzioni (il comportamento asintotico è il comportamento vicino all'infinito). È una notazione conveniente per confrontare una funzione complicata (il tempo o lo spazio effettivo dell'algoritmo) con una semplice (qualsiasi cosa semplice, di solito una funzione di potenza) vicino all'infinito o vicino a qualsiasi altra cosa. Ho solo spiegato di cosa si tratta (ha dato la definizione). Come calcolare con la grande O è una storia diversa, forse aggiungerò alcuni esempi, poiché sei interessato.
Alexey,

18

La notazione O grande è un modo per descrivere la velocità con cui un algoritmo verrà eseguito dato un numero arbitrario di parametri di input, che chiameremo "n". È utile in informatica perché macchine diverse funzionano a velocità diverse e dire semplicemente che un algoritmo richiede 5 secondi non ti dice molto perché, mentre potresti eseguire un sistema con un processore octo-core da 4,5 Ghz, potrei essere in esecuzione un sistema di 15 Mhz a 15 anni, che potrebbe richiedere più tempo indipendentemente dall'algoritmo. Quindi, invece di specificare la velocità di esecuzione di un algoritmo in termini di tempo, diciamo quanto è veloce in termini di numero di parametri di input o "n". Descrivendo gli algoritmi in questo modo, siamo in grado di confrontare le velocità degli algoritmi senza dover tenere conto della velocità del computer stesso.


11

Non sono sicuro di contribuire ulteriormente all'argomento, ma ho ancora pensato di condividere: una volta ho trovato questo post sul blog per avere alcune spiegazioni ed esempi abbastanza utili (anche se molto basilari) su Big O:

Tramite esempi, questo mi ha aiutato a mettere le basi nude nel mio teschio simile a un guscio di tartaruga, quindi penso che sia una bella lettura di 10 minuti in discesa per portarti nella giusta direzione.


@William ... e le persone tendono a morire di vecchiaia, le specie si estinguono, i pianeti diventano sterili ecc.
Priidu Neemre,

11

Vuoi sapere tutto quello che c'è da sapere sulla grande O? Anche io.

Quindi, per parlare della grande O, userò parole che hanno solo un battito in esse. Un suono per parola. Le parole piccole sono veloci. Conosci queste parole, e anche io. Useremo le parole con un suono. Sono piccoli. Sono sicuro che conoscerai tutte le parole che useremo!

Ora, parliamo di lavoro io e te. Il più delle volte, non mi piace il lavoro. Ti piace il lavoro? Potrebbe essere il tuo caso, ma sono sicuro di no.

Non mi piace andare al lavoro. Non mi piace passare il tempo al lavoro. Se avessi la mia strada, mi piacerebbe solo giocare e fare cose divertenti. Ti senti lo stesso di me?

Adesso a volte devo andare al lavoro. E 'triste ma vero. Quindi, quando sono al lavoro, ho una regola: cerco di fare meno lavoro. Il più vicino possibile a nessun lavoro possibile. Poi vado a giocare!

Quindi ecco la grande novità: la grande O può aiutarmi a non fare il lavoro! Posso giocare più volte, se conosco O grande. Meno lavoro, più gioco! Questo è ciò che Big O mi aiuta a fare.

Adesso ho del lavoro. Ho questo elenco: uno, due, tre, quattro, cinque, sei. Devo aggiungere tutte le cose in questo elenco.

Wow, odio il lavoro. Ma vabbè, devo farlo. Quindi eccomi qui.

Uno più due è tre ... più tre è sei ... e quattro è ... Non lo so. Mi sono perso. È troppo difficile per me fare nella mia testa. Non mi interessa molto per questo tipo di lavoro.

Quindi non facciamo il lavoro. Lascia che tu e io pensiamo quanto sia difficile. Quanto lavoro dovrei fare per aggiungere sei numeri?

Bene vediamo. Devo aggiungere uno e due, quindi aggiungerlo a tre, quindi aggiungerlo a quattro ... Tutto sommato, conto sei aggiunte. Devo fare sei aggiunte per risolvere questo.

Ecco che arriva O grande, per dirci quanto sia dura questa matematica.

Big O dice: dobbiamo fare sei aggiunte per risolvere questo. Una aggiunta, per ogni cosa da una a sei. Sei piccole parti di lavoro ... ogni parte di lavoro è un'aggiunta.

Bene, non farò il lavoro per aggiungerli ora. Ma so quanto sarebbe difficile. Sarebbe sei aggiunte.

Oh no, ora ho più lavoro. Sheesh. Chi produce questo tipo di cose ?!

Ora mi chiedono di aggiungere da uno a dieci! Perchè dovrei farlo? Non volevo aggiungere uno a sei. Aggiungere da uno a dieci ... beh ... sarebbe ancora più difficile!

Quanto più difficile sarebbe? Quanto più lavoro dovrei fare? Ho bisogno di più o meno passaggi?

Beh, immagino che dovrei fare dieci aggiunte ... una per ogni cosa da una a dieci. Dieci sono più di sei. Dovrei lavorare molto di più per aggiungere da uno a dieci, anziché da uno a sei!

Non voglio aggiungere adesso. Voglio solo pensare a quanto potrebbe essere difficile aggiungere così tanto. E spero di suonare il prima possibile.

Per aggiungere da uno a sei, questo è un po 'di lavoro. Ma vedi, per aggiungere da uno a dieci, questo è più lavoro?

Big O è tuo e mio amico. Big O ci aiuta a pensare a quanto lavoro dobbiamo fare, così possiamo pianificare. E, se siamo amici di O grande, può aiutarci a scegliere un lavoro che non è così difficile!

Ora dobbiamo fare un nuovo lavoro. Oh no Non mi piace per niente questa cosa del lavoro.

Il nuovo lavoro è: aggiungere tutte le cose da una a n.

Aspettare! Che cos'è n? Mi è mancato? Come posso aggiungere da uno a n se non mi dici cos'è n?

Beh, non so cosa sia n. Non mi è stato detto. Eri tu? No? Oh bene. Quindi non possiamo fare il lavoro. Accidenti.

Ma sebbene non faremo il lavoro ora, possiamo immaginare quanto sarebbe difficile, se sapessimo n. Dovremmo aggiungere n cose, giusto? Ovviamente!

Ora ecco che arriva O grande, e ci dirà quanto è difficile questo lavoro. Dice: aggiungere tutte le cose da una a N, una per una, è O (n). Per aggiungere tutte queste cose, [so che devo aggiungere n volte.] [1] Questo è grande O! Ci dice quanto sia difficile fare qualche tipo di lavoro.

Per me, penso alla grande O come a un grande, lento, capo uomo. Pensa al lavoro, ma non lo fa. Potrebbe dire: "Quel lavoro è veloce". Oppure, potrebbe dire: "Quel lavoro è così lento e duro!" Ma non fa il lavoro. Guarda solo il lavoro e poi ci dice quanto tempo potrebbe richiedere.

Mi interessa molto per la grande O. Perché? Non mi piace lavorare! A nessuno piace lavorare. Ecco perché tutti amiamo la grande O! Ci dice quanto velocemente possiamo lavorare. Ci aiuta a pensare a quanto è duro lavoro.

Uh oh, più lavoro. Ora, non facciamo il lavoro. Ma facciamo un piano per farlo, passo dopo passo.

Ci hanno dato un mazzo di dieci carte. Sono tutti confusi: sette, quattro, due, sei ... per niente diretti. E ora ... il nostro compito è di ordinarli.

Ergh. Sembra molto lavoro!

Come possiamo ordinare questo mazzo? Ho un piano.

Guarderò ogni coppia di carte, coppia per coppia, attraverso il mazzo, dalla prima all'ultima. Se la prima carta in una coppia è grande e la carta successiva in quella coppia è piccola, le cambio. Altrimenti, vado alla coppia successiva, e così via e così via ... e presto, il mazzo è finito.

Quando il mazzo è finito, chiedo: ho scambiato le carte in quel passaggio? Se è così, devo farlo ancora una volta, dall'alto.

Ad un certo punto, ad un certo momento, non ci saranno scambi e il nostro tipo di mazzo sarebbe finito. Così tanto lavoro!

Bene, quanto lavoro sarebbe, per ordinare le carte con quelle regole?

Ho dieci carte. E, la maggior parte delle volte - vale a dire, se non ho molta fortuna - devo passare attraverso l'intero mazzo fino a dieci volte, con fino a dieci scambi di carte ogni volta attraverso il mazzo.

Big O, aiutami!

Big O entra e dice: per un mazzo di n carte, ordinarlo in questo modo sarà fatto nel tempo O (N al quadrato).

Perché dice n al quadrato?

Bene, sai che n al quadrato è n volte n. Ora ho capito: n carte controllate, fino a ciò che potrebbe essere n volte nel mazzo. Sono due loop, ciascuno con n passaggi. Questo è molto più lavoro da fare. Molto lavoro, di sicuro!

Ora, quando la O grande dice che ci vorrà O (n al quadrato), non significa che n al quadrato aggiunge, sul naso. Potrebbe essere un po 'meno, per alcuni casi. Ma nel peggiore dei casi, saranno quasi n i passaggi di lavoro quadrati per ordinare il mazzo.

Ora qui è dove O grande è nostro amico.

Big O sottolinea questo: quando n diventa grande, quando ordiniamo le carte, il lavoro diventa MOLTO MOLTO PIÙ DURO rispetto al vecchio lavoro "aggiungi-queste-cose". Come facciamo a saperlo?

Bene, se n diventa davvero grande, non ci interessa cosa potremmo aggiungere a n o n al quadrato.

Per big n, n al quadrato è più grande di n.

Big O ci dice che ordinare le cose è più difficile che aggiungere cose. O (n al quadrato) è più di O (n) per il grande n. Ciò significa: se n diventa davvero grande, per ordinare un mazzo misto di n cose DEVE impiegare più tempo, piuttosto che aggiungere n cose miste.

Big O non risolve il lavoro per noi. Big O ci dice quanto è difficile il lavoro.

Ho un mazzo di carte. Li ho ordinati. Mi hai aiutato Grazie.

C'è un modo più veloce per ordinare le carte? Big O può aiutarci?

Sì, c'è un modo più veloce! Ci vuole del tempo per imparare, ma funziona ... e funziona abbastanza velocemente. Puoi provarlo anche tu, ma prenditi il ​​tuo tempo ad ogni passo e non perdere il tuo posto.

In questo nuovo modo di ordinare un mazzo, non controlliamo coppie di carte come facevamo tempo fa. Ecco le tue nuove regole per ordinare questo mazzo:

Uno: scelgo una carta nella parte del mazzo su cui lavoriamo ora. Puoi sceglierne uno per me, se lo desideri. (La prima volta che lo facciamo, "la parte del mazzo su cui lavoriamo ora" è l'intero mazzo, ovviamente.)

Due: lancio il mazzo su quella carta che hai scelto. Cos'è questo splay; come posso visualizzare? Bene, vado dalla carta iniziale in giù, una per una, e cerco una carta più alta della carta splay.

Tre: vado dalla carta fine in su e cerco una carta più bassa della carta splay.

Dopo aver trovato queste due carte, le cambio e continuo a cercare altre carte da scambiare. Cioè, torno al secondo passaggio e splay sulla carta che hai scelto ancora.

Ad un certo punto, questo ciclo (da due a tre) finirà. Termina quando entrambe le metà di questa ricerca si incontrano sulla carta splay. Quindi, abbiamo appena allargato il mazzo con la carta che hai scelto nel primo passaggio. Ora, tutte le carte vicino all'inizio sono più basse della carta splay; e le carte vicino alla fine sono più alte della carta splay. Bel trucco!

Quattro (e questa è la parte divertente): ora ho due mazzi piccoli, uno più basso della carta splay e uno più alto. Ora vado al primo passo, su ogni piccolo mazzo! Vale a dire, parto dal primo passaggio sul primo piccolo mazzo e quando quel lavoro è fatto, inizio dal primo passaggio sul prossimo piccolo mazzo.

Suddivido il mazzo in parti e seleziono ogni parte, più piccola e più piccola, e ad un certo momento non ho più lavoro da fare. Ora questo può sembrare lento, con tutte le regole. Ma credimi, non è affatto lento. È molto meno lavoro del primo modo di ordinare le cose!

Come si chiama questo tipo? Si chiama Quick Sort! Quel tipo è stato creato da un uomo chiamato CAR Hoare e lo ha chiamato Quick Sort. Ora, Quick Sort viene sempre utilizzato!

L'ordinamento rapido suddivide i mazzi grandi in quelli piccoli. Vale a dire, interrompe i grandi compiti in quelli piccoli.

Hmmm. Potrebbe esserci una regola lì dentro, credo. Per rendere piccoli i compiti più grandi, interrompili.

Questo tipo è abbastanza veloce. Quanto veloce? Big O ci dice: questo tipo ha bisogno del lavoro di O (n log n), nel frattempo.

È più o meno veloce del primo tipo? Grande O, ti prego, aiutami!

Il primo tipo era O (n quadrato). Ma l'ordinamento rapido è O (n log n). Sai che n log n è inferiore a n quadrato, per big n, giusto? Bene, è così che sappiamo che Quick Sort è veloce!

Se devi ordinare un mazzo, qual è il modo migliore? Bene, puoi fare quello che vuoi, ma sceglierei Ordinamento rapido.

Perché scelgo Ordinamento rapido? Non mi piace lavorare, ovviamente! Voglio che il lavoro venga svolto non appena posso farlo.

Come faccio a sapere che Quick Sort è meno lavoro? So che O (n log n) è inferiore a O (n quadrato). Gli O sono più piccoli, quindi Quick Sort è meno lavoro!

Ora conosci il mio amico Big O. Ci aiuta a fare meno lavoro. E se conosci la grande O, puoi fare anche meno lavoro!

Hai imparato tutto questo con me! Sei così intelligente! Grazie mille!

Ora che il lavoro è finito, andiamo a giocare!


[1]: c'è un modo per imbrogliare e aggiungere tutte le cose da una a una, tutte in una volta. Qualcuno di nome Gauss lo ha scoperto quando aveva otto anni. Non sono così intelligente, quindi non chiedermi come ha fatto .


9

Ho un modo più semplice per comprendere la complessità temporale che la metrica più comune per il calcolo della complessità temporale è la notazione O grande. Ciò rimuove tutti i fattori costanti in modo che il tempo di esecuzione possa essere stimato in relazione a N quando N si avvicina all'infinito. In generale puoi pensarlo in questo modo:

statement;

È costante. Il tempo di esecuzione dell'istruzione non cambierà in relazione a N

for ( i = 0; i < N; i++ )
  statement;

È lineare. Il tempo di esecuzione del loop è direttamente proporzionale a N. Quando N raddoppia, anche il tempo di esecuzione.

for ( i = 0; i < N; i++ ) 
{
for ( j = 0; j < N; j++ )
  statement;
}

È quadratico. Il tempo di esecuzione dei due loop è proporzionale al quadrato di N. Quando N raddoppia, il tempo di esecuzione aumenta di N * N.

while ( low <= high ) 
{
 mid = ( low + high ) / 2;
 if ( target < list[mid] )
 high = mid - 1;
 else if ( target > list[mid] )
  low = mid + 1;
else break;
}

È logaritmico. Il tempo di esecuzione dell'algoritmo è proporzionale al numero di volte in cui N può essere diviso per 2. Ciò è dovuto al fatto che l'algoritmo divide l'area di lavoro a metà con ogni iterazione.

void quicksort ( int list[], int left, int right )
{
  int pivot = partition ( list, left, right );
  quicksort ( list, left, pivot - 1 );
  quicksort ( list, pivot + 1, right );
}

È N * log (N). Il tempo di esecuzione è costituito da N loop (iterativi o ricorsivi) che sono logaritmici, quindi l'algoritmo è una combinazione di lineare e logaritmica.

In generale, fare qualcosa con ogni oggetto in una dimensione è lineare, fare qualcosa con ogni oggetto in due dimensioni è quadratico e dividere l'area di lavoro a metà è logaritmico. Esistono altre misure Big O come radice cubica, esponenziale e quadrata, ma non sono altrettanto comuni. La notazione O grande è descritta come O () dove si trova la misura. L'algoritmo quicksort verrebbe descritto come O (N * log (N)).

Nota: nulla di tutto ciò ha preso in considerazione le misure migliori, medie e nel caso peggiore. Ognuno avrebbe la propria notazione Big O. Si noti inoltre che questa è una spiegazione MOLTO semplicistica. Big O è il più comune, ma è anche più complesso che ho mostrato. Ci sono anche altre notazioni come big omega, little oe big theta. Probabilmente non li incontrerai al di fuori di un corso di analisi dell'algoritmo.

  • Vedi di più su: Qui

9

Di 'che ordini Harry Potter: completa la collezione di 8 film [Blu-ray] da Amazon e scarica la stessa collezione di film online allo stesso tempo. Vuoi testare quale metodo è più veloce. La consegna richiede quasi un giorno per arrivare e il download è stato completato circa 30 minuti prima. Grande! Quindi è una gara serrata.

Cosa succede se ordino diversi film Blu-ray come The Lord of the Rings, Twilight, The Dark Knight Trilogy, ecc. E scarico tutti i film online contemporaneamente? Questa volta, il completamento della consegna richiede ancora un giorno, ma il download online impiega 3 giorni per terminare. Per lo shopping online, il numero di articoli acquistati (input) non influisce sui tempi di consegna. L'output è costante. Lo chiamiamo O (1) .

Per il download online, il tempo di download è direttamente proporzionale alle dimensioni del file del filmato (input). Chiamiamo questo O (n) .

Dagli esperimenti, sappiamo che lo shopping online è migliore del download online. È molto importante comprendere la notazione O grande perché consente di analizzare la scalabilità e l' efficienza degli algoritmi.

Nota: la notazione O grande rappresenta lo scenario peggiore di un algoritmo. Supponiamo che O (1) e O (n) siano gli scenari peggiori dell'esempio precedente.

Riferimento : http://carlcheo.com/compsci


9

Supponiamo di parlare di un algoritmo A , che dovrebbe fare qualcosa con un set di dati di dimensioni n .

Quindi O( <some expression X involving n> )significa, in inglese semplice:

Se sei sfortunato quando esegui A, il completamento potrebbe richiedere fino a X (n) operazioni.

Come succede, ci sono alcune funzioni (pensale come implementazioni di X (n) ) che tendono a verificarsi abbastanza spesso. Questi sono ben noti e facilmente a confronto (Esempi: 1, Log N, N, N^2, N!, ecc ..)

Confrontandoli quando si parla di A e di altri algoritmi, è facile classificare gli algoritmi in base al numero di operazioni che potrebbero (nel peggiore dei casi) richiedere per il completamento.

In generale, il nostro obiettivo sarà trovare o strutturare un algoritmo A in modo tale che abbia una funzione X(n)che ritorni il numero più basso possibile.


8

Se hai una nozione adatta di infinito nella tua testa, allora c'è una breve descrizione:

La notazione O grande ti dice il costo per risolvere un problema infinitamente grande.

E inoltre

I fattori costanti sono trascurabili

Se esegui l'aggiornamento a un computer in grado di eseguire l'algoritmo due volte più velocemente, la grande notazione O non lo noterà. I miglioramenti del fattore costante sono troppo piccoli per essere notati anche nella scala con cui funziona la grande notazione O. Nota che questa è una parte intenzionale del design della notazione O grande.

Tuttavia, è possibile rilevare qualsiasi cosa "più grande" di un fattore costante.

Quando sei interessato a fare calcoli la cui dimensione è "grande" abbastanza da essere considerata come infinito approssimativamente, allora la notazione O grande è approssimativamente il costo per risolvere il tuo problema.


Se quanto sopra non ha senso, allora non hai una nozione intuitiva compatibile di infinito nella tua testa e probabilmente dovresti ignorare tutto quanto sopra; l'unico modo che conosco per rendere rigorose queste idee, o per spiegarle se non sono già intuitivamente utili, è insegnarti prima una notazione O grande o qualcosa di simile. (anche se, una volta compresa la grande notazione O in futuro, potrebbe essere utile rivisitare queste idee)


7

Qual è una semplice spiegazione inglese della notazione "Big O"?

Nota molto rapida:

La O in "Big O" si riferisce a "Ordine" (o precisamente "ordine di") in
modo che tu possa avere la sua idea letteralmente che è usato per ordinare qualcosa per confrontarli.

  • "Big O" fa due cose:

    1. Stima quanti passaggi del metodo viene applicato al computer per eseguire un'attività.
    2. Facilitare il processo di confronto con gli altri al fine di determinare se è buono o no?
    3. "Big O 'raggiunge i due precedenti con standardizzati Notations.
  • Esistono sette notazioni più utilizzate

    1. O (1), significa che il tuo computer esegue un'attività con 1step, è eccellente, ordinato n. 1
    2. O (logN), significa che il tuo computer completa un'attività con logNpassaggi, va bene, ordinato n. 2
    3. O (N), termina un'attività con Npassaggi, è giusto, Ordine n. 3
    4. O (NlogN), termina un'attività con O(NlogN)passaggi, non va bene, Ordine n. 4
    5. O (N ^ 2), esegui un'attività con N^2passaggi, non va bene, Ordine n. 5
    6. O (2 ^ N), esegui un'attività con 2^Npassaggi, è orribile, Ordine n. 6
    7. O (N!), Esegui un'attività con i N!passaggi, è terribile, Ordine n. 7

inserisci qui la descrizione dell'immagine

Supponiamo che tu ottenga una notazione O(N^2), non solo sei chiaro che il metodo richiede N * N passaggi per eseguire un'attività, ma vedi anche che non è buono come O(NlogN)dalla sua classifica.

Si prega di notare l'ordine alla fine della riga, solo per una migliore comprensione. Ci sono più di 7 notazioni se tutte le possibilità sono prese in considerazione.

In CS, la serie di passaggi per eseguire un'attività si chiama algoritmi.
In Terminologia, la notazione Big O viene utilizzata per descrivere le prestazioni o la complessità di un algoritmo.

Inoltre, Big O stabilisce il caso peggiore o misura i passi del limite superiore.
Si potrebbe fare riferimento a Big-Ω (Big-Omega) per il miglior caso.

Notazione Big-Ω (Big-Omega) (articolo) | Khan Academy

  • Riepilogo
    "Big O" descrive le prestazioni dell'algoritmo e le valuta.

    o affrontarlo formalmente, "Big O" classifica gli algoritmi e standardizza il processo di confronto.


6

Il modo più semplice per vederlo (in inglese semplice)

Stiamo cercando di vedere in che modo il numero di parametri di input influisce sul tempo di esecuzione di un algoritmo. Se il tempo di esecuzione dell'applicazione è proporzionale al numero di parametri di input, si dice che sia in Big O di n.

L'affermazione di cui sopra è un buon inizio ma non del tutto vera.

Una spiegazione più accurata (matematica)

supporre

n = numero di parametri di input

T (n) = La funzione effettiva che esprime il tempo di esecuzione dell'algoritmo in funzione di n

c = una costante

f (n) = Una funzione approssimativa che esprime il tempo di esecuzione dell'algoritmo in funzione di n

Quindi, per quanto riguarda Big O, l'approssimazione f (n) è considerata abbastanza buona fintanto che la condizione sotto è vera.

lim     T(n) ≤ c×f(n)
n→∞

L'equazione viene letta quando N si avvicina all'infinito, T di n è minore o uguale a c volte f di n.

In notazione O grande questo è scritto come

T(n)∈O(n)

Questo viene letto come T di n è nella grande O di n.

Ritorno in inglese

Basato sulla definizione matematica sopra, se dici che il tuo algoritmo è un O grande di n, significa che è una funzione di n (numero di parametri di input) o più veloce . Se il tuo algoritmo è Big O di n, allora diventa automaticamente anche Big O di n square.

Big O of n significa che il mio algoritmo funziona almeno così velocemente. Non puoi guardare la notazione Big O del tuo algoritmo e dire che è lento. Puoi solo dire che è veloce.

Dai un'occhiata a questo video tutorial su Big O di UC Berkley. In realtà è un concetto semplice. Se senti il ​​professor Shewchuck (noto anche come insegnante di livello divino) che lo spiega, dirai "Oh, questo è tutto!".


5

Ho trovato una spiegazione davvero grandiosa sulla notazione O grande, specialmente per una persona che non ha molta conoscenza della matematica.

https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/

La notazione O grande è usata in Informatica per descrivere le prestazioni o la complessità di un algoritmo. Big O descrive specificamente lo scenario peggiore e può essere utilizzato per descrivere il tempo di esecuzione richiesto o lo spazio utilizzato (ad esempio in memoria o su disco) da un algoritmo.

Chiunque abbia letto Perle di programmazione o altri libri di informatica e non abbia una preparazione in matematica avrà colpito un muro quando ha raggiunto capitoli che menzionano O (N log N) o altre sintassi apparentemente folli. Spero che questo articolo ti aiuti a comprendere le basi di Big O e Logarithms.

Come programmatore prima e matematico secondo (o forse terzo o quarto) ho trovato il modo migliore per comprendere a fondo Big O era quello di produrre alcuni esempi in codice. Quindi, di seguito sono riportati alcuni ordini comuni di crescita insieme a descrizioni ed esempi ove possibile.

O (1)

O (1) descrive un algoritmo che verrà sempre eseguito nello stesso tempo (o spazio) indipendentemente dalle dimensioni del set di dati di input.

bool IsFirstElementNull(IList<string> elements) {
    return elements[0] == null;
}

SU)

O (N) descrive un algoritmo le cui prestazioni cresceranno linearmente e in proporzione diretta alla dimensione del set di dati di input. L'esempio seguente dimostra anche come Big O favorisca lo scenario peggiore delle prestazioni; è possibile trovare una stringa corrispondente durante qualsiasi iterazione del ciclo for e la funzione tornerà presto, ma la notazione Big O assumerà sempre il limite superiore in cui l'algoritmo eseguirà il numero massimo di iterazioni.

bool ContainsValue(IList<string> elements, string value) {
    foreach (var element in elements)
    {
        if (element == value) return true;
    }

    return false;
} 

O (N 2 )

O (N 2 ) rappresenta un algoritmo le cui prestazioni sono direttamente proporzionali al quadrato della dimensione del set di dati di input. Questo è comune con gli algoritmi che implicano iterazioni nidificate sul set di dati. Le iterazioni nidificate più profonde comporteranno O (N 3 ), O (N 4 ) ecc.

bool ContainsDuplicates(IList<string> elements) {
    for (var outer = 0; outer < elements.Count; outer++)
    {
        for (var inner = 0; inner < elements.Count; inner++)
        {
            // Don't compare with self
            if (outer == inner) continue;

            if (elements[outer] == elements[inner]) return true;
        }
    }

    return false;
}

O (2 N )

O (2 N ) indica un algoritmo la cui crescita raddoppia con ogni additon al set di dati di input. La curva di crescita di una funzione O (2 N ) è esponenziale: inizia molto superficialmente, quindi aumenta meteoricamente. Un esempio di una funzione O (2 N ) è il calcolo ricorsivo dei numeri di Fibonacci:

int Fibonacci(int number) {
    if (number <= 1) return number;

    return Fibonacci(number - 2) + Fibonacci(number - 1);
}

logaritmi

I logaritmi sono leggermente più complicati da spiegare, quindi userò un esempio comune:

La ricerca binaria è una tecnica utilizzata per cercare set di dati ordinati. Funziona selezionando l'elemento intermedio del set di dati, essenzialmente la mediana, e lo confronta con un valore target. Se i valori corrispondono, restituirà successo. Se il valore target è superiore al valore dell'elemento sonda, prenderà la metà superiore del set di dati ed eseguirà la stessa operazione su di esso. Allo stesso modo, se il valore target è inferiore al valore dell'elemento sonda, eseguirà l'operazione rispetto alla metà inferiore. Continuerà a dimezzare il set di dati con ogni iterazione fino a quando non viene trovato il valore o fino a quando non è più possibile dividere il set di dati.

Questo tipo di algoritmo è descritto come O (registro N). Il dimezzamento iterativo dei set di dati descritti nell'esempio di ricerca binaria produce una curva di crescita che si alza all'inizio e si appiattisce lentamente all'aumentare della dimensione dei set di dati, ad esempio un set di dati di input contenente 10 elementi richiede un secondo per il completamento, un set di dati contenente 100 articoli richiede due secondi e un set di dati contenente 1000 articoli impiega tre secondi. Raddoppiare le dimensioni del set di dati di input ha scarso effetto sulla sua crescita poiché dopo una singola iterazione dell'algoritmo il set di dati verrà dimezzato e quindi alla pari con un set di dati di input pari alla metà della dimensione. Ciò rende gli algoritmi come la ricerca binaria estremamente efficienti quando si gestiscono set di dati di grandi dimensioni.


4

Questa è una spiegazione molto semplificata, ma spero che copra i dettagli più importanti.

Supponiamo che il tuo algoritmo che affronti il ​​problema dipenda da alcuni "fattori", ad esempio rendiamolo N e X.

A seconda di N e X, l'algoritmo richiederà alcune operazioni, ad esempio nel caso PEGGIORE 3(N^2) + log(X) operazioni.

Poiché Big-O non si preoccupa troppo del fattore costante (aka 3), il Big-O del tuo algoritmo è O(N^2 + log(X)). In pratica traduce "la quantità di operazioni necessarie al tuo algoritmo per le peggiori scale con questo".


4

Prefazione

algoritmo : procedura / formula per risolvere un problema


Come analizziamo gli algoritmi e come possiamo confrontare gli algoritmi tra loro?

esempio: a te e ad un amico viene chiesto di creare una funzione per sommare i numeri da 0 a N. Viene visualizzato f (x) e il proprio amico viene visualizzato con g (x). Entrambe le funzioni hanno lo stesso risultato, ma un algoritmo diverso. Per confrontare oggettivamente l'efficienza degli algoritmi utilizziamo la notazione Big-O .

Notazione Big-O: descrive la velocità con cui il runtime crescerà rispetto all'input man mano che l'input diventa arbitrariamente grande.

3 chiavi da asporto:

  1. Confronta la velocità con cui il runtime cresce NON confronta i runtime esatti (dipende dall'hardware)
  2. Solo interessato al runtime cresce rispetto all'input (n)
  3. Man mano che n diventa arbitrariamente grande, concentrati sui termini che cresceranno più velocemente man mano che n diventa grande (pensa all'infinito) analisi asintotica AKA

Complessità dello spazio: oltre alla complessità del tempo, ci preoccupiamo anche della complessità dello spazio (quanta memoria / spazio utilizza un algoritmo). Invece di controllare il tempo delle operazioni, controlliamo la dimensione dell'allocazione della memoria.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.