Scaricare la presentazione
La presentazione è in caricamento. Aspetta per favore
PubblicatoVincenza Sole Modificato 9 anni fa
2
23.10.20021 Strutture di dati F. Bombi 23 ottobre 2002
3
23.10.20022 Strutture di dati e tipi di dati astratti Considereremo un piccolo numero di strutture di dati e di algoritmi che risolvono problemi di interesse generale che le utilizzano Le strutture di dati saranno in genere prima descritte come tipi di dati astratti prescindendo dalla loro realizzazione mediante interfacce Java Per ogni tipo di dato astratto verranno poi discusse una o più classi che lo realizzano (passando dall’astrazione alla concretezza)
4
23.10.20023 Tipi di dati astratti Liste – Stack – Code Alberi (solo definizioni generali) – Alberi binari – Alberi binari di ricerca Tabelle hash Dizionari
5
23.10.20024 Liste Le liste possono essere considerate come contenitore di tipo dinamico Consideriamo solo liste semplici composte da elementi atomici tutti dello stesso tipo In modo formale una lista è definita ricorsivamente come: – Lista vuota se non contiene nessun elemento – Composta da una testa (che contiene il primo elemento) e da un resto (la lista privata del primo elemento, lista che può essere vuota)
6
23.10.20025 Operazioni elementari sulle liste l a Qualsiasi operazione su di una lista può essere definite in funzione di 4 operazioni primitive. Indicando con l una lista e con a un elemento atomico abbiamo: – vuota(l) : restituisce un booleano vero se la lista è vuota, falso in caso contrario – testa(l) : restituisce il valore del primo elemento della lista (che non deve essere vuota) – resto(l) : restituisce il valore della lista (che non deve essere vuota) privata del primo elemento – crea(a, l) : restituisce una lista composta dalla lista l alla quale è stato aggiunto in testa l’atomo a È un errore cercare di applicare le primitive testo() o resto() ad una lista vuota
7
23.10.20026 Proprietà delle liste Due liste sono uguali se: – Sono ambedue vuote – Oppure hanno testa uguale e (ricorsivamente) resto uguale Indicando con a un elemento atomico e con l una lista (anche vuota) è sempre vero che: – testa(crea(a,l)) è uguale ad a – resto(crea(a,l)) è la lista l
8
23.10.20027 Altre operazioni su liste testa - resto Utilizzando le quattro primitive e le definizioni date è facile definire altre operazioni sulle liste, ad esempio: La lunghezza di una lista è definita in forma ricorsiva come: – Zero se la lista è vuota – Altrimenti la lunghezza è 1 + la lunghezza del resto Ultimo elemento di una lista (non vuota): – Testa se il resto della lista è vuoto – Altrimenti è l’ultimo elemento del resto della lista Crea costruisce la lista aggiungendo nuovi elementi in testa, per aggiungere elementi in coda possiamo definire accoda (che aggiunge in coda alla lista l l’atomo a) come: – Se la lista l è vuota accoda è crea di a e l – Altrimenti accoda è crea della testa di l e di accoda di a al resto di l
9
23.10.20028 Liste con accesso limitato Prima di studiare in maggior dettaglio come si possano definire e realizzare liste di tipo generale (nelle quali è possibile accedere a qualsiasi elemento, togliere e inserire in un punto qualsiasi della lista), analizziamo due tipi di liste nelle quali l’accesso è limitato agli estremi. – Stack: con accesso ad un solo estremo della lista – Coda: con inserzione da un lato ed estrazione dell’altro
10
23.10.20029 Stack (pila o catasta) Uno stack è una lista nella quale si ha accesso solo all’elemento che si trova ad un estremo della lista (detta la testa dello stack) Le due operazioni fondamentali possibili in uno stack sono – Push: inserisci un nuovo elemento in testa allo stack – Pop: estrai l’ultimo elemento inserito nello stack (che ovviamente non può essere vuoto) Lo stack realizza una disciplina di inserzione e di estrazione degli elementi di tipo LIFO (Last In First Out): esce per primo l’ultimo elemento inserito nello stack Un disciplina LIFO è richiesta per la gestione di tutte le operazioni che possono essere nidificate e nella ricorsione per cui la gestione di stack è la base della soluzione di moltissimi problemi
11
23.10.200210 L’interfaccia: Stack Uno stack conterrà dati di un certo tipo, per cui dovremmo parlare di stack di interi, di stringhe, …. In Java converrà pensare sempre a stack di oggetti sfruttando poi le capacità del linguaggio per gestire oggetti di un prefissato tipo Uno stack può essere definito in modo astratto dalla seguente interfaccia Java: public interface Stack { void push (Object x); Object pop () throws Underflow; Object testa () throws Underflow; boolean vuoto (); }
12
23.10.200211 Realizzazione di uno stack La tecnica usata più di frequente per realizzare uno stack è quella di utilizzare un array di oggetti ed un cursore (detto stack pointer) per memorizzare la posizione della testa dello stack nell’array Se supponiamo che lo stack pointer indichi il primo elemento libero dell’array oltre la testa, il valore dello stack pointer coincide con il numero di elementi presenti nello stack
13
23.10.200212 Inserimento di un elemento in uno stack 2 primo secondo libero pointer primo secondo terzo 2 + 1 Inserzione di “terzo” libero terzo array 0 1 2 3 4 5 pointer 0 1 2 3 4 5
14
23.10.200213 Estrazione di due elementi dallo stack primo libero 3 – 2 = 1 estrazione di due oggetti escono: “terzo” e “secondo” 3 primo secondo terzo pointer libero terzo secondo pointer 0 1 2 3 4 5 0 1 2 3 4 5
15
23.10.200214 public class StackAr implements Stack { private Object[] v; private int sp; private static final int MAX = 10; public StackAr (){ sp = 0; v = new Object[MAX]; } public StackAr (int max) { sp = 0; v = new Object[max]; } public void push (Object x) { v[sp++] = x; } public Object pop () throws Underflow { if (sp == 0) throw new Underflow("Pop di stack vuoto"); else return v[--sp]; } public Object testa () throws Underflow { if (sp == 0) throw new Underflow("Testa di stack vuoto"); else return v[sp-1]; } public boolean vuoto () { return sp == 0; } } NB: la realizzazione non gestisce l’overflow del vettore v Importante: Tutti i metodi hanno complessità temporale O(1), in altri termini le operazioni su di uno stack richiedono un tempo indipendente dalle dimensioni dello stack
16
23.10.200215 Attenzione al package java.util Nel package java.util di Java esiste la classe Stack simile alla classe StackAr dotata dei seguenti metodi: – boolean empty () – Object peek () – Object pop () – Object push (Object item) – int search (Object o)
17
23.10.200216 Programma di prova Per provare la classe scriviamo un piccolo programma che legga un testo una riga alla volta, inserisca ciascuna riga in uno stack e poi vuoti lo stack trasferendo le riga in un secondo stack. Lo stack sarà poi vuotato trasferendo le righe in uscita nello stesso ordine nel quale sono state lette Il programma di prova verifica anche che testa() e pop() da uno stack vuoto producono un eccezione Esercizio: un numero eccessivo di push() produce un overflow dello stack, modificare il codice in modo da gestire questa evenienza allungando l’array quando necessario (e accorciandolo…)
18
23.10.200217 public class ProvaSA { public static void main (String[] arg) throws IOException { StackAr s1 = new StackAr(); StackAr s2 = new StackAr(); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String str = in.readLine(); while (str != null) { s1.push(str); str = in.readLine(); } while (!s1.vuoto()) { System.out.println(s1.testa()); s2.push(s1.pop()); } while (!s2.vuoto()) System.out.println(s2.pop()); try { s2.testa(); } catch (Underflow e) { System.out.println("Underflow di testa"); } s2.pop(); }
19
23.10.200218 Cosa occorre per la prova Abbiamo bisogno di mettere nel directory di lavoro i file – Underflow.javaUnderflow.class – Stack.javaStack.class – StackAr.javaStackAr.class – ProvaSA.java public class Underflow extends RuntimeException { public Underflow (String messaggio) {super(messaggio);} } Exception
20
23.10.200219 Esercizio: Parentesi In un programma scritto in Java compaiono tre tipi di parentesi – Tonde () – Quadre [] – Graffe {} Ciascun tipo di parentesi racchiude costrutti che possono essere nidificati, ad ogni parentesi aperta deve corrispondere una parentesi chiusa dello stesso tipo, vogliamo scrivere un programma che verifichi che le parentesi siano disposte in modo corretto (per semplicità supponiamo che il testo non contenga stringhe di caratteri e commenti)
21
23.10.200220 Soluzione 1 Una soluzione minimale potrebbe essere quella di gestire tre contatori, inizializzati a zero, che vengono incrementati ad ogni parentesi aperta e decrementati ad ogni chiusa. I contatori non devono mai assumere valori negativi. Al termine del testo si controlla che i contatori siano tornati a zero Anche se un testo corretto supera il test è facile verificare che anche un testo del tipo: [(]) supererebbe il test pur essendo scorretto
22
23.10.200221 Soluzione 2 Una soluzione adeguata si realizza nel modo seguente (utilizzando uno stack): – scandire il testo un carattere alla volta – quando si incontra una parentesi aperta inserirne il valore nello stack – quando si incontra una parentesi chiusa estrarre un elemento dallo stack e verificare che faccia il paio con la parentesi chiusa – al temine del testo lo stack deve essere vuoto
23
23.10.200222 public class Parentesi { public static void main (String[] arg) throws IOException { FileInputStream in = new FileInputStream(arg[0]); StackAr st = new StackAr(); int c = 0; int riga = 1; final String APRE = "([{"; final String CHIUDE = ")]}"; while ((c = in.read()) != -1) { if (c == '\n') riga++; else if (APRE.indexOf(c) != -1) st.push(new Integer(c)); else if (CHIUDE.indexOf(c) != -1) { if (st.vuoto()) System.out.println("Alla riga " + riga + " " + (char)c + " non bilanciata"); else { int x = ((Integer)st.pop()).intValue(); if (CHIUDE.indexOf(c) != APRE.indexOf(x)) System.out.println("Alla riga " + riga + " " + (char)c + " non bilancia " + (char)x); } Segue…
24
23.10.200223 if (!st.vuoto()) { System.out.print("Parentesi non chiuse: "); while (!st.vuoto()) System.out. print((char)((Integer)st.pop()).intValue() + " "); System.out.println(); } Segue… Il programma non gestisce correttamente stringhe e commenti. Esercizio: modificare il programma in modo da escludere dal controllo i commenti e le stringhe. Suggerimento: mantenere una variabile di stato che vale zero: quando si legge il testo 1 (uno): quando si entra in un commento di tipo “//” torna a zero quando la riga finisce 2 (due): quando si entra in un commento di tipo “/*” e torna a zero quando si incontra il token “*/” 3 (tre): quando si entra in una stringa (si incontra il carattere “) e torna a zero quando si incontra il carattere “
25
23.10.200224 Coda (queue) Un coda è una lista nelle quale si possono inserire elementi solo alla fine ed estrarre elementi solo dalla testa (o viceversa) Le due operazioni sono indicate come – accoda (enqueue) – togli dalla coda (dequeue) possibile solo se la coda non è vuota La coda realizza uno disciplina di inserzione ed estrazione di tipo FIFO (First In First Out) Code sono generalmente utilizzate per contenere dati in attesa per essere elaborati, oppure eventi in attesa di essere serviti
26
23.10.200225 Interface Coda public interface Coda { void accoda (Object x); Object togli () throws Underflow; Object testa () throws Underflow; boolean vuota (); }
27
23.10.200226 Realizzazione di una coda Come per uno stack la soluzione più semplice ed efficiente (overflow a parte) per realizzare una coda è quella di utilizzare un vettore e alcune variabili ausiliare per tenere conto della posizione occupata dalla testa e dalla coda della coda. In questo modo si evita di dover spostare gli elementi presenti nella coda all’estrazione
28
23.10.200227 Coda in un vettore 01234567891011121314 MAX = 15 A BC D testa coda Convenzioni: testa e coda inizializzati a 0 coda indica la prima cella libera testa indica il primo elemento in coda Si potrebbe dire anche che testa==coda indica che la coda è vuota ma c’è un problema …
29
23.10.200228 Vettore chiuso ad anello 01234567891011121314 Per chiudere il vettore ad anello basta pensare che l’elemento di indice 0 segua l’elemento di indice n-1 Per distinguere la coda vuota dall’overflow è necessario usare un variabile ausiliaria (o rinunciare ad una cella)
30
23.10.200229 public class CodaAr implements Coda { private Object[] v; private int testa; private int coda; private int taglia; private static final int MAX = 10; public CodaAr () { testa = coda = taglia = 0; v = new Object[MAX]; } public CodaAr (int max) { testa = coda = taglia = 0; v = new Object[max]; } public void accoda (Object x) { if (taglia == v.length) throw new Overflow("Accoda in coda piena"); v[coda++] = x; if (coda == v.length) coda = 0; taglia++; } segue… Nota: a differenza di quanto fatto per gli stack in questo caso è necessario gestire esplicitamente l’overflow
31
23.10.200230 public Object togli () throws Underflow { if (taglia == 0) throw new Underflow("Togli da coda vuota"); Object tmp = v[testa]; testa = (++testa)%v.length; taglia--; return tmp; } public Object testa () throws Underflow { if (taglia == 0) throw new Underflow("Testa da coda vuota"); return v[testa]; } public boolean vuota () { return taglia == 0; } } segue… NB: tutte le primitive richiedono un tempo costante indipendente dalla lunghezza della coda Esercizio: modificare il codice in modo che non si verifichi mai l’overflow, aumentano in mode automatico la dimensione dell’array. Domanda: cosa succede del tempo?
32
23.10.200231 Liste Stack e code sono liste nelle quali l’accesso è limitato ad una o ambedue le estremità. In una lista in generale vogliamo poter accedere a qualsiasi elemento in funzione della sua posizione nella lista, nota la posizione di un elemento della lista vogliamo poter passare alla posizione dell’elemento successivo ( o dell’elemento precedente), vogliamo infine poter togliere e inserire un elemento in una posizione qualsiasi
33
23.10.200232 Lista in un vettore È certamente possibile rappresentare una lista mediante un vettore di oggetti come abbiamo fatto per stack e code posizioneintero Una prima difficoltà nasce per rappresentare la posizione nella lista. Possiamo usare un intero ma in questo modo dobbiamo esporre all’utente i dettagli realizzativi della nostra classe e questo viola i paradigmi della programmazione ad oggetti tempo proporzionale alla lunghezza La difficoltà principale deriva però dal fatto le operazioni di inserzione o eliminazione richiedono un tempo proporzionale alla lunghezza della lista
34
23.10.200233 Lista in un vettore eliminazione di un oggetto 01234567891011121314 primoultimo Per rappresentare una lista in un vettore possiamo utilizzare due cursori che indicano l’inizio e la fine della parte in uso per la lista
35
23.10.200234 Inserzione di un oggetto 01234567891011121314 primoultimo X X
36
23.10.200235 Quanto tempo occorre? n Come al solito siamo interessati a stimare quanto tempo occorre per inserire o togliere un elemento da una lista di n elementi rappresentata su di un vettore. Il tempo è proporzionale al numero di mosse Risposta: 1 – Se siamo ottimisti: un tempo costante proporzionale a 1 n – Se siamo pessimisti: un tempo proporzionale a n – Se siamo degli statistici: un tempo proporzionale alla media di tutti i casi possibili T(n)=1/n i=n(n+1)/(2n)=(n+1)/2 n/2
37
23.10.200236 Uso di una catena L’uso di un vettore quale supporto di una struttura di dati dinamica soffre del possibile overflow condizione alla quale si può porre rimedio solo attraverso l’allocazione di un nuovo vettore e la copiatura dei dati presenti nella struttura e rende onerose le operazioni di inserzione e di eliminazione di un elemento in posizione qualsiasi linked list lista concatenata catena Questi limiti possono essere superati utilizzando, quale supporto, una struttura dinamica detta linked list (letteralmente lista concatenata) che noi però chiameremo catena per non generare confusione con il concetto di lista che è un’astrazione realizzabile con una catena ma anche con un array
38
23.10.200237 Come realizzare una catena Una catena può essere realizzata utilizzando oggetti composti ciascuno da due campi, il primo sarà utilizzato per contenere il riferimento all’elemento e il secondo sarà un riferimento alla prossima cella della catena class Cella { Object atomo; Cella prossima; Cella (Object x, Cella p) { atomo = x; prossima = p; } Cella () { atomo = prossima = null; } } null Possiamo assumere che l’ultima cella contenga un riferimento null per indicare che non esiste una cella successiva
39
23.10.200238 Lista in una catena a p Una cella di una catena Una lista: (A, B, C, D) a p a p a p a p null A B C D INIZIOFINE
40
23.10.200239 Realizzazione di uno stack public class StackLc implements Stack { private class Cella { Object atomo; Cella prossima; Cella (Object x, Cella p) { atomo = x; prossima = p; } } private Cella testa; public void push (Object x) {testa = new Cella(x, testa);} public Object pop () throws Underflow { if (testa == null) throw new Underflow("Pop di stack vuoto"); else { Object tmp = testa.atomo; testa = testa.prossima; return tmp; } Non occorre il costruttore quello di dafault va bene
41
23.10.200240 public Object testa () throws Underflow { if (testa == null) throw new Underflow("Testa di stack vuoto"); else return testa.atomo; } public boolean vuoto () { return testa == null; } } segue.. Convenzioni Convenzioni: Cella null lo stack vuoto è rappresentato da un riferimento a Cella null la catena di celle va dalla testa verso l’elemento più vecchio l’ultima cella è completata con un puntatore null La cella con cui viene costruita la catena è privata, è definita all’interno della classe StackLc per cui l’utente della classe non conosce i dettagli realizzativi
42
23.10.200241 Stack vuoto public void push (Object x) { testa = new Cella(x, testa); } null a p A testa a p B a p C public Object pop () { Object tmp = testa.atomo; testa = testa.prossima; return tmp; } tmp testa
43
23.10.200242 Operazioni in posizione qualsiasi Inserire e togliere in testa ad una catena è facile, la flessibilità della struttura di dati si vede però quando si vogliano eseguire operazioni di inserzione o eliminazione di elementi in un punto qualsiasi della lista
44
23.10.200243 Inserzione di un elemento Una lista: (A, B, C, D) a p a p a p a p null A B C D INIZIOFINE Nuova lista: (A, B, X, C, D) p a p X tmp tmp = new Cella(X, p.prossima); p.prossima = tmp; Per inserire un elemento è necessario conoscere la posizione dell’elemento precedente
45
23.10.200244 Eliminazione di un elemento Una lista: (A, B, C, D)Nuova lista: (A, B, D) a p a p a p a p null A B C D INIZIOFINE p p.prossima = p.prossima.prossima; a p C Per eliminare un elemento è necessario conoscere la posizione dell’elemento precedente
46
23.10.200245 Liste e iteratori liste L’utilizzo di liste dotate delle sole primitive crea, testa, resto sarebbe inaccettabile dal punto di vista dell’efficienza per cui di solito si definiscono liste dotate di un repertorio di primitive molto più ricco posizione Sino ad ora abbiamo lasciato nel vago come rappresentare la posizione di un elemento di una lista posizione Per rispettare i principi della programmazione ad oggetti una lista viene definita in un pacchetto mediante classi amiche una per costruire la lista l’altra (detta iteratore) per muoversi nella lista senza dover conoscere i dettagli realizzativi e per compiere operazioni ad una determinata posizione iteratoreastrazione Un iteratore è un’astrazione del concetto di posizione che nasconde i dettagli della sua realizzazione all’utente
47
23.10.200246 public class Lista { public Lista () { … } public int size () { … } public void addFirst (Object x) { … } public void addLast (Object x) { … } public Object getFirst () { … } public Object getLast () { … } public Object removeFirst () { … } public Object[] toArray () { … } public String toString () { … } }
48
23.10.200247 public class Iteratore { public Iteratore (Lista x) { … } public Iteratore (Iteratore x) { … } public void add (Object x) { … } public boolean hasNext () { … } public void next () { … } public Object get () { … } public void remove () { … } public void set (Object x) { …} }
49
23.10.200248 Realizzazione in un vettore Per realizzare una lista possiamo utilizzare come contenitore degli elementi un vettore Potremmo pensare di riempire solo in parte il vettore (come abbiamo fatto per stack e code) Se pensiamo che le operazioni principali siano quelle di inserzione o eliminazione di un elemento qualsiasi, dato che questo richiede di spostare, in media, la metà degli elementi della lista si è ritenuto accettabile copiare ogni volta l’intero array Si è quindi fatta la convenzione di utilizzare sempre un vettore di dimensioni pari alla taglia della lista
50
23.10.200249 package vettore; import java.util.NoSuchElementException; public class Lista { Object[] atomo; public Lista () { atomo = new Object[0]; } public int size () { return atomo.length; } public void addFirst (Object x) { Object[] tmp = new Object[atomo.length+1]; tmp[0] = x; for (int i = 0; i < atomo.length; i++) tmp[i+1] = atomo[i]; atomo = tmp; } …. } package vettore; import java.util.NoSuchElementException; public class Iteratore { private int posizione; private Lista l; public Iteratore (Lista x) { l = x; posizione = 0; } public boolean hasNext () { if (posizione == l.atomo.length) return false; else return true; } public void next () { if (!hasNext()) throw new NoSuchElementException ("non c'e'"); posizione++; } … } segue La lista è conservata in un array di dimensioni esatte. L’array viene copiato ad ogni operazione in modo da poter aggiustare le dimensioni L’iteratore conosce la lista e conserva la posizione corrente con un cursore. Lista e itaratore devono condividere un campo (il vettore) che quindi non può essere né pubblico né privato (default = amico nell’ambito del pacchetto)
51
23.10.200250 Elementi ripetuti Data una lista di oggetti, vogliamo eliminare dalla lista eventuali elementi ripetuti in modo che, per ogni valore, ci sia al più un elemento presente Usiamo il seguente algoritmo sia p la posizione del primo elemento della lista mentre p non è alla fine della lista sia q la posizione dell’elemento che segue p mentre q non è alla fine della lista se l’elemento p è uguale all’elemento q eliminare q altrimenti avanzare q al prossimo elemento avanzare p al prossimo elemento
52
23.10.200251 import vettore.*; import java.io.*; public class EliminaDoppi { public static void main (String[] arg) throws IOException { BufferedReader in = new BufferedReader(new FileReader(arg[0])); String str; Lista l = new Lista(); Iteratore p = new Iteratore(l); while ((str = in.readLine()) != null) l.addLast(str); System.out.println("Dati di ingresso, taglia " + l.size()); while (p.hasNext()) { System.out.println(p.get()); p.next(); } System.out.println("Elimina doppi"); Lettura dei dati da un file e Stampa dei dati per verifica segue
53
23.10.200252 p = new Iteratore(l); while (p.hasNext()) { Comparable tmp = (Comparable)p.get(); p.next(); Iteratore q = new Iteratore(p); while (q.hasNext()) { if (tmp.compareTo(q.get()) == 0) q.remove(); else q.next(); } for (p = new Iteratore(l); p.hasNext(); p.next()) System.out.println(p.get()); } L’algoritmo in pseudocodice: sia p la posizione del primo elemento della lista mentre p non è alla fine della lista sia q la posizione dell’elemento che segue p mentre q non è alla fine della lista se l’elemento p = q eliminare q altrimenti avanzare q al prossimo elemento avanzare p al prossimo elemento Domanda: quale è la complessità temporale dell’algoritmo? È possibile fare meglio?
54
23.10.200253 Come costruire un package Per costruire un package cioè un insieme di classi correlate che forniscano un determinato servizio utilizziamo il seguente procedimento (semplificato) che assume che il package sia un sottopaccheto di quello di dafault (senza nome) composto dalle classi inserite della cartella (directory) di lavoro vettorecatenaNella cartella di lavoro creiamo una cartella con il nome del pacchetto, nel nostro caso vettore oppure catena Inseriamo nella cartella i file che contengono i componenti del pacchetto, nel nostro caso Lista.javaLista.java Iteratore.javaIteratore.java Inseriamo nella cartella di lavoro la classe o le classi che importano il pacchetto, nel nostro caso EliminaDoppi.javaEliminaDoppi.java
55
23.10.200254 Il package lista Directory di lavoro vettoreEliminaDoppi.java Lista.java Itearatore.java I file componenti il pacchetto vettore vettore devono contenere come prima riga la dichiarazione package vettore; I file che utilizzano il pacchetto devono importarlo con import vettore.*; Se vogliamo semplificare le cose rinunciando all’incapsulamento fornito dal pacchetto basta inserire tutti i file nel directory di lavoro ed eliminare le dichiarazioni package lista; package lista; e l’istruzione import lista.*; import lista.*;
56
23.10.200255 Lista e Iteratore in una catena O(n) La realizzazione delle classi Lista e Iteratore mediante un array sono perfettamente funzionanti secondo le specifiche ma alcuni metodi hanno una complessità temporale O(n) catena O(1) Utilizzando una catena di celle è possibile realizzare le due classi in modo che tutti i metodi abbiano complessità O(1) Abbiamo visto che per inserire o togliere un elemento da una catena è necessario disporre di un riferimento alla cella che precede l’elemento da eliminare o da inserire. Questo imporrebbe di trattare in modo diverso l’inserimento o la cancellazione del primo elemento null Per rendere più lineare la realizzazione conviene rappresentare una lista vuota mediante una cella vuota invece che un riferimento null come fatto per un array
57
23.10.200256 Lista in una catena a p a p a pnull A B D a pnull Testa a pnull null Rappresentazione di una lista vuota La posizione di un elemento è individuata da un riferimento alla cella che precede l’elemento. Un riferimento all’ultima cella fine si riferisce alla fine della lista cioè alla posizione che segue l’ultimo elemento presente nella lista Fine
58
23.10.200257 package catena; import java.util.NoSuchElementException; public class Lista { Cella testa; Cella fine; int taglia; public Lista () { testa = fine = new Cella(null, null); taglia = 0; } public int size () { return taglia; } La classe lista comprende tre campi: testa fine taglia (due riferimenti a Cella e un int) che devono essere visibili anche alla classe IteraCatena package catena; class Cella { Cella prossima; Object atomo; Cella (Object x, Cella p) { atomo = x; prossima = p; } } Cella.java La classe Cella.java ha visibilità di classe come pure i suoi campi in modo che questi sono visibili nei metodi del pacchetto “catena” segue
59
23.10.200258 public void addFirst (Object x) { taglia++; testa.prossima = new Cella(x, testa.prossima); if (testa == fine) { fine = testa.prossima; } public void addLast (Object x) { taglia++; fine.prossima = new Cella(x, fine.prossima); fine = fine.prossima; } public Object getFirst () { if (taglia == 0) throw new NoSuchElementException ("Lista vuota"); return testa.prossima.atomo; } public Object getLast () { if (taglia == 0) throw new NoSuchElementException ("Lista vuota"); return fine.atomo; } segue
60
23.10.200259 public Object removeFirst () { if (taglia == 0) throw new NoSuchElementException ("Lista vuota"); taglia--; Object tmp = testa.prossima.atomo; testa.prossima = testa.prossima.prossima; if (testa.prossima == null) fine = testa; return tmp; } public Object[] toArray () { Object[] tmp = new Object[taglia]; Cella p = testa.prossima; for (int i = 0; i < taglia; i++) { tmp[i] = p.atomo; p = p.prossima; } return tmp; } segue
61
23.10.200260 public String toString () { String tmp = ""; if (taglia == 0) return tmp; tmp += testa.prossima.atomo; Cella p = testa.prossima.prossima; for (int i = 1; i < taglia; i++) { tmp += "," + p.atomo; p = p.prossima; } return tmp; } segue
62
23.10.200261 package catena; import java.util.NoSuchElementException; public class Iteratore { Cella posizione; Lista l; public Iteratore (Lista x) { l = x; posizione = l.testa; } public Iteratore (Iteratore x) { l = x.l; posizione = x.posizione; } public void add (Object x) { l.taglia++; posizione.prossima = new Cella(x, posizione.prossima); if (l.fine == posizione) l.fine = posizione.prossima; } segue
63
23.10.200262 public boolean hasNext () { if (posizione == l.fine) return false; else return true; } public void next () { if (!hasNext()) throw new NoSuchElementException ("L'elemento non c'e'"); posizione = posizione.prossima; } public Object get () { if (!hasNext()) throw new NoSuchElementException ("L'elemento non c'e'"); return posizione.prossima.atomo; } segue
64
23.10.200263 public void remove () { if (!hasNext()) throw new NoSuchElementException ("Prima di rimuovere devi guardare"); if (posizione.prossima == l.fine) { l.fine = posizione; posizione.prossima = null; } else posizione.prossima = posizione.prossima.prossima; l.taglia--; } public void set (Object x) { if (!hasNext()) throw new NoSuchElementException ("Prima di rimuovere devi guardare"); posizione.prossima.atomo = x; } I metodi che tolgono o inseriscono elemento devono gestire oltre ai riferimenti, per mantenere l’integrità della catena, anche il campo taglia taglia (che memorizza il numero di elementi presenti) e il fine riferimento alla fine della catena quando si inserisce appunto alla fine o quando si elimina l’ultima cella segue
65
23.10.200264 public Object togli () throws NonTrovato { if (posizione.prossima == null) throw new NonTrovato("togli non presente"); Object tmp = posizione.prossima.atomo; if (posizione.prossima == l.fine) l.fine = posizione; posizione.prossima = posizione.prossima.prossima; l.taglia--; return tmp; } public void inserisci (Object x) { posizione.prossima = new Cella(x, posizione.prossima); l.taglia++; if (posizione == l.fine) l.fine = posizione.prossima; }
66
23.10.200265 Provare Catena Lista Iteratore EliminaDoppi Per provare la nuova realizzazione di Lista e Iteratore possiamo utilizzare di nuovo la classe EliminaDoppi. Per fare questo è sufficiente modificare l’importazione del pacchetto da: import vettore.*; a: import catena.*;
Presentazioni simili
© 2024 SlidePlayer.it Inc.
All rights reserved.