Strutture dati elementari Dott. Ezio Bartocci, Dott. Francesco De Angelis Laboratorio di Algoritmi e strutture Dati - AA 2006/2007 Dip. di Matematica e Informatica Università di Camerino [email protected], [email protected] 24 novembre 2006 Introduzione Costrutti di base Array Liste concatenate Strutture dati e algoritmi La decisione più importante nella fase di implementazione di un’applicazione è la scelta della struttura più oppurtuna per rappresentare i dati. Per uno stesso insieme di dati, vi sono strutture dati che richiedono più spazio di altre. Per uno stesso insieme di operazioni sui dati, alcune strutture dati portano a implementare algoritmi più efficienti di altri. 2 / 36 Introduzione Costrutti di base Array Liste concatenate Costrutti di base Tutti i dati che vengono elaborati su un calcolatore alla fine sono scomposti in singoli bit, ma scrivere programmi che elaborino direttamente questi bit sarebbe quantomeno noioso. I tipi ci consentono di specificare come andremo a usare un particolare insieme di bit, mentre i metodi permettono di definire le operazioni che verranno eseguite sui dati. Usiamo le classi di Java per descrivere le informazioni che elaboriamo, per definire i metodi che agiscono su queste informazioni e per costruire oggetti che le memorizzano effettivamente. 3 / 36 Introduzione Costrutti di base Array Liste concatenate Strutture dati Le stutture dati che andremo a vedere sono formate da oggetti e da riferimenti a oggetti. Quando scriviamo programmi, di solito vogliamo elaborare informazioni derivanti da qualche descrizione formale (matematica) o informale del mondo in cui viviamo. Gli ambienti di programmazione devono già avere in sè gli elementi di base di queste descrizioni, vale a dire numeri e caratteri. 4 / 36 Introduzione Costrutti di base Array Liste concatenate Strutture dati semplici Tipi di dati primitivi in Java boolean - valori booleani char - caratteri byte - numeri interi a 8 bit short - numeri interi a 16 bit int - numeri interi a 32 bit long - numeri interi a 64 bit float - numeri in virgola mobile a 32 bit double - numeri in virgola mobile a 64 bit 5 / 36 Introduzione Costrutti di base Array Liste concatenate Tipi di dati standard Definition Un tipo di dato è definito da un insieme di valori e da una collezione di operazioni su questi valori. Le operazioni sono associate ai tipi e non viceversa. Quando eseguiamo un operazione, dobbiamo assicurare che tanto gli operandi quanto il risultato siano del tipo corretto. In alcuni casi, Java esegue conversioni in modo automatico; in altri usiamo il cast, cioè l’esplicita conversione di tipo. Ad esempio, se x ed N sono interi, questa espressione include entrambi i tipi di conversione: ((float) x) / N 6 / 36 Introduzione Costrutti di base Array Liste concatenate Operazioni sui tipi di dati standard Molte delle operazioni associate ai tipi di dati standard (i.e. le operazioni aritmetiche) sono già in effetti incorporate nel linguaggio Java, altre operazioni sono implementate sotto forma di metodi in librerie standard di Java, altre ancora sono costituite dai metodi Java definiti nei programmi che scriviamo. Spesso abbiamo bisogno di definire i nostri tipi di dati per organizzare i programmi in maniera efficace. 7 / 36 Introduzione Costrutti di base Array Liste concatenate Classi Esempio di classe class Studente { private String nome; private String cognome; private int anno; private int matricola; //Costruttore Studente(String nome, String cognome, int anno, int matricola){ this.nome = nome; this.cognome = cognome; this.anno = anno; this.matricola = matricola; } int getMatricola(){ return matricola;} int getAnno(){ return anno; } public String toString(){ return nome + " " + cognome; } } Tutti i programmi in Java sono basati sul meccanismo delle classi. La classe definisce tutte le proprietà degli oggetti appartenenti a quella classe, detti attributi, e le funzioni che verranno usate per agire su di essi, detti metodi. Le classi sono dei prototipi di oggetti, ovvero sono delle strutture astratte che possono essere instanziate creando uno o più oggetti. 8 / 36 Introduzione Costrutti di base Array Liste concatenate Programmazione object-oriented Possiamo pensare alle classi come a un meccanismo che ci consente non solo di aggregare dati, ma anche di definire operazioni su quei dati. Anche se ci sono diversi oggetti che appartengono a una classe, tutti questi oggetti sono simili nel senso che i valori assumibili dai loro dati membro sono gli stessi e che l’insieme delle operazioni che possono essere eseguite sui dati membro è lo stesso. Nella programmazione object-oriented sono gli oggetti che elaborano i loro dati membro (invece di avere metodi liberi che agiscono sui dati memorizzati negli oggetti) 9 / 36 Introduzione Costrutti di base Array Liste concatenate Classi e metodi Definizione di un metodo class LogTable{ static int lg (int N){ for (int i=0; N > 0; i++){ N/=2;} return i; } public static void main (String[] args){ for (int N=100; N < 10000; N*=10) Out.println(lg(N) + " " + N); } } Programma Java più semplice class HelloWorld{ public static void main (String[] args){ System.out.println("Hello World !!"); } } In Java, per implementare nuove operazioni sui dati, definiamo metodi in classi. Il metodo lg nell’esempio implementa una funzione matematica a un solo argomento che corrisponde al logaritmo intero in base 2. In Java, gli argomenti vengono detti parametri e il valore della funzione è chiamato valore di ritorno. Ogni programma Java è una classe che include la definizione del metodo main. Il programma Java più semplice che si possa scrivere è quello formato dal solo metodo main. 10 / 36 Introduzione Costrutti di base Array Liste concatenate Polimorfismo Overloading degli operatori class ValoreAssoluto { static int abs(int value){ return value >= 0 ? value : -value; } static double abs(double value){ return value >= 0 ? value : -value; } } La definizione di un metodo inizia con la sua segnatura, che definisce il tipo del suo valore di ritorno, il suo nome e i tipi dei suoi parametri. L’uso di nomi identici per metodi differenti si dice overloading, se il sistema è in grado di distinguerli tramite le differenze nelle loro segnature. I tipi dei parametri e la presenza o l’assenza di un valore di ritorno possono servire a distinguere tra loro metodi aventi lo stesso nome. 11 / 36 Introduzione Costrutti di base Array Liste concatenate Ereditarietà Classe Punto class Point{ double x, y; Point { x = Math.random(); y = Math.random(); } } Classe punto etichettato class LabeledPoint extends Point{ String id; void label(String name){id = name;} public String toString(){ return id + "(" + x + ", " + y + ")";} } Spesso ci troviamo a dover costruire un nuovo tipo di dato arricchendone uno già esistente. Per agevolare questo tipico compito Java offre la possibilità di definire una classe che ne estende un’altra. Il tipo di dato definito dalla classe estesa è determinato dai membri della classe base più tutti i membri della classe estesa. La classe estesa può tanto definire nuovi membri quanto ridefinire membri della classe base. 12 / 36 Introduzione Costrutti di base Array Liste concatenate Array Definition Un array è un insieme fissato di elementi dello stesso tipo memorizzati in modo contiguo e accessibili per mezzo di un indice. L’Array in Java come nella maggiorparte dei linguaggi di programmazione è definito come primitiva di linguaggio. Denoteremo l’i-esimo elemento di un array a con a[i]. 13 / 36 Introduzione Costrutti di base Array Liste concatenate Operare su un Array Crivello di Eratostene class Primes{ public static void main(String[] args){ int N = Integer.parseInt(arg[0]); int[] a = new int[N]; for (int i=2; i < N; i++) a[i]= 1; for (int i=2; i < N; i++) if (a[i] != false) for (int j=i; j*i < N; j++) a[i*j] = 0; for (int i=2; i < N; i++) if (i > N - 100) if (a[i]) Out.print(" " + i); Out.println(); } } Example i 2 3 4 5 6 7 8 9 10 11 12 2 1 1 1 1 1 1 1 1 1 1 1 3 5 a[i] 1 1 0 1 0 1 0 0 0 1 0 0 14 / 36 Introduzione Costrutti di base Array Liste concatenate Allocazione affidabile di un array Example int[] a; try { a = new int[N]; } catch (OutOfMemoryError e){ Out.println("Out of memory"); return; } } Se nel programma precedente digitassimo come argomento sulla riga di comando un numero estremamente grande, si produrrebbe nel sistema un’eccezione OutOfMemoryError (mancanza di memoria) E’ buona pratica di programmazione tenere sotto controllo tutti gli errori che possono capitare. Quindi potrebbe essere utile sostituire le righe del codice che creano l’array di interi con il codice qui a fianco. 15 / 36 Introduzione Costrutti di base Array Liste concatenate Array di Oggetti 1.7 1.0 3.5 2.0 Array di oggetti Points 2.3 6.7 0 class ClosePoints{ public static void main (String[] args){ int cnt = 0; N = Integer.parseInt(args[0]); double d = Double.parseDouble(args[1]); Point[] a = new Point[N]; for (int i = 0; i < N; i++) a[i] = new Point(); for (int i = 0; i < N; j++) if (a[i].distance(a[j]) < d) cnt++; Out.print(cnt + " pairs " ); Out.println(" closer than " + d); } 1 2.0 6.1 2 3 5.0 4.0 4 5 6 6.1 3.9 1.4 3.2 In Java, un array di oggetti è, in realtà, un array di riferimenti a oggetti, come mostrato dalla figura qui sopra 16 / 36 Introduzione Costrutti di base Array Liste concatenate Lista concatenata Quando abbiamo necessità di scandire una collezione di elementi in modo sequenziale, uno dopo l’altro, una scelta conveniente è quella di organizzare gli elementi in una lista concatenata. In una lista concatenata, ogni elemento contiene le informazioni necessarie per accedere all’elemento successivo. Vantaggi rispetto all’Array Flessibilità di modifica Svantaggi rispetto all’Array Onerosità nell’accesso ai suoi elementi: l’unico modo per raggiungere un dato elemento della lista è quello di seguire le connessioni della lista dall’inizio 17 / 36 Introduzione Costrutti di base Array Liste concatenate Definizione Definition Una lista concatenata è un insieme di elementi, dove ogni elemento è inserito in un nodo contenente anche un link (cioè una connessione o riferimento) a un (altro) nodo. Le liste sono dette a volte strutture autoreferenzianti proprio per aver definito i nodi in termini di riferimenti ad altri nodi. Sebbene di solito i link di un nodo puntino ad altri nodi, tali link potrebbero anche puntare al nodo medesimo, e quindi dar luogo a strutture circolari. 18 / 36 Introduzione Costrutti di base Array Liste concatenate Vediamo in Java Classe Node class Node{ Object item; Node next; Node(Object v) { item = v; next = null; } } Adotteremo queste convenzioni per il link del nodo finale: è un link nullo che non porta ad alcun nodo punta ad un nodo fittizio che non contiene alcun elemento punta indietro al primo nodo della lista, creando quindi una lista circolare Instanziare una lista di 2 nodi //x is an Object Node t = new Node(x); punta indietro al primo nodo della lista, creando quindi una lista circolare. 19 / 36 Introduzione Costrutti di base Array Liste concatenate Cancellazione e Inserimento in una lista Cancellazione Inserimento t = x.next; x.next = t.next; //oppure x.next = x.next.next; t.next = x.next; x.next = t; t t x t x x t x t x 20 / 36 Introduzione Costrutti di base Array Liste concatenate Esempio di lista circolare Problema di Giuseppe Flavio class Node { int val; Node next; Node (int v){ val = v;} } class Josephus{ public static void main (String args[]){ int N = Integer.parseInt(args[0]); int M = Integer.parseInt(args[1]); Node t = new Node(1); Node x = t; for (int i = 2; i <= N; i++){ x = (x.next = new Node(i)); x.next = t; while (x != x.next){ for (int i = 1; i < M; i++) x = x.next; x.next = x.next.next; } Out.println("L’eletto è " + x.val); } } Immaginiamo che N persone debbano eleggere un leader nel modo seguente le persone si dispongono in cerchio eliminano una persona ogni M, seguendo l’ordine del cerchio e richiudendo il cerchio ad ogni eliminazione. Il problema è quello di scoprire quale persona rimarrà per ultima. 21 / 36 Introduzione Costrutti di base Array Liste concatenate Risultato dell’elezione di Giuseppe Flavio 4 3 2 3 4 1 2 3 4 1 5 2 9 6 9 6 7 8 7 3 4 6 9 6 2 8 8 8 7 8 3 2 6 9 9 2 2 6 9 8 8 2 8 9 8 22 / 36 Introduzione Costrutti di base Array Liste concatenate Definizione più restrittiva Definition Una lista concatenata consta di un link nullo oppure di un link ad un nodo, che contiene un elemento e un link a una lista concatenata. Questa definizione è più restrittiva di quella precedente, ma corrisponde in modo più preciso all’idea che abbiamo di una lista quando scriviamo codice per elaborarla. 23 / 36 Introduzione Costrutti di base Array Liste concatenate Attraversamento di una lista Codice per attraversare una lista for (Node t = x; t != null; t = t.next) visit(t.item); Una delle più comuni operazioni su liste è quella dell’attraversamento: scandiamo tutti gli elementi della lista in maniera sequenziale, eseguendo una qualche operazione su ognuno dei nodi Ad esempio, se xreferenzia il primo nodo di una lista, l’ultimo nodo ha il link a NULLe visitè un metodo che prende un elemento come parametro, possiamo scrivere il codice riportato qui a fianco 24 / 36 Introduzione Costrutti di base Array Liste concatenate Inversione di un lista Metodo Reverse Node reverse (Node x){ Node t, y = x, r = null; while (y != null){ t = y.next; y.next = r; r = y; y = t; } return r; } Questo metodo inverte i link di una lista, restituendo un puntatore al nodo finale il quale a sua volta punta al penultimo, e così via. Il link del primo nodo della lista originale è posto a NULL. Per effettuare le operazioni è necessario mantenere link a tre nodi consecutivi nella lista. r y t r y 25 / 36 Introduzione Costrutti di base Array Liste concatenate Ordinamento per insersione in una lista Class Node class Node { int val; Node next; Node(int v, Node t) { val = v; next = t; } } Metodo create() public Node create(){ Node a = new Node (0, null); for (In.init(); !In.empty();) a.next= new Node(In.getInt(), a.next); return a; } Metodo sort() Node sort(Node a){ Node t, u, x, b = new Node(o, null); while (a.next != null) { t = a.next; u = t.next; a.next = u; for (x = b; x.next != null; x = x.next) if (x.next.val > t.val) break; t.next = x.next; x.next = t; } return b; } 26 / 36 Introduzione Costrutti di base Array Liste concatenate Risultato dell’ordinamento Manteniamo un puntatore t al primo nodo della lista non ordinata (in alto) 627 a 758 113 t 101 515 838 b 758 Quindi, scandendo la lista puntata da b, cerchiamo il primo nodo x con x.next.item > t.item (oppure con x.next=null) e inseriamo t nella lista appena dopo x. 113 a Queste operazioni riducono di 1 la lunghezza della lista a e aumentano di 1 quella della lista b, mantenendo quest’ultima in ordine (in basso). 627 758 b t 838 x 758 a 101 b 113 113 515 627 838 Ripetendo il procedimento, giungiamo prima o poi a esaurire a ed avere in bla lista ordinata dei nodi. 27 / 36 Introduzione Costrutti di base Array Liste concatenate Convenzioni su testa e coda in liste concatenate Coda circolare, mai vuota primo inserimento: inserisci t dopo x: cancella dopo x: ciclo di attraversamento: testa se un solo elemento: head.next = head; t.next = x.next; x.next = t; x.next = x.next.next; t = head do { · · · t = t.next; } while (t != head); if (head.next == head) 28 / 36 Introduzione Costrutti di base Array Liste concatenate Convenzioni su testa e coda in liste concatenate Riferimento in testa, coda a null inizializza: inserisci t dopo x: cancella dopo x: ciclo di attraversamento: testa se vuota: head = null if (x == null) { head = t; head.next = null; } else { t.next = x.next; x.next = t; } t = x.next; x.next = t.next; for (t = head; t != null; t = t.next) if (head == null) 29 / 36 Introduzione Costrutti di base Array Liste concatenate Convenzioni su testa e coda in liste concatenate Nodo fittizio in testa, coda a null inizializza: inserisci t dopo x: cancella dopo x: ciclo di attraversamento: testa se vuota: head = new Node(); head.next = null; t.next = x.next; x.next = t; t = x.next; x.next = t.next; for (t = head; t != null; t = t.next) if (head == null) 30 / 36 Introduzione Costrutti di base Array Liste concatenate Convenzioni su testa e coda in liste concatenate Nodi fittizi in testa e in coda inizializza: inserisci t dopo x: cancella dopo x: ciclo di attraversamento: testa se vuota: head = new Node(); z = new Node(); head.next = z; z.next = z; t.next = x.next; x.next = t; x.next = x.next.next; for (t = head; t != null; t = t.next) if (head.next == z) 31 / 36 Introduzione Costrutti di base Array Liste concatenate Lista doppiamente concatenata Inserimento Cancellazione t.next.prev = t.prev; t.prev.next = t.next; t.next = x.next; x.next.prev = t; x.next = t; t.prev = x; t t x t t x t x 32 / 36 Introduzione Costrutti di base Array Liste concatenate Ordinamento di stringhe con lista 3 n o w 2 i s 3 t h e 4 t i m e 3 f o r 3 a l l 3 n o w 2 i s 3 t h e 4 t i m e 3 f o r 3 a l l 0 1 2 3 4 5 0 1 2 3 4 5 33 / 36 Introduzione Costrutti di base Array Liste concatenate Se non basta: Una multilista 758 a 838 113 627 101 b 515 34 / 36 Introduzione Costrutti di base Array Liste concatenate Grafi e liste di adiacenza 0 6 2 1 7 3 4 5 Grafo 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 1 1 1 0 0 1 1 1 1 1 0 0 0 0 0 1 1 0 1 1 0 0 0 1 0 0 0 1 1 1 0 0 0 0 0 1 1 1 1 1 1 0 0 0 1 1 0 0 1 0 0 0 1 0 1 0 1 1 1 0 0 0 0 1 Rappresentazione del grafo con una matrice d'adiacenza 0 7 5 1 7 0 2 7 0 3 5 4 4 6 5 0 6 4 0 1 2 7 2 1 5 7 3 4 3 0 6 4 Rappresentazione del grafo con liste di adiacenza 35 / 36 Introduzione Costrutti di base Array Liste concatenate Riferimenti [Sed03] §3 - Strutture Dati Elementari 36 / 36