23.10.20021 Strutture di dati F. Bombi 23 ottobre 2002.

Slides:



Advertisements
Presentazioni simili
Java Stream e File. La classe File Per operare con lintero file java mette a disposizione la classe File Per utilizzare la classe File è necessario importare.
Advertisements

Tipi di dato astratti Lista, Pila, Coda, Albero.
Estendere i linguaggi: i tipi di dato astratti
Strutture dati lineari
Capitolo 8 Array Lucidi relativi al volume: Java – Guida alla programmazione James Cohoon, Jack Davidson Copyright © The McGraw-Hill Companies srl.
1 Astrazioni sui dati : Specifica ed Implementazione di Tipi di Dato Astratti in Java.
LIP: 4 Aprile 2008 ECCEZIONI. Eccezioni Come si definiscono eccezioni Come si lanciano Come si gestiscono (gestione esplicita o di default)
Liste di Interi Esercitazione. Liste Concatenate Tipo di dato utile per memorizzare sequenze di elementi di dimensioni variabile Definizione tipicamente.
MultiSet, Liste Ordinate
Le gerarchie di tipi.
LIP: 19 Aprile Contenuto Soluzione Compitino Tipo di dato MultiSet, estensione con sottoclasse.
1 Le gerarchie di tipi: implementazioni multiple e principio di sostituzione.
Liste Ordinate 3 Maggio Ultima Lezione Abbiamo visto i tipi di dato astratti IntList e StringList Realizzano liste di interi e di stringhe Realizzati.
LIP: 1 Marzo 2005 Classe Object e Vettori. Partiamo da Lesercizio dellultima esercitazione realizzato tramite array Vedremo come si puo fare in modo piu.
Lez. 121 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Progettazione.
Strutture dati elementari
Fondamenti di Informatica II Ingegneria Informatica / Automatica (A-I) Meccanica Prof. M.T. PAZIENZA a.a – 3° ciclo.
Fondamenti di Informatica II Ingegneria Informatica / Automatica (A-I) Meccanica Prof. M.T. PAZIENZA a.a – 3° ciclo.
1 Corso di Laurea in Biotecnologie Informatica (Programmazione) Introduzione a JAVA Anno Accademico 2009/2010.
Astrazioni sul controllo Iteratori. 2 Nuove iterazioni Definendo un nuovo tipo come collezione di oggetti (p. es., set) si vorrebbe disporre anche di.
Scomposizione di stringhe
1 Le gerarchie di tipi. 2 Supertipi e sottotipi 4 un supertipo –class –interface 4 può avere più sottotipi –un sottotipo extends il supertipo ( class.
JAVA Franco Bombi 8 ottobre FB Introduzione Java è un linguaggio di impiego generale, basato su classi e orientato agli oggetti Java.
Esercizio 10.* Un cassiere vuole dare un resto di n centesimi di euro usando il minimo numero di monete. a) Descrivere un algoritmo goloso per fare ciò.
30 ottobre Mergesort F. Bombi 30 ottobre 2002.
Corso di informatica Athena – Periti Informatici
1 Astrazioni sui dati : Ragionare sui Tipi di Dato Astratti dispense prof. G. Levi.
Fopndamenti di programmazione. 2 La classe String Una stringa è una sequenza di caratteri La classe String è utilizzata per memorizzare caratteri La classe.
Corso JAVA Lezione n° 11 Istituto Statale di Istruzione Superiore “F. Enriques”
Conversione da base 10 a base X (Utilizzo della pila)
Complessità di un algoritmo
Astrazione procedurale ed eccezioni
Nota (rif. alla lezione precedente): Vector vs ArrayList Le classi generiche Vector e ArrayList sono sostanzialmente equivalenti, ma: I metodi.
CORSO DI PROGRAMMAZIONE II Lezione 22
s STRINGHE DI CARATTERI a p e \0
Ingresso e uscita in Java F. Bombi 10 ottobre 2002.
1 Record, tabelle, relazioni F. Bombi 1 novembre 2001.
Fondamenti di informatica Oggetti e Java Luca Cabibbo Luca Cabibbo – Fondamenti di informatica: Oggetti e Java Copyright © 2004 – The McGraw-Hill Companies.
1 novembre I nomi in Java F. Bombi 1 novembre 2002.
Heap concetti ed applicazioni. maggio 2002ASD - Heap2 heap heap = catasta condizione di heap 1.albero binario perfettamente bilanciato 2.tutte le foglie.
Esercitazione su Vector. Permette di definire collezioni di dati generiche, che sono in grado di memorizzare elementi di ogni sottotipo di Object Definito.
AlgoLab - Pile e Code Pile e code Laboratorio di Algoritmi 02/03 Prof. Ugo de’ Liguoro.
Capitolo 6 Iterazione Lucidi relativi al volume: Java – Guida alla programmazione James Cohoon, Jack Davidson Copyright © The McGraw-Hill Companies.
Eccezioni Metodi parziali Eccezioni: Usi e Metodi parziali Eccezioni: rimuovere i requires Eccezioni: rimuovere i requires Eccezioni: definizione, sollevamento,
1 Tipi di Dato §descrittori, tipi, controllo e inferenza dei tipi §specifica (semantica) e implementazione di tipi di dato l implementazioni “sequenziali”
1 Eccezioni in Java. 2 Ricordiamo che 4 una procedura può terminare –normalmente, ritornando un risultato –in modo eccezionale ci possono essere diverse.
13 ottobre Decisioni F. Bombi 13 ottobre 2002.
Liste di Interi Esercitazione. Una variante Liste concatenate di Integers Non modificabile Costruttori per creare la lista vuota o un nodo Metodi d’istanza.
Liste Concatenate 11 Aprile E’ una delle strutture dati fondamentali in tutti i linguaggi di programmazione di alto livello Una Lista Concatenata.
Liste di Interi Esercitazione. IntList Lista di interi Una lista è una disposizione ordinata di elementi ( non in modo crescente-descrescente, ma per.
1 Gerarchie e polimorfismo: liste. 2 Generalizzare le liste di interi  List 4 lista di oggetti –non modificabile 4 vorremo poi definire un sottotipo.
1 Laboratorio di Introduzione alla Programmazione §II MODULO §3 crediti §Esame e voto unico (su 6 crediti totali)
Ese 3 (del 3 Aprile 2003). Testo Progettare la specifica e l’implementazione del tipo di dato astratto modificabile Stack, supponendo che gli elementi.
Ese 3 (del 3 Aprile 2003). Testo Progettare la specifica e l’implementazione del tipo di dato astratto modificabile Stack, supponendo che gli elementi.
LIP: 22 Marzo 2005 Eccezioni. Eccezioni-Richiami Come si definiscono eccezioni Come si lanciano Come si gestiscono (gestione esplicita o di default)
LIP: 2 Maggio 2008 Classi Astratte. Cos’e’ una Classe Astratta una classe astratta e’ un particolare tipo di classe permette di fornire una implementazione.
Sommario Oggetti immutabili e non Tipi Primitivi: String, Arrays.
Ese 1 e 3 (del 6 Aprile 2005). Primo Ese Si identifichino gli errori che il compilatore segnalerebbe per il seguente programma Tipi Legami tra dichiarazioni.
LIP: 11 Maggio 2007 Classi Astratte. Cos’e’ una Classe Astratta una classe astratta e’ un particolare tipo di classe permette di fornire una implementazione.
Liste Concatenate 28 Marzo Avviso Martedi’ 4 Aprile: Verifica di LIP Per iscriversi (obbligatorio) inviare un e- mail entro venerdi’ 31 Marzo a.
1 Un esempio con iteratore: le liste ordinate di interi.
LIP: 4 Maggio 2007 Interfacce. Cos’e’ una Interfaccia una interfaccia e’ un particolare tipo di classe contiene solo la specifica non ha implementazione.
LIP: 15 Marzo 2005 Vettori di interi. Esercizio proposto Definire una classe VectorInt i cui oggetti sono vettori omogenei di interi ordinati in modo.
LIP: 18 Aprile 2008 Interfacce. Rappresentazione Lista val next vuota Lista vuota: any true Lista non vuota: any true 154 false 24 false.
Corso di Algoritmi e Strutture Dati con Laboratorio Java Collections Framework (II parte)
1 Metodo I metodi sono uno strumento che i programmatori usano per strutturare i programmi, sia per renderli più facili da capire che per permettere il.
Lezione n. Parole chiave: Corso di Laurea: Insegnamento: Docente: A.A Salvatore Cuomo Strutture dati di tipo astratto 19 Strutture dati,
Eccezioni in Java. Le eccezioni in Java Exception handling: insieme di costrutti e regole sintattiche e semantiche presenti nel linguaggio allo scopo.
1 MODULO STRUTTURE DATI FONDAMENTALI: Strutture dinamiche classe 4° INDUSTRIALE INFORMATICA Focus on.
Transcript della presentazione:

Strutture di dati F. Bombi 23 ottobre 2002

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)

Tipi di dati astratti Liste – Stack – Code Alberi (solo definizioni generali) – Alberi binari – Alberi binari di ricerca Tabelle hash Dizionari

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)

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

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

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

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

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

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 (); }

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

Inserimento di un elemento in uno stack 2 primo secondo libero pointer primo secondo terzo Inserzione di “terzo” libero terzo array pointer

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

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

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)

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…)

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(); }

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

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)

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

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

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…

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 “

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

Interface Coda public interface Coda { void accoda (Object x); Object togli () throws Underflow; Object testa () throws Underflow; boolean vuota (); }

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

Coda in un vettore 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 …

Vettore chiuso ad anello 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)

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

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?

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

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

Lista in un vettore eliminazione di un oggetto 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

Inserzione di un oggetto primoultimo X X

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

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

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

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

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

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

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

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

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

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

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

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 () { … } }

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) { …} }

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

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)

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

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

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?

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

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.*;

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

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

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

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

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

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

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

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

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

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; }

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.*;