Big O Domanda su un algoritmo con (n ^ 2 + n) / 2 tasso di crescita


16

Sto ponendo questa domanda perché sono confuso su un aspetto della notazione O grande.

Sto usando il libro, Strutture di dati e astrazioni con Java di Frank Carrano. Nel capitolo "Efficienza degli algoritmi" mostra il seguente algoritmo:

int sum = 0, i = 1, j = 1
for (i = 1 to n) {
    for (j = 1 to i)
        sum = sum + 1
}

Inizialmente descrive questo algoritmo con un tasso di crescita di (n 2  + n) / 2 . Il che guardarlo sembra intuitivo.

Tuttavia, si afferma quindi che (n 2  + n) / 2 si comporta come n 2 quando n è grande. Nello stesso paragrafo afferma (n 2  + n) / 2 si comporta anche molto simile a n 2 / 2 . Lo usa per classificare l'algoritmo sopra come O (n 2 ) .

Ottengo che (n 2  + n) / 2 è simile a n 2 / 2 , perché la percentuale saggia, n fa poca differenza. Quello che non capisco è perché (n 2  + n) / 2 e n 2 sono simili, quando n è grande.

Ad esempio, se n = 1.000.000 :

(n^2 + n) / 2 =  500000500000 (5.000005e+11)
(n^2) / 2     =  500000000000 (5e+11)
(n^2)         = 1000000000000 (1e+12)

Quest'ultimo non è affatto simile. In effetti, ovviamente, è il doppio di quello centrale. Quindi, come può Frank Carrano dire che sono simili? Inoltre, come viene classificato l'algoritmo come O (n 2 ) . Guardando quel ciclo interno direi che era n 2 + n / 2


Se sei interessato, ho dato una risposta per tre loop nidificati con controllo del diagramma dell'albero di esecuzione Un puzzle relativo ai loop nidificati
Grijesh Chauhan



1
fondamentalmente l'idea è che man mano che ncresce, sia le funzioni 'n ^ 2' che la tua funzione si comportano in modo simile, c'è una costante differenza nel loro tasso di crescita. Se hai un'espressione complessa, domina la funzione che cresce più velocemente.
AK_

1
@MichaelT: Non penso che questo sia un duplicato di quella domanda, poiché l'altro è semplicemente una questione di conteggio errato. Questa è una domanda più sottile sul perché i termini minori (in particolare, i moltiplicatori costanti e i polinomi di grado inferiore) vengono ignorati. A quanto pare, l'interrogante qui capisce già la questione sollevata nell'altra domanda e una risposta sufficiente per quella domanda non risponderà a questa.
sdenham,

Risposte:


38

Quando si calcola la complessità Big-O di un algoritmo, la cosa mostrata è il fattore che dà il maggior contributo all'aumento del tempo di esecuzione se il numero di elementi su cui viene eseguito l'algoritmo aumenta.

Se si dispone di un algoritmo con una complessità di (n^2 + n)/2e si raddoppia il numero di elementi, la costante 2non influisce sull'aumento del tempo di esecuzione, il termine nprovoca un raddoppio nel tempo di esecuzione e il termine n^2provoca un aumento quadruplo dell'esecuzione tempo.
Poiché il n^2termine ha il maggior contributo, la complessità di Big-O è O(n^2).


2
Mi piace, sta diventando un po 'più chiaro.
Andrew S

7
Questo è molto ondulato a mano. Potrebbe essere vero o potrebbe essere falso. Se riesci a prendere una piccola quantità di matematica, vedi una delle risposte di seguito.
usr

2
Questo ragionamento è troppo vago: significherebbe che potremmo concludere ciò O(n * log n) = O(n), il che non è vero.
cfh

Potrebbe non essere la risposta più precisa o la più semanticamente corretta, ma ciò che è importante qui è che mi ha portato a iniziare a capire il punto centrale e penso che fosse l'obiettivo dell'autore. È volutamente vago poiché i dettagli possono spesso distrarre dai principi fondamentali. È importante vedere il bosco per gli alberi.
Andrew S

Bart stava davvero parlando di termini, non di fattori. Comprendendolo, non possiamo concludere questo O(n * log n) = O(n). Penso che ciò dia una buona spiegazione della logica alla base della definizione.
Mark Foskey

10

La definizione è quella

f(n) = O(g(n))

se esiste una costante C> 0 tale che, per tutto n maggiore di qualche n_0, abbiamo

|f(n)| <= C * |g(n)|

Questo è chiaramente vero per f (n) = n ^ 2 e g (n) = 1/2 n ^ 2, dove la costante C dovrebbe essere 2. È anche facile vedere che è vero per f (n) = n ^ 2 eg (n) = 1/2 (n ^ 2 + n).


