Spiegare la rilevanza della complessità asintotica degli algoritmi per esercitarsi nella progettazione di algoritmi


40

Negli algoritmi e nella complessità ci concentriamo sulla complessità asintotica degli algoritmi, ovvero la quantità di risorse che un algoritmo utilizza mentre la dimensione dell'input va all'infinito.

In pratica, è necessario un algoritmo che funzioni rapidamente su un numero finito (sebbene possibilmente molto grande) di istanze.

Un algoritmo che funziona bene in pratica sul numero finito di casi a cui siamo interessati non deve avere una buona complessità asintotica (una buona prestazione su un numero finito di casi non implica nulla riguardo alla complessità asintotica). Allo stesso modo, un algoritmo con una buona complessità asintotica potrebbe non funzionare bene nella pratica sul numero finito di casi a cui siamo interessati (ad esempio a causa di costanti di grandi dimensioni).

Perché usiamo la complessità asintotica? In che modo queste analisi asintotiche sono legate alla progettazione di algoritmi nella pratica?


Un'altra domanda rilevante è: perché ignoriamo i fattori costanti ?
Raffaello

Risposte:


24

La domanda interessante è: qual è l'alternativa? L'unico altro metodo che conosco è il testing / benchmarking. Programmiamo gli algoritmi, li eseguiamo su (un campione rappresentativo) del set di input finito e confrontiamo i risultati. Ci sono un paio di problemi con questo.

  • I risultati non sono generali in termini di macchine. Esegui il tuo benchmark su un altro computer e otterrai risultati diversi di sicuro, quantitativamente e forse anche qualitativamente.
  • I risultati non sono generali in termini di linguaggi di programmazione. Lingue diverse possono causare risultati molto diversi.
  • I risultati non sono generali in termini di dettagli di implementazione. Confronti letteralmente i programmi , non gli algoritmi; piccoli cambiamenti nell'implementazione possono causare enormi differenze nelle prestazioni.
  • Se il caso peggiore è raro, un campione di input casuale potrebbe non contenere un'istanza errata. Questo è giusto se si è interessati alle prestazioni medie del caso, ma alcuni ambienti richiedono garanzie nel caso peggiore.
  • In pratica, i set di input cambiano. In genere, gli input diventano più grandi nel tempo. Se non si ripete il benchmark ogni sei mesi (sì, alcuni dati crescono così velocemente), i risultati non saranno utili presto¹.

Detto questo, ignorare tutti i tipi di effetti e costanti nell'analisi è tipico, ma può essere definito pigro (rispetto alla pratica). Serve a confrontare le idee algoritmiche più che a individuare le prestazioni di una determinata implementazione (anche pseudocodice). È noto alla comunità che ciò è grossolano e che spesso è necessario uno sguardo più attento; ad esempio, Quicksort è meno efficiente dell'ordinamento per inserzione per input (molto) piccoli. Ad essere onesti, un'analisi più precisa è di solito difficile ².

Un'altra giustificazione a posteriori del punto di vista formale e astratto è che a questo livello le cose sono spesso più chiare. Pertanto, decenni di studi teorici hanno prodotto una serie di idee algoritmiche e strutture di dati utili nella pratica. L'algoritmo teoricamente ottimale non è sempre quello che si desidera utilizzare nella pratica - ci sono altre considerazioni ma le prestazioni da fare; pensa che Fibonacci si accumuli - e questa etichetta potrebbe non essere nemmeno unica. È difficile per un programmatore tipico interessato all'ottimizzazione delle espressioni aritmetiche trovare una nuova idea a questo livello (per non dire che non accade); tuttavia può (e dovrebbe) eseguire quelle ottimizzazioni sull'idea assimilata.

Esistono strumenti teorici formali per colmare il divario nella pratica in una certa misura. Ne sono esempi

  • considerando la gerarchia della memoria (e altri I / O),
  • analisi del caso medio (se del caso),
  • analizzare il numero di singole dichiarazioni (anziché misure di costo astratte) e
  • determinando fattori costanti.

Ad esempio, Knuth è noto per contare letteralmente il numero di diverse istruzioni (per una data implementazione in un dato modello), consentendo un confronto preciso degli algoritmi. Questo approccio è impossibile a livello astratto e difficile da realizzare in modelli più complessi (si pensi a Java). Vedi [4] per un esempio moderno.

