La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Lezione 7 I Tipi di Dato Astratto (Abstract Data Type)

Presentazioni simili


Presentazione sul tema: "Lezione 7 I Tipi di Dato Astratto (Abstract Data Type)"— Transcript della presentazione:

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)

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) 1for j  2 to lenght[A] 2do key  A[j] 3  si inserisce A[j] nella sequenza ordinata A[1..j-1] 4i  j - 1 5while i>0 e A[i]>key 6do A[i+1]  A[i] 7i  i - 1 8A[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) nc11 for j  2 to lenght[A] n-1c22 do key  A[j] n-103  si inserisce A[j]... n-1c44i  j - 1  j=2..n t j c55while i>0 e A[i]>key  j=2..n (t j -1) c66do A[i+1]  A[i]  j=2..n (t j -1) c77i  i - 1 n-1c88A[i+1]  key Dove t j è 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 t j )+c6.(  j=2..n (t j -1)) +c7.(  j=2..n (t j -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]

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 t j =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).n 2 +(c1+c2+c4+c5/2-c672-c7/2+c8).n- (c2+c4+c5+c8) T(n)=a.n 2 +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 t j =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 ci per 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.n 2 +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 10 6 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 2n 2 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(10 6 ) 2 /10 8 = 5.56 ore mentre il personal computer impiega log 10 6 /10 6 = 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)   (n 2 ) –f(n) =  (n 2 ) 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):  c 1, c 2, n 0 tali che  n  n 0 0  c 1 g(n)  f(n)  c 2 g(n) } ovvero f(n) appartiene a  (g(n)) se esistono due costanti c 1, c 2 tali che essa possa essere schiacciata fra c 1 g(n) e c 2 g(n) per n sufficientemente grandi

38 Notazione  (g(n)) Graficamente n0n0 c 1 g(n) c 2 g(n) f(n)

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, n 0 tali che  n  n 0 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 n0n0 c g(n) f(n)

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, n 0 tali che  n  n 0 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 n0n0 c g(n) f(n)

