Programmazione di potenza: O (1 ^ N), O (N ^ 1), O (2 ^ N), O (N ^ 2) tutto in uno


65

Scrivi un programma (o una funzione) che mostri quattro grandi complessità O time comuni a seconda di come viene eseguito. In qualsiasi forma assume un numero intero positivo N che si può presumere sia inferiore a 2 31 .

  1. Quando il programma viene eseguito nella sua forma originale , dovrebbe avere una complessità costante . Cioè, la complessità dovrebbe essere Θ (1) o, equivalentemente, Θ (1 ^ N) .

  2. Quando il programma viene invertito ed eseguito, dovrebbe avere una complessità lineare . Cioè, la complessità dovrebbe essere Θ (N) o, equivalentemente, Θ (N ^ 1) .
    (Questo ha senso poiché N^1è 1^Ninvertito.)

  3. Quando il programma viene raddoppiato , cioè concatenato a se stesso, ed eseguirlo dovrebbe avere esponenziale complessità, in particolare 2 N . Cioè, la complessità dovrebbe essere Θ (2 ^ N) .
    (Questo ha senso dal momento che 2in 2^Nè il doppio di 1in 1^N.)

  4. Quando il programma è raddoppiato e invertita ed eseguirlo dovrebbe avere polinomiale complessità, in particolare N 2 . Cioè, la complessità dovrebbe essere Θ (N ^ 2) .
    (Questo ha senso poiché N^2è 2^Ninvertito.)

Questi quattro casi sono gli unici che devi gestire.

Nota che per precisione sto usando la notazione big theta (Θ) invece della grande O perché i tempi di esecuzione dei tuoi programmi devono essere limitati sia sopra che sotto dalle complessità richieste. Altrimenti, scrivere una funzione in O (1) soddisferebbe tutti e quattro i punti. Non è troppo importante capire la sfumatura qui. Principalmente, se il tuo programma sta eseguendo operazioni k * f (N) per qualche costante k, è probabile che sia in Θ (f (N)).

Esempio

Se il programma originale fosse

ABCDE

quindi eseguirlo dovrebbe richiedere tempo costante. Cioè, se l'ingresso N è 1 o 2147483647 (2 31 -1) o qualsiasi valore in mezzo, dovrebbe terminare all'incirca nello stesso intervallo di tempo.

La versione inversa del programma

EDCBA

dovrebbe richiedere un tempo lineare in termini di N. Cioè, il tempo necessario per terminare dovrebbe essere approssimativamente proporzionale a N. Quindi N = 1 impiega il minor tempo e N = 2147483647 impiega di più.

La versione raddoppiata del programma

ABCDEABCDE

dovrebbe prendere tempo da due a-the-N in termini di N. Cioè, il tempo necessario per terminare dovrebbe essere approssimativamente proporzionale 2 N . Quindi se N = 1 termina in circa un secondo, N = 60 richiederebbe più tempo dell'età dell'universo per terminare. (No, non devi provarlo.)

La versione raddoppiata e invertita del programma

EDCBAEDCBA

dovrebbe richiedere un tempo quadrato in termini di N. Cioè, il tempo necessario per terminare dovrebbe essere approssimativamente proporzionale a N * N. Quindi se N = 1 termina in circa un secondo, N = 60 impiegherebbe circa un'ora per terminare.

Dettagli

  • Devi mostrare o sostenere che i tuoi programmi sono in esecuzione nelle complessità che dici di essere. Fornire alcuni dati sui tempi è una buona idea, ma cerca anche di spiegare perché teoricamente la complessità è corretta.

  • Va bene se in pratica i tempi dei tuoi programmi non sono perfettamente rappresentativi della loro complessità (o addirittura deterministici). ad esempio, l'ingresso N + 1 a volte potrebbe essere più veloce di N.

  • L'ambiente si sta eseguendo i programmi in fa materia. Puoi fare ipotesi di base su come le lingue popolari non perdano mai intenzionalmente tempo negli algoritmi ma, ad esempio, se conosci la tua versione particolare di Java implementa il bubble sort anziché un algoritmo di ordinamento più veloce , dovresti tenerne conto se esegui un ordinamento .

  • Per tutte le complessità qui ipotizziamo che stiamo parlando di scenari nel caso peggiore , non nel caso migliore o nel caso medio.

  • La complessità spaziale dei programmi non ha importanza, ma solo la complessità temporale.

  • I programmi possono produrre qualsiasi cosa. Importa solo che assumano un intero positivo N e abbiano la complessità temporale corretta.

  • Sono ammessi commenti e programmi multilinea. (Si può presumere che la versione \r\ninversa sia \r\nper la compatibilità con Windows.)

