Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni - Luigi Ferrari - Genova

Pagina creata da Giorgio Gentile
 
CONTINUA A LEGGERE
Corso di programmazione a oggetti - Modulo 4 - Strutture dati e collezioni - Luigi Ferrari - Genova
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 - Luigi Ferrari - Genova
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 - Luigi Ferrari - Genova
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 - Luigi Ferrari - Genova
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 - Luigi Ferrari - Genova
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 - Luigi Ferrari - Genova
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 - Luigi Ferrari - Genova
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 - Luigi Ferrari - Genova
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 - Luigi Ferrari - Genova
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 - Luigi Ferrari - Genova
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