Questo è tecnicamente un algoritmo O (1) per "Hello World"?


117

Questo sarebbe classificato come un algoritmo O (1) per "Hello, World!" ??

public class Hello1
{
   public static void Main()
   {
      DateTime TwentyYearsLater = new DateTime(2035,01,01);
      while ( DateTime.Now < TwentyYearsLater )
      { 
          System.Console.WriteLine("It's still not time to print the hello ...");
      }
      System.Console.WriteLine("Hello, World!");
   }
}

Sto pensando di utilizzare il

DateTime TwentyYearsLater = new DateTime(2035,01,01);
while ( DateTime.Now < TwentyYearsLater )
{ 
   // ... 
}

frammento di codice come un ciclo occupato da inserire come uno scherzo ogni volta che qualcuno chiede un algoritmo di una certa complessità. Sarebbe corretto?


15
Questa non è O(N)complessitàO(1)
Fabjan

19
@SubparWebDev No, non sai quante volte passerà attraverso il ciclo, anche se conosci l'esatta differenza di tempo tra l'avvio del programma e la data specificata. Dipende dalla velocità del computer in esecuzione, da cos'altro è in esecuzione su di esso, da come la CPU pianifica l'attività, ecc.
Servizio

131
@Fabjan Non esiste un Nalgoritmo da cui dipenda, quindi non si può effettivamente dire che sia un algoritmo O (N).
Servizio

29
Tecnicamente non c'è input, quindi Nnon ha nemmeno alcun senso. Ma potresti considerare DateTime.Nowun input che lo renda ancora dipendente dal risultato. Se puoi assumere un valore realistico per DateTime.Now, allora sì, il programma ripete un numero costante di volte.
colpisci il

43
L'affermazione del problema deve definire cosa sia N.
Yacoub Massad

Risposte:


406

La notazione Big O in questo contesto viene utilizzata per descrivere una relazione tra la dimensione dell'input di una funzione e il numero di operazioni che devono essere eseguite per calcolare il risultato per quell'input.

La tua operazione non ha input a cui l'output possa essere correlato, quindi usare la notazione Big O non ha senso. Il tempo impiegato dall'operazione è indipendente dagli input dell'operazione (che è ... nessuno). Poiché non esiste una relazione tra l'input e il numero di operazioni eseguite, non è possibile utilizzare Big O per descrivere quella relazione inesistente


6
Di cosa O(max(1, 2035 - yearTheProgramIsStarted))?
Bergi

