Comments
Description
Transcript
Liste, Pile e Code
Liste Pile e Code Pag. 1/18 Liste, Pile e Code Le liste, le pile e le code rappresentano tre ADT elementari che ricorrono frequentemente nella vita reale. Esempi usuali di una lista, una pila ed una coda sono rispettivamente una lista della spesa, una pila di piatti e una coda in un pubblico esercizio. La prima rappresenta un insieme di oggetti da acquistare che possono essere scritti (inseriti) o depennati (cancellati) senza alcuna particolare regola. La seconda anch’essa rappresenta un insieme di oggetti, però gestiti attraverso l’applicazione di una regola pratica: prelevo in testa ed inserisco in testa. Infine l’ultima è gestita mediante una regola dettata dal vivere civile: la prima persona che entra è la prima persona ad essere servita! Nell’esamina dei tre ADT introdurremo un altro concetto, quello di struttura dati, termine che utilizzeremo quando nella disamina ci occuperemo sia della organizzazione delle informazioni che della efficiente implementazione di un operatore; concetti che come vedremo sono fortemente connessi tra loro. 1. L’ADT Lista Una lista consiste in una sequenza di n oggetti ( a1, …, an ) appartenenti ad un insieme S. La lunghezza di una lista è determinata dal numero dei suoi elementi. Definiamo vuota una lista che ha lunghezza zero. Un elemento di una lista è caratterizzato da una posizione: indichiamo con ai l’elemento in posizione i-esima all’interno di una lista L=( a1, a2, …, an ). Esempio 1.1 Esempi di lista sono: L1 = ( 71, 21, 39 ) dove Lunghezza(L) = 3 e a1 = 71, a2 = 21, a3 = 39 L2 = ( 23, 45, 139, 13 ) dove Lunghezza(L) = 4 e a1 = 23, a2 =45, a3 = 139, a4 = 13 Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 2/18 Le operazioni tipiche su una lista sono la ricerca di un elemento, l’estrazione di un elemento presente in una data posizione, l’inserimento e la cancellazione. Utilizzando i formalismi dell’ADT: Tipo di dato: Lista Insieme L di ‘n’ elementi estratti da un insieme S Operazioni: CercaChiave( elemento e )Æposizione Cerca l’elemento ‘e’ all’interno della lista L e restituisce l’indice compreso tra 1 e la lunghezza della lista nel caso in cui e viene trovato, altrimenti restituisce 0. CercaPosizione( posizione i )Æelemento Restituisce l’elemento in posizione i-esima se i è un valore compreso tra 1 e la lunghezza della lista Inserisci( elemento e, posizione i ) Inserisce l’elemento’ e’ all’interno della lista L in posizione’ i’ Cancella( posizione i ) Cancella l’elemento presente all’interno della lista L nella posizione ’ i’ 2.1 Implementazione mediante vettore La struttura dati lista indicizzata rappresenta l’implementazione dell’ADT lista mediante l’uso di un vettore. Tale struttura dati richiede la definizione di un vettore V di dimensione prefissata, m, dove l'elemento i-esimo della lista, ai, occupa la posizione ‘i-1’. VÆ 1 6 2 23 3 L=(1, 6, 2, 23, 3) contatore=4 Gestiamo le posizioni libere del vettore mediante l’utilizzo di una variabile, contatore, tramite la quale teniamo traccia della dimensione corrente della lista. Tale variabile assume inizialmente il valore 0, viene incrementata ad ogni operazione di inserimento e decrementata ad ogni operazione di cancellazione. Poiché la dimensione m del vettore deve essere prestabilita, la struttura dati lista indicizzata non consente la rappresentazione di liste contenenti più di m elementi, ovvero la struttura dati lista indicizzata è non dinamica. Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 3/18 L’organizzazione delle informazioni contenute all’interno della lista, così come dettate dalla struttura dati lista indicizzata, influenza ad ogni modo la realizzazione degli operatori dichiarati nella specifica. Vediamo come. Inserimento L'inserimento di un elemento nel vettore V alla posizione i richiede, prima di poter dar luogo all’operazione, lo spostamento a destra di tutti gli elementi aventi indice maggiore o uguale ad i-1. In tal modo liberiamo la cella (i-1)-esima del vettore, all’interno della quale possiamo memorizzare l’elemento da inserire. VÆ 1 6 VÆ 1 6 VÆ 1 6 2 50 Contatore = 5 23 3 2 23 3 Contatore = 5 2 23 3 Contatore = 6 Figura 2.1 – Inserimento in posizione 3 dell’elemento 50 Nel caso pessimo, inserimento in prima posizione, il numero di passi da eseguire è dipendente dal numero di elementi presenti all’interno della lista. Al crescere del numero degli elementi, crescono in maniera linearmente proporzionale il numero di spostamenti verso destra da eseguire. Diremo quindi che l’operazione di inserimento richiede tempo linearmente proporzionale ad n. Cancellazione La cancellazione di un elemento in posizione ‘i’ prevede, contrariamente all’operazione di inserimento, uno spostamento a sinistra degli elementi aventi indice maggiore ad i-1. Nel caso pessimo, rappresentato dalla cancellazione in prima posizione, l’operazione di cancellazione richiede un tempo proporzionale ad n. Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 4/18 CercaChiave La ricerca della chiave richiede la scansione, a partire dalla prima posizione, dell’intero vettore. Pertanto, nel caso pessimo, elemento non presente, tale operazione richiede tempo proporzionale ad n. CercaPosizione Diamo luogo all’operazione di CercaPosizione restituendo il valore presente alla posizione i-esima del vettore. Il costo di tale operazione è indipendente dal numero di elementi presenti all’interno della lista. Il tempo di accesso alla cella i-esima è indipendente dal numero di elementi presenti nel vettore. Diremo in tal caso che l’operazione di CercaPosizione richiede tempo costante. 2.2 Implementazione mediante puntatori La struttura dati lista concatenata specifica una organizzazione delle informazioni contenute all’interno della lista alternativa a quella della struttura dati lista indicizzata. Necessitiamo in tal caso della definizione di un record contenente un campo chiave ed un campo che denomineremo successivo. chiave succ. Figura 2.2 – Record Infine, realizziamo la lista definendo n record, uno per ogni elemento della lista, in modo tale che l'i-esimo record contenga all’interno del campo chiave l'elemento ai della lista e all’interno del campo successivo l'indirizzo del record contenente l’elemento ai+1 della lista. Il campo successivo dell'ultimo record assume il valore NULL. Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 5/18 Gestiamo le dimensioni della lista utilizzando una variabile, Testa, che punta l’elemento posto ad inizio lista. Tale variabile assume inizialmente il valore NULL. Testa a1 a2 ... an null Figura 2.3 – lista concatenata Il vantaggio di questo tipo di implementazione è dato dal fatto che non vi sono vincoli sulla dimensione della lista; l'unico vincolo è dato dalla quantità di memoria disponibile. Come già fatto per la struttura dati lista indicizzata, analizziamo ora come tale rappresentazione influenzi l’implementazione degli operatori dell’ADT Lista. Inserimento Per inserire un elemento nel vettore V alla posizione i occorre: • • • • • creare (ovvero, allocare dinamicamente) un nuovo record; assegnare al campo chiave del nuovo record il valore dell’elemento; scandire la lista sino a giungere al record i-esimo; assegnare al campo successivo del record precedente quello i-esimo l'indirizzo del nuovo record; assegnare al campo successivo del nuovo record l'indirizzo del record i-esimo. Poiché nel caso peggiore, inserimento in ultima posizione, occorre scandire l'intera lista, l'operazione di inserimento richiede tempo proporzionale alla dimensione corrente della lista. Testa a1 a2 ... an null a Figura 2.4 – Inserimento nella lista concatenata Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 6/18 Cancellazione La cancellazione necessita dell’utilizzo di due puntatori, p e q. Ci si sposta con uno, p, sull’elemento i-esimo e con l’altro, q, sull’elemento precedente a quello i-esimo. Dopodichè il campo successivo del record puntato da q sarà posto uguale al campo successivo del record puntato da p ed infine viene deallocato il record puntato da p. q p Testa a1 a2 a3 a4 ... an null Figura 2.5 – Cancellazione in una lista concatenata Poiché nel caso peggiore, cancellazione in ultima posizione, occorre scandire l'intera lista, l'operazione di cancellazione richiede tempo proporzionale alla dimensione corrente della lista. CercaChiave La ricerca della chiave necessita della scansione della lista con l’utilizzo di un puntatore che salta da elemento in elemento seguendo l’informazione memorizzata nel campo successivo di ogni record. Nel caso pessimo, chiave non esistente, l’operazione di ricerca richiede tempo proporzionale alla dimensione della lista. CercaPosizione L’operazione di ritorno dell’elemento presente in posizione i-esima ha un tempo anch’esso proporzionale alla dimensione della lista, poichè con l’utilizzo di un puntatore che salta da elemento in elemento, seguendo le informazioni presenti nel campo successivo, dobbiamo dapprima posizionarci sul record i-esimo e poi ritornare il valore assunto dal campo chiave. Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 7/18 3. L’ADT Lista Ordinata Una lista ordinata L è una sequenza di n elementi (al, ...,an ) tratti da un insieme totalmente ordinato S. Gli elementi di una lista ordinata sono regolati dalla seguente relazione: ai ≤ ai+1 ∀ i∈{1, … n-1 }. La presenza di un ordinamento degli elementi non rende applicabile l'operatore di inserimento in posizione arbitraria. Gli operatori tipici per una lista ordinata sono l’operatore di ricerca, l’operatore di inserimento e l’operatore di cancellazione. Tipo di dato: Lista ordinata Insieme L di ‘n’ elementi ( al, ...,an ) estratti da un insieme S totalmente ordinato e tali che ai ≤ ai+1 ∀ i∈{1, … n-1 } Operazioni: Cerca( elemento e )Æposizione Cerca l’elemento ‘e’ all’interno della lista L e restituisce l’indice compreso tra 1 e la lunghezza della lista nel caso in cui e viene trovato, altrimenti restituisce 0 Inserisci( elemento e ) Inserisce l’elemento ’e’ all’interno della lista L Cancella( elemento e ) Cancella l’elemento presente all’interno della lista L 3.1 Implementazione mediante vettore L’utilizzo della struttura dati lista indicizzata rappresenta la pratica più comune per la rappresentazione di una lista ordinata. In tal caso, similmente all’ADT Lista, necessitiamo di un vettore V di dimensioni fisse, all’interno del quale ospitiamo gli elementi della lista ordinata in ordine crescente. Una variabile contatore indicherà la dimensione corrente della lista. Vediamo come l’utilizzo di una simile struttura dati influenzi l’implementazione degli operatori di ricerca, inserimento e cancellazione. Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 8/18 Ricerca La ricerca per chiave avviene attraverso l’algoritmo di ricerca binaria. Sfruttiamo la caratteristica di ordinamento degli elementi dell’ADT dando luogo ai seguenti passi: o confronto: confrontiamo il valore da ricercare, k, con il valore, v, presente alla posizione centrale della porzione di vettore effettivamente utilizzata (mediana) se k = v, allora l'elemento è stato trovato se k < v allora ricerchiamo l'elemento ricorsivamente nella prima metà del vettore poiché esso contiene elementi minori di k. Ci disinteressiamo dell'altra metà dell'insieme se k > v allora ricerchiamo l'elemento ricorsivamente nella seconda metà del vettore poiché esso contiene solo elementi maggiori di k. Ci disinteressiamo anche in questo caso dell'altra metà dell'insieme Ricerca Binaria RicercaBinaria( elemento k, intero iniziovettore, intero finevettore ) { Lista L Vettore V se finevettore == iniziovettore allora { se V[iniziovettore] != k allora elemento non trovato altrimenti elemento trovato } mediana = (finevettore-iniziovettore)/2 se V[mediana] = k allora { elemento trovato } altrimenti { se V[mediana] >= k allora RicercaBinaria( k, iniziovettore, mediana-1 ) altrimenti RicercaBinaria( k, mediana+1, finevettore ) } } Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 9/18 Inserimento Diamo luogo all’operazione di inserimento mediante l’esecuzione dei seguenti quattro passi: o ricerchiamo l'elemento contenente la chiave assegnata. Tale operazione restituisce un indice i che rappresenta la posizione che la chiave dovrebbe occupare; o eseguiamo uno Shift a destra degli elementi aventi indice maggiore di i; o inseriamo il nuovo elemento in posizione i; o incrementiamo la variabile contatore. Cancellazione L'operazione di cancellazione consiste di tre fasi: - ricerchiamo l'elemento contenente la chiave assegnata. Tale operazione restituisce un indice i che rappresenta la posizione che la chiave occupa; - operiamo lo Shift a sinistra degli elementi aventi indice maggiore di i; - decrementiamo la variabile contatore. E’ dimostrabile che se adoperiamo l’algoritmo di ricerca binaria, l’operazione di ricerca di un elemento in una lista ordinata richiede nel caso pessimo, elemento non trovato, un tempo logaritmico nel numero di elementi. Mentre, a causa delle operazioni di shift, sia l’inserimento che la cancellazione richiedono nel caso pessimo, inserimento e cancellazione in prima posizione, un tempo lineare con il numero di elementi della lista. Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 10/18 4. Il tipo di dato astratto Pila Una pila è una collezione di elementi che ammette esclusivamente due operazioni: • PUSH: inserimento di un elemento ad una estremità, detta sommità; • POP: estrazione dell'elemento presente nella sommità. PUSH POP Elemento 3 Elemento 2 Elemento 1 Figura 4.1: pila Possiamo considerare una pila come una specializzazione dell’ADT lista in cui l'ultimo elemento inserito è anche il primo ad essere estratto. Denominiamo con l’acronimo LIFO (Last In First Out) la politica di gestione degli elementi di una pila. Tipo di dato: Pila Sequenza P di ‘n’ elementi ( al, ...,an ) estratti da un insieme S Operazioni: IsEmpty()Æresult Restituisce true se P è vuota altrimenti restituisce false Push( elemento e ) Inserisce l’elemento ’e’ in testa alla pila P Pop()Æelemento Restituisce l’elemento in testa alla pila P Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 11/18 4.1 Implementazione tramite vettore Possiamo rappresentare l’ADT Pila mediante la struttura dati Lista Indicizzata. E’ una soluzione facilmente applicabile. Definiamo un vettore V, di dimensione prefissata, MAX, ed una variabile, Testa, che punta in testa alla pila. Assumiamo inoltre che quanto Testa è uguale al valore -1 allora la pila è vuota, mentre se Testa è uguale al valore MAX-1 il vettore è saturo: la pila non può ospitare alcun elemento. Testa = n-1 0 a1 1 a2 2 a3 n-1 ... ... MAX -1 an Figura 4.2 – Pila di ‘n’ elementi rappresentata con vettori Operatore POP Diamo luogo alla operazione di estrazione, restituendo il valore memorizzato all’interno della cella indicata dalla variabile Testa, V[Testa], e decrementando di una unità la variabile Testa. Operatore PUSH Diamo altresì luogo alla operazione di inserimento, incrementando prima il valore della variabile Testa e memorizzando all’interno della cella indicata dalla variabile incrementata, V[Testa], il valore da inserire. Le operazioni di inserimento ed estrazione richiedono tempo costante. I numeri di passi da eseguire per implementare i due operatori è indipendente dal numero di elementi presenti all’interno della pila. Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 12/18 4.2 Implementazione tramite puntatori La struttura dati lista concatenata è un’alternativa alla rappresentazione sopra descritta. La variabile di tipo puntatore, denominata Testa, rappresenta l’ultimo elemento della pila. In tal caso la condizione di pila vuota è data da: Testa = NULL. Similmente alla rappresentazione mediante utilizzo dei vettori, questa implementazione è caratterizzata da tempi costanti sia per l'operazione di inserimento che per l’operazione di estrazione. Inserimento Diamo luogo all'operazione di inserimento modificando la lista come di seguito raffigurato: Testa a1 a2 a3 a4 NULL PUSH Testa Testa Nuova chiave a1 a2 a3 a4 NULL Creiamo un nuovo record, poniamo il campo successivo uguale al valore assunto da Testa ed infine spostiamo Testa verso il nuovo elemento creato. Inserimento Pila – Lista concatenata Push( elemento e) { Record r alloca un nuovo record, r poni il campo valore di r uguale ad e, rÆvalore=e poni il campo successivo di r uguale a Testa, rÆsuccessivo=Testa poni Testa uguale ad r, Testa=r } Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI _ Liste Pile e Code Pag. 13/18 Estrazione Diamo luogo all’operazione di estrazione modificando la lista come di seguito raffigurato: Creiamo un nuovo record, poniamo il campo successivo uguale al valore assunto da Testa ed infine spostiamo Testa verso il nuovo elemento creato. Estrazione Pila – Lista concatenata Pop()Æelemento { Puntatore p p = Testa Testa = TestaÆSuccessivo Dealloca p } Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI _ Liste Pile e Code Pag. 14/18 5. Il tipo di dato astratto Coda Una coda è una collezione di elementi che ammette esclusivamente due operazioni: • l'inserimento di un elemento ad una estremità, detta fine • l'estrazione dell'elemento presente nell'altra estremità, detta inizio. Estrazione Inserimento Inizio a1 Fine a2 a3 ... an Figura 5.1 – Coda Denominiamo la politica di gestione degli elementi di una coda con l’acronimo FIFO, First In First Out. Tipo di dato: Coda Sequenza Q di ‘n’ elementi ( al, ...,an ) estratti da un insieme S Operazioni: IsEmpty()Æresult Restituisce true se Q è vuota altrimenti restituisce false Inserisci( elemento e ) Inserisce l’elemento ’e’ alla fine di Q Estrazione()Æelemento Restituisce l’elemento posto all’inizio di Q 5.1 Implementazione tramite vettore circolare Una coda è realizzabile attraverso l’utilizzo della struttura dati vettore circolare. Quest’ultimo rappresenta un tradizionale vettore di ampiezza prefissata, la cui caratteristica di circolarità è data dalla regola di incremento utilizzata per la scansione delle celle. Si da luogo a tale operazione utilizzando una variabile intera, index, incrementata secondo la formula: index = (index + 1) mod MAX, dove MAX è la dimensione del vettore. In tal modo index assume valori compresi tra zero e MAX-1. Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 15/18 Rappresentiamo la coda mediante vettore circolare definendo un vettore V di ampiezza MAX e due contatori, inizio e fine, che incrementiamo modulo MAX. Inizio farà riferimento alla cella del vettore che precede il primo elemento, fine farà riferimento alla cella del vettore che contiene l’ultimo elemento. Inizio = (Inizio+1)mod MAX Inizio Fine Fine = (Fine+1)mod MAX 0 a1 a1 1 2 ... an MAX-1 Figura 5.2 – Coda rappresentata con vettore circolare Possiamo pertanto definire quattro stati della coda: 1. Stato iniziale: inizio = fine = 0 2. Coda vuota: inizio = fine 3. Coda non vuota: inizio ≠ fine 4. Coda piena: (fine + 1 ) mod n = inizio Nota Una delle particolarità di questa rappresentazione è data dal fatto che non si avrà mai un riempimento fisico del vettore che si potrà riempire fino ad un massimo di MAX-1 elementi. Questo perché se inserissimo un elemento anche nella unica posizione che eventualmente rimarrebbe vuota non saremmo più in grado di distinguere quando la coda è piena e quando la coda è vuota. Implementiamo gli operatori di inserimento ed estrazione. Inserimento Diamo luogo all’operazione di inserimento incrementando la variabile fine e memorizzando nella cella del vettore indicata da fine il valore da inserire. L’operazione di inserimento ha un tempo di esecuzione costante. Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 16/18 _ Inserimento Coda – Vettore Circolare Inserisci( elemento e) { Vettore V se ((fine+1) mod MAX) == inizio allora coda piena altrimenti fine = ( fine + 1 ) mod MAX V[fine] = e } Estrazione Diamo luogo all’operazione di estrazione incrementando la variabile inizio e ritornando il valore memorizzato nella cella del vettore indicata da inizio. L’operazione di estrazione ha un tempo di esecuzione costante. _ Estrazione Coda – Vettore Circolare Estrazione()ÆElemento Vettore V se inizio = fine allora coda è vuota altrimenti inizio = ( inizio + 1 ) mod MAX ritorna V[inizio] 5.2 Implementazione tramite lista L’utilizzo della struttura dati lista concatenata richiede l’utilizzo di due variabili di tipo puntatore:inizio e fine. Esse faranno riferimento rispettivamente al primo ed all'ultimo elemento della coda. Inizio Fine a1 a2 ... an null Figura 5.5.- Coda realizzata con lista concatenata Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI Liste Pile e Code Pag. 17/18 I quattro stati della coda sono pertanto: 1. Stato iniziale: inizio = fine = NULL 2. Coda vuota: inizio = NULL 3. Coda non vuota: inizio ≠ fine 4. Coda piena: Non applicabile, la struttura dati è dinamica Inserimento Diamo luogo all’operazione di inserimento allocando un nuovo record, linkando quest’ultimo al record puntato da fine ed infine spostando il puntatore fine verso l’ultimo elemento inserito. L’operazione di inserimento ha un tempo di esecuzione costante. Inserimento Coda – Lista concatenata _ Inserisci(Elemento e) { Puntatore p p = alloca record pÆchiave = e pÆnext = NULL fineÆnext = p fine = p } Estrazione Diamo luogo all’operazione di estrazione spostando il puntatore inizio e deallocando il record precedentemente puntato da inizio e contenente quindi il valore da estrarre. Estrazione Coda – Lista concatenata Estrazione()ÆElemento { Puntatore p Elemento e se inizio = NULL allora coda è vuota altrimenti p = inizio inizio = inizioÆnext e = pÆchiave dealloca p ritorna e Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI _ Liste Pile e Code Pag. 18/18 } Appunti Laboratorio di Algoritmi e Strutture Dati – Prof. Maurizio GIACCI