43 Notazione o(g(n)) Il limite asintotico superiore può essere stretto o no 2 n 2 = O(n 2 ) è stretto 2 n = O(n 2 ) 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  n 0 tali che  n  n 0 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 lim x  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  n 0 tali che  n  n 0 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 lim x  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 n n=  (n 2 ) dalla definizione di  (g(n)) si ha che si devono trovare delle costanti c 1, c 2 tali che 1/2 n n possa essere schiacciata fra c 1 n 2 e c 2 n 2 per n sufficientemente grandi, ovvero per n>n 0 c 1 n 2  1/2 n n  c 2 n 2 c 1 n 2  1/2 n n è vera per n  7 e per c 1  1/14 1/2 n n  c 2 n 2 è vera per n  1 e per c 2  1/2 quindi per n 0 =7 c 1 = 1/14 e c 2 = 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 c 1 un valore più piccolo del coefficiente del termine più grande e a c 2 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 n 2 + b n + c =  (n 2 ) ovvero  j=o..d a j n j =  (n d ) inoltre dato che una costante è un polinomio di grado 0 si scrive: c =  (n 0 ) =  (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(n 2 )

50 La notazione asintotica nelle equazioni Seguendo la notazione n = O(n) possiamo pensare di scrivere anche espressioni del tipo 2n 2 +3n+1= 2n 2 +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

51 La notazione asintotica nelle equazioni La notazione asintotica può anche apparire a sinistra di una equazione come in 2n 2 +O(n)= O(n 2 ) il significato è che indipendentemente da come viene scelta la funzione anonima a sinistra è sempre possibile trovare una funzione anonima a destra che soddisfa l’equazione per ogni n in questo modo possiamo scrivere: 2n 2 +3n+1= 2n 2 +O(n)=O(n 2 )

52 Le funzioni di interesse O(1)il tempo costante è caratteristico di istruzioni che sono eseguite una o al più poche volte. O(log n)il tempo logaritmico è caratteristico di programmi che risolvono un problema di grosse dimensioni riducendone la dimensione di un fattore costante e risolvendo i singoli problemi più piccoli. quando il tempo di esecuzione è logaritmico il programma rallenta solo leggermente al crescere di n: se n raddoppia log n cresce di un fattore costante piccolo.

53 Le funzioni di interesse O(n)il tempo lineare è caratteristico di programmi che eseguono poche operazioni su ogni elemento dell’input. Se la dimensione dell’ingresso raddoppia, raddoppia anche il tempo di esecuzione. O(n log n)il tempo n log n è caratteristico di programmi che risolvono un problema di grosse riducendoli in problemi più piccoli, risolvendo i singoli problemi più piccoli e ricombinando i risultati per ottenere la soluzione generale. Se n raddoppia n log n diventa poco più del doppio.

54 Le funzioni di interesse O(n 2 )il tempo quadratico è caratteristico di programmi che elaborano l’input a coppie. Algoritmi con tempo quadratico si usano per risolvere problemi abbastanza piccoli. Se n raddoppia n 2 quadruplica. O(2 n )il tempo esponenziale è caratteristico di programmi che elaborano l’input considerando tutte le possibili permutazioni. Rappresentano spesso la soluzione naturale più diretta e facile di un problema. Algoritmi con tempo esponenziale raramente sono applicabili a problemi pratici. Se l’input raddoppia il tempo di esecuzione viene elevato al quadrato

55 La conversione dei secondi Secondi minuti ore giorni settimane mesi anni decenni secoli mai

56 Andamento dei tempi di calcolo

57 Tempo impiegato da un calcolatore capace di 10^6 operazioni al secondo

58 Strutture dati elementari Le strutture dati vettore e lista sono fra le strutture dati più usate e semplici il loro scopo è quello di permettere l’accesso ai membri di una collezione generalmente omogenea di dati per alcuni linguaggi di programmazione sono addirittura primitive del linguaggio (vettori in C/C++ e liste in LISP) Sebbene sia possibile realizzare l’una tramite l’altra, i costi associati alle operazioni di inserzione e cancellazione variano notevolmente nelle diverse implementazioni

59 Vettori Un vettore è una struttura dati che permette l’inserimento di dati e l’accesso a questi tramite un indice intero generalmente la memorizzazione avviene in aree contigue di memoria nella maggior parte degli elaboratori vi è una corrispondenza diretta con la memoria centrale (questo implica alta efficienza)

60 Esempio di programma che usa vettori Crivello di Eratostene static const int N = 1000; int main(){ int i, a[N]; //inizializzazione a 1 del vettore for (i = 2; i < N; i++) a[i] = 1; for (i = 2; i < N; i++) if (a[i]) //se numero primo elimina tutti multipli for (int j = i; j*i < N; j++) a[i*j] = 0; //stampa for (i = 2; i < N; i++) if (a[i]) cout << " " << i; cout << endl; }

61 Crivello di Eratostene Intuitivamente: –si prende un vettore di N elementi a 1 –si parte dal secondo elemento e si cancellano (mettono a 0) tutti gli elementi di posizione multipla di 2 –si considera l’elemento successivo che non sia stato cancellato –questo elemento non è divisibile per alcun numero precedente (altrimenti sarebbe stato messo a 0) e deve pertanto essere primo –si cancellano pertanto tutti i suoi multipli

62 Liste Una lista concatenata è un insieme di oggetti, dove ogni oggetto è inserito in un nodo che contiene anche un link (un riferimento) ad un (altro) nodo si usa quando è necessario scandire un insieme di oggetti in modo sequenziale è vantaggiosa quando sono previste frequenti operazioni di cancellazione o inserzioni lo svantaggio sta nel fatto che si può accedere ad un elemento di posizione i solo dopo aver acceduto a tutti gli i-1 elementi precedenti

63 Liste Di norma si pensa ad una lista come ad una struttura che implementa una disposizione sequenziale di oggetti in linea di principio tuttavia l’ultimo nodo potrebbe linkare il primo ed avremo così una lista circolare

64 Liste Una lista può essere: –concatenata semplice: un solo link –concatenata doppia (bidirezionale): due link le liste bidirezionali hanno un link al nodo che le precede nella sequenza ed uno al nodo che le segue con le liste concatenate semplici non è possibile risalire al nodo precedente ma si deve nuovamente scorrere tutta la sequenza le liste concatenate doppie tuttavia occupano più spazio in memoria

65 Convenzioni In una lista si ha sempre un nodo detto testa ed un modo convenzionale per indicare la fine della lista La testa di una lista semplice non ha predecessori I tre modi convenzionali di trattare il link del nodo dell’ultimo elemento sono: –link nullo –link a nodo fittizio o sentinella –link al primo nodo (lista circolare)

66 Implementazione C++ La struttura di un nodo di una lista si implementa in C++ attraverso l’uso dei puntatori struct Node { int key Node * next; }; struct Node { int key Node * next; Node * prec; };

67 Esempio di lista (Problema di Giuseppe Flavio) struct node{ int item; node* next; node(int x, node* t){ item = x; next = t; } }; typedef node * link; int main(int argc, char * argv[]){ int i, N = atoi(argv[1]), M = atoi(argv[2]); link t = new node(1, 0); t->next = t; link x = t; for (i = 2; i <= N; i++) //creazione della lista x = (x->next = new node(i, t)); while (x != x->next){ //eliminazione for (i = 1; i next; //spostamento x->next = x->next->next; } cout item << endl;//stampa l’ultimo elemento }

68 Spiegazione intuitiva Si parte da una lista circolare di N elementi Si elimina l’elemento di posizione M dopo la testa ci si muove a partire dall’elemento successivo di M posizioni e si elimina il nodo corrispondente Vogliamo trovare l’ultimo nodo che rimane

69 Operazioni definite sulla lista Per una lista si possono definire le operazioni di: –inserimento –cancellazione –ricerca di seguito se ne danno le implementazioni in pseudocodice per una lista bidirezionale

70 Rappresentazione grafica della inserzione t x t x x

71 Rappresentazione grafica della cancellazione t t

72 Inserimento List-Insert(L,x) 1next[x]  head[L] 2if head[L]  NIL 3then prev[head[L]]  x 4head[L]  x 5prev[x]  NIL

73 Cancellazione List-Delete(L,x) 1if prev[x]  NIL 2then next[prev[x]]  next[x] 3else head[L]  next[x] 4if next[x]  NIL 5then prev[next[x]]  prev[x]

74 Nota: Memory leakage Quando si cancella un nodo si deve porre attenzione alla sua effettiva deallocazione dallo heap nel caso in cui si elimini un nodo solamente rendendolo inaccessibile non si libera effettivamente la memoria se vi sono molte eliminazioni si può rischiare di esaurire la memoria disponibile

75 Ricerca List-Search(L,k) 1x  head[L] 2while x  NIL e key[x]  k 3do x  next[x] 4return x

76 La sentinella Si può semplificare la gestione delle varie operazioni se si eliminano i casi limite relativi alla testa e alla coda per fare questo si utilizza un elemento di appoggio detto NIL[L] che sostituisca tutti i riferimenti a NIL tale elemento non ha informazioni significative nel campo key ed ha inizialmente i link next e prev che puntano a se stesso

77 Implementazioni con sentinella List-Delete(L,x) 1next[prev[x]]  next[x] 2prev[next[x]]  prev[x] List-Insert(L,x) 1next[x]  next[nil[L]] 2prev[next[nil[l]]]  x 3next[nil[L]]  x 4prev[x]  nil[L] List-Search(L,k) 1x  next[nil[L]] 2while x  nil[L] e key[x]  k 3do x  next[x] 4return x

78 Rappresentazione Grafica nil[L] nil[L] nil[L] inserzione cancellazione

79 Implementazione di lista con più vettori Si può rappresentare un insieme dei oggetti che abbiano gli stessi campi con un vettore per ogni campo per realizzare una lista concatenata si possono pertanto utilizzare tre vettori: due per i link e uno per la chiave un link adesso è solo l’indice della posizione del nodo puntato nell’insieme di vettori per indicare un link nullo di solito si usa un intero come 0 o -1 che sicuramente non rappresenti un indice valido del vettore

80 Esempio key next prev head /25 527/

81 Nota L’uso nello pseudocodice della notazione next[x] prev[x] e key[x] corrisponde proprio alla notazione utilizzata nella maggior parte dei linguaggi di programmazione per indicare l’implementazione vista

82 Implementazione lista con singolo vettore La memoria di un calcolatore può essere vista come un unico grande array. Un oggetto è generlamente memorizzato in un insieme contiguo di celle di memoria, ovvero i diversi campi dell’oggetto si trovano a diversi scostamenti dall’inizio dell’oggetto stesso si può sfruttare questo meccanismo per implementare liste in ambienti che non supportano i puntatori: –il primo elemento contiene la key –il secondo elemento l’indice del next –il terzo elemento l’indice del prev

83 Esempio / /4 key next prev


Scaricare ppt "Lezione 7 I Tipi di Dato Astratto (Abstract Data Type)"

Presentazioni simili


Annunci Google