1 / 83

Lezione 7

Lezione 7. I Tipi di Dato Astratto (Abstract Data Type). Sommario. Cosa sono le Strutture Dati Astratte? Le strutture dati Le operazioni Come scegliere fra varie implementazioni? Analisi degli algoritmi Le strutture dati elementari vettori liste. Cosa sono gli ADT.

remy
Télécharger la présentation

Lezione 7

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. Lezione 7 I Tipi di Dato Astratto (Abstract Data Type)

  2. Sommario • Cosa sono le Strutture Dati Astratte? • Le strutture dati • Le operazioni • Come scegliere fra varie implementazioni? • Analisi degli algoritmi • Le strutture dati elementari • vettori • liste

  3. Cosa sono gli ADT • Cosa è un tipo di dato • Cosa è un tipo di dato astratto • Quali sono le operazioni definibili

  4. Quale è la questione? • Come organizzare (strutturare) i dati perché sia possibile elaborarli agevolmente tramite algoritmi? • Importanza: • in alcune applicazioni la scelta della struttura dati è l’unica scelta importante • data una struttura dati l’implementazione di un algoritmo può risultare più efficiente • guadagno di tempo o di spazio

  5. Tipo di dato • Definizione: Un tipo di dato è definito da un insieme di valori e da una collezione di operazioni su questi valori • Es: un tipo di dato è il tipo intero in cui l’insieme di valori è costituito dai numeri naturali e le operazioni dalla somma, sottrazione, moltiplicazione, divisione, etc.

  6. Verso l’astrazione • Preoccupazione principale nello scrivere un programma: • applicazione alla più ampia varietà possibile di situazioni • riutilizzo del programma • astrazione dalle implementazioni per poter lavorare a livelli di complessità maggiore

  7. L’astrazione • Si può lavorare a diversi livelli di astrazione: • bit: entità di informazione binaria (astrae dal supporto fisico (tecnologia elettronica) con cui è rappresentato) • modello di calcolatore (astrae dalla rappresentazione dell’informazione) • linguaggi di programmazione (si astrae dal linguaggio macchina e quindi dal modello di calcolatore) • algoritmi (si astrae dai linguaggi di programmazione) • ADT (si astrae dalle implementazioni algoritmiche)

  8. Utilità delle astrazioni • Lavorare a livelli alti di astrazione permette di lavorare in modo semplice su problemi complessi • si possono analizzare gli algoritmi indipendentemente dai linguaggi con i quali sono poi implementati • si possono realizzare programmi complessi tramite le strutture dati astratte indipendentemente dalla loro implementazione algoritmica

  9. Tipo di dato astratto • Definizione: Un ADT (Abstract Data Type) è un tipo di dato accessibile solo attraverso una interfaccia • Si chiama programma client il programma che usa ADT • si chiama implementazione il programma che specifica il tipo di dato (cioè i valori e le operazioni)

  10. Esempio • Un ADT che rappresenti un punto bidimensionale mette a disposizione delle operazioni come ad es. l’assegnazione, il confronto, la somma • questo viene fatto senza rivelare i dettagli implementativi interni: l’interfaccia maschera l’implementazione • è possibile rappresentare un punto mediante due coordinate cartesiane x,y oppure mediante coordinate polari r, • si vuole poter cambiare la rappresentazione interna senza che il programma client debba essere modificato

  11. Proprietà degli ADT • Gli ADT di interesse descrivono insiemi o collezioni di elementi (che a loro volta possono essere ADT) • Queste collezioni possono essere dinamiche, ovvero il numero di elementi può variare: si possono aggiungere o togliere elementi dalla collezione • Gli elementi hanno generalmente una struttura costituita da una chiave e (eventualmente) da altri dati satellite • La chiave ha in genere valori in un insieme totalmente ordinato(per cui vale la proprietà di tricomia cioè per ogni coppia di elementi a,b nell’insieme deve valere esattamente una delle seguenti relazioni: a=b, a<b, a>b)

  12. Operazioni per un ADT • inserimento di un nuovo elemento • cancellazione di uno specifico elemento • ricerca di un elemento avente una chiave specificata • minimo e massimo ovvero restituzione dell’elemento con chiave più piccola o più grande • successore e predecessore ovvero restituzione dell’elemento con la minore chiave maggiore di una data chiave (o la maggiore chiave minore) • selezione del k-esimo elemento più piccolo • ordinamento ovvero attraversamento della collezione in ordine di chiave • unione di due collezioni

  13. Quali ADT vedremo? • Vettori, Liste, Alberi, Grafi • Pile, Code e Code con priorità • Tabelle di simboli e alberi di ricerca • Ci interesseremo particolarmente delle operazioni di ordinamento e di ricerca

  14. ADT di Prima Categoria • Per una maggiore flessibilità è necessario garantire di poter utilizzare istanze degli ADT come parametri in ingresso o in uscita a funzioni, o averne istanze multiple (ad esempio un vettore di istanze) • Definizione: Un tipo di dato di prima categoria è un tipo di dato del quale possono esistere istanze multiple e che possiamo assegnare a variabili che sono dichiarate in modo specifico per memorizzare queste istanze

  15. E le implementazioni? • La differenza fra due implementazioni algoritmiche delle operazioni che permettono l’uso delle interfacce sta nell’efficienza • per poter caratterizzare l’efficienza si ricorre all’analisi degli algoritmi • l’analisi permette di stabilire quale algoritmo sia migliore in funzione delle caratteristiche dei dati su cui lavoriamo • es. l’algoritmo migliore che implementa l’operazione di ordinamento per collezioni di dati quasi ordinate è diverso da quello migliore per collezioni di dati ordinati casualmente

  16. Analisi • L’oggetto del discorso • Algoritmi e pseudocodice • Cosa significa analizzare un algoritmo • Modello di calcolo • Analisi del caso peggiore e del caso medio • Ordini di grandezza • La notazione asintotica • La velocità di crescita delle funzioni

  17. Algoritmi • Le operazioni su un ADT vengono implementate tramite algoritmi • durante l’analisi degli algoritmi conviene astrarsi dallo specifico linguaggio di programmazione • per fare questo si usa un linguaggio detto pseudocodice • nello pseudocodice si impiegano metodi espressivi più chiari e concisi che nei linguaggi di programmazione reali • nello pseudocodice si possono usare frasi in linguaggio naturale per sintetizzare procedure complesse ma non ambigue

  18. Convenzioni sullo pseudocodice • Adotteremo le stesse convenzioni utilizzate nel libro “Introduzione agli algoritmi” di T.H.Cormen, C.E.Leiserson, R.L.Rivest Jackson Libri,1999 • Le indentazioni indicano la struttura dei blocchi • i costrutti iterativi while,repeat e for e quelli condizionali if, then, else hanno la stessa interpretazione dei linguaggi Pascal o C • il simbolo “” indica un commento

  19. Convenzioni sullo pseudocodice • l’assegnamento si indica con il simbolo ‘’ come in i3 • si indica l’accesso all’elemento di posizione i-esima di un array A tramite la notazione A[i] • si accede agli attributi o campi di un oggetto usando il nome del campo seguito dal nome dell’oggetto fra parentesi quadre come in length[A] per denotare la lunghezza del vettore A • nelle procedure o funzioni i parametri sono passati per valore (per copia)

  20. Esempio INSERTION-SORT(A) 1 for j  2 to lenght[A] 2 do keyA[j] 3 si inserisce A[j] nella sequenza ordinata A[1..j-1] 4 i  j - 1 5 while i>0 e A[i]>key 6 do A[i+1] A[i] 7 i  i - 1 8 A[i+1]  key

  21. Spiegazione intuitiva • Supponiamo di avere i primi x elementi del vettore già ordinati • consideriamo l’elemento di posizione x+1 e chiamiamolo key • l’idea è di scorrere gli elementi già ordinati e più grandi di key e di trovare la posizione giusta di key • mentre si scorrono gli elementi si scambia di posizione l’elemento che stiamo confrontando con key • appena si trova un elemento più piccolo di key ci si ferma

  22. Cosa significa analizzare un algoritmo • Analizzare un algoritmo significa determinare le risorse richieste per il completamento con successo dell’algoritmo stesso • le risorse di interesse possono essere quelle di memoria, di tempo, numero di porte di comunicazione, numero di porte logiche • noi saremo interessati principalmente alla risorsa di tempo computazionale

  23. Modello di calcolo • Per poter indicare il tempo di calcolo è necessario specificare un modello (ancorché astratto) di calcolo • Noi faremo riferimento ad un modello di calcolo costituito da un mono processore con accesso casuale della memoria (Random Access Machine RAM) • in questo modello ogni istruzione è eseguita in successione (ovvero senza concorrenza) • ogni istruzione viene eseguita in tempo costante anche se in generale diverso da istruzione a istruzione

  24. Dimensione dell’input • Per poter comparare l’efficienza di due algoritmi in modo generale si definisce una nozione di dimensione dell’input e si compara il tempo di calcolo dei due algoritmi in relazione ad esso • per un algoritmo di ordinamento è ragionevole aspettarsi che al crescere del numero di dati da ordinare cresca il tempo necessario per completare l’algoritmo • in questo caso la dimensione dell’input coincide con la numerosità dei dati in ingresso

  25. Dimensione dell’input • Nota: non sempre la dimensione dell’input coincide con il numero di elementi in ingresso • un algoritmo di moltiplicazione fra due numeri naturali ha come dimensione il numero di bit necessari per rappresentare la codifica binaria dei numeri • Nota: non sempre la dimensione dell’input è rappresentabile con una sola quantità • un algoritmo che opera su grafi ha come dimensione il numero di nodi e di archi del grafo

  26. Analisi del tempo computazionale • Lo scopo dell’analisi del tempo computazionale è di dare una descrizione sintetica del tempo di calcolo dell’algoritmo al variare della dimensione dell’ingresso • inizieremo con un calcolo esatto del tempo • successivamente utilizzeremo un formalismo più sintetico e compatto che fa uso degli ordini di grandezza

  27. Esempio Sia n  length[A] N° Costo INSERTION-SORT(A) n c1 1 for j  2 to lenght[A] n-1 c2 2 do keyA[j] n-1 0 3 si inserisce A[j] ... n-1 c4 4 i  j - 1 j=2..n tj c5 5 while i>0 e A[i]>key j=2..n (tj-1) c6 6 do A[i+1] A[i] j=2..n (tj-1) c7 7 i  i - 1 n-1 c8 8 A[i+1]  key Dove tj è il numero di volte che l’istruzione while è eseguita per un dato valore di j Il tempo complessivo è dato da: T(n)=c1.n + c2.(n-1)+c4.(n-1)+c5.(j=2..n tj)+c6.(j=2..n (tj-1)) +c7.(j=2..n (tj-1))+c8.(n-1)

  28. Caso migliore/peggiore • Anche a parità di numerosità dei dati in ingresso il tempo di esecuzione può dipendere da qualche caratteristica complessiva sui dati, ad esempio da come sono ordinati inizialmente • si distinguono pertanto i casi migliore e peggiore a seconda che i dati abbiano (a parità di numerosità) le caratteristiche che rendono minimo o massimo il tempo di calcolo del dato algoritmo • nell’esempio dell’insertion sort • il caso migliore è che i dati siano già ordinati • il caso peggiore è che siano ordinati in senso inverso

  29. Analisi del caso migliore • Per ogni j=2,3,…,n in 5) si ha che A[i]<key quando i ha il suo valore iniziale di j-1 • quindi vale tj=1 per ogni j=2,3,…,n • il tempo di esecuzione diviene quindi: T(n)=c1.n+c2(n-1)+c4.(n-1)+c5.(n-1)+c8.(n-1) ovvero T(n)=(c1+c2+c4+c5+c8).n -(c2+c4+c5+c8) ovvero T(n)=a.n+b • diciamo che T(n) è una funzione lineare di n

  30. Analisi del caso peggiore • Se l’array è ordinato in ordine decrescente allora si deve confrontare l’elemento key=A[j] con tutti gli elementi precedenti A[j-1], A[j-2],…,A[1] • in questo caso si ha che tj=j per j=2,3,4,…,n • si ha che: j=2..n j = n(n+1)/2 -1 j=2..n (j-1) = n(n-1)/2 • il tempo di esecuzione diviene quindi: T(n)=c1.n+c2(n-1)+c4.(n-1) +c5.(n(n+1)/2 -1) +c6.(n(n-1)/2 ) +c7.(n(n-1)/2 )+c8.(n-1) T(n)=(c5/2+c6/2+c7/2).n2+(c1+c2+c4+c5/2-c672-c7/2+c8).n-(c2+c4+c5+c8) T(n)=a.n2+b.n+c • diciamo che T(n) è una funzione quadratica di n

  31. Analisi del caso medio • Se si assume che tutte le sequenze di una data numerosità siano equiprobabili allora mediamente per ogni elemento key=A[j] vi saranno metà elementi nei restanti A[1,..,j-1] che sono più piccoli e metà che sono più grandi • di conseguenza in media tj=j/2 per j=2,3,4,…,n • si computa T(n) come nel caso peggiore • il tempo di calcolo risulta di nuovo quadratico in n

  32. Quale caso analizzare? • Come è accaduto anche nel caso appena visto, spesso il caso medio è dello stesso ordine di grandezza del caso peggiore • inoltre la conoscenza delle prestazioni nel caso peggiore fornisce una limitazione superiore al tempo di calcolo, cioè siamo sicuri che mai per alcuna configurazione dell’ingresso l’algoritmo impiegherà più tempo • infine per alcune operazioni il caso peggiore si verifica abbastanza frequentemente (ad esempio il caso di ricerca con insuccesso) • pertanto si analizzerà spesso solo il caso peggiore

  33. Ordine di grandezza • Per facilitare l’analisi abbiamo fatto alcune astrazioni • si sono utilizzate delle costanti ciper rappresentare i costi ignoti delle istruzioni • si è osservato che questi costi forniscono più dettagli del necessario, infatti abbiamo ricavato che il tempo di calcolo è nel caso peggiore T(n)=a.n2+b.n+c ignorando così anche i costi astratti ci • si può fare una ulteriore astrazione considerando solo l’ordine di grandezza del tempo di esecuzione perché per input di grandi dimensioni è solo il termine principale che conta e dire che T(n)=(n)

  34. Un algoritmo è tecnologia • Si consideri il seguente caso: • si abbia un personal computer capace di eseguire 106 operazioni al secondo ed un supercomputer 100 volte più veloce • si abbia un codice di insertion sort che una volta ottimizzato sia in grado di ordinare un vettore di n numeri con 2n2 operazioni • si abbia un altro algoritmo (mergesort) in grado di fare la stessa cosa con 50 n log n operazioni • si esegua l’insertion sort su un milione di numeri sul supercomputer e il mergsort sul personal computer • il risultato è che il supercomputer impiega 2(106)2/108= 5.56 ore • mentre il personal computer impiega 50 106 log 106 /106= 16.67 minuti

  35. Efficienza asintotica • L’ordine di grandezza del tempo di esecuzione di un algoritmo caratterizza in modo sintetico l’efficienza di un algoritmo e consente di confrontare fra loro algoritmi diversi per la soluzione del medesimo problema • quando si considerano input sufficientemente grandi si sta studiando l’efficienza asintotica dell’algoritmo • ciò che interessa è la crescita del tempo di esecuzione al tendere all’infinito della dimensione dell’input • in genere un algoritmo asintoticamente migliore di un altro lo è in tutti i casi (a parte input molto piccoli)

  36. Notazione Asintotica • La notazione asintotica è un modo per indicare certi insiemi di funzioni caratterizzati da specifici comportamenti all’infinito • Questi insiemi sono indicati come  O  o  • quando una funzione f(n) appartiene ad uno di questi insiemi lo si indica equivalentemente come • f(n)  (n2) • f(n) = (n2) • la seconda notazione è inusuale ma vedremo che ha dei vantaggi di uso

  37. Notazione (g(n)) • Con la notazione (g(n)) si indica l’insieme di funzioni f(n) che soddisfano la seguente condizione (g(n))={f(n):  c1, c2, n0 tali che  n n0 0  c1 g(n)  f(n)  c2 g(n) } • ovvero f(n) appartiene a (g(n)) se esistono due costanti c1, c2 tali che essa possa essere schiacciata fra c1 g(n) e c2 g(n) per n sufficientemente grandi

  38. Notazione (g(n)) • Graficamente c2 g(n) f(n) c1 g(n) n0

  39. Notazione O(g(n)) • Con la notazione O(g(n)) si indica l’insieme di funzioni f(n) che soddisfano la seguente condizione O(g(n))={f(n):  c, n0 tali che  n n0 0  f(n)  c g(n) } • ovvero f(n) appartiene a O(g(n)) se esiste una costante c tali che essa possa essere maggiorata da c g(n) per n sufficientemente grandi

  40. Notazione O(g(n)) • Graficamente c g(n) f(n) n0

  41. Notazione (g(n)) • Con la notazione (g(n)) si indica l’insieme di funzioni f(n) che soddisfano la seguente condizione (g(n))={f(n):  c, n0 tali che  n n0 0  c g(n)  f(n) } • ovvero f(n) appartiene a (g(n)) se esiste una costante c tali che essa sia sempre maggiore di c.g(n) per n sufficientemente grandi

  42. Notazione (g(n)) • Graficamente f(n) c g(n) n0

  43. Notazione o(g(n)) • Il limite asintotico superiore può essere stretto o no • 2 n2 = O(n2) è stretto • 2 n = O(n2) non è stretto • con la notazione o(g(n)) si indica un limite superiore non stretto • formalmente, con la notazione o(g(n)) si indica l’insieme di funzioni f(n) che soddisfano la seguente condizione o(g(n))={f(n):  c>0  n0 tali che  n n0 0  f(n)  c g(n) }

  44. Notazione o(g(n)) • La definizione di o() differisce da quella di O() per il fatto che la maggiorazione i o() vale per qualsiasi costante positiva mentre in O() vale per una qualche costante • L’idea intuitiva è che la f(n) diventa trascurabile rispetto alla g(n) all’infinito ovvero limx f(n)/g(n)=0

  45. Notazione (g(n)) • Analogamente nel caso di limite inferiore non stretto si definisce che con la notazione (g(n)) si indica l’insieme di funzioni f(n) che soddisfano la seguente condizione (g(n))={f(n):  c>0  n0 tali che  n n0 0  c g(n)  f(n)} • Qui l’idea intuitiva è che sia la g(n) a diventare trascurabile rispetto alla f(n) all’infinito ovvero limx f(n)/g(n)=

  46. Tralasciare i termini di ordine più basso • Giustifichiamo perché è possibile tralasciare i termini di ordine più basso, ovvero perché possiamo scrivere 1/2 n2 - 3 n= (n2) • dalla definizione di (g(n)) si ha che si devono trovare delle costanti c1, c2 tali che 1/2 n2 - 3 n possa essere schiacciata fra c1 n2 e c2 n2 per n sufficientemente grandi, ovvero per n>n0 c1 n2  1/2 n2 - 3 n  c2 n2 c1 n2  1/2 n2 - 3 n è vera per n  7 e per c1  1/14 1/2 n2 - 3 n  c2 n2 è vera per n  1 e per c2  1/2 • quindi per n0=7 c1 = 1/14 e c2 = 1/2 si è soddisfatta la tesi (altri valori sono possibili ma basta trovarne alcuni)

  47. Tralasciare i termini di ordine più basso • Intuitivamente si possono tralasciare i termini di ordine più basso perché una qualsiasi frazione del termine più alto prima o poi sarà più grande di questi • quindi assegnando a c1 un valore più piccolo del coefficiente del termine più grande e a c2 un valore più grande dello stesso consente di soddisfare le disegualianze della definizione di (g(n)) • il coefficiente del termine più grande può poi essere ignorato perché cambia solo i valori delle costanti

  48. Nota • In sintesi si può sempre scrivere che a n2 + b n + c = (n2) • ovvero j=o..d ajnj= (nd) • inoltre dato che una costante è un polinomio di grado 0 si scrive: c = (n0) = (1)

  49. Uso della notazione asintotica • Dato che il caso migliore costituisce un limite inferiore al tempo di calcolo, si usa la notazione (g(n)) per descrivere il comportamento del caso migliore • analogamente dato che il caso peggiore costituisce un limite superiore al tempo di calcolo, si usa la notazione O(g(n)) per descrivere il comportamento del caso peggiore • Per l’algoritmo di insertion sort abbiamo trovato che nel caso migliore si ha T(n)= (n) e nel caso peggiore T(n)=O(n2)

  50. La notazione asintotica nelle equazioni • Seguendo la notazione n = O(n) possiamo pensare di scrivere anche espressioni del tipo • 2n2+3n+1= 2n2+O(n) • il significato di questa notazione è che con O(n) vogliamo indicare una anonima funzione che non ci interessa specificare (ci basta che sia limitata superiormente da n) • nel nostro caso questa funzione è proprio 3n+1 che è O(n) • tramite l’uso della notazione asintotica possiamo eliminare da una equazione dettagli inessenziali

More Related