Ci sarà sempre un divario tra teoria e pratica. Attualmente stiamo lavorando a uno strumento³ con l'obiettivo di combinare il meglio di entrambi i mondi per fare previsioni valide sia per i costi algoritmici che per il tempo di esecuzione (in media), ma finora non siamo stati in grado di eliminare scenari in cui un algoritmo ha un costi ma tempo di esecuzione inferiore (su alcune macchine) rispetto a uno equivalente (sebbene sia possibile rilevarlo e supportare la ricerca del motivo).

Consiglio ai professionisti di usare la teoria per filtrare lo spazio degli algoritmi prima di eseguire benchmark:

if ( input size forever bounded? ) {
  benchmark available implementations, choose best
  schedule new benchmarks for when machine changes
}
else {
  benchmark implementations of all asymptotically good algorithms
  choose the best
  schedule new benchmarks for when machine changes or inputs grow significantly
}

  1. Ci possono essere cambiamenti pazzeschi nelle prestazioni assolute e relative quando aumenta il numero di errori nella cache, cosa che di solito accade quando gli input crescono ma la macchina rimane invariata.
  2. Come in, i principali ricercatori del settore non sono in grado di farlo.
  3. Trova lo strumento qui . Un esempio di utilizzo è stato pubblicato in Dual Pivot Quicksort di Engineering Java 7 utilizzando MaLiJAn di S. Wild et al. (2012) [ prestampa ]
  4. Analisi media dei casi di Dual Pivot Quicksort di Java 7 di S. Wild e M. Nebel (2012) - [ prestampa ]

3
Probabilmente, il puro atto di studiare la teoria degli algoritmi affina la tua vista e allena il tuo cervello di astrazione per gli algoritmi, dandoti un altro strumento per valutare il codice nella programmazione quotidiana. Estrarre dal codice, valutare il principio, migliorarlo e tradurlo in codice. Esempio: "Ah, vedo, vuoi programmare un dizionario. Ma essenzialmente programmi elenchi; perché non provare gli alberi?"
Raffaello

I limiti dell'analisi asintotica diventano evidenti quando scavi più a fondo; Quicksort è un esempio di spicco .
Raffaello

1
FWIW, ho scritto un'istantanea più recente delle mie opinioni sulla notazione di Landau qui .
Raffaello

11

Presumo che questa domanda derivi dall'insegnamento di un corso che include l'analisi asintotica. Esistono diverse risposte possibili sul perché questo materiale viene insegnato in classi introduttive:

  • L'analisi asintotica è un'astrazione matematica che cede all'analisi. Come matematici (presumibilmente), vogliamo essere in grado di analizzare gli algoritmi e l'unico modo per domare la loro complessità è usare l'analisi asintotica.

  • La valutazione delle prestazioni asintotiche di un algoritmo evidenzia alcuni principi che sono utili nella pratica: ad esempio, concentrati su quella parte del codice che richiede la maggior parte del tempo e attualizza qualsiasi parte del codice che assume una parte asintoticamente trascurabile del tempo .

  • Alcune delle tecniche di analisi asintotica sono utili. Mi riferisco qui principalmente al cosiddetto "teorema del maestro", che in molte circostanze è una buona descrizione della realtà.

  • C'è anche una ragione storica: quando le persone hanno iniziato ad analizzare gli algoritmi, hanno seriamente pensato che la complessità asintotica riflette l'uso pratico. Tuttavia, alla fine sono stati smentiti. La stessa cosa è successa con P come classe di problemi risolvibili in modo efficiente, e NP come classe di problemi intrattabili, entrambi in pratica fuorvianti.

Personalmente, penso che l'analisi asintotica sia una parte ragionevole del curriculum. Parti più discutibili includono la teoria del linguaggio formale e la teoria della complessità (tutto ciò che ha a che fare con una macchina di Turing). Alcune persone sostengono che mentre questi argomenti non sono utili al potenziale programmatore in sé, le infondono un certo pensiero mentale che è necessario per essere una buona pratica. Altri sostengono che la teoria a volte influenza la pratica, e questi rari casi sono sufficienti per giustificare l'insegnamento di queste materie piuttosto arcane al pubblico generale dell'informatica. Preferirei che imparassero la storia o la letteratura, o qualsiasi altra materia a cui fossero effettivamente interessati; entrambi sono rilevanti per le loro prospettive di lavoro future e più importanti per loro come gli esseri umani.


Grazie Yuval. La motivazione è principalmente interessata a come spiegare agli studenti l'utilità dell'analisi asintotica e la sua rilevanza per la pratica di progettare e utilizzare algoritmi in applicazioni reali (dove la maggior parte delle volte è chiaro che siamo interessati solo a un finito, sebbene possibilmente un numero molto elevato di casi), non giustificando il curriculum.
Kaveh,