Grandi promemoria

Dal più veloce al più lento è O(1), O(N), O(N^2), O(2^N)(ordine 1, 2, 4, 3 sopra).

I termini più lenti dominano sempre, ad es O(2^N + N^2 + N) = O(2^N).

O(k*f(N)) = O(f(N))per costante k. Quindi O(2) = O(30) = O(1)e O(2*N) = O(0.1*N) = O(N).

Ricorda O(N^2) != O(N^3)e O(2^N) != O(3^N).

Cheat sheet O grande e pulito.

punteggio

Questo è un normale codice golf. Vince il programma originale più breve (quello a tempo costante) in byte.


Ottima domanda! Punto minore: non è necessario specificare il caso peggiore / il caso migliore / il caso medio, poiché l'unico input che conta come dimensione N è il numero N stesso (che BTW non è la solita nozione di dimensione input: sarebbe considera l'input N come log delle dimensioni N). Quindi esiste un solo caso per ogni N, che è contemporaneamente il caso migliore, peggiore e medio.
ShreevatsaR,

5
Sembra che tu abbia deviato dalle solite definizioni di complessità. Definiamo sempre la complessità temporale di un algoritmo in funzione della dimensione del suo input . Nel caso in cui l'input sia un numero, la dimensione dell'input è il logaritmo in base 2 di quel numero. Quindi il programma n = input(); for i in xrange(n): passha una complessità esponenziale, perché prende delle 2 ** kmisure, dov'è k = log_2(n)la dimensione dell'input. Dovresti chiarire se questo è il caso, poiché cambia drasticamente i requisiti.
wchargin,

Risposte:


36

Python 3 , 102 byte

try:l=eval(input());k=1#)]0[*k**l(tnirp
except:k=2#2=k:tpecxe
print(k**l*[0])#1=k;))(tupni(lave=l:yrt

Provalo online!

Questo è di O (1 ^ n), poiché questo è ciò che fa il programma:

  • valutare l'input
  • crea l'array [0]
  • stampalo

invertito:


try:l=eval(input());k=1#)]0[*l**k(tnirp
except:k=2#2=k:tpecxe
print(l**k*[0])#1=k;))(tupni(lave=l:yrt

Provalo online!

Questo è di O (n ^ 1), poiché questo è ciò che fa il programma:

  • valutare l'input
  • crea l'array [0] * input (0 ripetuto tante volte quanto l'input)
  • stampalo

raddoppiato:

try:l=eval(input());k=1#)]0[*k**l(tnirp
except:k=2#2=k:tpecxe
print(k**l*[0])#1=k;))(tupni(lave=l:yrt
try:l=eval(input());k=1#)]0[*k**l(tnirp
except:k=2#2=k:tpecxe
print(k**l*[0])#1=k;))(tupni(lave=l:yrt

Provalo online!

Questo è di O (2 ^ n), poiché questo è ciò che fa il programma:

  • valutare l'input
  • crea l'array [0]
  • stampalo
  • prova a valutare l'input
  • fallire
  • crea l'array [0] * (2 ^ input) (0 ripetuto tante volte quanto 2 ^ input)
  • stampalo

Raddoppiato e invertito:


try:l=eval(input());k=1#)]0[*l**k(tnirp
except:k=2#2=k:tpecxe
print(l**k*[0])#1=k;))(tupni(lave=l:yrt
try:l=eval(input());k=1#)]0[*l**k(tnirp
except:k=2#2=k:tpecxe
print(l**k*[0])#1=k;))(tupni(lave=l:yrt

Provalo online!

Questo è di O (n ^ 2), poiché questo è ciò che fa il programma:

  • valutare l'input
  • crea l'array [0] * input (0 ripetuto tante volte quanto l'input)
  • stampalo
  • prova a valutare l'input
  • fallire
  • crea l'array [0] * (input ^ 2) (0 ripetuto tante volte quante sono gli input al quadrato)
  • stampalo

Perché non si blocca in attesa dell'interazione dell'utente quando ci sono più chiamate a input()?
Jonathan Allan,

