1 / 70

Algoritmi e complessità

Algoritmi e complessità. La complessità Complessità in tempo e spazio Complessità asintotica Algoritmi e complessità Ricerca e ordinamento La macchina di Turing e le classi di complessità. La complessità. La complessità.

rumor
Télécharger la présentation

Algoritmi e complessità

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Algoritmi e complessità • La complessità • Complessità in tempo e spazio • Complessità asintotica • Algoritmi e complessità • Ricerca e ordinamento • La macchina di Turing e le classi di complessità

  2. La complessità

  3. La complessità • L’analisi di complessità definisce le risorse teoricamente consumate da un algoritmo • complessità temporale: tempo necessario all’esecuzione dell’algoritmo • complessità spaziale: memoria necessaria all’esecuzione dell’algoritmo • Poiché ad ogni algoritmo corrispondono più implementazioni (più programmi), lo studio della complessità non definisce esattamente il tempo e la memoria usata: si concentra sulle proprietà che sono indipendenti dell’implementazione fornendo un’idea di quanto sia efficiente un algoritmo

  4. Che cosa si misura?  1 • Complessità temporale • Si contano le istruzioni eseguite dall’algoritmo • Poiché le istruzioni potrebbero essere di natura diversa, si individuano quelle che incidono principalmente sul tempo di esecuzione • Le operazioni in virgola mobile: le più lente da eseguire per una CPU; sono predominanti se il loro numero è paragonabile al numero delle altre istruzioni • Le istruzioni di controllo(gli if) e le istruzioni più frequenti (sono predominanti se sono in numero molto superiore alle altre istruzioni) • Le istruzioni di accesso alla memoria secondaria e alle periferiche: sono decine di migliaia di volte più lente delle istruzioni svolte nella memoria principale; se un’applicazione ne richiede molte, queste potrebbero essere predominanti (ad es., nei database, l’analisi di complessità è concentrata sugli accessi al disco)

  5. Che cosa si misura?  2 • Complessità spaziale • Si misurano le “posizioni” di memoria occupate dai dati necessari allo svolgimento dell’algoritmo • La complessità spaziale sarà misurata relativamente alla memoria principale se i dati dell’algoritmo possono essere allocati in memoria principale, in base all’occupazione di memoria secondaria quando le strutture dati dell’algoritmo sono troppo grandi per poter risiedere nella memoria centrale

  6. Complessità asintotica • Lo studio della complessità si concentra su i casi in cui il problema è grande: • non importa se un programma di contabilità impiega 1 o 100 millisecondi a calcolare il bilancio • cambia molto se il programma della segreteria impiega 1 o 10 secondi a trovare i dati di uno studente nell’archivio • Complessità asintotica • Definisce le risorse usate da un algoritmo al crescere della dimensione del problema affrontato • Ad esempio: come cambia il tempo di accesso ai dati quando cresce il numero degli studenti nell’archivio della segreteria

  7. Complessità temporale asintotica  1 • Formalmente, si usa il concetto matematico di ordine di grandezza • sia n la dimensione del problema, cioè la dimensione dell’input dell’algoritmo • siaT(n) il tempo impiegato per l’esecuzione dell’algoritmo quando l’ingresso ha dimensione n • sia f(n) una qualsiasi funzione di n, ad esempio 3, n, n2, n5, 2n • Si dice che la complessità asintotica dell’algoritmo è dell’ordine di f(n) e si scrive O(f(n)) se esiste una costante  tale che T(n)f(n) • Osservazione importante: in base alla definizione data, algoritmi che differiscono solo per una costante moltiplicativa hanno lo stesso ordine di complessità • Esempio: due algoritmi che richiedono 4n e 7n operazioni sono entrambi O (n)

  8. Complessità temporale asintotica  2 • Informalmente… • L’ordine O(f(n)) fornisce una misura della complessità temporale di ogni programma che implementa l’algoritmo • Esempio: Calcolare la somma degli elementi di un array • n: numero di elementi dell’array • complessità: O (n)

  9. Complessità media e relativa al caso peggiore • Un algoritmo può richiedere un numero di operazioni diverse per ingressi di dimensione uguale: • complessità media: complessità valutata su tutti i possibili ingressi • complessità nel caso peggiore: complessità dell’algoritmo per l’ingresso che richiede più operazioni • Di solito, quando si parla di complessità, ci si riferisce alla complessità nel caso peggiore

  10. Complessità asintotica: array e liste • Array • n: numero degli elementi dell’array • Ricerca/inserimento/cancellazione di un elemento • Complessità O (n) • Liste semplici • n:numero delle posizioni nella lista • Ricerca/cancellazione di un elemento • Complessità O (n) • Inserimento di un elemento all’inizio della lista (in testa) • Complessità O (1)

  11. 6 3 8 1 4 7 9 Complessità asintotica: alberi binari • Alberi binari di ricerca • n: numero dei nodi dell’albero • Inserire, eliminare o ricercare un elemento in un albero binario bilanciato • Complessità: O (log2n)

  12. Complessità asintotica: tabelle hash • Problema Memorizzare in maniera opportuna un insieme di dati  tipicamente sotto forma di record  in modo da poter reperire un qualsiasi elemento dell’insieme con un numero “piccolo” di tentativi • Cosa significa “piccolo” ? • Indipendente (o quasi) dalla dimensione della tabella su cui si effettua la ricerca, quindi con una complessità in tempo pari ad O (1)

  13. Funzioni hash  1 • h: K → {0, 1, 2, …, m–1} • K: insieme dei valori distinti che possono essere assunti dalle chiavi dei record • m: dimensione del vettore in cui si intende memorizzare la tabella • Ipotesi:K sottoinsieme dei numeri naturali • Possibile funzione di accesso: h(k) k MOD m, kK • Valore della funzione sempre compreso fra 0 em–1

  14. Funzioni hash  2 • Se K non è un sottoinsieme dei numeri naturali • Esempio: insieme di stringhe alfanumeriche • La funzione hash si applica a numeri • Per utilizzarla in corrispondenza di una chiave non numerica occorre associare alla chiave un valore numerico • Necessità di definire funzioni hash generali • Associazione di un valore numerico ad una chiave di qualunque tipo • Applicazione della funzione hash a tale valore • Esempio: si utilizza la somma dei codici ASCII dei caratteri che costituiscono la stringa

  15. Collisioni  1 • Associazione, da parte di una trasformazione, della stessa posizione a chiavi distinte • Sinonimi • Esempio:[10,12,20,23,27,30,31,39,42,44,45,49,53,57,60] • h(chiave)  (chiave MOD 15) • Posizione 0 ← 30, 45, 60 • Posizione 8 ← 23, 53 • Posizione 12←12, 27, 42, 57 • Ciascuna posizione dell’array può contenere al più un elemento; occorre… • Ridurre al massimo le collisioni • Gestirle quando si verificano

  16. Collisioni  2 • Funzioni di hashing perfetto(che evitano i duplicati) sono rare, anche per tabelle grandi • Esempio: paradosso del compleanno Dato un gruppo di 23 persone, ci sono più del 50% di probabilità che due di esse siano nate nello stesso giorno dell’anno • In altre parole, se scegliamo una funzione aleatoria (a valori casuali) che trasforma 23 chiavi in un indirizzo di una tabella di 365 elementi, la probabilità che due chiavi NON collidano è solo 0.4927 (meno della metà) • Individuare una funzione di accesso che porti ad un numero ridotto di collisioni è un problema complesso

  17. h(chiave)  (chiave MOD 19) Collisioni  3 • Tuttavia… numero di collisioni ridotto drasticamente se accettiamo uno spreco del 25% di memoria extra • Esempio: array di 19 elementi (indicizzati da 0 a 18) • Posizione 0 ← 57 • Posizione 8 ← 27 • Posizione 1 ← 20, 39 • Posizione 10 ←10 • Posizione 3 ← 60 • Posizione 11 ← 30, 49 • Posizione 4 ← 23, 42 • Posizione 12 ←12, 31 • Posizione 6 ← 44 • Posizione 15 ← 53 • Posizione 7 ← 45 • Collisioni non eliminate del tutto

  18. Gestione delle collisioni  1 • Uso di liste concatenate destinate alla memorizzazione degli elementi che, in inserimento, hanno portato ad una collisione • Ricerca di un elemento di chiavek • Si calcolah(k) • Se si verifica una collisione allora si accede alla lista associata alla posizioneh(k) e la si scandisce

  19. Gestione delle collisioni  2 • Il costo dell’operazione di ricerca  realizzata in modo lineare relativamente alle liste di elementi in collisione  si mantiene pressoché indipendente da n(numero degli elementi contenuti nella tabella) • Inserimento/cancellazione costano O (1) • Metodo non adatto a reperire sottoinsiemi di dati con chiave che soddisfi una data relazione

  20. Complessità asintotica fattoriale • Sia dato un programma che prende in ingresso i partecipanti ad una competizione e genera (ad esempio per stamparle) tutte le possibili classifiche finali • n: numero di partecipanti • Complessità: O (n!) • Si osservi che n! è un numero molto grande anche per n relativamente piccoli 20!  2.432.902.008.176.640.0002.41018

  21. Complessità asintotica polinomiale • Sia dato un programma che ha come ingresso due array a, b e cerca tutte le coppie (i,j) tali che a[i]b[j] • n: dimensione di a • m: dimensione di b • Complessità: O (nm) void search(int a[], int b[], int alength, int blength) { … for(i0;ialength;i){ for(j0;jblength;j){ if(a[i]b[j]) printf(“Trovata corrispondenza: a[%d]b[%d]%d”, i, j, a[i]); } } }

  22. Algoritmi facili e difficili • In base alla loro complessità temporale asintotica, gli algoritmi sono tipicamente divisi in classi • Algoritmi a complessità costanteO (1) o lineareO (n) • Molto veloci, “scalabili” • Algoritmi a complessità polinomiale O(na) per un qualche valore a • Usabili se l’esponente a è piccolo • Algoritmi a complessità esponenziale O(an) per un qualche valore a (1) • Usabili solo per n molto piccoli

  23. Algoritmi a complessità esponenziale • Fondamentale: perché gli algoritmi a complessità esponenziale sono considerati quasi inusabili? • Perché richiedono talmente tante operazioni che probabilmente anche i calcolatori futuri non saranno in grado di eseguire in tempi ragionevoli le loro possibili implementazioni (programmi) per dati in ingresso ad alta dimensionalità

  24. Esempio • Si consideri il programma che genera tutte le classifiche finali di una competizione con n partecipanti, complessità O (n!) (è esponenziale, perché ) • Con 20 concorrenti le classifiche sono 20! 2.41018 • Un computer che generi 1 miliardo di classifiche al secondo, circa 31016 l’anno, impiegherebbe circa 79 anni per generare tutte le classifiche richieste • Tendenzialmente, i computer diverranno sempre più veloci e fra dieci anni forse saranno abbastanza veloci da realizzare in un mese quello per cui adesso occorrono 79 anni ma… • …comunque, fra dieci anni per risolvere il problema con 21 partecipanti occorreranno ancora 21 mesi e per 25 partecipanti circa 531300 anni!!

  25. Algoritmi e complessità

  26. La ricerca dicotomica  1 • Per “cercare” un elemento in un vettore ordinato esiste un metodo detto ricerca binaria o dicotomica • Si confronta il valore val da ricercare con l’elemento centrale del vettore A[length/2] • Se val è minore dell’elemento mediano, si ripete la ricerca sulla metà sinistra del vettore, altrimenti si ricerca nella metà destra

  27. 0 2 4 5 8 9 13 16 20 23 27 30 34 35 0 2 4 5 8 9 13 16 20 23 27 30 34 35 0 2 4 5 8 9 13 16 20 23 27 30 34 35 0 2 4 5 8 9 13 16 20 23 27 30 34 35 La ricerca dicotomica  2 • Esempio: ricerca del numero 23 Si confronta 23 con 13 Ci si concentra sulla metà destra (da ind. 8 a ind. 14): si confronta 23 con 27 Ci si concentra sulla metà sinistra (da ind. 8 a ind. 10): si confronta 23 con 20 Ci si concentra sulla metà destra (da ind. 9 a ind. 9): trovato!!

  28. Implementazione della ricerca dicotomica int search(int val, int A[], int from,int to) { int center(fromto)/2; if (from  to) return 1; if (fromto) { if(A[from]val) {return from;} return 1;} // si esegue solo se A[from]!val //si esegue solo se (fromto) if (valA[center]){ return search(val,A,from,center1);} if (valA[center]){ return search(val,A,center1,to);} return center; }

  29. Complessità della ricerca dicotomica • La ricerca dicotomica divide il vettore in due ad ogni passo: • dopo p passi la dimensione del vettore è • nel caso peggiore, la ricerca si ferma quando è 1, cioè quando plog2n • Quindi la ricerca dicotomica è O (log2n)

  30. Mergesort  1 • Il Mergesort è un algoritmo basato sul paradigma del divide et impera • Una strategia divide et impera consiste nel suddividere un problema in sottoproblemi, nel risolvere i sottoproblemi, e nel ricomporli per ottenere la soluzione del problema originale • Il Mergesort è composto da due fasi: • una fase di divisione del vettore da ordinare in sottovettori • una fase di ricomposizione dei risultati (merge)

  31. 6 1 2 8 3 4 7 5 Divisione 3 4 7 5 6 1 2 8 3 4 5 7 1 2 6 8 1 2 3 4 5 6 7 8 Mergesort  2 • Idea • Dato un vettore da ordinare, lo si divide in due sottovettori di ugual dimensione, si ordinano i sottovettori e poi si “fondono” insieme Ordinamento Ordinamento Fusione

  32. 6 1 2 8 3 4 7 5 3 4 7 5 6 1 2 8 6 1 2 8 3 4 7 5 6 3 1 4 2 7 8 5 Mergesort: la divisione ricorsiva • Come si ordinano i due sottovettori ? • Applicando ricorsivamente la divisione fino a quando il vettore contiene un solo elemento: in tal caso l’ordinamento è banale Divisione

  33. 1 2 3 4 5 6 7 8 3 4 5 7 1 2 6 8 1 6 2 8 5 7 3 4 6 3 1 4 2 7 8 5 Mergesort: la fusione ricorsiva  1 • I sottovettori ordinati verranno poi ricorsivamente fusi Fusione

  34. i j k Mergesort: la fusione ricorsiva  2 • La fusione viene realizzata utilizzando due indici che scorrono i due sottovettori da fondere: • Ad ogni passo si confrontano i due elementi indicati dagli indici i e j, A[i], A[j] • Si copia l’elemento minore in un vettore d’appoggio e si incrementa l’indice corrispondente • Si torna al passo 1. fino a quando i due vettori non sono stati completamente visitati 1 2 3 4 5 6 7 8

  35. Complessità del Mergesort • Il Mergesort ha complessità O(nlog2n) sia nel caso medio che nel caso pessimo • Mergesort è un algoritmoottimo! • La sua complessità asintotica è la migliore possibile • Comunque… • …esistono algoritmi che per alcuni ingressi fanno meglio di nlog2n(ad es., Bubblesort su vettori ordinati) • …esistono altri algoritmi con complessità nlog2n nel caso pessimo  Heapsort

  36. Quicksort  1 • Quicksort, come Mergesort, è un algoritmo divide et impera • Idea • Si divide il vettore A in due sottovettori, che contengono rispettivamente tutti gli elementi maggiori e minori di (per esempio) A[0], cioè il primo elemento del vettore  detto perno • Si ripete ricorsivamente la divisione…

  37. 4 2 1 6 3 8 7 5 3 2 1 4 6 8 7 5 3 2 1 6 8 7 5 1 2 3 5 6 7 8 1 2 5 7 8 Quicksort  2 Si ripartisce il vettore rispetto ad A[1]  4 Si divide rispetto a 3 Si divide rispetto a 6

  38. Si scorrono i, j confrontando con 4 4 2 8 6 3 1 7 5 j i Si scambiano gli elementi 4 2 8 6 3 1 7 5 i j Si scorrono i, j confrontando con 4 4 2 1 6 3 8 7 5 j i Quicksort: l’operazione perno 1 • Come si divide il vettore? • Si usano due indici i, j che scorrono il vettore da sinistra e da destra, rispettivamente • L’indice i scorre fino a quando A[i]A[1] • L’indice j scorre fino a quando A[j]A[1] • Si effettua lo scambio fra A[i] e A[j] e quindi si procede come sopra

  39. 4 2 1 6 3 8 7 5 4 2 1 3 6 8 7 5 3 2 1 4 6 8 7 5 Quicksort: l’operazione perno  2 • Alla fine si scambia il perno con l’elemento in posizione j Si scambiano gli elementi i j Si scambia A[j] con il perno j

  40. Implementazione void perno(int A[], int from, int to) { int ifrom1, jto; while(ij){ while(A[i]A[from]) i; while(A[j]A[from]) j; if(ij) scambia(A,i,j); } scambia(A,from,j);}

  41. Complessità del Quicksort • Il Quicksort ha complessità media O (nlogn) • Il caso pessimo si verifica quando il perno finisce in fondo o in testa al vettore • In tal caso, Quicksort ha complessità pari ad O (n2)

  42. Mergesort vs Quicksort • Mergesort ha il vantaggio di avere complessità sempre O (nlog n) • Quicksort ha il vantaggio di non richiedere un vettore di appoggio: ordina il vettore “in loco” (minore complessità spaziale) • In media, Quicksort si comporta “bene” e, per questo motivo, in pratica spesso è preferito a Mergesort

  43. Heap  1 • Ospita gli elementi dell’insieme A di cardinalità n, su cui è definita una relazione d’ordine totale “” • Lo heap (mucchio) è un albero binario • Proprietà 1 L’albero è quasi perfettamente bilanciato • È completo fino al livellok1, cioè contiene il numero massimo di nodi, 2k1, mentre al livello k contiene un numero di nodi (foglie) compreso tra 1 e 2k I nodi a livello massimo sono tutti addossati a sinistra • Proprietà 2 Ogni nodo contiene un elemento  dell’elemento contenuto nel padre

  44. 23 38 18 22 17 5 10 28 12 63 Heap  2 • Noto il valore di n, la forma dell’albero è fissata dalla Proprietà 1 • L’allocazione degli elementi nei nodi può variare, nel rispetto della Proprietà 2 • L’elemento massimo dell’insieme è allocato nella radice • Nello heap, i sottoalberi di ciascun nodo sono ancora heap • Lo heap può essere allocato in un array

  45. Heap  3 • Con l’allocazione lineare… • A[1] è l’elemento contenuto nella radice dello heap • Per ogni A[i], gli elementi corrispondenti ai figli sinistro e destro, se esistono, sono memorizzati in A[2i] e A[2i1] • Se 2in e/o 2i1n il figlio sinistro e/o destro di A[i] non esiste nell’albero • A[2i]A[i] e A[2i1]A[i], quando tali elementi sono definiti

  46. Heapsort  1 • Lo heap trova la sua applicazione più elegante nel metodo di ordinamento noto come Heapsort • Si estrae l’elemento massimo dallo heap (quello nella radice, o in prima posizione nella rappresentazione lineare) • Si ricostruisce lo heap • …fino a quando non ci sono più elementi nello heap (ovvero gli elementi del vettore sono ordinati)

  47. 12 28 10 12 23 38 18 22 5 17 10 18 63 23 38 22 17 28 5 Heapsort  2 • Come si ricostruisce lo heap, dopo l’estrazione della radice? Continua…

  48. 18 12 10 38 23 38 22 5 17 28 28 23 18 22 17 10 12 Si scambia A[1] con A[2] 5 Heapsort  3 • Si considera il massimo fra i due figli della radice e, se max{A[2],A[3]}A[1], si effettua lo scambio Continua…

  49. 38 12 10 38 23 18 22 5 17 28 18 23 28 22 17 10 12 Si scambia A[2] con A[5] 5 Heapsort  4 • Si considera il massimo fra i due figli di A[2] e, se max{A[4],A[5]}A[2], si effettua lo scambio Continua…

  50. 12 18 23 28 22 17 5 5 10 38 23 28 22 17 18 12 10 Heapsort  5 • Si estrae A[1] che è l’elemento più grande… e si ricomincia il procedimento di ricostruzione

More Related