1
Sono confuso dalla tua premessa. Sembra che il gruppo target sia composto sia da matematici che da aspiranti programmatori, che è una strana combinazione e non caratterizza gli informatici. (Inoltre, non condivido la tua opinione sui linguaggi formali, ma questo è un altro argomento.)
Raffaello

Al contrario, suppongo che il gruppo target sia aspiranti programmatori. Tuttavia, gran parte del curriculum è lì per il bene dei teorici informatici. Naturalmente, questi due gruppi hanno esigenze contrastanti. Poiché la maggior parte degli studenti universitari sono aspiranti programmatori, penso che il curriculum dovrebbe essere orientato verso di loro, ma alcuni accademici non sono d'accordo. Forse vogliono insegnare ai futuri professori. Forse puoi spiegare il loro punto di vista.
Yuval Filmus,

3
@YuvalFilmus Ho spesso spiegato che non credo che CS = TCS + Programmazione. Se insegni un corso CS (in un'università) e la maggior parte dei tuoi studenti vuole diventare programmatore, qualcosa non funziona (imho). Direi che qualsiasi scienziato informatico può trarre profitto da una solida educazione in algoritmi, linguaggi formali e persino una teoria della complessità (e molte altre cose, come il funzionamento di compilatori e CPU).
Raffaello

2
@Wildcard Architettura del computer, computer grafica, intelligenza artificiale, ricerca nel linguaggio di programmazione, ... - l'elenco è infinito! TCS è davvero una nicchia e la programmazione non è che uno strumento per (la maggior parte) dei ricercatori CS.
Raffaello

7

Esistono due motivi gravi per utilizzare l'analisi asintotica dei tempi di esecuzione:

  • per sottrarre dettagli non importanti. In molte applicazioni in cui abbiamo bisogno di algoritmi non banali, la maggior parte del tempo viene impiegata in istanze problematiche che richiedono un numero medio-elevato di operazioni e siamo più interessati alla tendenza generale che al conteggio esatto delle operazioni. In queste applicazioni, il comportamento per la piccola non è interessante.n

  • per consentire la tracciabilità matematica. I casi in cui è possibile trovare espressioni esatte per il conteggio delle operazioni sono eccezionali. Studiare gli asintotici apre più possibilità (come le approssimazioni asintotiche di funzioni complicate sono utili).

E ce ne sono molti altri (come indipendenza della macchina, significatività, comparabilità ...).


n

Beh, non penso che sia una regola. Più dati butti via, più deboli sono le dichiarazioni che puoi fare. La prospettiva asintotica (e, soprattutto, "big-oh") crea affermazioni come "Quicksort è più veloce di Insertionsort" che, se non falso, non è del tutto vero. (Sì, sto dicendo che l'analisi dell'algoritmo viene spesso insegnata in modo sbagliato, imho.)
Raffaello

6

Come indicato nella risposta di Raffaello, il calcolo esatto del tempo di esecuzione nel caso peggiore può essere molto difficile. Il calcolo esatto può anche non essere necessario poiché il modello RAM introduce già approssimazioni. Ad esempio, tutte le operazioni richiedono davvero lo stesso tempo? Implementazioni specifiche (hardware, ottimizzazioni) potrebbero accelerare un algoritmo con fattori costanti. Vogliamo capire quanto sia efficace un algoritmo indipendente da questi fattori. Questa è una grande motivazione per l'uso dell'analisi asintotica.


3

Perché gli asintotici sono "semplici" (beh, più semplici che fare l'analisi esatta per casi finiti, comunque).

Confronta ad esempio l'enciclopedia "The Art of Computer Programming" di Knuth, che esegue un'analisi dettagliata di tutti gli algoritmi importanti (e molti di quelli non così importanti) con l'analisi della regola empirica che è spesso sufficiente per ottenere una stima asintotica ( o solo un limite), come praticato nella maggior parte dei libri sugli "algoritmi".

Hai certamente ragione. Se il problema è abbastanza importante, un'analisi in stile Knuth (o forse un po 'meno dettagliata) potrebbe essere giustificata. In molti casi, è sufficiente un accenno alla complessità asintotica (forse media con dispersione) adattata ai dati sperimentali. Nella maggior parte dei casi , fare una classificazione approssimativa degli algoritmi concorrenti, in quanto un primo round di eliminazione del confronto degli asintotici può essere abbastanza preciso. E se non ci sono contendenti, ottenere la cattiva notizia del costo esatto nei minimi dettagli è solo masochismo.


2
Questa è solo metà della verità: in primo luogo, sembra che tu scriva pensando a "big-oh" (che la domanda non menziona). In secondo luogo, gli asintotici "big-oh" sono noti per fallire in modo spettacolare per i "round di eliminazione" quando si selezionano gli algoritmi: gli input sono finiti nella realtà.
Raffaello

3

Qui per analisi asintotica presumo che intendiamo il comportamento dell'algoritmo quando la dimensione dell'input va all'infinito.

Il motivo per cui utilizziamo l'analisi asintotica è perché è utile nel prevedere il comportamento degli algoritmi nella pratica . Le previsioni ci consentono di prendere decisioni, ad esempio quando abbiamo algoritmi diversi per un problema quale dovremmo usare? (Essere utili non significa che sia sempre corretto.)

La stessa domanda può essere posta su qualsiasi modello semplificato del mondo reale. Perché utilizziamo modelli matematici semplificati del mondo reale?

Pensa alla fisica. La fisica newtoniana classica non è buona quanto la fisica relativistica nel predire il mondo reale. Ma è un modello abbastanza buono per costruire automobili, grattacieli, sottomarini, aeroplani, ponti, ecc. Ci sono casi in cui non è abbastanza buono, ad esempio se vogliamo costruire un satellite o inviare una sonda spaziale a Plutone o prevedere il movimento di enormi oggetti celesti come stelle e pianeti o oggetti ad altissima velocità come elettroni. È importante sapere quali sono i limiti di un modello.

  1. È in genere un'approssimazione abbastanza buona del mondo reale. In pratica vediamo spesso che un algoritmo con una migliore analisi asintotica funziona meglio nella pratica. Raramente è un caso che un algoritmo abbia un comportamento asintotico migliore. Quindi, se gli input possono essere abbastanza grandi, in genere possiamo fare affidamento sull'analisi asintotica come prima previsione del comportamento degli algoritmi. Non è così se sappiamo che gli input saranno piccoli. A seconda delle prestazioni che desideriamo, potremmo aver bisogno di fare un'analisi più attenta, ad es. Se abbiamo informazioni sulla distribuzione degli input che verrà fornita l'algoritmo, possiamo fare un'analisi più attenta per raggiungere gli obiettivi che abbiamo (ad es. Veloce su 99 % di input). Il punto è come primo passo l'analisi asintotica è un buon punto di partenza. In pratica dovremmo anche effettuare test delle prestazioni, ma tieni presente che ha anche i suoi problemi.

  2. AAAha una migliore complessità asintotica. Quali di questi non sono migliori dell'altro in tutti gli input? Quindi diventa più complicato e dipende da ciò a cui teniamo. Ci preoccupiamo per input di grandi dimensioni o input di piccole dimensioni? Se ci preoccupiamo di input di grandi dimensioni, non è comune che un algoritmo abbia una migliore complessità asintotica ma si comporti peggio di input di grandi dimensioni che ci interessano. Se ci preoccupiamo di più di piccoli input, l'analisi asintotica potrebbe non essere così utile. Dovremmo confrontare il tempo di esecuzione degli algoritmi sugli input che ci interessano. In pratica, per compiti complicati con requisiti complessi l'analisi asintotica potrebbe non essere altrettanto utile. Per semplici problemi di base coperti dai manuali di algoritmo è abbastanza utile.

In breve, la complessità asintotica è un'approssimazione relativamente facile da calcolare della complessità effettiva degli algoritmi per semplici compiti di base (problemi in un manuale di algoritmi). Man mano che costruiamo programmi più complicati, i requisiti di prestazione cambiano e diventano più complicati e l'analisi asintotica potrebbe non essere altrettanto utile.


È bene confrontare l'analisi asintotica con altri approcci per prevedere le prestazioni degli algoritmi e confrontarli. Un approccio comune è il test delle prestazioni rispetto a input casuali o benchmark. È comune quando il calcolo della complessità asintotica è difficile o non fattibile, ad esempio quando stiamo usando l'euristica come nella risoluzione SAT. Un altro caso è quando i requisiti sono più complicati, ad esempio quando le prestazioni di un programma dipendono da fattori esterni e il nostro obiettivo potrebbe essere quello di avere qualcosa che termina con dei limiti di tempo fissi (ad es. Pensare all'aggiornamento dell'interfaccia mostrata a un utente) sul 99% del ingressi.

Ma tieni presente che anche l'analisi delle prestazioni ha i suoi problemi. Non fornisce concessioni matematiche sulle prestazioni a meno che effettivamente eseguiamo il test delle prestazioni su tutti gli input che verranno dati all'algoritmo (spesso non calcolabili dal punto di vista computazionale) (e spesso non è possibile decidere che alcuni input non verranno mai forniti). Se ci prova su contro un campione casuale o un punto di riferimento stiamo implicitamente assumendo una certa regolarità sulle prestazioni degli algoritmi, ovvero l'algoritmo eseguire in modo simile a input che non erano parte del test prestazioni.

Il secondo problema con i test delle prestazioni è che dipendono dall'ambiente di test. Vale a dire che le prestazioni di un programma non sono determinate dai soli input ma da fattori esterni (ad es. Tipo di macchina, sistema operativo, efficienza dell'algoritmo codificato, utilizzo della CPU, tempi di accesso alla memoria, ecc.) Alcuni dei quali potrebbero variare tra le diverse serie di il test sulla stessa macchina. Anche in questo caso stiamo assumendo che gli ambienti particolari in cui viene eseguito il test delle prestazioni siano simili all'ambiente reale a meno che non eseguiamo i test delle prestazioni su tutti gli ambienti in cui è possibile eseguire il programma (e come possiamo prevedere su quali macchine qualcuno potrebbe eseguire un ordinamento algoritmo attivo tra 10 anni?).