1
È una scappatoia che "fine della trasmissione" viene trasmessa alla fine della trasmissione?
Leaky Nun,

1
Puoi spiegarlo?
Brain Guider,

1
Ri: l'argomento "fine del file", stai guardando le cose al contrario. Quando si utilizza un terminale, le richieste di input si bloccano perché è collegato qualcosa; ctrl-D può essere inviato per non inviare esplicitamente alcun input. Se l'input è collegato a un file vuoto (come fa TIO se si lascia vuota la casella di input) o se è collegato a un file in cui è stato letto tutto l'input, non è necessario che la richiesta di input faccia qualcosa; sa già che non ci sono input. Su UNIX / Linux, "fine del file" e "nessun input disponibile" sono indistinguibili sui file normali.

3
Per il primo caso, kè l'input, ed lè uno, quindi stai ancora elaborando 1**k, giusto? Cosa dovrebbe richiedere del O(log(k))tempo, nonostante il fatto che tu e io e tutti sappiamo in anticipo che è uno?
Richard Rast,

18

Perl 5, 82 73 71 + 1 (per -n flag) = 72 byte

Sono certo di poter giocare a questo (molto) di più, ma è ora di andare a letto, ho trascorso abbastanza tempo a fare il debug così com'è, e sono orgoglioso di ciò che ho finora finora.

#x$=_$;
$x.=q;#say for(1..2**$_)#)_$..1(rof _$=+x$;;
eval $x;$x=~s/#//;

Il programma stesso non utilizza l'input e valuta solo una stringa che inizia con un commento e quindi effettua una singola sostituzione di stringa, quindi questo è chiaramente in tempo costante. È sostanzialmente equivalente a:

$x="#";
eval $x;
$x=~s/#//;

raddoppiato:

#x$=_$;
$x.=q;#say for(1..2**$_)#)_$..1(rof _$=+x$;;
eval $x;$x=~s/#//;
#x$=_$;
$x.=q;#say for(1..2**$_)#)_$..1(rof _$=+x$;;
eval $x;$x=~s/#//;

Il bit che impiega effettivamente il tempo esponenziale è il secondo eval: valuta il comando say for(1..2**$_), che elenca tutti i numeri da 1 a 2 ^ N, che richiede chiaramente tempo esponenziale.

invertito:

;//#/s~=x$;x$ lave
;;$x+=$_ for(1..$_)#)_$**2..1(rof yas#;q=.x$
;$_=$x#

Questo (ingenuamente) calcola la somma dell'input, che richiede chiaramente un tempo lineare (poiché ogni aggiunta è in tempo costante). Il codice che viene effettivamente eseguito è equivalente a:

$x+=$_ for(1..$_);
$_=$x;

L'ultima riga è tale che quando questi comandi vengono ripetuti impiegherà un tempo quadratico.

Invertito e raddoppiato:

;//#/s~=x$;x$ lave
;;$x+=$_ for(1..$_)#)_$**2..1(rof yas#;q=.x$
;$_=$x#
;//#/s~=x$;x$ lave
;;$x+=$_ for(1..$_)#)_$**2..1(rof yas#;q=.x$
;$_=$x#

Questo ora prende la somma della somma dell'input (e la aggiunge alla somma dell'input, ma qualunque cosa). Dal momento che ordina N^2aggiunte, questo richiede un tempo quadratico. È sostanzialmente equivalente a:

$x=0;
$x+=$_ for(1..$_);
$_=$x;
$x+=$_ for(1..$_);
$_=$x;

La seconda riga trova la somma dell'input, facendo Naggiunte, mentre la quarta fa summation(N)aggiunte, che è O(N^2).


Grande! Farlo in un linguaggio tradizionale sarebbe stato difficile! Questo ha il mio voto!
Arjun,

Ben fatto, è molto carino. Probabilmente intendevi $x.=q;##say...invece invece $x.=q;#say...(con due #invece di 1). (Questo spiegherebbe perché hai contato 76 byte invece di 75). Inoltre, dovresti contare -nflag nel tuo byte, poiché il tuo programma non fa molto senza di esso.
Dada,

@Dada Ho trasposto accidentalmente evali s///comandi e. Se fai il evalprimo, hai solo bisogno di quello #. Buona pesca!
Chris,