4
"Se esiste una costante C> 0 tale che, per tutti i n," dovrebbe essere "Se esistono delle costanti C, n_0 tale che, per tutti i n> n_0",
Taemyr

@Taemyr: fintanto che la funzione gè diversa da zero, in realtà non è necessario in quanto è sempre possibile aumentare la costante C per rendere vera l'istruzione per i primi n_0 valori finiti.
cfh

No, finché stiamo guardando le funzioni non esiste un numero finito di potenziali valori n_0.
Taemyr,

@Taemyr: n_0 è un numero finito. Scegli C = max {f (i) / g (i): i = 1, ..., n_0}, quindi l'istruzione sarà sempre valida per i primi valori n_0, come puoi facilmente verificare.
cfh

In CS questo è meno preoccupante perché di solito n è la dimensione di input, e quindi discreta. Nel qual caso si può scegliere C in modo tale che n_0 = 1 funzioni. Ma la definizione formale è n più ampia di qualche soglia, il che rimuove un sacco di nitpicking nell'applicare la definizione.
Taemyr,

6

Quando parli di complessità, sei interessato solo alle variazioni del fattore tempo in base al numero di elementi ( n).

Come tale è possibile rimuovere qualsiasi fattore costante (come 2qui).

Questo ti lascia con O(n^2 + n).

Ora, per un ragionevole ragionevole, nil prodotto, cioè n * n, sarà significativamente più grande del solo n, motivo per cui ti è permesso saltare anche quella parte, il che ti lascia davvero con una complessità finale di O(n^2).

È vero, per i piccoli numeri ci sarà una differenza significativa, ma questo diventa più marginalmente più grande ndiventa.


Quanto grande deve essere n affinché la differenza diventi marginale? Inoltre, perché viene rimosso / 2, la sua esistenza dimezza il valore?
Andrew S,

6
@AndrewS Perché Big O Notation parla di crescita. La divisione per 2 è irrilevante al di fuori del contesto di benchmark e timestamp perché alla fine non cambia il tasso di crescita. Il componente più grande, tuttavia, lo fa e quindi è tutto ciò che tieni.
Neil,

2
@Niel, geniale così chiaro. Vorrei che i libri lo mettessero così. A volte penso che gli autori sappiano troppo che dimenticano che i semplici mortali non possiedono la loro conoscenza funzionale e quindi non chiariscono punti importanti, ma invece lo seppelliscono in una descrizione matematica formale o omettono tutti insieme credendolo implicito.
Andrew S,

Vorrei poter votare questa risposta più di una volta! @Neil, dovresti scrivere i libri di Big O.
Tersosauros,

3

Non è che "(n² + n) / 2 si comporta come n² quando n è grande", è che (n² + n) / 2 cresce comeall'aumentare di n .

Ad esempio, poiché n aumenta da 1.000 a 1.000.000

(n² + n) / 2  increases from  500500 to  500000500000
(n²) / 2      increases from  500000 to  500000000000
(n²)          increases from 1000000 to 1000000000000

Allo stesso modo, poiché n aumenta da 1.000.000 a 1.000.000.000

(n² + n) / 2  increases from  500000500000 to  500000000500000000
(n²) / 2      increases from  500000000000 to  500000000000000000
(n²)          increases from 1000000000000 to 1000000000000000000

Crescono in modo simile, il che è ciò che riguarda Big O Notation.

Se tracciate (n² + n) / 2 e n² / 2 su Wolfram Alpha , sono così simili che sono difficili da distinguere per n = 100. Se traccia tutte e tre le trame su Wolfram Alpha , vedrai due linee separate da un fattore costante di 2.


Questo va bene, mi rende molto chiaro. Grazie per la risposta.
Andrew S

2

Sembra proprio che tu debba elaborare un po 'di più la grande notazione O. Quanto è conveniente questa notazione, è molto fuorviante a causa dell'uso di un segno uguale, che non viene utilizzato qui per indicare l'uguaglianza delle funzioni.

Come sapete, questa notazione esprime un confronto asintotico di funzioni e scrivere f = O (g) significa che f (n) cresce al più velocemente di g (n) come n va all'infinito. Un modo semplice per tradurre questo è dire che la funzione f / g è limitata. Ma ovviamente, dobbiamo occuparci dei posti in cui g è zero e finiamo con la definizione più solida che puoi leggere quasi ovunque .

Questa notazione risulta essere molto conveniente per l'informatica - ecco perché è così diffusa - ma dovrebbe essere gestita con cura poiché il segno uguale che vediamo lì non denota una uguaglianza di funzioni . Questo è più o meno come dire che 2 = 5 mod 3 non implica che 2 = 5 e se sei appassionato di algebra, puoi effettivamente capire la grande notazione O come un uguaglianza modulo qualcosa.

