Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni - Luigi Ferrari - Genova
←
→
Trascrizione del contenuto della pagina
Se il tuo browser non visualizza correttamente la pagina, ti preghiamo di leggere il contenuto della pagina quaggiù
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni Rel. 1.6 9.4.2020 Luigi Ferrari
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni Indice 1. Modulo 1 - Concetti base...................................................................................................2 2. Modulo 2 - Basi di Java..................................................................................................... 3 3. Modulo 3 - Relazioni tra gli oggetti...................................................................................4 4. Modulo 4 - Strutture dati e collezioni............................................................................... 5 4.1. UD 1: Tipi di dati astratti (ADT)...................................................................................................5 4.1.1. Astrazione delle strutture dati...................................................................................................6 4.1.2. Sequenza o lista.....................................................................................................................10 4.1.3. Algoritmi base per le liste.......................................................................................................11 4.1.4. Realizzazione di una lista di oggetti.......................................................................................12 4.1.5. Alberi....................................................................................................................................... 16 4.1.6. Alberi binari di ricerca.............................................................................................................17 4.1.7. Grafi........................................................................................................................................ 21 4.1.8. Tabelle hash........................................................................................................................... 22 4.1.9. Il problema delle collisioni...................................................................................................... 23 4.1.10. Autovalutazione.....................................................................................................................24 4.2. UD 2: Collezioni e iteratori.........................................................................................................32 4.2.1. Concetto di collezione............................................................................................................ 32 4.2.2. Il Collection Framework di Java............................................................................................. 33 4.2.3. Operazioni sulle collezioni...................................................................................................... 34 4.2.4. Il pattern Iterator: un metodo standard di accesso alle collezioni.......................................... 34 4.2.5. Uso degli iteratori e il ciclo for potenziato.............................................................................. 37 4.2.6. Invalidazione degli iteratori..................................................................................................... 38 4.2.7. Rimozione e modifica di un elemento da una collezione....................................................... 39 4.2.8. Realizzazione di un iteratore.................................................................................................. 39 4.2.9. Autovalutazione.......................................................................................................................42 4.3. UD 3: Programmazione generica (generics)............................................................................ 42 4.3.1. Programmazione generica (generics).....................................................................................43 4.3.2. Metodi generici....................................................................................................................... 46 4.3.3. Convenzioni sui nomi............................................................................................................. 46 4.3.4. Autovalutazione.......................................................................................................................47 4.4. UD 4: Le classi del framework delle collezioni in Java...........................................................49 4.4.1. Gerarchia delle classi............................................................................................................. 49 4.4.2. Insiemi: l'interfaccia Set..........................................................................................................51 4.4.3. Liste ad accesso posizionale: interfaccia List........................................................................ 52 4.4.4. Code: interfaccia Queue e Deque..........................................................................................55 4.4.5. Mappe o dizionari: interfaccia Map........................................................................................ 58 4.4.6. Contains e l'uguaglianza di oggetti........................................................................................ 60 4.4.7. Collezioni di riferimenti; swallow e deep copy........................................................................61 4.4.8. Ordinamento di collezioni....................................................................................................... 62 4.4.9. La classe Collections: uso avanzato dei generics..................................................................63 -i- Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni 4.4.10. Trucchi per la manipolazione di collezioni............................................................................64 4.4.11. Collezioni sincronizzate (argomento avanzato).................................................................... 65 4.4.12. Autovalutazione.....................................................................................................................65 - ii - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni Licenza Quest'opera è stata rilasciata con licenza Creative Commons Attribuzione - Condividi allo stesso modo 3.0 Unported. Per leggere una copia della licenza visita http://creativecommons.org/licenses/by-sa/3.0/deed.it o spedisci una lettera a Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. Al link http://creativecommons.org/licenses/by-nc-sa/3.0/legalcode il testo completo della licenza di Creative Commons. Corso di OOP by Luigi Ferrari is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0). Tu sei libero: • di riprodurre, distribuire, comunicare al pubblico, esporre in pubblico, rappresentare, eseguire e recitare quest'opera • di modificare quest'opera alle seguenti condizioni: • Attribuzione: Devi attribuire la paternità dell'opera nei modi indicati dall'autore o da chi ti ha dato l'opera in licenza e in modo tale da non suggerire che essi avallino te o il modo in cui tu usi l'opera. • Condividi allo stesso modo: Se alteri o trasformi quest'opera, o se la usi per crearne un'altra, puoi distribuire l'opera risultante solo con una licenza identica o equivalente a questa. • Non commerciale: Non puoi usare quest'opera per fini commerciali. Prendendo atto che: • Rinuncia: E' possibile rinunciare a qualunque delle condizioni sopra descritte se ottieni l'autorizzazione dal detentore dei diritti. • Pubblico Dominio: Nel caso in cui l'opera o qualunque delle sue componenti siano nel pubblico dominio secondo la legge vigente, tale condizione non è in alcun modo modificata dalla licenza. Altri Diritti La licenza non ha effetto in nessun modo sui seguenti diritti: • le eccezioni, libere utilizzazioni e le altre utilizzazioni consentite dalla legge sul diritto d'autore; • i diritti morali dell'autore; • i diritti che altre persone possono avere sia sull'opera stessa che su come l'opera viene utilizzata, come il diritto all'immagine o alla tutela dei dati personali. Nota: ogni volta che usi o distribuisci quest'opera, devi farlo secondo i termini di questa licenza, che va comunicata con chiarezza. -1- Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni 4. Modulo 4 - Strutture dati e collezioni Durata: 5-6 settimane. Prerequisiti Moduli precedenti. In particolare: • Conoscere e utilizzare gli array. • Conoscere come sono gestiti gli oggetti in memoria. • Saper combinare classi attraverso il meccanismo di composizione. • Conoscere i concetti di polimorfismo e di ereditarietà Obiettivi del modulo • Conoscere le diverse tipologie di strutture dati astratte (ADT). • Conoscere alcuni algoritmi per gestire le strutture dati astratte. • Saper individuare una collezione di oggetti nelle diverse situazioni. • Scegliere tra diverse alternative quale tipo di collezione è più confacente al problema. • Conoscere e utilizzare i pattern Iterator e Composite. • Saper realizzare delle collezioni di oggetti non standard. • Saper testare un programma attraverso dei casi fissi di test. Argomenti Liste, alberi binari, tabelle hash. Pile, code, insiemi, mappe. Pattern iterator. Il framework delle collezioni. Concetto di programmazione generica (generics): motivazioni e vantaggi. 4.1. UD 1: Tipi di dati astratti (ADT) Obiettivi • Conoscere i diversi tipi di ADT, con esempi di utilizzazione pratica. • Conoscere il concetto di lista concatenata e di albero binario. • Conoscere una possibile implementazione di lista concatenata e di un albero binario. • Conoscere gli algoritmi di base per manipolare liste e alberi • Comprendere come possono essere utilizzate le liste concatenate per realizzare altre strutture dati dinamiche. • Saper disegnare una lista o un albero in base ai valori forniti • Sapere le differenze tra albero binario e albero binario di ricerca (BST) • Saper utilizzare e scrivere algoritmi ricorsivi • Saper realizzare alcuni algoritmi che utilizzano liste concatenate e alberi binari. -5- Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni Riferimenti • Horstmann AlgoritmieStruttureDati-cap04-PileECode.pdf, 28 pagg. • Sito del libro "Introduction to Programming in Java: An Interdisciplinary Approach" di Robert Sedgewick and Kevin Wayne, molto curato e con ottimi spunti http://introcs.cs.princeton.edu/java/40algorithms/ oppure https:// algs4.cs.princeton.edu/34hash/ • Gnarley Trees: sito contenente una classificazione dei diversi tipo di alberi (e non solo!) http://people.ksp.sk/ ~kuko/gnarley-trees/ E' possibile scaricare il file gt.jar che contiene l'eseguibile di una serie di animazioni. La vecchia versione (trees.jar) non è più disponibile. • Il sito dell'Università di Roma contiene appunti sui grafi molto curati, con gli algoritmi di soluzione http:// twiki.di.uniroma1.it/twiki/view/Algoritmi2/PALGappunti2014 • una dispensa con esercizi svolti su algoritmi avanzati http://www.cs.unibo.it/~donat/esercizi-svolti- algoritmi-7.pdf • Strutture dati, con appunti e slide di Alberto Montresor http://cricca.disi.unitn.it/montresor/teaching/asd/ materiale/lucidi/ • Algoritmo di Dijkstra: dispense del prof. Francesco Capezio. 4.1.1. Astrazione delle strutture dati Per la realizzazione di programmi un po' più complessi ci si è accorti ben presto che il semplice concetto di array non era sufficiente per rappresentare le diverse tipologie di dati e soprattutto che gli algoritmi basati sugli array sono spesso inefficienti. Nel contempo si sono notate alcune somiglianze tra problematiche in campi molto diversi. Per fare un esempio, la coda per fare il check-in prima di salire sull'aereo è "simile", come struttura, alla coda di stampa dei documenti inviati alla stampante. Questo ha portato alla ricerca e individuazione di strutture dati "astratte", cioè utilizzabili in ambiti diversi. L'acronimo usato è ADT, che sta per Abstract Data Type, il loro comportamento è definito da un insieme di operazioni. What Abstraction Means Abstract data types are an instance of a general principle in software engineering, which goes by many names with slightly different shades of meaning. Here are some of the names that are used for this idea: • Abstraction: Omitting or hiding low-level details with a simpler, higher-level idea. • Modularity: Dividing a system into components or modules, each of which can be designed, implemented, tested, reasoned about, and reused separately from the rest of the system. • Encapsulation: Building walls around a module (a hard shell or capsule) so that the module is responsible for its own internal behavior, and bugs in other parts of the system can't damage its integrity. • Information hiding: Hiding details of a module's implementation from the rest of the system, so that those details can be changed later without changing the rest of the system. • Separation of concerns: Making a feature (or "concern") the responsibility of a single module, rather than spreading it across multiple modules. As a software engineer, you should know these terms, because you will run into them frequently. The fundamental purpose of all of these ideas is to help achieve the three important properties that we care: safety from bugs, ease of understanding, and readiness for change. Fonte: -6- Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni http://web.mit.edu/6.005/www/fa14/classes/08-abstract-data-types/#summary Tipologie di strutture dinamiche Tra le strutture dati astratte più comuni ci sono: • sequenza o lista: struttura dati lineare, ordinata secondo un qualche criterio e gestita da politiche di accesso predefinite (LIFO, FIFO, sequenziale, random...) ; • insieme: è una struttura in cui è ininfluente l'organizzazione, ma in cui NON si possono avere "doppioni"; • tabella: come si intuisce è organizzata in righe e colonne; consente una gestione cui siamo spesso abituati; • gerarchia o albero: le informazioni sono organizzate in maniera gerarchica a partire da una "radice", da cui si dipartono due o più "figli" o "rami". All'estremo opposto vi sono le "foglie", elementi senza figli; • grafo: a differenza degli alberi la struttura non è gerarchica, per ogni elemento di informazione ci sono collegamenti con altri elementi, mono o bidirezionali, eventualmente con un costo di attraversamento; • mappa o dizionario: in questa categoria ci sono una vasta quantità di strutture differenti legate dalla presenza di elementi in cui è individuabile una caratteristica identificativa, la "chiave", unica per ciascuno degli elementi. Svolgono la funzione di rendere molto veloce l'accesso all'informazione associata, come l'indice di un libro e il CAP di un indirizzo. Nella vita reale In informatica La lista, nella sua accezione più ampia, è un elenco in cui non è importante l'ordine. In informatica ne sono esempio agli array, ma esistono anche versioni dinamiche (liste collegate). Una lista (check list) è una sequenza La coda è una struttura lineare, utilizzata in tutti i casi in cui occorre collegare in maniera lasca due o più entità che lavorano a velocità diverse: ne sono esempi la scrittura su disco, la stampa di file, la trasmissione e la ricezione di messaggi su una rete di comunicazione. Una coda. -7- Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni Nella vita reale In informatica Lo stack è una struttura lineare utilizzata molto nei calcolatori, ad esempio per la chiamata di funzioni, per la ricorsione, per il parsing di espressioni algebriche. Pila di libri La tabella, come è facilmente intuibile, compare in quasi tutte le applicazioni informatiche, ed è uno dei concetti su cui si fonda la realizzazione dei database. Tabella di un orario delle lezioni Il concetto di insieme, cioè di un aggregato di oggetti tra loro tutti diversi, spesso è associato ad altre caratteristiche. E' utilizzato nella definizione di codici, comandi, valori ammissibili. Ad esempio nelle interfacce grafiche, dove l'ordine in cui vengono impartiti i diversi comandi è scelto dall'utente, si ha un insieme di azioni possibili, ad ognuna I dischetti della tombola delle quali corrisponde l'attivazione di una sono un esempio di insieme procedura. -8- Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni Nella vita reale In informatica Un esempio di sequenza ordinata sono gli stessi programmi, dove le istruzioni vengono appunto eseguiti in sequenza. I giorni della settimana sono una sequenza ordinata L'esempio più semplice è quello della struttura delle directory del nostro hard disk, ma sono molto usati per consentire ricerche veloci nei database (una particolare tipologia, gli alberi binari di ricerca, o BST). La struttura gerarchica di un organigramma I grafi sono utilizzati ad esempio per instradare i pacchetti di dati sulla rete internet; sono uno dei fondamenti degli studi sull'intelligenza artificiale. Grafo di una rete di trasporto (fonte: http://www.metrogenova.com/pdf/ mobilita/studi/Genova2020.pdf) Le mappe, o dizionari, sono utilizzate per aumentare la velocità di accesso alle informazioni, spesso memorizzate in altre strutture dati complesse, come i database. La singola informazione è associata ad una Un QR code (o uno shorten URL https:// "chiave" e attraverso un indice o tabella hash goo.gl/bCPpqg) indirizza ad un sito l'accesso all'informazione è immediata. -9- Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni 4.1.2. Sequenza o lista Nel seguito saranno analizzate un po' più in dettaglio alcune ADT proponendone diverse realizzazioni: infatti per ogni tipologia spesso esistono diverse soluzioni, ognuna delle quali presenta vantaggi e svantaggi. Scegliere tra esse quella più adatta al nostro problema concreto è una delle competenze dello sviluppatore professionista. Iniziamo con le sequenze o liste: la realizzazione più comune è quella che utilizza gli array . Gli elementi sono tutti contigui in memoria; la dimensione è statica ma è possibile modificarla crendo array più grandi e copiandovi i dati; la gestione degli spazi vuoti, come visto in precedenza, può essere fatta con diversi criteri (sentinella, contatore, "buchi"). Gli array, gestiti con contatore nella maggior parte dei casi, di solito presentano buone prestazioni. Una soluzione alternativa è la lista concatenata, che presenta una struttura dati lineare, dove ogni elemento ("nodo") è collegato al successivo. Accedendo al primo elemento è possibile scorrere la lista per utilizzare i valori successivi. Una lista concatenata (linked list) A differenza degli array, gli elementi sono creati dinamicamente e quindi non sono tutti vicini nella memoria dell'elaboratore. Ogni elemento deve consentire di "individuare" il successivo mediante un collegamento ad esso. Una lista concatenata è definibile in maniera ricorsiva: una lista è la lista vuota oppure una coppia (x,c) dove "x" è una informazione, detta testa (head), e "c" è a sua volta una lista, detta coda (tail) della lista. Nel confronto con gli array, le liste concatenate presentano vantaggi in caso di inserimento o eliminazione di elementi in posizione qualunque della lista: la loro struttura, che cresce e diminuisce dinamicamente, non necessita infatti delle operazioni di compattamento o espansione degli array, consentendo di evitare cicli di spostamento degli elementi successivi a quello da inserire o togliere. Per contro il consumo di memoria è maggiore e gli algoritmi sono un po' più complessi. Realizzazione di una lista concatenata La ricorsione nella definizione si traduce spesso in ricorsione nei metodi che elaborano le liste; ad esempio l'aggiunta di un elemento e la somma degli elementi di una lista di numeri potrebbero essere realizzati come nella semplice classe seguente: public class ListaInt{ private int testa; private ListaInt coda; public ListaInt(int info){ - 10 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni testa = info; coda = null; } public void add(int dato){ if(coda==null) coda = new ListaInt(dato); else coda.add(dato); } public int somma(){ if (coda==null) return testa; return testa + coda.somma(); } } Non tutte le implementazioni delle liste consentono un uso diretto della ricorsione. 4.1.3. Algoritmi base per le liste Nell'esempio visto sopra gli elementi vengono inseriti sempre in coda a quelli presenti, mentre non è stato previsto alcun metodo per togliere un elemento dalla lista. Come abbiamo già detto, in altri casi le politiche di gestione possono essere diverse. Ad esempio avremo: • code: in cui l'accesso è di tipo FIFO (first in, first out), ottenuto consentendo solo l'aggiunta degli elementi in coda e l'estrazione dalla testa; • pile o stack: con accesso LIFO (last in, first out); • liste ordinate: dove gli elementi sono memorizzati in ordine in base al loro valore. Code e pile limitano le modalità di accesso alle estremità, nella lista ordinata invece gli inserimenti possono avvenire in qualunque posizione e la struttura dinamica delle liste offre i maggiori vantaggi. Esaminiamo in particolare proprio una lista ordinata, in cui occorre un criterio di ordinamento tra gli elementi per poterli inserire nella giusta posizione, modificando i collegamenti tra i diversi nodi. Inserimento di un elemento (fonte: Wikipedia) L'algoritmo di inserimento in un punto qualsiasi della lista NON VUOTA di un elemento "n" è il seguente: • portare un puntatore prec all'elemento che precede il punto di inserimento; • modificare i legami con le seguenti istruzioni: n.next = prec.next; prec.next = n; - 11 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni Questo algoritmo vale anche se si vuole inserire l'elemento in fondo alla lista (operazione di append ). Allo stesso modo la rimozione di un elemento non necessita di compattare la lista. Rimozione di un elemento (fonte: Wikipedia) L'algoritmo di eliminazione di un elemento "n" diverso dal primo da una lista è il seguente: • portare un puntatore prec all'elemento da togliere; • modificare il legame con l'istruzione prec.next = prec.next.next; Sarà compito del garbage collector eliminare dalla memoria l'elemento tolto dalla lista se non più utilizzato. Altri algoritmi molto comuni sono quelli di "attraversamento" di una lista, utilizzati ad esempio per sommare tutti gli elementi (il metodo somma() già visto), per ricercare la presenza di un elemento, oppure per stampare tutti gli elementi contenuti in essa. Negli esercizi di autovalutazione ne sono proposti alcuni. E' possibile spesso sfruttare le caratteristiche di ricorsività delle liste collegate. 4.1.4. Realizzazione di una lista di oggetti Le liste di solito sono collezioni di oggetti, o meglio di riferimenti ad oggetti. Possiamo definire un'interfaccia per evidenziarne i metodi. Una interfaccia per l'ADT Sequenza (esempi/Collezioni/Sequenza.java) /** * Una sequenza di oggetti. * * @author L. Ferrari * @version 14.4.2019 */ public interface Sequenza{ /** Aggiunge un elemento alla sequenza. Può essere inserito * in qualunque posizione, non interessa l'ordine. * @param dato dato da aggiungere */ public void add(Object dato); /** Restituisce il numero di elementi presenti nella sequenza * @return dimensione della lista */ public int size(); /** - 12 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni * Verifica se la sequenza contiene almeno un elemento * @return true se la sequenza è vuota, false altrimenti */ default public boolean isEmpty(){ return size() == 0;} /** * Restituisce l'elemento nella posizione indicata * @param pos posizione dell'elemento (0 per il primo). * Deve essere minore di size() * @return elemento in quella posizione * @throws IllegalArgumentException se pos >= size() o pos < 0 * E' una eccezione NON controllata, può non essere dichiarata * nell'interfaccia del metodo */ public Object get(int pos); /** * Toglie un elemento dalla lista * @param pos posizione dell'elemento (0 per il primo). * Deve essere minore di size(). * @return elemento tolto dalla lista * @throws IllegalArgumentException se pos >= size() o pos < 0 * E' una eccezione NON controllata, può non essere dichiarata * nell'interfaccia del metodo */ public Object remove(int pos); } Si osservi l'utilizzo della parola chiave default per il metodo isEmpty(), che consente alle classi che implementano Sequenza di non scrivere il codice. La realizzazione può essere fatta con con array o con strutture dinamiche. In entrambi i casi il contenuto di ogni elemento è un riferimento ad un oggetto: modificare lo stato di un oggetto non comporta la modifica del contenuto della lista, così come l'eliminazione di un elemento non comporta la "distruzione" automatica dell'oggetto. Una sequenza gestita con array - 13 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni Una sequenza gestita con lista concatenata Lasciando per esercizio l'implementazione dell'interfaccia Sequenza con array, si propone una realizzazione con lista concatenata (non è l'unico modo, ne esistono diversi altri che, ad esempio, utilizzano delle classi interne). Studiare con attenzione il codice: esempi/Collezioni/SequenzaConLista.java /** * Una sequenza di oggetti realizzata con una lista. * I dati sono inseriti in fondo alla lista. * * @author L. Ferrari * @version 31.1.2018 */ public class SequenzaConLista implements Sequenza { protected Object testa; protected SequenzaConLista coda; /** Crea una lista a partire dal primo elemento * @param dato primo dato da inserire nella lista */ public SequenzaConLista(Object dato){ testa = dato; coda = null; } /** Crea una lista vuota */ public SequenzaConLista(){ this(null); } /** * @return true se la lista è vuota, false altrimenti */ public boolean isEmpty(){ return (testa==null); } /** Aggiunge un elemento in fondo alla lista * @param dato dato da aggiungere */ public void add(Object dato){ if(isEmpty()) testa=dato; else if(coda==null) coda = new SequenzaConLista(dato); else coda.add(dato); // ricorsione } - 14 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni /** Restituisce il numero di elementi nella lista * @return dimensione della lista */ public int size(){ if (isEmpty()) return 0; SequenzaConLista corrente=this; int i; for(i=0; corrente!=null; i++) corrente = corrente.coda; return i; } /** * Restituisce l'elemento nella posizione indicata * @param pos posizione dell'elemento (0 per il primo). * Deve essere minore di size() * @return elemento in quella posizione * @throws IllegalArgumentException se pos >= size() o pos < 0 * E' una eccezione NON controllata, può non essere dichiarata * nell'interfaccia del metodo */ public Object get(int pos){ if (pos>=size() || pos
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni /** Crea una stringa con gli elementi contenuti nella lista * @return la stringa */ public String toString(){ String s = ""; SequenzaConLista corrente=this; do{ s += corrente.testa+" "; corrente = corrente.coda; }while(corrente != null); return s; } } Qualche osservazione: • il metodo add() è realizzato in modo ricorsivo, mentre i metodi size() e get() sono iterativi; • i metodi get() e remove() hanno la precondizione che la posizione scelta sia compresa tra 0 e size() -1. Il metodo per gestire eventuali violazioni è di lanciare una runtime exception. Questo metodo consente di non appesantire il codice che lo utilizza, consentendo il controllo. L'alternativa, restituire null, non fornisce alcuna indicazione in caso di malfunzionamento e potrebbe non essere controllato. • Il metodo remove() ha una gestione diversa per il primo elemento perchè non può modificare il puntatore dell'oggetto: potrebbero esserci altri puntatori alla testa della lista che non verrebbero aggiornati. • il metodo isEmpty(), pur previsto nell'interfaccia già codificato, qui è stato ridefinito perchè usato da size(). Vediamo un esempio di utilizzo, con oggetti di tipo String: SequenzaConLista lista = new SequenzaConLista(); lista.add("Paolo"); lista.add("Giorgio"); lista.add("Anna"); lista.add("Elena"); System.out.println("All'inizio: "+lista+ " ("+lista.size()+" elementi)"); lista.remove(0)); //"Paolo" lista.remove(1)); //"Anna" lista.remove(1)); // Elena" System.out.println("Alla fine: "+lista+ " ("+lista.size()+" elementi)"); L'output è questo: All'inizio: Paolo Giorgio Anna Elena (4 elementi) Alla fine: Giorgio (1 elementi) 4.1.5. Alberi Delle diverse tipologie di alberi ne analizziamo a titolo di esempio solo uno, l'albero binario di ricerca, che è molto utilizzato in informatica per memorizzare in maniera efficiente grandi quantità di dati, in modo da ridurre i tempi di ricerca di una informazione. Le altre tipologie possono essere utilizzate in un ampio insieme di ambiti. A titolo di esempio: - 16 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni • Valutazione di espressioni algebriche: l'albero in questo caso è binario, i nodi foglia contengono numeri mentre gli altri le operazioni da applicare ai loro sottoalberi. Espressione algebrica in un albero • Rappresentazione di documenti: i file HTML o XML possono essere rappresentati e analizzati come alberi generici, con un numero indefinito di figli. Ogni nodo è di solito un tag e può avere al suo interno (rappresentati dai figli) altri tag. • Strutture dati gerarchiche: nella grafica, ad esempio, possono essere rappresentati con alberi i componenti di una finestra: pannelli, bottoni, check box, menù, etc., che possono, a loro volta, contenere altri componenti. La realizzazione in un linguaggio a oggetti di un albero binario può essere effettuata in molti modi diversi, utilizzando una o più classi. Molto spesso le realizzazioni usano il design pattern composite in cui ogni oggetto è un elemento finale (foglia) oppure contiene a sua volta degli altri alberi. 4.1.6. Alberi binari di ricerca Un albero binario di ricerca (binary search tree o BST), è un albero binario in cui i valori dei figli di un nodo sono ordinati, usualmente avendo valori minori di quelli del nodo di partenza nei figli a sinistra e valori più grandi nei figli a destra. Albero di ricerca Più precisamente: - 17 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni • sui valori delle informazioni (indicate spesso come "chiavi") dei suoi nodi è definito un ordinamento totale: non ci possono essere quindi due nodi con la stessa chiave; • per ogni nodo n dell'albero, tutte le informazioni ( chiavi ) dei nodi contenuti nel sottoalbero sinistro di n hanno valore strettamente minore della informazione contenuta in n; tutte le chiavi dei nodi contenuti nel sottoalbero destro di n hanno valore strettamente maggiore della informazione contenuta in n. L'ordinamento totale che useremo per confrontare le chiavi è quello definito dal metodo compareTo: quindi assumiamo che le informazioni siano oggetti che implementano l'interfaccia Comparable. Con queste premesse, la visita simmetrica di un albero binario di ricerca produce le informazioni in ordine naturale, inteso come l'ordinamento non decrescente rispetto al tipo degli elementi. Gli algoritmi classici di ricerca, inserimento ed eliminazione di una informazione possono tenere conto della proprietà di ordinamento, rendendole molto efficienti. Inserimento di un nuovo nodo Se un albero contiene un milione di elementi ed è bilanciato, cioè "ben fatto", è possibile sapere se una chiave è presente accedendo al più a 20 nodi dell'albero stesso, contro i 500.000 (medi!) di una lista dinamica. Rispetto agli array, che se sono ordinati hanno le stesse prestazioni nella ricerca utilizzando il metodo dicotomico, gli alberi sono dinamici e quindi molto più veloci quando ci sono molte operazioni di inserimento o cancellazione. Vediamo ora un esempio di realizzazione di un BST in grado di memorizzare qualunque tipo di oggetto, purchè implementi l'interfaccia Comparable (cioè fornisca un metodo compareTo per consentire l'ordinamento). Esempio di realizzazione di un albero binario di ricerca (Esempi/Collezioni/AlberoBST.java) /** * Definisce un albero binario di ricerca (BST). * Negli alberi binari di ricerca ogni nodo non foglia ha nel sottoalbero * di sinistra elementi strettamente minori e nel sottoalbero * di destra elementi strettamente maggiori del suo. */ - 18 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni public class AlberoBST{ private Object info; private AlberoBST sx; private AlberoBST dx; /** * Costruisce un albero vuoto */ public AlberoBST() { this.info = null; this.sx = null; this.dx = null; } /** * Aggiunge un elemento all'albero. * Attenzione: compareTo può lanciare eccezioni. * @param dato elemento da aggiungere * @return true se aggiunto, false altrimenti (nel caso esistesse già) */ public boolean add(Comparable dato){ if(isEmpty()) { info = dato; dx = new AlberoBST(); sx = new AlberoBST(); return true; } int cfr = dato.compareTo(info); if (cfr==0) return false; if(cfr>0) return dx.add(dato); else return sx.add(dato); } /** * Informazione contenuta nella radice. * L'albero non deve essere vuoto! * @return il dato nella radice dell'albero * @throws IllegalStateException se l'albero è vuoto */ public Object getInfo() { if(isEmpty()) throw new IllegalStateException("Tree is empty"); return info; } /** * Dice se l'albero e' vuoto * @return true se l'albero e' vuoto */ public boolean isEmpty() { return info==null; } /** * Dimensione dell'albero * @return numero di elementi contenuti */ public int size() { if(isEmpty()) return 0; int d=1; if(!sx.isEmpty()) d+=sx.size(); if(!dx.isEmpty()) d+=dx.size(); return d; } - 19 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni /** * Costruisce una stringa che rappresenta l'albero binario. * @return rappresentazione dell'albero binario */ public String toString(){ // contiene una doppia ricorsione nascosta return isEmpty()?"-": "("+getInfo()+","+sx+","+dx+")"; } /** * Verifica se l'albero e' una foglia, cioe' se non ha figli. * @return true se l'albero e' una foglia */ public boolean isLeaf(){ return sx.isEmpty() && dx.isEmpty(); } /** * restituisce il contenuto dell'albero come array dei suoi elementi. * Gli elementi nella lista sono ordinati dal più piccolo al più grande. * @return un array con tutti gli elementi contenuti nell'albero, ordinati * secondo la definizione del metodo compareTo(). */ public Object[] toArray(){ Object[] arr = new Object[size()]; toArrayRecurse(this, arr, 0); return arr; } private int toArrayRecurse(AlberoBST a, Object[] arr, int dim){ if (a.isEmpty()) return dim; if (!sx.isEmpty()) dim=toArrayRecurse(a.sx, arr, dim); arr[dim++]=a.getInfo(); if (!dx.isEmpty()) dim=toArrayRecurse(a.dx, arr, dim); return dim; } } Osservazioni: • il metodo size() fornisce una indicazione per gli algoritmi di "accumulo", che sono utilizzati per "sommare" i dati contenuti in un albero. • provare a creare un albero, aggiungere due o più oggetti di tipo String e poi un numero intero. Il programma termina con una eccezione di tipo ClassCastException, nonostante sia il tipo String che Integer implementino l'interfaccia Comparable. Provare a spiegare perchè. Un esempio di utilizzo, ancora con oggetti di tipo String è il seguente: Esempio di uso di un BST (Esempi/Collezioni/TestAlberoBST.java) public class TestAlberoBST { public static void main(String[] args) { System.out.print('\u000C'); AlberoBST tree = new AlberoBST(); tree.add("def"); tree.add("abc"); - 20 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni tree.add("ghi"); tree.add("lmn"); tree.add("efg"); tree.add("cde"); tree.add("abc"); // non lo dovrebbe aggiungere! System.out.println(tree); Object[] arr = tree.toArray(); System.out.print("Lista elementi = ["); for(Object x : arr) System.out.print( x+ ", "); System.out.println("]"); } } L'output ottenuto è questo: (def,(abc,-,(cde,-,-)),(ghi,(efg,-,-),(lmn,-,-))) Lista elementi = [abc, cde, def, efg, ghi, lmn, ] 4.1.7. Grafi I grafi rappresentano la struttura dati più complessa che è possibile costruire con dei nodi. In un grafo ciascun nodo può puntare a uno o più nodi ed essere a sua volta puntato da più nodi. Graficamente (appunto) possiamo immaginare che ciascun nodo sia puntato e punti a sua volta al nodo a cui è collegato attraverso un arco. Un grafo Nel caso della figura gli archi consentono di andare da un nodo all'altro nei due sensi, in altri casi gli archi sono rappresentati con delle frecce per indicare il senso di percorrenza (sono a "senso unico"). Gestire un grafo risulta molto più difficile rispetto a una lista o a un albero perché, al contrario di liste e alberi, il grafo non ha un nodo di "testa" o "radice" che permetta sicuramente di raggiungere tutti gli altri. Per rappresentare un grafo occorre memorizzare i nodi e i collegamenti tra essi in una struttura dinamica apposita. Una soluzione consiste nel creare una lista, detta lista delle adiacenze, con tutti i nodi del grafo e associare a ciascuno di essi un'altra lista con tutti i nodi che sono collegati a esso. Volendo salvare il grafo dell'immagine precedente in una lista delle adiacenze avremo una struttura del tipo seguente. - 21 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni Lista delle adiacenze Matrice di incidenza Un modo alternativo di rappresentare i collegamenti tra i nodi di un grafo è una "matrice di incidenza". Possiamo memorizzare tutti i nostri nodi sempre attraverso una lista ma, anzichè creare un'altra lista per ciascun nodo, costruiamo una matrice con queste regole: • ogni nodo viene numerato da 1 a N • la matrice sarà quadrata, N righe per N colonne • per ciascuna riga, se il nodo con il numero della riga è collegato al nodo con il numero della colonna conterrà 1, altrimenti 0 Nel caso del grafo dell'esempio la matrice delle incidenze sarà la seguente. Matrice di incidenza Nel nostro caso abbiamo una matrice simmetrica dato che nel grafo abbiamo immaginato che ciascun arco sia dotato di doppia freccia, ma è solo una eccezione. Nel caso che un arco sia monodirezionale ci sarà 1 solo nel "verso" della freccia. La matrice di incidenza può contenere anche il "costo" per passare da un nodo all'altro e permettere quindi di rappresentare situazioni complesse, come ad esempio i costi (o i tempi) di di collegamento tra due località. Alcuni problemi classici dei grafi, come il "Problema del commesso viaggiatore", del cammino minimo, le reti neurali, sono molto complessi e la soluzione, se esiste, può richiedere tempi di esecuzione proibitivi. Una materia di studio che li utilizza è la Programmazione Lineare. 4.1.8. Tabelle hash Una tabella hash è una struttura dati usata per mettere in corrispondenza una chiave con un valore. Viene usata per l'implementazione di strutture dati astratte come le Map. - 22 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni L'hash table è molto utilizzata nei metodi di ricerca nominati Hashing. Una ricerca basata su hashing è completamente diversa da una basata su confronti, come ad esempio la ricerca sequenziale o la ricerca binaria. Invece di muoversi nella struttura in funzione dell'esito dei confronti tra valori, si cerca di accedere agli elementi nella tabella in modo diretto, tramite operazioni aritmetiche che trasformano le chiavi in indirizzi della tabella. In una tabella di hashing ben dimensionata il costo medio di ricerca di ogni elemento è indipendente dal numero di elementi. Trovare una efficiente strategia di hashing è uno dei problemi classici dell'informatica; sono stati proposti vari tipi di algoritmi, efficienti secondo diversi criteri e quindi da scegliere a seconda della situazione specifica. Esempio di hashing In una rubrica a ogni nome corrisponde un insieme di informazioni (indirizzo, numeri di telefono, e-mail...). Volendola memorizzare in maniera efficiente, nella soluzione con tabella hash occorre un algoritmo che, a partire dal nome, ottenga un indice. Tramite l'indice si accede a una "riga" di una tabella, contenente il riferimento alle informazioni cercate. Esempio di struttura dati che usa tabella hash Per ottenere l'indice a partire dalla chiave deve essere quindi usata una funzione di hash che abbia la caratteristica, non banale, di fornire per ogni chiave un valore diverso. Un esempio di funzione di hash per una chiave di tipo stringa potrebbe essere quella di sommare i codici ASCII delle lettere che la compongono e fare l'operazione di modulo sulla dimensione della tabella di hash. 4.1.9. Il problema delle collisioni L'insieme degli indici ottenibili dalle chiavi deve essere mantenuto ragionevolmente piccolo, in modo da non sprecare troppa memoria per la memorizzazione della tabella di hash. Con 7000 record possiamo pensare di avere una tabella di hash di 10.000 record, mentre 300.000 sarebbero certamente troppi. Tuttavia è molto difficile scegliere una tabella di hash che fornisca valori diversi per tutte le possibili chiavi, anzi di solito questo non è proprio possibile. Si ha una collisione quando due chiavi diverse producono lo stesso indice. A questo problema sono state dedicate molte ricerche e diverse soluzioni. - 23 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni Una, denominata Open addressing, consiste nel far puntare gli elementi della tabella di hash non all'elemento direttamente, ma a una lista che contiene a sua volta gli elementi. Collisione risolta con liste Una seconda soluzione prevede l'uso di una funzione di rehashing, cioè l'applicazione di una nuova funzione di hash se la prima fornisce un elemento non vuoto nella tabella. Se l'oggetto non corrisponde alla chiave si riapplica la seconda funzione più volte fino a che non si trova l'elemento corrispondente alla chiave oppure un elemento vuoto (che significa l'assenza dell'elemento cercato). Sulla rete si trovano molti siti che trattano queste soluzioni approfonditamente. 4.1.10. Autovalutazione ADT Domande sulle strutture dati astratte 1. Elencare i diversi tipi di strutture dati astratte (ADT) e per ognuna le principali caratteristiche. 2. Rappresentare con uno schema la propria famiglia indicando una casella per ogni persona e inserire i rapporti di genitorialità. Quale struttura conviene utilizzare? 3. Rappresentare la ricetta per realizzare un piatto di pastasciutta con una casella per ogni operazione (scolare la pasta, riempire la pentola di acqua, buttare la pasta, metterla al fuoco, aggiungere il sale...) e indicare la sequenza corretta: quale struttura dati si utilizza? 4. Rappresentare le pagine internet di un sito utilizzando una casella per ogni pagina e indicare con dei collegamenti i link tra di essi. Quale struttura dati viene rappresentata? 5. Considerare l'organizzazione di queste dispense guardando l'indice: quale struttura dati viene rappresentata? Motivare la risposta. Sequenze o liste Domande sulle sequenze 1. Disegnare una lista del tipo ListaInt, visto prima, che contenga i seguenti valori: 5, 7, 3, 5, -1, 8. 2. Data una lista come quella della domanda precedente, con 6 valori, applicare il metodo che effettua la somma, spiegando quali valori vengono via via sommati per ottenere il risultato. - 24 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni 3. Spiegare la procedura di inserimento di un nuovo elemento in una lista realizzata con la classe ListaInt. 4. Una lista ha un unico punto di accesso. Spiegare le differenze tra gli algoritmi che aggiungono gli elementi a) preservando l'ordine di arrivo (cioè il primo elemento è il primo aggiunto, il secondo quello inserito subito dopo e così via) o b) in ordine inverso. 5. Spiegare la differenza tra una lista e una lista ordinata. 6. Disegnare una lista in cui gli elementi interi inseriti sono in ordine crescente. Spiegare l'algoritmo necessario ad aggiungere un elemento alla lista già ordinata in modo che lo rimanga. Attenzione ai casi limite. 7. Fare un confronto tra la lista dinamica, la lista dinamica ordinata e gli array, indicando almeno un caso in cui le prestazioni di ciascuna tipologia sono migliori. 8. Nel caso non si vogliano duplicati in una lista, quali metodi, tra quelli visti, devono essere modificati? 9. Spiegare perchè il metodo remove della classe SequenzaConLista ha una gestione diversa per il primo elemento. Fornire un esempio in cui lo stesso trattamento degli altri elementi causerebbe dei malfunzionamenti. 10. In una lista ordinata come si può trovare l'elemento maggiore? E quello minore? Quale dei due sembra essere più veloce? 11. Scrivere l'algoritmo per verificare la presenza di un valore qualunque in una lista ordinata. Come si può gestire l'assenza del valore? E la possibilità che compaiano più copie? Esercizi sulle sequenze 1. Ricerca del maggiore: considerare il primo esempio di lista (ListaInt) e scrivere un metodo max(), non ricorsivo, che restituisce il più grande valore contenuto nella lista. 2. Altro metodo di somma: considerare il seguente esempio di metodo alternativo per calcolare la somma degli elementi. public static int somma(ListaInt a) { if(a.coda==null) return a.testa; return a.testa+somma(a.coda); } Individuare le differenze con quello presentato in precedenza e valutarne vantaggi e svantaggi. Scrivere una classe di test che verifica il funzionamento corretto di entrambi (attenzione al diverso modo di chiamare i metodi). 3. Ricerca valore: scrivere per la classe ListaInt un metodo che restituisca la posizione di un elemento nella lista (0 per il primo), -1 se non presente. Scrivere poi un programma di prova. 4. Lista di interi ordinata: creare una classe ListaIntOrdinata che eredita da ListaInt e che ha la caratteristica di mantenere i suoi elementi sempre in ordine crescente. (Suggerimento: dovrebbe essere necessario scrivere soltanto i costruttori e il metodo add). - 25 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni 5. Aggiunta di una lista: realizzare nella classe ListaInt il metodo add(ListaInt lista) che aggiunge un'intera lista. Creare dei test che ne verifichino il funzionamento anche nei casi particolari (lista vuota...). 6. Ordinamento *: aggiungere alla classe ListaInt il metodo sort() che ordina i suoi elementi in modo crescente senza usare strutture dati ausiliarie. 7. Sequenza di oggetti qualunque: studiare e poi scrivere un programma di prova per la classe SequenzaConLista. Il programma crea una lista, aggiunge a essa tre oggetti di classe qualunque (ad esempio String, Rectangle...) e stampa il contenuto della lista. Aggiungere chiamate agli altri metodi e verificare il comportamento. 8. Versioni ricorsive: per la classe SequenzaConLista scrivere i metodi size e get in versione ricorsiva. 9. Sequenza alternativa: realizzare l'interfaccia Sequenza nella versione che utilizza gli array al posto delle liste dinamiche. Realizzare un programma di test che verifichi il corretto funzionamento delle due versioni e evidenzi pregi e difetti di ognuna delle due soluzioni (usando sequenze di grandi dimensioni e le classi che consentono di misurare il trascorrere del tempo). 10. Conteggio delle occorrenze: scrivere un metodo per la classe SequenzaConLista che conti quante volte l'informazione x (un oggetto qualunque) compare in una lista concatenata. Per il confronto usare il metodo equals(). 11. Lista con classe interna: una realizzazione alternativa di una lista è la seguente: /** * ListaCollegata è una versione semplificata * di realizzazione di una lista. * Non contiene iteratori e non usa eccezioni * @author L.Ferrari * @version 2.0 del 27.3.2009 */ public class ListaCollegata { /** Classe interna privata, non viene restituita da alcun metodo della * classe esterna pubblica */ private class Elemento { Object dato; Elemento next; } // Classe pubblica private Elemento primo; /** Costruttore: crea una lista vuota */ public ListaCollegata() { primo = null; } /** Controlla se la lista e' vuota * @return true se la lista e' vuota */ public boolean isEmpty(){ return primo==null; } - 26 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni /** Estrae il primo elemento della lista * @return in primo elemento. Null se lista vuota */ public Object getFirst() { if(isEmpty())return null; return primo.dato; } /** inserisce un elemento in testa alla lista * @param obj oggetto da inserire */ public void add(Object obj) { Elemento nuovo = new Elemento(); nuovo.dato = obj; nuovo.next = primo; primo = nuovo; } In questa soluzione si fa uso di una classe interna Elemento, che è funzionale alla realizzazione della lista, mentre rimane completamente nascosta a chi utilizza la classe ListaCollegata. Completarla con i metodi remove(), che toglie il primo elemento, remove(int pos) che toglie quello nella posizione indicata, size(), get(int pos), toString(). Scrivere una classe di test che ne verifichi il funzionamento. 12. Lista concatenata bidirezionale: in questo tipo di lista ogni elemento ha due puntatori, uno al precedente e uno al successivo. Disegnare un esempio con quattro numeri a caso, con aggiunta in coda. A questo punto si può attraversare la lista a partire dal primo elemento o dall'ultimo inserito. Realizzare una classe ListaBidir di questo tipo e verificarne il funzionamento con classi di test automatici. Prevedere almeno i tre metodi indicati (inserimento, visita "in avanti" e visita "indietro"). 13. Lista bidirezionale ordinata: è una classe che mantiene gli elementi della lista bidirezionale in ordine crescente, secondo un qualche metodo di ordinamento (interfaccia Comparable). La classe eredita dalla ListaBidir dell'esercizio precedente? Motivare la risposta e poi realizzare la nuova classe con almeno gli stessi metodi. Alberi Domande sugli alberi 1. Disegnare un albero binario di ricerca contenente i numeri da 1 a 10 quando sono inseriti prima i pari e poi i dispari in ordine crescente. 2. Spiegare il concetto di altezza di un albero. 3. Fare un esempio in cui la struttura ad albero non fornisce migliorie rispetto alla lista. In altre parole, quando si ottiene un albero che "degenera" in una lista? 4. Spiegare il procedimento di inserimento di un elemento in un albero BST. 5. Spiegare, con un esempio, come si può trovare il più piccolo elemento contenuto in un albero BST. 6. BST sta per Bynary Search Tree: Crearne uno in cui sono inseriti, in sequenza, i valori 1, 3, 5, 7, 9 e un secondo in cui sono inseriti i valori nell'ordine 5, 7, 1, 3, 9. Quanti nodi si devono - 27 - Rel. 1.6 del 9.4.2020 - Licensed under Creative Commons BY-NC-SA 3.0, Luigi Ferrari, 2004-2020, luigi.ferrari@istruzione.it
Puoi anche leggere