@Chris Giusto, funziona davvero. Potresti essere in grado di omettere l'ultimo #: $x=~s/#//;invertito produce ;//#/s~=x$, che nel tuo contesto non fa nulla, come farebbe con un vantaggio #. (Non l'ho provato però). Indipendentemente da ciò, avere un +1!
Dada,

@Dada Bella cattura ancora una volta!
Chris,

17

In realtà , 20 byte

";(1╖╜ⁿr"ƒ"rⁿ@╜╖1(;"

Provalo online!

Ingresso: 5

Produzione:

rⁿ@╜╖1(;
[0]
5

invertito:

";(1╖╜@ⁿr"ƒ"rⁿ╜╖1(;"

Provalo online!

Produzione:

rⁿ╜╖1(;
[0, 1, 2, 3, 4]
5

raddoppiato:

";(1╖╜ⁿr"ƒ"rⁿ@╜╖1(;"";(1╖╜ⁿr"ƒ"rⁿ@╜╖1(;"

Provalo online!

Produzione:

rⁿ@╜╖1(;
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
rⁿ@╜╖1(;
rⁿ@╜╖1(;
[0]

Raddoppiato e invertito:

";(1╖╜@ⁿr"ƒ"rⁿ╜╖1(;"";(1╖╜@ⁿr"ƒ"rⁿ╜╖1(;"

Provalo online!

Produzione:

rⁿ╜╖1(;
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
rⁿ╜╖1(;
rⁿ╜╖1(;
[0, 1, 2, 3, 4]

Idea principale

In realtà è un linguaggio basato su stack.

  • abcè un programma che ha complessità O (1 n ) e il suo doppio ha complessità O (2 n ).
  • defè un programma che ha complessità O (n 1 ) e il suo doppio ha complessità O (n 2 ).

Quindi, la mia presentazione è "abc"ƒ"fed", dove ƒviene valutato. In questo modo, "fed"non verrà valutato.

Generazione di programma individuale

Lo pseudocodice del primo componente ;(1╖╜ⁿr:

register += 1 # register is default 0
print(range(register**input))

Lo pseudocodice del secondo componente ;(1╖╜ⁿ@r:

register += 1 # register is default 0
print(range(input**register))

Non avrei mai pensato che sarebbe stato possibile! Ottimo lavoro, signore! +1
Arjun,

@Arjun Grazie per il tuo apprezzamento.
Leaky Nun,

Questo è eccellente e aumenta davvero la sfida non usando commenti IMO. Eccezionale!
ShreevatsaR,

1
Bene, questo in realtà ha dei commenti ... le stringhe non sono valutate e sono dei NOP ...
Leaky Nun

4

Gelatina , 20 byte

In parte ispirato alla soluzione Actually di Leaky Nun .

Le nuove linee principali e finali sono significative.

Normale:


⁵Ŀ⁵
R
R²
2*R
‘
⁵Ŀ⁵

Provalo online!

Ingresso: 5

Produzione:

610

invertito:


⁵Ŀ⁵
‘
R*2
²R
R
⁵Ŀ⁵

Provalo online!

Ingresso: 5

Produzione:

[1, 2, 3, 4, 5]10

raddoppiato


⁵Ŀ⁵
R
R²
2*R
‘
⁵Ŀ⁵

⁵Ŀ⁵
R
R²
2*R
‘
⁵Ŀ⁵

Provalo online!

Ingresso: 5

Produzione:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]10

Raddoppiato e invertito


⁵Ŀ⁵
‘
R*2
²R
R
⁵Ŀ⁵

⁵Ŀ⁵
‘
R*2
²R
R
⁵Ŀ⁵

Provalo online!

Ingresso: 5

Produzione:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]10

Spiegazione

Qui si trova la chiave Ŀ, che significa "chiama il collegamento all'indice n come una monade". I collegamenti sono indicizzati dall'alto verso il basso a partire da 1, escluso il collegamento principale (quello più in basso). Ŀè anche modulare, quindi se provi a chiamare il collegamento numero 7 su 5 collegamenti, in realtà chiamerai collegamento 2.

Il collegamento chiamato in questo programma è sempre quello nell'indice 10 ( ) indipendentemente dalla versione del programma. Tuttavia, il collegamento all'indice 10 dipende dalla versione.

Quello che appare dopo che ciascuno Ŀè lì, quindi non si rompe quando è invertito. Il programma si interromperà al momento dell'analisi se non ci sono numeri prima Ŀ. Avere un after è un nilad fuori posto, che va direttamente all'output.

La versione originale chiama il collegamento , che calcola n + 1.

La versione inversa chiama il collegamento R, che genera l'intervallo 1 .. n.

La versione doppia chiama il collegamento 2*R, che calcola 2 n e genera l'intervallo 1 .. 2 n .

La versione raddoppiata e invertita chiama il collegamento ²R, che calcola n 2 e genera l'intervallo 1 .. n 2 .


4

Befunge-98 , 50 byte

Normale

\+]#:\-1vk  !:;#
@k!:-1 .: ;#]#$ <;
[*:>@ 
&$< ^&;

Questo è di gran lunga il programma più semplice del 4 - gli unici comandi eseguiti sono i seguenti:

\+]
  !
  : @
&$< ^&;

Questo programma esegue alcune operazioni irriverenti prima di premere un comando "gira a destra" ( ]) e una freccia. Quindi preme 2 comandi "take input". Poiché c'è solo 1 numero in input e per come TIO tratta &i messaggi, il programma termina dopo 60 secondi. Se ci sono 2 ingressi (e poiché posso senza aggiungere byte), l'IP si sposterebbe nella funzione "fine programma".

Provalo online!

Reversed

;&^ <$&
 @>:*[
;< $#]#; :. 1-:!k@
#;:!  kv1-\:#]+\

Questo è un po 'più complicato. i comandi rilevanti sono i seguenti:

;&^  $
  >:*[
;< $#]#; :. 1-:!k@
  :

che equivale a

;&^                   Takes input and sends the IP up. the ; is a no-op
  :                   Duplicates the input.
  >:*[                Duplicates and multiplies, so that the stack is [N, N^2
     $                Drops the top of the stack, so that the top is N
     ]#;              Turns right, into the loop
         :.           Prints, because we have space and it's nice to do
            1-        Subtracts 1 from N
              :!k@    If (N=0), end the program. This is repeated until N=0
;< $#]#;              This bit is skipped on a loop because of the ;s, which comment out things

La parte importante qui è la :. 1-:!k@punta. È utile perché finché spingiamo la complessità corretta nello stack prima di eseguire in una complessità temporale inferiore, possiamo ottenere quella desiderata. In questo modo verrà utilizzato nei restanti 2 programmi.

Provalo online!

raddoppiato

\+]#:\-1vk  !:;#
@k!:-1 .: ;#]#$ <;
[*:>@ 
&$< ^&;\+]#:\-1vk  !:;#
@k!:-1 .: ;#]#$ <;
[*:>@ 
&$< ^&;

E i comandi rilevanti sono:

\+]
  !
  :
&$< ^&;\+]#:\-1vk  !:;#
@k!:-1 .: ;#]#$ <;

Questo programma si articola in 2 loop. Innanzitutto, segue lo stesso percorso del normale programma, che inserisce 1 e N nello stack, ma invece di &passare al secondo , l'IP salta su un commento in un loop che spinge 2^N:

        vk!:    If N is 0, go to the next loop.
      -1        Subtract 1 from N
 +  :\          Pulls the 1 up to the top of the stack and doubles it
  ]#            A no-op
\               Pulls N-1 to the top again

Gli altri bit sulla linea 4 vengono saltati usando ;s

Dopo che (2 ^ N) è stato inserito nella pila, ci spostiamo lungo una linea nel ciclo di stampa di cui sopra. A causa del primo ciclo, la complessità temporale è Θ (N + 2 ^ N), ma ciò si riduce a Θ (2 ^ N).

Provalo online!

Raddoppiato e invertito

;&^ <$&
 @>:*[
;< $#]#; :. 1-:!k@
#;:!  kv1-\:#]+\;&^ <$&
 @>:*[
;< $#]#; :. 1-:!k@
#;:!  kv1-\:#]+\

I comandi pertinenti:

;&^

;< $#]#; :. 1-:!k@

 @>:*[

  :

Funziona in modo quasi identico al programma invertito, ma N^2non viene rimosso dallo stack, poiché la prima riga della seconda copia del programma viene aggiunta all'ultima riga del primo, il che significa che il comando drop ( $) non viene mai eseguito , quindi entriamo nel ciclo di stampa con N^2nella parte superiore della pila.

Provalo online!

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.