Ora, per tornare alla tua domanda specifica, è del tutto inutile calcolare alcuni valori numerici e confrontarli: per quanto grande sia un milione, non tiene conto del comportamento asintotico. Sarebbe più utile tracciare il rapporto delle funzioni f (n) = n (n-1) / 2 e g (n) = n² - ma in questo caso speciale possiamo facilmente vedere che f (n) / g (n) è minore di 1/2 se n> 0 implica che f = O (g) .

Per migliorare la comprensione della notazione, è necessario

  • Lavora con una definizione chiara, non con un'impressione confusa basata su elementi simili : come hai appena sperimentato, un'impressione così confusa non funziona bene.

  • Prenditi del tempo per elaborare esempi in dettaglio. Se elaborerai solo cinque esempi in una settimana, sarà sufficiente per migliorare la tua sicurezza. Questo è uno sforzo che vale sicuramente la pena.


Nota algebrica laterale Se A è l'algebra di tutte le funzioni Ν → Ν e C la sottoalgebra delle funzioni limitate, data una funzione f l'insieme delle funzioni appartenenti a O (f) è un C -submodule di A , e regole di calcolo sul grande O notazione descrive semplicemente come A opera su questi sottomoduli. Pertanto, l'uguaglianza che vediamo è un'uguaglianza di C -submodules di A , questo è solo un altro tipo di modulo.


1
Quell'articolo di Wikipedia è difficile da seguire dopo la prima piccola parte. È stato scritto per matematici esperti da matematici esperti e non è il tipo di testo introduttivo che mi aspetterei da un articolo enciclopedico. Grazie per la tua comprensione, anche se va tutto bene.
Andrew S,

Tu sopravvaluti il ​​livello nel testo di Wikipedia! :) Di certo non è ben scritto. Graham, Knuth e Patashnik hanno scritto un libro adorabile "Concrete Mathematics" per gli studenti di CS. Puoi anche provare "the Art of Computer Programming" o un libro di teoria dei numeri scritto negli anni '50 (Hardy & Wright, Rose) poiché di solito si rivolgono al livello degli studenti delle scuole superiori. Non è necessario leggere l'intero libro, se ne scegli uno, solo la parte sull'asintotico! Ma prima devi decidere quanto devi capire. :)
Michael Le Barbier Grünewald,

1

Penso che tu fraintenda il significato della notazione O grande.

Quando vedi O (N ^ 2) significa sostanzialmente: quando il problema diventa 10 volte più grande, il tempo per risolverlo sarà: 10 ^ 2 = 100 volte più grande.

Dai un pugno a 1000 e 10000 nella tua equazione: 1000: (1000 ^ 2 + 1000) / 2 = 500500 10000: (10000 ^ 2 + 10000) / 2 = 50005000

50005000/500500 = 99,91

Quindi, mentre la N è diventata 10 volte più grande, le soluzioni sono diventate 100 volte più grandi. Quindi si comporta: O (N ^ 2)


1

se n fosse un 1,000,000allora

(n^2 + n) / 2  =  500000500000  (5.00001E+11)
(n^2) / 2      =  500000000000  (5E+11)
(n^2)          = 1000000000000  (1E+12)

1000000000000.00 cosa?

Mentre la complessità ci dà un modo per prevedere un costo nel mondo reale (secondi o byte a seconda che si tratti di complessità temporale o complessità dello spazio), non ci fornisce un numero di secondi o qualsiasi altra unità particolare.

Ci dà un certo grado di proporzione.

Se un algoritmo deve fare qualcosa n² volte, allora ci vorrà n² × c per un valore di c che è il tempo impiegato da ogni iterazione.

Se un algoritmo deve fare qualcosa n² ÷ 2 volte, allora impiegherà n² × c per un valore di c che è il doppio del tempo impiegato da ogni iterazione.

In entrambi i casi, il tempo impiegato è ancora proporzionale a n².

Ora, questi fattori costanti non sono qualcosa che possiamo semplicemente ignorare; in effetti puoi avere il caso in cui un algoritmo con complessità O (n²) fa meglio di uno con complessità O (n), perché se stiamo lavorando su un numero limitato di elementi, l'impatto dei fattori di consenso è maggiore e può sopraffare altre preoccupazioni . (In effetti, anche O (n!) È uguale a O (1) per valori sufficientemente bassi di n).

Ma non sono ciò che la complessità ci dice.