19
@Bergi [In realtà no [( stackoverflow.com/questions/34048740/… ), non puoi semplicemente descrivere il numero di iterazioni del ciclo basandoti esclusivamente sul tempo in cui esegui il lavoro. E ovviamente lo combini con il fatto che l'orologio di sistema può essere modificato dall'utente in qualsiasi momento in qualsiasi momento desideri, ecc. E non hai ancora un input ben formato che possa essere accuratamente correlato a un numero di operazioni necessarie per produrre l'output. Diamine, anche l'output stesso non è coerente.
Servizio

23
Si potrebbe sostenere che lo stato del sistema (che include l'orologio) fa parte dell'input per il programma. In questo senso, potresti usare la data come parametro di input, anche se non esplicito. È strano, però.
Connor Clark

9
Per essere più chiari, l'input "implicito" è il delta tra il 1 ° gennaio 2035 e oggi.
Connor Clark

6
@Hoten Ma l'ora di sistema non è un valore fisso. Questa funzione non è la stessa che accettare DateTimecome input l'ora di inizio. Come ho detto prima, l'orologio di sistema può cambiare nel tempo . E ancora, non puoi mappare direttamente l'input quazi che stai descrivendo a un output fisso. Non esiste un numero noto di operazioni eseguite per un dato orario di inizio, o anche per due programmi che ottengono sempre un valore ragionevole di DateTime.Now, quindi non puoi metterle in relazione mentre l'ora cambia, perché non puoi nemmeno metterle in relazione quando l'ora non cambia.
Servizio

88

Notazione Big-O significa approssimativamente "data un'operazione su una quantità di lavoro, N, quanto tempo di calcolo, proporzionale a N, richiede l'algoritmo?". Ad esempio, l'ordinamento di un array di dimensione N può richiedere N ^ 2, Nlog (N), ecc.

Non ha una quantità di dati di input su cui agire. Quindi non lo è O(anything).

Persino peggio; questo non è tecnicamente un algoritmo. Un algoritmo è un metodo per calcolare il valore di una funzione matematica: le funzioni matematiche sono una mappatura da un input a un output. Poiché questo non richiede input e non restituisce nulla, non è una funzione, in senso matematico. Da wikipedia:

Un algoritmo è un metodo efficace che può essere espresso in una quantità finita di spazio e tempo e in un linguaggio formale ben definito per il calcolo di una funzione. Partendo da uno stato iniziale e da un input iniziale (forse vuoto), le istruzioni descrivono un calcolo che, una volta eseguito, procede attraverso un numero finito di stati successivi ben definiti, producendo eventualmente "output" e terminando in uno stato finale finale.

Ciò che è, tecnicamente, è un sistema di controllo. Da wikipedia;

Un sistema di controllo è un dispositivo, o insieme di dispositivi, che gestisce, comanda, dirige o regola il comportamento di altri dispositivi o sistemi.

Per le persone che desiderano una risposta più approfondita sulla differenza tra le funzioni matematiche e gli algoritmi e le capacità più potenti dei computer di fare cose con effetti collaterali come l'output della console, la visualizzazione della grafica o il controllo dei robot, leggete questo documento sul Forte ipotesi di Church Turing

Astratto

La visione classica del calcolo posiziona il calcolo come una trasformazione a scatola chiusa di input (numeri razionali o stringhe finite) in output. Secondo la visione interattiva del calcolo, il calcolo è un processo interattivo in corso piuttosto che una trasformazione basata sulla funzione di un input in un output. Nello specifico, la comunicazione con il mondo esterno avviene durante il calcolo, non prima o dopo di esso. Questo approccio cambia radicalmente la nostra comprensione di cosa sia il calcolo e di come viene modellato.

L'accettazione dell'interazione come nuovo paradigma è ostacolata dalla Strong Church-Turing Thesis (SCT), la convinzione diffusa che le macchine di Turing (TM) catturino tutti i calcoli, quindi i modelli di calcolo più espressivi delle TM sono impossibili. In questo articolo, mostriamo che SCT reinterpreta l'originale Church-Turing Thesis (CTT) in un modo che Turing non ha mai inteso; la sua comunemente presunta equivalenza all'originale è un mito. Identifichiamo e analizziamo le ragioni storiche della diffusa credenza in SCT. Solo accettando che è falso possiamo iniziare ad adottare l'interazione come paradigma alternativo di calcolo


Non è necessario che sia una sequenza. È solo un input di dati e la notazione di Landau descrive il tempo di esecuzione in relazione ad alcune metriche su quei dati, in genere qualcosa legato alle dimensioni.
Bergi

@Bergi - sì, vedi il tuo punto! Sto solo facendo un'approssimazione, davvero, ma sì, se puoi misurare la quantità di lavoro da fare e la quantità di passaggi necessari per arrivarci, big-o riflette la relazione di queste due misure. Più vicino?
Steve Cooper

@kapep - non è una funzione pura perché è un metodo void, ma se contiamo l'output della console, è ancora casuale; potrebbe produrre uno qualsiasi di {"Hello, World!", "Non è ancora il momento di stampare il ciao ... \ nHello, World!", "Non è ancora il momento di stampare il ciao ... Non è ancora il momento di stampare the hello ... \ nHello, World! ", ...}
Steve Cooper

1
La stampa su stdout non è un output?
rpax

4
@rpax Non matematicamente, no. Una funzione è una traduzione immutabile da input a output; ad esempio, "quadrato" è la funzione che restituisce sempre 9 se si immette 3. Un metodo c # è solo una funzione matematica se una chiamata con gli stessi parametri fornisce sempre lo stesso valore di ritorno. Altrimenti, se ha effetti collaterali come scrivere sulla console, visualizzare grafici, allocare memoria, quelle non sono funzioni matematiche. (Aggiungerò un collegamento alla mia risposta che ci fornisce dettagli strazianti :))
Steve Cooper

41

No, il tuo codice ha una complessità temporale di O(2^|<DeltaTime>|),

Per una corretta codifica dell'ora corrente.
Per favore, lasciami prima scusarmi per il mio inglese.

Cos'è e come funziona Big O in CS

La notazione Big O non viene utilizzata per collegare l'input di un programma con il suo tempo di esecuzione .
La notazione Big O è, lasciandosi alle spalle il rigore, un modo per esprimere il rapporto asintotico di due quantità .

Nel caso dell'analisi algoritmica queste due grandezze non sono l'input (per il quale bisogna prima avere una funzione di "misura") e il tempo di esecuzione.
Sono la lunghezza della codifica di un'istanza del problema 1 e una metrica di interesse.

Le metriche comunemente utilizzate sono

  1. Il numero di passaggi necessari per completare l'algoritmo in un dato modello di calcolo.
  2. Lo spazio richiesto, se esiste un tale concetto, dal modello di calcolo.

Implicitamente si assume una TM come modello in modo che il primo punto si traduca nel numero di applicazioni della funzione di transizione 2 , cioè "passi", e il secondo traduca il numero di celle di nastro differenti scritte almeno una volta .

È anche spesso implicitamente assunto che possiamo usare una codifica polinomiale al posto di quella originale, ad esempio una funzione che ricerca un array dall'inizio alla fine ha O(n)complessità nonostante il fatto che una codifica di un'istanza di tale array dovrebbe avere una lunghezza di n*b+(n-1)dove bè il numero (costante) di simboli di ogni elemento. Questo perché bè considerata una costante del modello di calcolo e quindi le espressioni sopra e nsono asintoticamente uguali.

Questo spiega anche perché un algoritmo come la Trial Division è un algoritmo esponenziale nonostante sia essenzialmente un for(i=2; i<=sqr(N); i++)algoritmo simile 3 .

Vedi questo .

Ciò significa anche che la notazione O grande può utilizzare tutti i parametri necessari per descrivere il problema, non è insolito avere un parametro k per alcuni algoritmi.

Quindi non si tratta di "input" o di "non c'è input".

Caso di studio ora

La notazione Big O non mette in discussione il tuo algoritmo, presuppone solo che tu sappia cosa stai facendo. È essenzialmente uno strumento applicabile ovunque, anche ad algoritmi che possono essere deliberatamente complicati (come il tuo).

Per risolvere il tuo problema hai usato la data corrente e una data futura, quindi devono essere parte del problema in qualche modo; in poche parole: fanno parte dell'istanza del problema.

Nello specifico l'istanza è:

<DeltaTime>

Dove il <>mezzo qualsiasi, non patologico, codifica di scelta.

Vedi sotto per chiarimenti molto importanti .

Quindi il tuo tempo di grande complessità O è giusto O(2^|<DeltaTime>|), perché fai un numero di iterazioni che dipende dal valore del tempo corrente. Non ha senso inserire altre costanti numeriche poiché la notazione asintotica è utile in quanto elimina le costanti (quindi ad esempio l'uso di O(10^|<DeltaTime>|*any_time_unit)è inutile).

Dove sta la parte difficile

Abbiamo fatto un'ipotesi importante sopra: che il modello di calcolo reifica 5 volte, e per tempo intendo il tempo fisico (reale?). Non esiste un tale concetto nel modello computazionale standard, una MT non conosce il tempo, colleghiamo il tempo al numero di passaggi perché è così che funziona la nostra realtà 4 .

Nel tuo modello, tuttavia, il tempo fa parte del calcolo, puoi usare la terminologia delle persone funzionali dicendo che Main non è puro ma il concetto è lo stesso.

Per capirlo bisogna notare che nulla impedisce al Framework di utilizzare un tempo falso che viene eseguito due volte, cinque, dieci volte più velocemente del tempo fisico. In questo modo il codice verrà eseguito in "metà", "un quinto", "un decimo" del "tempo".

Questa riflessione è importante per la scelta della codifica di <DeltaTime>, questo è essenzialmente un modo condensato di scrivere <(CurrentTime, TimeInFuture)>. Poiché il tempo non esiste al priorato, la codifica di CurrentTime potrebbe benissimo essere la parola Now (o qualsiasi altra scelta) il giorno prima potrebbe essere codificata come Ieri , rompendo l'assunto che la lunghezza della codifica aumenti come il tempo fisico va avanti (e quello di DeltaTime diminuisce)

Dobbiamo modellare correttamente il tempo nel nostro modello computazionale per fare qualcosa di utile.

L'unica scelta sicura che possiamo fare è codificare timestamp con lunghezze crescenti (ma ancora senza utilizzare unario) man mano che il tempo fisico avanza. Questa è l'unica vera proprietà del tempo di cui abbiamo bisogno e quella che la codifica deve catturare. È solo con questo tipo di codifica che il tuo algoritmo può dare una complessità temporale.

La tua confusione, se esiste, deriva dal fatto che la parola tempo nelle frasi "Qual è la sua complessità temporale ?" e "Quanto tempo ci vorrà?" significa cose molto diverse

Purtroppo la terminologia usa le stesse parole, ma puoi provare a usare la "complessità dei passaggi" nella tua testa e porsi nuovamente la tua domanda, spero che ti aiuterà a capire che la risposta è davvero ^ _ ^


1 Questo spiega anche la necessità di un approccio asintotico poiché ogni istanza ha una lunghezza diversa, ma non arbitraria.
2 Spero di usare qui il termine inglese corretto.
3 Anche questo è il motivo per cui spesso troviamo i log(log(n))termini in matematica.
4 Ad esempio , un gradino deve occupare un intervallo di tempo finito, ma non nullo, né non connesso.
5 Ciò significa che il modo computazionale come conoscenza del tempo fisico in esso, cioè può esprimerlo con i suoi termini. Un'analogia è il modo in cui i generici funzionano nel framework .NET.


3
"Quindi il tuo grande tempo di esecuzione O è solo" .. Sono sicuro che intendevi 'grande O complessità' ?. Inoltre, possiamo ancora chiamare 'deltaTime' il nostro 'n' giusto .. quindi stai dicendo O (2 ^ N) qualcosa come la complessità dell'algoritmo di Fibonacci. Come sei arrivato alla "2 ^"?
Ross

@ Ross, grazie per il punto. Sono venuto con 2 per abitudine a lavorare con i numeri binari. Il punto è che i passaggi sono lineari con la lunghezza della rappresentazione del numero. La base effettiva non è molto importante e varia in base alla codifica specifica. È pseudo lineare .
Yuni Mj

Mi dispiace, ma potresti per favore elaborare di più nella tua risposta come hai concluso che la complessità è O(2^n)? Non è chiaro per i principianti.
Arturo Torres Sánchez

2
@YuniMj Mentre il vostro ragionamento non è tecnicamente sbagliato, penso che insistendo per misurare la dimensione del DeltaTimeposto del suo valore , si sta solo aggiungendo ulteriore confusione. Ad esempio, ma quel ragionamento che nessun algoritmo di ordinamento ottimale ha complessità temporale $ O (n \ cdot log n) $. Perché? Perché hai solo un numero finito di oggetti distinguibili da ordinare, nel qual caso puoi sempre usare l'ordinamento per bucket per ordinare in $ O (n) $. Oppure la dimensione del tuo oggetto è illimitata, nel qual caso $ O (n \ cdot log n) $ non regge, poiché un singolo confronto non avrà più un tempo costante ...
fgp

1
FWIW O (2 ^ n)! = O (10 ^ n) stackoverflow.com/questions/19081673/…
Nathan FD

29

Sebbene ci siano un sacco di ottime risposte qui, lasciatemi riformulare un po 'tutte.

La notazione Big-O esiste per descrivere le funzioni . Quando viene applicato all'analisi di algoritmi, ciò richiede di definire prima alcune caratteristiche di questo algoritmo in termini di una funzione . La scelta comune consiste nel considerare il numero di passaggi in funzione della dimensione dell'input . Come notato in altre risposte, trovare una tale funzione nel tuo caso sembra strano, perché non esiste un "input" chiaramente definito. Possiamo ancora provare a farlo, però:

  • Possiamo considerare il tuo algoritmo come una funzione costante che accetta qualsiasi input di qualsiasi dimensione, lo ignora, attende un periodo di tempo fisso e termina. In questo caso il suo tempo di esecuzione è f (n) = const , ed è un algoritmo di tempo O (1). Questo è quello che ti aspettavi di sentire, giusto? Sì, tecnicamente è un algoritmo O (1) .
  • Possiamo considerare il TwentyYearsLaterparametro di interesse simile alla "dimensione dell'input". In questo caso il runtime è f (n) = (nx) dove x è l '"ora attuale" al momento dell'invocazione. Se visto in questo modo, è un algoritmo in tempo O (n). Aspettati questo controargomentazione ogni volta che mostri il tuo algoritmo tecnicamente O (1) ad altre persone.
  • Oh, ma aspetta, se k =TwentyYearsLater è l'input, allora la sua dimensione n è, in realtà, il numero di bit necessari per rappresentarlo, cioè n = log (k) . La dipendenza tra la dimensione dell'input n runtime e quindi f (n) = 2 ^ n - x . Sembra che il tuo algoritmo sia diventato esponenzialmente lento! Ugh.
  • Un altro input al programma è infatti il flusso di risposte fornite dal SO alla sequenza di DateTime.Nowinvocazioni nel loop. Possiamo effettivamente immaginare che l'intera sequenza sia fornita come input nel momento in cui eseguiamo il programma. Il tempo di esecuzione può quindi essere considerato come dipendente dalla proprietà di questa sequenza, ovvero dalla sua lunghezza fino al primo TwentyYearsLaterelemento. In questo caso il tempo di esecuzione è di nuovo f (n) = ne l'algoritmo è O (n) .

Ma poi di nuovo, nella tua domanda non hai nemmeno detto di essere interessato al runtime. E se intendessi usare la memoria? A seconda di come modellate la situazione, potete dire che l'algoritmo è O (1) -memory o, forse, O (n) -memory (se l'implementazione di DateTime.Nowrichiede di tenere traccia dell'intera sequenza di invocazione in qualche modo).

E se il tuo obiettivo era inventare qualcosa di assurdo, perché non vai all in e dici che sei interessato a come la dimensione del codice dell'algoritmo in pixel sullo schermo dipende dal livello di zoom scelto. Potrebbe essere qualcosa come f (zoom) = 1 / zoom e puoi dichiarare con orgoglio che il tuo algoritmo ha una dimensione di O (1 / n) pixel!


+1. Credo che il "flusso di risposte fornite dal sistema operativo alla sequenza di DateTime.Nowinvocazioni" sia il vero input qui. Ma penso che la conclusione non dovrebbe essere che è O (n), ma è O (k), dove k è la lunghezza fino al primo TwentyYearsLaterelemento
metà

7
Questa è la risposta migliore finora - affinché Big O sia significativo devi applicare semantiche / assunzioni matematiche all'implementazione fisica (essenzialmente definendo un modello matematico per il programma con una definizione significativa di "input"). In questo senso la complessità del "programma" dipende dalla semantica applicata - se si assume che N è la differenza di tempo che scala linearmente con il numero di operazioni, è O (n). Se si assume un numero fisso di operazioni come risultato di un periodo di tempo fisso, è O (1).
Ant P

21

Devo essere leggermente in disaccordo con Servy. C'è un input per questo programma, anche se non è ovvio, e questo è il tempo del sistema. Questo potrebbe essere un tecnicismo che non avevi previsto, ma la tua TwentyYearsFromNowvariabile non è a vent'anni dal tempo del sistema ora , è staticamente assegnata al 1 ° gennaio 2035.

Quindi se prendi questo codice e lo esegui su una macchina che ha l'ora di sistema del 1 gennaio 1970, ci vorranno 65 anni per completarlo, indipendentemente dalla velocità del computer (potrebbero esserci alcune variazioni se il suo orologio è difettoso ). Se prendi questo codice e lo esegui su una macchina con ora di sistema del 2 gennaio 2035, verrà completato quasi istantaneamente.

Direi che il tuo input,, nè January 1st, 2035 - DateTime.Now, ed è O (n).

Poi c'è anche la questione del numero di operazioni. Alcune persone hanno notato che i computer più veloci raggiungeranno il ciclo più velocemente, causando più operazioni, ma questo è irrilevante. Quando si lavora con la notazione O grande, non si considera la velocità del processore o il numero esatto di operazioni. Se si prende questo algoritmo e lo si esegue su un computer, quindi lo si esegue di nuovo ma per 10 volte più a lungo sullo stesso computer, ci si aspetterebbe che il numero di operazioni crescesse dello stesso fattore di 10 volte.

Per quanto riguarda questo:

Sto pensando di utilizzare lo snippet di codice [codice redatto] come un ciclo occupato da inserire come uno scherzo ogni volta che qualcuno chiede un algoritmo di una certa complessità. Sarebbe corretto?

No, non proprio. Altre risposte hanno coperto questo, quindi volevo solo menzionarlo. In genere non è possibile correlare anni di esecuzione a nessuna notazione O grande. Per esempio. Non c'è modo di dire 20 anni di esecuzione = O (n ^ 87) o qualsiasi altra cosa per quella materia. Anche nell'algoritmo che hai fornito, potrei cambiare l' TwentyYearsFromNowanno 20110, 75699436 o 123456789 e il big-O è ancora O (n).


7
Il tempo non è un input per la funzione, è lo stato in costante cambiamento che viene osservato durante l'esecuzione del metodo. L'orologio di sistema può anche essere modificato mentre la funzione è in esecuzione . Affinché Big O sia significativo, è necessario che ogni input corrisponda a 1-1 con un valore di output, oltre a un numero di operazioni necessarie per calcolarlo. Per questa operazione l'output non è nemmeno consistente per lo stesso input (infatti varia selvaggiamente ), inoltre il numero di operazioni eseguite varia anche selvaggiamente.
servizio

When working with big-O notation, we don't consider the speed of the processor or the exact number of operations.Questa è una dichiarazione falsa. Praticamente qualsiasi operazione sensata di cui si prova a calcolare il valore Big O non cambierà il numero di operazioni eseguite in base all'hardware, ma questa sì . Big O è solo un modo per correlare il numero di operazioni alla dimensione dell'input. Per la maggior parte delle operazioni che è indipendente dall'hardware di sistema. In questo caso non lo è .
Servy

If you took this algorithm and ran it on a computer, and then ran it again but for 10x longer on the same computer, you would expect the number of operations to grow by the same factor of 10x.Anche questa è un'affermazione falsa. L'ambiente non modificherà necessariamente il numero di operazioni nel ciclo in modo lineare. Potrebbero, ad esempio, esserci altri programmi sul computer che utilizzano più o meno tempo della CPU in momenti diversi, modificando costantemente nel tempo il tempo concesso a questa applicazione.
Servy

Sono con @Servy su questo, ma per un motivo leggermente diverso. La funzione principale non accetta parametri e non restituisce alcun input. È una funzione di nil => nil, se vuoi. Non importa che ore siano, comunque non restituisce nulla.
Steve Cooper

1
Se stiamo usando questa definizione: "In matematica, una funzione è una relazione tra un insieme di input e un insieme di output consentiti con la proprietà che ogni input è correlato esattamente a un output." (wikipedia) - e stiamo contando l'output della console come 'l'output della funzione', questo varia, allungandosi su un computer più veloce, perché scriverà "" Non è ancora il momento di stampare il ciao ... "" più spesso.
Steve Cooper

13

L'analisi Big-O si occupa della quantità di elaborazione coinvolta poiché la quantità di dati elaborati aumenta senza limiti.

Qui, hai davvero a che fare solo con un singolo oggetto di dimensioni fisse. In quanto tale, l'applicazione dell'analisi big-O dipende fortemente (principalmente?) Da come definisci i tuoi termini.

Ad esempio, si potrebbe intendere la stampa dell'output in generale e l'imposizione di un'attesa così lunga che una quantità ragionevole di dati verrà stampata esattamente nello stesso periodo di tempo. Devi anche aggiungere un po 'di più in termini di definizioni alquanto insolite (se non del tutto sbagliate) per andare molto lontano - in particolare, l'analisi big-O è solitamente definita in termini di numero di operazioni fondamentali necessarie per eseguire un compito particolare (ma si noti che la complessità può essere considerata anche in termini di cose come l'uso della memoria, non solo l'uso della CPU / le operazioni eseguite).

Il numero di operazioni fondamentali di solito si traduce abbastanza da vicino al tempo impiegato, quindi non è un enorme sforzo considerare i due come sinonimi. Sfortunatamente, tuttavia, siamo ancora bloccati con quell'altra parte: la quantità di dati elaborati aumenta senza limiti. Stando così le cose, nessun ritardo fisso che puoi imporre funzionerà davvero. Per equiparare O (1) a O (N), dovresti imporre un ritardo infinito in modo che qualsiasi quantità fissa di dati impieghi un'eternità per essere stampata, proprio come farebbe una quantità infinita di dati.


10

big-O rispetto a cosa?

Sembra che tu stia intuendo che twentyYearsLaterè un "input". Se davvero hai scritto la tua funzione come

void helloWorld(int years) {
   // ...
}

Sarebbe O (N) dove N = anni (o semplicemente dire O(years) ).

Direi che il tuo algoritmo è O (N) rispetto al numero che ti capita di scrivere nella riga di codice che inizia con twentyYearsLater =. Ma le persone di solito non considerano i numeri nel codice sorgente effettivo come input. Potrebbero considerare l'input della riga di comando come input o l'input della firma della funzione come input, ma molto probabilmente non il codice sorgente stesso. Questo è ciò che stai discutendo con il tuo amico: è questo l '"input"? Hai impostato il tuo codice in modo da farlo sembrare intuitivo come un input, e puoi sicuramente chiedere il suo grande tempo di esecuzione O rispetto al numero N sulla riga 6 del tuo programma, ma se usi una tale scelta non predefinita come input devi davvero essere esplicito al riguardo.

Ma se si considera l'input come qualcosa di più usuale, come la riga di comando o l'input della funzione, non c'è alcun output e la funzione è O (1). Ci vogliono vent'anni, ma poiché big-O non cambia fino a un multiplo costante, O (1) = O (venti anni).

Domanda simile: qual è il tempo di esecuzione di:

void sortArrayOfSizeTenMillion(int[] array)

Supponendo che faccia quello che dice e che l'input sia valido, e l'algoritmo sfrutta un quicksort o un bubble sort o qualcosa di ragionevole, è O (1).


L'hardcoding dell'input non significa che l'input scompaia. Né sono quicksort e bubblesort di complessità temporale O (1) in nessun caso. bigocheatsheet.com
Theo Brinkman

@TheoBrinkman Se vuoi essere tecnico, in un modello di macchina di Turing, codificare ciò che pensi dell'input, nella macchina di Turing stessa, lo rende, per definizione, non l'input. La macchina di Turing funzionerà quindi in un tempo costante indipendentemente dall'input effettivo che ha. In un certo senso non sta eseguendo un "bubble sort" poiché non sta ordinando nulla ma piuttosto opera sulla propria rappresentazione, tuttavia in termini non tecnici ovviamente potresti descrivere l'algoritmo come un bubble sort.
Djechlin

In termini ugualmente "non tecnici", potresti descrivere l'algoritmo in questione come un ponte sospeso.
Theo Brinkman

@TheoBrinkman no, non potresti. Non avrebbe senso per nessuno.
Djechlin

Ha senso tanto quanto descriverlo come un bubble sort O (1).
Theo Brinkman

8

Questo "algoritmo" è correttamente descritto come O (1) o tempo costante. È stato affermato che non vi è alcun input per questo programma, quindi non vi è alcun N da analizzare in termini di Big Oh. Non sono d'accordo sul fatto che non ci sia alcun input. Quando viene compilato in un eseguibile e invocato, l'utente può specificare qualsiasi input di lunghezza arbitraria. Quella lunghezza di input è la N.

Il programma ignora semplicemente l'input (di qualsiasi lunghezza), quindi il tempo impiegato (o il numero di istruzioni macchina eseguite) è lo stesso indipendentemente dalla lunghezza dell'input (dato ambiente fisso = ora di inizio + hardware), quindi O (1 ).


Ma il numero di operazioni non è necessariamente coerente, anche con la stessa ora di inizio e hardware. Inoltre, per richiedere un algoritmo O (1) l'output dovrebbe essere sempre costante, e non lo è, varierà selvaggiamente in base all'ora di inizio e all'hardware. Potrebbe anche essere molto facilmente infinito, il che non è certamente costante. Non esiste alcuna relazione tra l'input definito e il numero di operazioni eseguite. Non è costante, è solo indefinito. Non puoi nominare un numero finito e sapere che ci saranno sempre meno operazioni di quello.
Servizio

Il tempo reale massimo che occorrerà è di 20 anni. Se lo avviamo in futuro, sì, ci vorrà più tempo. Supponiamo che esista un limite inferiore finito sulla quantità di tempo impiegata da un'iterazione di ciclo e che stiamo eseguendo su hardware seriale. Quindi, posso limitare il numero di volte in cui verrà eseguito il ciclo, il che significa che l'intero calcolo può essere limitato da una funzione costante, indipendentemente dalla dimensione dell'input ignorato.
waldol1

Let's suppose that there is a finite lower bound on the amount of time a loop iteration takesÈ un falso presupposto. Il programma può essere eseguito per sempre. Tutto quello che devo fare è impostare il mio orologio di sistema su 50 anni da adesso, avviarlo e non finirà mai. Oppure potrei continuare a spostare l'orologio indietro più velocemente di quanto non si sposti in avanti, o avviarlo da un punto indeterminato nel passato . Semplicemente non puoi presumere che ci sia un limite inferiore sulla durata del programma; può funzionare per sempre. Ma, anche se consideriamo vera la tua (falsa) supposizione, non puoi comunque mettere in relazione il numero di operazioni eseguite con l'input.
Servy

Un'iterazione a ciclo singolo richiede una quantità di tempo limitata. Potrebbe essere possibile che venga eseguito un numero infinito di volte, ma ciascuna dovrebbe essere approssimativamente costante. Non vedo alcun problema con questa ipotesi.
waldol1

Con quella logica [completamente errata] ogni singolo algoritmo ogni è sempre O (1) perché ogni singola operazione è sempre costante. Stai semplicemente dimostrando che non sai nemmeno cosa sia Big O. È uno strumento per (nel contesto) descrivere la relazione tra la dimensione dell'input e il numero di operazioni rilevanti eseguite. O (1) significa che esiste un numero costante di operazioni eseguite indipendentemente dall'input. Qui là c'è un numero costante di operazioni eseguite indipendentemente dall'input, ci sono potenzialmente infinite operazioni eseguite, infinite! = Costanti.
Servy

6

Una cosa che mi sorprende non è stata ancora menzionata: la notazione O grande è un limite superiore!

Il problema che tutti hanno notato è che non c'è N che descrive gli input per l'algoritmo, quindi non c'è niente con cui fare analisi big-O. Tuttavia, questo è facilmente mitigato con alcuni trucchi di base, come accettare int ne stampare le volte "Hello World" n. Ciò aggirerebbe quella lamentela e tornerebbe alla vera domanda su come funziona quella DateTimemostruosità.

Non esiste alcuna garanzia effettiva che il ciclo while venga mai interrotto. Ci piace pensare che deve in un certo tempo, ma ritengono che DateTime.nowrestituisce la data e l'ora del sistema . In realtà non vi è alcuna garanzia che ciò stia aumentando in modo monotono. È possibile che ci sia una scimmia addestrata patologicamente che cambia costantemente la data e l'ora del sistema fino al 21 ottobre 2015 12:00:00 UTC fino a quando qualcuno non dà alla scimmia delle scarpe che si adattano automaticamente e un hoverboard. Questo ciclo può effettivamente essere eseguito per un tempo infinito!

Quando approfondisci la definizione matematica delle notazioni con O grande, sono limiti superiori. Dimostrano lo scenario peggiore, non importa quanto improbabile. Lo scenario peggiore * qui è un runtime infinito, quindi siamo costretti a dichiarare che non esiste una notazione O grande per descrivere la complessità di runtime di questo algoritmo. Non esiste, proprio come 1/0 non esiste.

* Modifica: dalla mia discussione con KT, non è sempre valido presumere che lo scenario che stiamo modellando con la notazione O grande sia il caso peggiore. Nella maggior parte dei casi, se un individuo non riesce a specificare quale caso stiamo utilizzando, intendeva esplorare il caso peggiore. Tuttavia, è possibile eseguire un'analisi della complessità di grandi dimensioni sul runtime nel migliore dei casi.


2
O è, in un certo senso, un "limite superiore", in effetti, ma non significa che puoi parlare solo di "complessità del caso peggiore" usando la notazione O. Complessità attesa, complessità nel migliore dei casi, qualsiasi altra proprietà funzionale: tutte possono essere discusse in termini di limiti O.
KT.

La complessità del caso migliore @KY è chiamata little-o e la complessità prevista è big-theta. big-o è sempre la complessità del caso peggiore, secondo la sua definizione matematica.
Cort Ammon

No, qui ti sbagli. Ricontrolla le definizioni.
KT.

@KT Ok, li ricontrollo. Ricontrolla anche loro. en.wikipedia.org/wiki/Big_O_notation It's under Family of Bachmann – Landau notations
Cort Ammon

Suppongo che potresti fare qualcosa di folle come prendere una funzione fe dichiarare che la funzione gè la stessa di f, ma con un dominio limitato per includere solo fil caso migliore, e poi fare grandi-oh g, ma inizia a suonare degenerato quando lo fai quello.
Cort Ammon

5

La complessità viene utilizzata per misurare la "potenza" computazionale in termini di tempo / spazio. La notazione Big O viene utilizzata per confrontare quali problemi sono "calcolabili" o "non calcolabili" e anche per confrontare quali soluzioni -algoritmi- sono migliori di altre. Pertanto, puoi dividere qualsiasi algoritmo in due categorie: quelle che possono essere risolte in tempo polinomiale e quelle che non possono.

Problemi come il Setaccio di Erathostene sono O (n ^ exp) e quindi sono risolvibili per piccoli valori di n. Sono calcolabili, ma non in tempo polinomiale (NP) e quindi quando viene chiesto se un dato numero è primo o meno, la risposta dipende dalla grandezza di tale numero. Inoltre, la complessità non dipende dall'hardware, quindi avere computer più veloci non cambia nulla ...

Hello World non è un algoritmo e come tale è insensato tentare di determinarne la complessità, che non è nessuno. Un semplice algoritmo può essere qualcosa di simile: dato un numero casuale, determina se è pari o dispari. Ora, importa che il numero dato abbia 500 cifre? No, perché devi solo controllare se l'ultima cifra è pari o dispari. Un algoritmo più complesso sarebbe quello di determinare se un dato numero si divide equamente per 3. Sebbene alcuni numeri siano "facili" da calcolare, altri sono "difficili" e questo è a causa della sua grandezza: confronta il tempo necessario per determinare il rimanente tra un numero con una cifra e l'altro con 500 cifre.

Un caso più complesso sarebbe decodificare un testo. Hai un'apparente matrice casuale di simboli che sai anche trasmettere un messaggio a coloro che hanno la chiave di decrittografia. Supponiamo che il mittente abbia utilizzato la chiave a sinistra e il tuo Hello World leggesse: Gwkki Qieks. La soluzione "big-hammer, no-brain" produrrebbe tutte le combinazioni per quelle lettere: da Aaaa a Zzzz e quindi cercare un dizionario di parole per identificare quali parole sono valide e condividere le due lettere comuni nella cifra (i, k) in la stessa posizione. Questa funzione di trasformazione è ciò che misura Big O!


4

Alla maggior parte delle persone sembrano mancare due cose molto importanti.

  1. Il programma ha avere un input. È la data / ora hardcoded con cui viene confrontata l'ora di sistema. Gli input sono sotto il controllo della persona che esegue l'algoritmo e l'ora del sistema no. L'unica cosa che la persona che esegue questo programma può controllare è la data / ora che ha codificato nel confronto.

  2. Il programma varia in base al valore di input , ma non alla dimensione dell'input set di , che è ciò di cui si occupa la notazione O grande.

Pertanto, è indeterminato e la migliore notazione "O grande" per questo programma sarebbe probabilmente O (null), o forse O (NaN).


1
(2) è completamente sbagliato. Di solito viene considerata la "lunghezza dell'input". Per un elenco o un array di oggetti di dimensione fissa (come gli interi) sarà effettivamente la dimensione dell'insieme. Per fattorizzare un numero come 1395195191600333 sarà la lunghezza della sua rappresentazione binaria (o decimale, ecc.), Cioè il numero di cifre. Come affermato la tua definizione in (2) proibisce l'uso di big-O per discutere la complessità di "findPrimeFactors (int num)", a cui la maggior parte dei crittografi si opporrà.
Djechlin

4

Tutti hanno correttamente sottolineato che tu non definisci N , ma la risposta è no secondo l'interpretazione più ragionevole. Se N è la lunghezza della stringa che stiamo stampando e "ciao mondo!" è solo un esempio, come si potrebbe dedurre dalla descrizione di questo come un algoritmo "per" hello, world!, l'algoritmo è O ( N ), perché potresti avere una stringa di output che impiega trenta, quaranta o cinquanta anni per essere stampata, e tu stai aggiungendo solo un tempo costante a questo. O ( kN + c ) ∈ O ( N ).

Addendum:

Con mia grande sorpresa, qualcuno lo sta contestando. Ricorda le definizioni di grande O e grande Θ. Supponiamo di avere un algoritmo che attende una quantità costante di tempo c e quindi stampa un messaggio di lunghezza N in tempo lineare. (Questa è una generalizzazione dell'esempio di codice originale). Diciamo arbitrariamente che aspettiamo vent'anni per iniziare a stampare e che la stampa di un trilione di caratteri richiede altri vent'anni. Sia c = 20 ek = 10¹², per esempio, ma qualsiasi numero reale positivo andrà bene. È un tasso di d = c / k (in questo caso 2 × 10⁻¹¹) anni per carattere, quindi il nostro tempo di esecuzione f ( N ) è asintoticamentedN + canni. Ogni volta che N > k , dN = c / k N > c . Pertanto, dN < dN + c = f ( N ) <2 dN per ogni N > k , e f ( N ) ∈ Θ ( N ). QED


Dove abbiamo N = 13.
djechlin

Ma non stampa solo "Hello world", stampa un numero imprecisato di righe "Non è ancora ora". Inoltre, Big O non viene realmente utilizzato per confrontare la dimensione dell'input con la dimensione dell'output, viene generalmente utilizzato per confrontare la dimensione dell'input con il numero di operazioni o la quantità di memoria utilizzata.
Servizio

@Servy È una memoria costante, ma limitavo implicitamente il tempo di esecuzione. Anche la dimensione dell'output è O ( N ), per una stringa arbitraria: la stringa che stampiamo quando è il momento potrebbe essere arbitrariamente grande, anche in confronto a vent'anni di messaggi di attesa.
Davislor

@Servy ho modificato per chiarire che, no, N qui non è la dimensione dell'output. Non sono sicuro di come ho dato quell'impressione, ma rimuoverò ogni ambiguità.
Davislor

1
Quindi, se si assume che il programma riceva un input, quando non lo fa, che l'output può essere arbitrariamente grande, quando non può, che il ciclo non fa nulla, quando lo fa, e che l'output è correlato a l'input, quando non lo è, allora sì, il programma è lineare. Ovviamente, ognuna di queste ipotesi è completamente falsa, quindi la conclusione che ne hai tratto non è valida. Se sei in grado di dimostrare il tuo punto di vista senza fare false supposizioni, allora significherebbe qualcosa.
Servizio

4

Penso che le persone si stiano allontanando perché il codice non sembra un algoritmo tradizionale. Ecco una traduzione del codice che è più ben formata, ma rimane fedele allo spirito della domanda di OP.

void TrolloWorld(long currentUnixTime, long loopsPerMs){
    long laterUnixTime = 2051222400000;  //unix time of 01/01/2035, 00:00:00
    long numLoops = (laterUnixTime-currentUnixTime)*loopsPerMs;

    for (long i=0; i<numLoops; i++){
        print ("It's still not time to print the hello …");
    }
    print("Hello, World!");
}

Gli input sono espliciti mentre prima erano dati implicitamente dal momento in cui il codice veniva avviato e dalla velocità dell'hardware che esegue il codice. Il codice è deterministico e ha un output ben definito per determinati input.

A causa delle limitazioni imposte agli input che possiamo fornire, esiste un limite superiore al numero di operazioni che verranno eseguite, quindi questo algoritmo è in realtà O (1).


2

A questo punto, sì

Questo algoritmo ha un input implicito, ovvero l'ora in cui il programma viene avviato. Il tempo di esecuzione varierà linearmente 1 a seconda di quando viene avviato. Durante l'anno 2035 e dopo, il ciclo while termina immediatamente e il programma termina dopo operazioni costanti 2 . Quindi si potrebbe dire che il runtime è O(max(2035 - start year, 1))3 . Ma poiché il nostro anno di inizio ha un valore minimo, l'algoritmo non impiegherà mai più di 20 anni per essere eseguito (ovvero un valore costante).

Puoi rendere il tuo algoritmo più in linea con il tuo intento definendo DateTime TwentyYearsLater = DateTime.Now + new TimeSpan(365*20,0,0,0);4

1 Ciò vale per il senso più tecnico del tempo di esecuzione misurato come numero di operazioni perché esiste un numero massimo di operazioni per unità di tempo.
2 Supponendo che il recupero DateTime.Nowsia un'operazione costante, il che è ragionevole.
3 Sto in qualche modo abusando della grande notazione O qui perché questa è una funzione decrescente rispetto a start year, ma potremmo facilmente correggerla esprimendola in termini di years prior to 2035.
4 Quindi l'algoritmo non dipende più dall'input implicito dell'ora di inizio, ma non ha conseguenze.


1

Direi che questo è O (n). utilizzando http://www.cforcoding.com/2009/07/plain-english-explanation-of-big-o.html come riferimento.

Cos'è Big O?

La notazione Big O cerca di descrivere la complessità relativa di un algoritmo riducendo il tasso di crescita ai fattori chiave quando il fattore chiave tende verso l'infinito.

e

Il miglior esempio di Big-O a cui riesco a pensare è fare aritmetica. Le operazioni aritmetiche di base che abbiamo imparato a scuola erano:

Inoltre; sottrazione; moltiplicazione; e divisione. Ognuno di questi è un'operazione o un problema. Un metodo per risolverli è chiamato algoritmo.

Per il tuo esempio,

dato l'input di n = 20 (con unità anni).

l'algoritmo è una funzione matematica f (). dove f () è in attesa di n anni, con stringhe di "debug" in mezzo. Il fattore di scala è 1. È possibile ridurre / o aumentare f () modificando questo fattore di scala.

anche in questo caso l'uscita è 20 (cambiando l'ingresso cambia l'output in modo lineare).

essenzialmente la funzione è

f(n) = n*1 = n
    if  n = 20, then 
f(20) = 20 
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.