Θ(nlgn)Θ(n2)Θ(lgn)O(n)



Mi piace questa risposta abbastanza per votare adesso. Due note: 1) Userei "costo" anziché "complessità" qui. In parte per motivi di pet peeve, ma anche perché ci sono molte misure di costo immaginabili (il che complica tutte le considerazioni che menzioni). 2) Potresti voler fare un pass per la lingua polacca. ;)
Raffaello

@Raphael, grazie. Sto programmando di fare presto un'altra modifica. :)
Kaveh,

-2

O(n2)O(nlogn)O(n2) perché finisca in confronto a quicksort.

ora immagina che l' attesa si ripeta nel codice tutte le volte che viene chiamato il codice. come si quantificano / giustificano matematicamente questa apparente superiorità dell'algoritmo quicksort? (cioè il suo nome è davvero giustificato o è solo uno slogan di marketing?) tramite misurazioni della complessità asintotica. uno rimane a guardare le animazioni soggettivamente la sensazione che il bubbleort sia in qualche modo un algoritmo più debole e l'analisi della complessità asintotica può dimostrarlo quantitativamente. ma nota che l'analisi della complessità asintotica è solo uno strumento nella borsa degli strumenti per analizzare gli algoritmi e non è sempre l'ultimo.

e vale la pena guardare anche il codice side-by-side. bubblesort sembra concettualmente più semplice e non utilizza la ricorsione. quicksort non è compreso immediatamente il principio pivot "mediana di 3". bubblesort potrebbe essere implementato solo in loop senza una subroutine, mentre quicksort potrebbe in genere avere almeno una subroutine. questo dimostra che una maggiore raffinatezza del codice può talvolta migliorare la complessità asintotica a scapito della semplicità del codice. a volte esiste un compromesso estremo simile al concetto di rendimenti marginali decrescenti (originati dall'economia) in cui enormi amminismi di complessità del codice [che richiedono interi documenti pieni di dati e prove per giustificare] acquista solo piccoli miglioramenti nella complessità asintotica. questo si presenta come esempio esp conmoltiplicazione della matrice e può anche essere rappresentata graficamente .


C'è un sacco di territorio tra "guardare animazioni" e analisi formale, come ad esempio benchmark di runtime estesi. Sono in realtà un'area valida per loro, poiché non abbiamo teoria per spiegare tutte le cose che influenzano i tempi di esecuzione.
Raffaello

@raphael hai coperto il benchmarking nella tua risposta; è una buona risposta. ma nota che l'animazione / visualizzazione può essere strettamente correlata al benchmarking. in realtà ci sono molte spiegazioni di ciò che influenza i runtime [trattati in altre risposte] ma in una certa misura il suo "rumore" e la complessità asintotica "attenuano / mediano il rumore". questo è un altro esercizio per vedere come lo fa davvero.
vzn,

Le animazioni, tuttavia, non filtrano il rumore. Inoltre, l'occhio umano viene facilmente ingannato e non è possibile guardare le animazioni per un campione di dimensioni ragionevoli di elenchi di dimensioni ragionevoli (diciamo, 1000 liste per dimensioni in milioni per confrontare gli algoritmi di ordinamento) e decidere quale algoritmo era più veloce (in media).
Raffaello

nn

n
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.