Sommario Collezioni • Una collezione (contenitore) è un oggetto che raggruppa gg pp ppiù dati in una singola g struttura. Vi sono due tecniche fondamentali per rappresentare collezioni di elementi: quella basata su strutture i di i indicizzate ( (array, compostii da d elementi l i consecutivi) i i) e quella basata su strutture collegate (list, composte da nodi: ogni nodo contiene dati e collegamenti ad altri nodi). • Collezioni: – Array e liste. – Nodi: dati e riferimenti. riferimenti • Liste: – LinkedList: specifica e implementazione. – Prestazioni. Informatica Medica, I semestre, C++ • Le collezioni sono usate per memorizzare, leggere, manipolare dati strutturati e per trasmettere tali dati da un metodo ad un altro. 1 Informatica Medica, I semestre, C++ 2 Collezioni Collezioni • Fino ad ora si sono usate le strutture dati array (semplici e performanti). La loro caratteristica è di avere celle memorizzate sequenzialmente che sono accessibili attraverso indici con costo O(1). • Tuttavia si può pensare di usare strutture dati in cui ll’ordinamento ordinamento dei dati è solo a livello logico (senza usare un sottostante ordinamento fisico). • Questo Q t sii puòò realizzare li associando i d ad d ognii elemento anche un puntatore all’elemento che lo segue nella ll sequenza. • Per ora vediamo di realizzare questa idea di dati più collegamenti: un oggetto (nodo) che contenga, oltre all’informazione sul dato memorizzato, anche un puntatore al nodo che contiene ll’elemento elemento che nella sequenza lo segue. • Esempio di due nodi: Informatica Medica, I semestre, C++ Informatica Medica, I semestre, C++ 3 <<oggetto>> Node data link <<oggetto>> Node data link=0 4 Collezioni Nodi Struttura indicizzata (array) v ‘ ’ ‘a’ ‘ ’ ‘c’ ‘ ’ ‘a’ ‘ ’ ‘r’ ‘f’ 0 1 2 3 4 indice • L’idea di dati più collegamenti è implementata con una classe l Node. • Lo stato di tale classe è descritto da un campo che contiene i il dato d ( (per esempio i di tipo i float o Persona) e un campo puntatore (Node *) al nodo collegato. collegato • Il comportamento è descritto dai metodi per leggere e scrivere lo stato, stato oltre al costruttore e distruttore. distruttore Struttura collegata s data: ‘ d ‘a’ ’ link data: ‘r’ link data: ‘a’ link data: ‘f’ f Link=0 data: ‘c’ link Informatica Medica, I semestre, C++ 5 Nodi: Node.h #ifndef NODE_H #define NODE_H class Node{ char data; Node *link; public: N d ( h Node(char d Node d, N d *l=0); *l 0) ~Node(); char GetData()const; Node * GetLink()const; void SetData(char d); void SetLink(Node *l); }; #endif Informatica Medica, I semestre, C++ • N Nell’implementazione ll’i l i che h segue il tipo i d l dato del d memorizzato nel nodo è char. Informatica Medica, I semestre, C++ 6 Nodi: Node.cpp pp La dichiarazione del campo link di un nodo può sembrare non corretta. Tuttavia è corretta, corretta ciò che dichiara è un puntatore ad uun ogge oggettoo d di tipo po Node ode e non un oggetto stesso. 7 #include "Node.h" Node.h #include <iostream> using namespace std; Node::Node(char d, Node *l){ data=d; link=l; cout<<"Node("<<data<<")"<<endl; } Node::~Node(){ cout<<"~Node("<<data<<")"<<endl; } char Node::GetData()const{ return data; } Node * Node::GetLink()const{ return link; } void Node::SetData(char d){ d data=d; d } void Node::SetLink(Node *l){ link=l; ; } Informatica Medica, I semestre, C++ Vista la dichiarazione del campo link li k di un nodo, d nel costruttore non si deve allocare un oggetto Node, ma solo assegnare un puntatore passato al costruttore. 8 Nodi: test.cpp pp Si collega ll l’ l’oggetto tt nodo n ad un altro oggetto nodo attraverso il puntatore a tale nodo . void test1(){ Node *n=new Node('A'); n->SetLink(new Node('B')); cout<<n->GetData()<<" "; cout<<n->GetLink()->GetData()<<endl; delete n->GetLink(); delete n; } Si deve prima distruggere ll’oggetto oggetto nodo collegato e poi il nodo che contiene il collegamento ll Attraverso n sii Att accede al contenuto del nodo collegato senza il nome simbolico. Node(A) Node(B) AB ~Node(B) ~Node(A) 9 Liste: introduzione Informatica Medica, I semestre, C++ 10 Liste: specifica p • Uno svantaggio delle liste concatenate consiste nell’onerosità dell’accesso ai suoi elementi: l’unico modo per raggiungere un elemento della lista è quello di seguire le connessioni della lista dall’inizio in modo sequenziale. Mentre negli array è possibile accedere a qualsiasi elemento attraverso un indice. p delle liste sono diversi da • I metodi di manipolazione quelli visti per gli array. generale una lista rende più semplice la • In generale, riorganizzazione degli elementi. Informatica Medica, I semestre, C++ • Gli array presentano due limitazioni: – Modificare M difi l dimensione la di i d ll’ dell’array richiede i hi d la l creazione di un nuovo array e la copia di tutti i dati dall’array dall array originale a quello nuovo. nuovo – Inserire un elemento nell’array richiede lo scorrimento di dati nell nell’array array. • Queste limitazioni possono essere superate da strutture concatenate: una collezione di nodi che contengono dati e puntatori ad altri nodi. nodi In particolare si fa riferimento alle liste concatenate ((linked list). ) Una possibile uscita Informatica Medica, I semestre, C++ Liste: introduzione 11 • Una possibile interfaccia pubblica (comportamento) è laa segue seguente: te: I Inserisce i l' l'oggetto x in i fondo f d alla ll lista. class LinkedList{ ... public: LinkedList(); ~LinkedList(); bool Add(char x); bool Remove(char x); bool Contains(char x)const; bool IsEmpty()const; void Clear(); void Print() const; }; Informatica Medica, I semestre, C++ Cancella C ll la l prima i dell'oggetto x. V ifi Verifica se l'oggetto x. l la li t lista occorrenza contiene ti V ifi se la Verifica l lista li t è logicamente l i t vuota. Svuota la lista. lista Stampa a monitor il contenuto d ll lista. della li t 12 Liste: comportamento Liste: esempio p Sia L un oggetto di tipo LinkedList: ecco alcune operazioni Invocazione metodo Stato della lista risultato L.IsEmpty(); [] true L.Add(‘a’); [a] true L.Add(‘b’); ( ); [a b] true L.Add(‘c’); [a b c] true L.Contains(‘b’); ‘ ’ [a b c] true L.Remove(‘b’); [a c] true L.Contains(‘b’); [a c] false L.IsEmpty(); [[a c]] false Informatica Medica, I semestre, C++ • Si scriva una funzione che legge caratteri da tastiera e li memorizza i i una lista. in li L’inserimento i i è terminato i d l carattere dal ‘.’. Si stampi il contenuto della lista prima e dopo la rimozione di un elemento. elemento • Utilizzando la lista, lista non e necessario conoscere il numero di caratteri da memorizzare: viene allocata la giusta quantità di elementi. • Quando si rimuove un elemento, non e necessario spostare una parte degli elementi rimasti per riempire il vuoto lasciato dalla rimozione (come si farebbe con gli array). 13 Liste: esempio p 14 Liste: implementazione p void test2(){ Si utilizza un oggetto di tipo lista LinkedList L; conoscendo la sua interfaccia pubblica. char h c; N Non è necessario i sapere come è cin>>c; implementato: la lista è un ADT. while(c!='.'){ L.Add(c); Una possibile uscita cin>>c; ABC. } Node(A) cout<<"---------"<<endl; Node(B) Node(C) L.Print(); --------L.Remove('B'); ABC L.Print(); ~Node(B) cout<<"---------"<<endl; cout<< <<endl; AC } --------( ) ~Node(A) ~Node(C) Informatica Medica, I semestre, C++ Informatica Medica, I semestre, C++ 15 • Una possibile realizzazione della lista consiste nel considerare una sequenza di nodi che contengono un dato e un puntatore al nodo successivo nella sequenza. Tale sequenza è realizzata nella classe LinkedList che implementa l’interfaccia mostrata nei lucidi precedenti. gg di tipo p LinkedList è manipolato p • Un oggetto utilizzando i metodi resi disponibili dall’interfaccia ppubblica ((comportamento), p ), non si accede direttamente alla sequenza di nodi. È un tipo p di dato astratto,, ADT. Informatica Medica, I semestre, C++ 16 Nodi : operazioni principali, rimozione Nodi: operazioni p p principali p Node *current current • Le due principali operazioni da realizzare sulle liste concatenate (sui nodi della lista nella fase di implementazione) p ) sono: la rimozione di un elemento della lista e l’inserimento di un elemento nella lista. Quindi diminuire o aumentare la l lunghezza h di una lista. li • La semplicità di tali operazioni è la ragione d’essere delle liste concatenate: si modifica solo i puntatori, i inoltre i l solo l quelli lli relativi l i i aii nodi di da d manipolare. Informatica Medica, I semestre, C++ 17 Nodi : operazioni principali, inserimento <<oggetto>> Node data: ‘A’ link <<oggetto>> Node data: ‘C’ link <<oggetto>> Node data: ‘B’ link=0 Il nodo da inserire viene messo nella catena di collegamenti ((l’operazione p è fatta sul nodo che si vuole inserire e sul nodo che precede la posizione di inserimento): tmp->SetLink(current->GetLink()); current->SetLink(tmp); current >SetLink(tmp); <<oggetto>> gg Node data: ‘A’ link <<oggetto>> gg Node data: ‘B’ link Informatica Medica, I semestre, C++ <<oggetto>> Node data: ‘B’ link <<oggetto>> Node data: ‘C’ link Il nodo da rimuovere viene tolto dalla catena di collegamenti e distrutto (l’operazione è eseguita sul nodo che precede quello che si vuole rimuovere): Node *tmp=current->GetLink(); current->SetLink(current->GetLink()->GetLink()); d l t tmp; delete t <<oggetto>> Node data: ‘A’ link <<oggetto>> gg Node data: ‘B’ li k link <<oggetto>> Node data: ‘C’ link Informatica Medica, I semestre, C++ 18 Liste: implementazione p Node *tmp tmp Node *current <<oggetto>> Node data: ‘A’ link <<oggetto>> gg Node data: ‘C’ link 19 • Vediamo una possibile implementazione della lista. • Si usa un campo first che mantiene il puntatore al primo nodo della lista e un campo last che mantiene un puntatore all’ultimo nodo. • L’ultimo nodo è caratterizzato dal campo link con valore 0. • Il costruttore crea una lista vuota: cioè i due campi puntatori hanno valore 0. • Il distruttore deve deallocare i nodi ancora presenti nella lista. Informatica Medica, I semestre, C++ 20 Liste: implementazione p Liste: LinkedList.h #ifndef LINKEDLIST_H #define LINKEDLIST_H #include "Node.h" class LinkedList{ Node * first; Node * last; public: LinkedList(); ~LinkedList(); bool Add(char x); bool Remove(char x); bool Contains(char x)const; bool IsEmpty()const; void Clear(); void Print() const; }; #endif • Una possibile rappresentazione grafica: <<oggetto>> LinkedList first last <<oggetto>> Node data link <<oggetto>> Node data link … <<oggetto>> Node data link=0 Informatica Medica, I semestre, C++ 21 Liste: LinkedList.cpp pp #include "Node.h" #include "LinkedList.h" #include <iostream> using g namespace p std; ; LinkedList::LinkedList(){ (){ first=last=0; } LinkedList:: LinkedList(){ LinkedList::~LinkedList(){ Clear(); } Informatica Medica, I semestre, C++ Informatica Medica, I semestre, C++ 22 Liste: LinkedList.cpp pp bool LinkedList::Contains(char x)const { for(Node *i=first; i!=0; i=i->GetLink()) if (i->GetData()==x) return true; return false; } La lista viene creata vuota: non esiste nessun nodo collegato collegato. Il distruttore deve deallocare i nodi ancora presenti nella lista,, q p quindi deve eseguire un Clear(). In tal modo si fattorizza il codice, cioè non si duplica esplicitamente il codice ma si richiama il metodo opportuno opportuno. 23 Per verificare la presenza di un dato oggetto x nella lista, si devono esaminare i nodi della lista. lista Si può fare con un ciclo for: si definisce il contatore i come un puntatore di tipo Node* a cui si assegna g il p puntatore al p primo nodo. Si esce dal ciclo q quando si finiscono i nodi, cioè il link vale 0. L’incremento si ottiene con l’assegnamento i=i->GetLink(), che fa puntare il contatore i al nodo d collegato. ll Informatica Medica, I semestre, C++ 24 Liste: ricerca Liste: LinkedList.cpp pp Node *i=first; bool LinkedList::IsEmpty()const{ return first first==0; 0; } i first <<oggetto>> Node data link <<oggetto>> Node data link <<oggetto>> Node data link bool LinkedList::Add(char x) { Lista vuota: modifico if (IsEmpty()) ( ()) entrambi i campi first=last=new Node(x); else{ last->SetLink(new Node(x)); last=last->GetLink(); } Lista s non o vuo vuota:: modifico od co return true; solo in campo che punta } i=i->GetLink() i i >GetLink() i first <<oggetto>> Node data li k link <<oggetto>> Node data li k link <<oggetto>> Node data li k link La lista è vuota se il campo che h punta t all primo nodo vale 0 all’ultimo nodo Informatica Medica, I semestre, C++ 25 Liste: inserimento last <<oggetto>> Node data link <<oggetto>> Node data link=0 Lista non vuota: last->SetLink(new Node(x)); <<oggetto>> Node data link <<oggetto>> Node data link=0 last=last->GetLink(); last <<oggetto>> Node data link <<oggetto>> Node data link Informatica Medica, I semestre, C++ 26 Liste: prestazioni p last <<oggetto>> Node data link Informatica Medica, I semestre, C++ • Aggiungere un nodo alla lista ha un costo computazionale costante O(1): ( ) è indipendente i di d d l numero di nodi dal di nella ll lista. Non si devono copiare gli elementi in una nuova struttura come nel caso di array pieni. struttura, pieni • Verificare se la lista contiene un dato oggetto implica l’uso di un ciclo for. Si consideri il caso medio: se ogni nodo della lista ha la stessa probabilità di contenere il dato, allora si ha una iterazione per verificare il primo nodo, due iterazioni per verificare il secondo nodo, e in una sequenza lunga n si ha in media n 1 1 n(n 1) O ( n) i 2 n i 1 n <<oggetto>> Node data link=0 27 Informatica Medica, I semestre, C++ 28 Liste: LinkedList.cpp bool LinkedList::Remove(char x) { L’ L’operazione i di rimozione i i è eseguita i if (IsEmpty()) return false; sul nodo che precede quello che si if (first->GetData()==x) vuole rimuovere { if (first==last) last=0; Node *tmp=first->GetLink(); Controllo il primo elemento delete first; first=tmp; first tmp; return true; } for (Node *i=first; i->GetLink()!=0; i=i->GetLink()) { if (i->GetLink()->GetData()==x) Se non è il primo elemento { if (i (i->GetLink()==last) >GetLink() last) last=i; Node *tmp=i->GetLink(); i->SetLink(i->GetLink()->GetLink()); delete tmp; return true; } } return false; 29 } Informatica Medica, I semestre, C++ <<oggetto>> Node data link LINKEDLIST: rimozione <<oggetto>> Node data link Primo elemento <<oggetto>> Node data link Node *tmp=first->GetLink(); delete first; first tmp <<oggetto>> Node data link first=tmp; <<oggetto>> Node data link <<oggetto>> Node data link first <<oggetto>> Node data link <<oggetto>> Node data link Informatica Medica, I semestre, C++ 30 Liste: LinkedList.cpp Liste: prestazioni p • Considerazioni analoghe a quelle sviluppate per calcolare la complessità computazionale media del metodo per la ricerca di un dato valgono anche per il metodo di rimozione di un dato. • Quindi il metodo Remove() ha in media un costo lineare cioè O(n). lineare, O(n) Informatica Medica, I semestre, C++ first 31 void LinkedList::Clear() { Node *i=first; while(i!=0) { Node *tmp=i->GetLink(); delete i; i=tmp; } first=0; last 0; last=0; } Per svuotare la lista si devono deallocare tutti i nodi presenti e aggiornare lo stato. void LinkedList::Print()const { for(Node *i=first; i!=0; i=i->GetLink()) cout<<i->GetData()<<" cout<<i >GetData()<< "; ; cout<<endl; } Informatica Medica, I semestre, C++ 32 Liste: considerazioni Liste doppiamente pp concatenate • Se è necessario un accesso immediato a ciascun elemento, l allora ll l’ l’array è la l scelta l migliore. i li • Se si accede frequentemente solo ad alcuni elementi l i (i primi, i i gli li ultimi) l i i) e se la l modifica df d ll della struttura è il cuore di un algoritmo, allora la lista concatenata è la scelta migliore. migliore • Un vantaggio delle liste è che usano la quantità minima di memoria necessaria e sono facilmente ridimensionabili. Al contrario un array può essere modificato in dimensione solo di quantità fisse (per esempio dimezzato o raddoppiato). Informatica Medica, I semestre, C++ 33 Liste con salti • Per trovare un elemento in una lista è necessaria uno scansione sequenziale. Anche se la lista è ordinata è sempre necessaria una scansione sequenziale. questo pproblema utilizzando una lista con • Si evita q salti: liste che consentano di saltare alcuni nodi pper evitare manipolazioni p sequenziali. q • La struttura interna è più complessa: nodi con un numero di campi riferimento diverso. diverso Informatica Medica, I semestre, C++ 35 • Esistono altri tipi di lista, vediamone alcuni. • La lista descritta è di tipo semplicemente concatenata: t t i nodi di hanno h i f informazione i solo l sull loro successore, per cui non si ha accesso al loro predecessore. predecessore • Si può definire una lista doppiamente concatenata in modo che ogni nodo abbia due campi riferimento: uno al successore e uno al p predecessore. • In tal modo è possibile percorrere la lista nei due sensi. Informatica Medica, I semestre, C++ 34