In pratica, ci sono alcuni modi in cui possiamo migliorare le prestazioni di un algoritmo:

  1. Migliora l'efficienza di ogni iterazione: O (n²) funziona ancora in n² × c secondi, ma c è più piccolo.
  2. Riduci il numero di casi visti: O (n²) viene eseguito ancora in n² × c secondi, ma n è più piccolo.
  3. Sostituisci l'algoritmo con uno che abbia gli stessi risultati, ma una complessità inferiore: ad esempio se potessimo riqualificare qualcosa di O (n²) in qualcosa di O (n log n) e quindi modificato da n² × c₀ secondi a (n log n) × c₁ secondi .

O per guardarlo in un altro modo, abbiamo f(n)×cpochi secondi e puoi migliorare le prestazioni riducendo c, riducendo no riducendo ciò che frestituisce per un dato n.

Il primo che possiamo fare con alcune micro-op all'interno di un loop o utilizzando hardware migliore. Darà sempre un miglioramento.

Il secondo che possiamo fare, forse identificando un caso in cui possiamo cortocircuitare l'algoritmo prima che tutto sia esaminato o filtrare alcuni dati che non saranno significativi. Non darà un miglioramento se il costo di fare questo supera il guadagno, ma sarà generalmente un miglioramento più grande rispetto al primo caso, specialmente con un grande n.

Il terzo che possiamo fare usando un algoritmo completamente diverso. Un classico esempio potrebbe essere la sostituzione di un ordinamento a bolle con un quicksort. Con un basso numero di elementi potremmo aver peggiorato le cose (se c₁ è maggiore di c₀), ma in genere consente i maggiori guadagni, specialmente con n molto grandi.

Nell'uso pratico, le misure di complessità ci consentono di ragionare sulle differenze tra gli algoritmi proprio perché ignorano la questione di come la riduzione di n o c aiuterà, a concentrarsi sull'esaming f()


"O (n!) È uguale a O (1) per valori sufficientemente bassi di n" è semplicemente sbagliato. Deve esserci un modo migliore per spiegare che "quando nè sufficientemente basso, il Big-O non ha importanza".
Ben Voigt,

@BenVoigt Devo ancora trovarne uno con lo stesso impatto retorico che ho avuto quando l'ho letto per la prima volta; non è originariamente mio, l'ho rubato a Eric Lippert, che potrebbe averlo originato o averlo preso da qualcun altro. Ovviamente fa riferimento a battute come "π è uguale a 3 per piccoli valori di π e grandi valori di 3" che è ancora più vecchio.
Jon Hanna,

0

Fattore costante

Il punto della notazione O grande è che puoi scegliere un fattore costante arbitrariamente grande in modo che O (funzione (n)) sia sempre più grande della funzione C * (n). Se l'algoritmo A è un miliardo di volte più lento dell'algoritmo B, allora hanno la stessa complessità O, purché tale differenza non aumenti quando n cresce arbitrariamente.

Supponiamo un fattore costante di 1000000 per illustrare il concetto: è un milione di volte più grande del necessario, ma ciò dimostra il punto in cui sono considerati irrilevanti.

(n ^ 2 + n) / 2 "si adatta dentro" O (n ^ 2) perché per qualsiasi n, non importa quanto grande, (n ^ 2 + n) / 2 <1000000 * n ^ 2.

(n ^ 2 + n) / 2 "non si adatta" a un insieme più piccolo, ad es. O (n) perché per alcuni valori (n ^ 2 + n) / 2> 1000000 * n.

I fattori costanti possono essere arbitrariamente grandi: un algoritmo con tempo di esecuzione di n anni ha una complessità O (n) che è "migliore" di un algoritmo con un tempo di esecuzione di n * log (n) microsecondi.


0

Big-O riguarda "quanto è complicato" un algoritmo. Se hai due algoritmi e uno richiede n^2*kpochi secondi per essere eseguito e l'altro richiede n^2*jpochi secondi per essere eseguito, allora puoi discutere su quale è meglio e potresti essere in grado di fare alcune ottimizzazioni interessanti per provare a influenzare ko j, ma entrambi questi algoritmi sono molto lenti rispetto a un algoritmo che richiede n*ml'esecuzione. Non importa quanto piccole siano le costanti ko j, per un input abbastanza grande, l' n*malgoritmo vincerà sempre, anche se mè abbastanza grande.

Quindi chiamiamo i primi due algoritmi O(n^2)e chiamiamo il secondo O(n). Suddivide piacevolmente il mondo in classi di algoritmi. Questo è ciò che riguarda big-O. È come dividere i veicoli in auto, camion e autobus, ecc ... C'è molta variazione tra le auto e puoi passare tutto il giorno a discutere se una Prius è migliore di una Chevy Volt, ma alla fine della giornata se tu bisogno di mettere 12 persone in una, quindi questa è una discussione piuttosto insensata. :)

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.