Sommario Collezioni • Una collezione (contenitore) è un oggetto che raggruppa più dati in una singola struttura. Vi sono due tecniche fondamentali per rappresentare collezioni di elementi: quella basata su strutture indicizzate (array, composti da elementi consecutivi) 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. • Liste: – LinkedList: specifica e implementazione. – Prestazioni. • Le collezioni sono usate per memorizzare, leggere, manipolare dati strutturati e per trasmettere tali dati da un metodo ad un altro. • Pile (stack) Informatica Medica, I semestre, C++ 1 Informatica Medica, I semestre, C++ 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 l’ordinamento dei dati è solo a livello logico (senza usare un sottostante ordinamento fisico). • Questo si può realizzare associando ad ogni elemento anche un puntatore all’elemento che lo segue nella 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 l’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 Collezioni v indice ‘a’ ‘c’ ‘a’ ‘r’ ‘f’ 0 1 2 3 4 data: ‘a’ link data: ‘c’ link Informatica Medica, I semestre, C++ 4 • L’idea di dati più collegamenti è implementata con una classe Node. • Lo stato di tale classe è descritto da un campo che contiene il dato (per esempio di tipo float o Persona) e un campo puntatore (Node *) al nodo collegato. • Il comportamento è descritto dai metodi per leggere e scrivere lo stato, oltre al costruttore e distruttore. Struttura collegata data: ‘a’ link <<oggetto>> Node data link=0 Nodi Struttura indicizzata (array) s 2 data: ‘r’ link data: ‘f’ Link=0 5 • Nell’implementazione che segue il tipo del dato memorizzato nel nodo è char. Informatica Medica, I semestre, C++ 6 Nodi: Node.h #ifndef NODE_H #define NODE_H class Node{ char data; Node *link; public: Node(char d, Node *l=0); ~Node(); char GetData()const; Node * GetLink()const; void SetData(char d); void SetLink(Node *l); }; #endif Nodi: Node.cpp La dichiarazione del campo link di un nodo può sembrare non corretta. Tuttavia è corretta, ciò che dichiara è un puntatore ad un oggetto di tipo Node e non un oggetto stesso. Informatica Medica, I semestre, C++ Nodi: test.cpp void test1(){ Node *n=new Node('A'); n->SetLink(new Node('B')); 7 Si collega l’oggetto nodo n ad un altro oggetto nodo attraverso il puntatore a tale nodo . cout<<n->GetData()<<" "; cout<<n->GetLink()->GetData()<<endl; delete n->GetLink(); delete n; } Si deve prima distruggere l’oggetto nodo collegato e poi il nodo che contiene il collegamento Attraverso n si 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++ 8 Liste: introduzione • Gli array presentano due limitazioni: – Modificare la dimensione dell’array richiede la creazione di un nuovo array e la copia di tutti i dati dall’array originale a quello nuovo. – Inserire un elemento nell’array richiede lo scorrimento di dati nell’array. Informatica Medica, I semestre, C++ 10 Liste: specifica • 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. • I metodi di manipolazione delle liste sono diversi da quelli visti per gli array. • In generale, una lista rende più semplice la riorganizzazione degli elementi. Informatica Medica, I semestre, C++ Vista la dichiarazione del campo link di un nodo, nel costruttore non si deve allocare un oggetto Node, ma solo assegnare un puntatore passato al costruttore. • Queste limitazioni possono essere superate da strutture concatenate: una collezione di nodi che contengono dati e puntatori ad altri nodi. In particolare si fa riferimento alle liste concatenate (linked list). Una possibile uscita Informatica Medica, I semestre, C++ #include "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){ data=d; } void Node::SetLink(Node *l){ link=l; } 11 • Una possibile interfaccia pubblica (comportamento) è la seguente: Inserisce l'oggetto x in fondo alla 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++ lista. Cancella la prima dell'oggetto x. Verifica se l'oggetto x. la lista occorrenza contiene Verifica se la lista è logicamente vuota. Svuota la lista. Stampa a monitor il contenuto della lista. 12 Liste: comportamento Liste: esempio 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 in una lista. L’inserimento è terminato dal carattere ‘.’. Si stampi il contenuto della lista prima e dopo la rimozione di un elemento. • Utilizzando la 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 Informatica Medica, I semestre, C++ Liste: esempio Liste: implementazione void test2(){ Si utilizza un oggetto di tipo lista LinkedList L; conoscendo la sua interfaccia pubblica. char c; Non è necessario 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; AC } --------~Node(A) ~Node(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. • Un oggetto di tipo LinkedList è manipolato utilizzando i metodi resi disponibili dall’interfaccia pubblica (comportamento), non si accede direttamente alla sequenza di nodi. È un tipo di dato astratto, ADT. Informatica Medica, I semestre, C++ 16 Nodi : operazioni principali, rimozione Nodi: operazioni principali Node *current • Le due principali operazioni da realizzare sulle liste concatenate (sui nodi della lista nella fase di implementazione) sono: la rimozione di un elemento della lista e l’inserimento di un elemento nella lista. Quindi diminuire o aumentare la lunghezza di una lista. • La semplicità di tali operazioni è la ragione d’essere delle liste concatenate: si modifica solo i puntatori, inoltre solo quelli relativi ai nodi da manipolare. Informatica Medica, I semestre, C++ 14 17 <<oggetto>> Node data: ‘A’ link <<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()); delete tmp; <<oggetto>> Node data: ‘A’ link <<oggetto>> Node data: ‘B’ link Informatica Medica, I semestre, C++ <<oggetto>> Node data: ‘C’ link 18 Nodi : operazioni principali, inserimento Node *tmp Node *current <<oggetto>> Node data: ‘A’ link <<oggetto>> Node data: ‘B’ link=0 <<oggetto>> Node data: ‘C’ link Il nodo da inserire viene messo nella catena di collegamenti (l’operazione è fatta sul nodo che si vuole inserire e sul nodo che precede la posizione di inserimento): tmp->SetLink(current->GetLink()); current->SetLink(tmp); <<oggetto>> Node data: ‘A’ link <<oggetto>> Node data: ‘B’ link <<oggetto>> Node data: ‘C’ link Informatica Medica, I semestre, C++ 19 Liste: implementazione <<oggetto>> LinkedList first last <<oggetto>> Node <<oggetto>> Node data link … <<oggetto>> Node data link=0 Informatica Medica, I semestre, C++ 21 Liste: LinkedList.cpp #include "Node.h" #include "LinkedList.h" #include <iostream> using namespace std; LinkedList::LinkedList(){ first=last=0; } LinkedList::~LinkedList(){ Clear(); } Informatica Medica, I semestre, C++ • 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: LinkedList.h • Una possibile rappresentazione grafica: data link Liste: implementazione #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 Informatica Medica, I semestre, C++ 22 Liste: LinkedList.cpp La lista viene creata vuota: non esiste nessun nodo collegato. Il distruttore deve deallocare i nodi ancora presenti nella lista, 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. 23 bool LinkedList::Contains(char x)const { for(Node *i=first; i!=0; i=i->GetLink()) if (i->GetData()==x) return true; return false; } Per verificare la presenza di un dato oggetto x nella lista, si devono esaminare i nodi della lista. Si può fare con un ciclo for: si definisce il contatore i come un puntatore di tipo Node* a cui si assegna il puntatore al primo nodo. Si esce dal ciclo 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 collegato. Informatica Medica, I semestre, C++ 24 Liste: ricerca Liste: LinkedList.cpp Node *i=first; bool LinkedList::IsEmpty()const{ return first==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 non vuota: modifico return true; solo in campo che punta } i=i->GetLink() i first <<oggetto>> Node data link <<oggetto>> Node data link <<oggetto>> Node data link La lista è vuota se il campo che punta al primo nodo vale 0 all’ultimo nodo Informatica Medica, I semestre, C++ 25 Informatica Medica, I semestre, C++ Liste: inserimento Liste: prestazioni last <<oggetto>> Node data link <<oggetto>> Node data link=0 <<oggetto>> Node data link <<oggetto>> Node data link Lista non vuota: last->SetLink(new Node(x)); last last=last->GetLink(); <<oggetto>> Node data link <<oggetto>> Node data link <<oggetto>> Node data link=0 last • Aggiungere un nodo alla lista ha un costo computazionale costante O(1): è indipendente dal numero di nodi nella lista. Non si devono copiare gli elementi in una nuova struttura, come nel caso di array 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) ∑ i = n 2 = O ( n) n i =1 <<oggetto>> Node data link=0 Informatica Medica, I semestre, C++ 26 27 Informatica Medica, I semestre, C++ Liste: LinkedList.cpp bool LinkedList::Remove(char x) { L’operazione di rimozione è eseguita 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; return true; } for (Node *i=first; i->GetLink()!=0; i=i->GetLink()) { if (i->GetLink()->GetData()==x) Se non è il primo elemento { if (i->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++ first 28 LINKEDLIST: rimozione <<oggetto>> Node data link <<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 Informatica Medica, I semestre, C++ <<oggetto>> Node data link 30 Liste: LinkedList.cpp Liste: prestazioni • 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). Informatica Medica, I semestre, C++ 31 Liste: considerazioni void LinkedList::Print()const { for(Node *i=first; i!=0; i=i->GetLink()) cout<<i->GetData()<<" "; cout<<endl; } Informatica Medica, I semestre, C++ 32 33 • Esistono altri tipi di lista, vediamone alcuni. • La lista descritta è di tipo semplicemente concatenata: i nodi hanno informazione solo sul loro successore, per cui non si ha accesso al loro predecessore. • Si può definire una lista doppiamente concatenata in modo che ogni nodo abbia due campi riferimento: uno al successore e uno al predecessore. • In tal modo è possibile percorrere la lista nei due sensi. Informatica Medica, I semestre, C++ Liste con salti 34 Pile • Per trovare un elemento in una lista è necessaria uno scansione sequenziale. Anche se la lista è ordinata è sempre necessaria una scansione sequenziale. • Si evita questo problema utilizzando una lista con salti: liste che consentano di saltare alcuni nodi per evitare manipolazioni sequenziali. • La struttura interna è più complessa: nodi con un numero di campi riferimento diverso. Informatica Medica, I semestre, C++ Per svuotare la lista si devono deallocare tutti i nodi presenti e aggiornare lo stato. Liste doppiamente concatenate • Se è necessario un accesso immediato a ciascun elemento, allora l’array è la scelta migliore. • Se si accede frequentemente solo ad alcuni elementi (i primi, gli ultimi) e se la modifica della struttura è il cuore di un algoritmo, allora la lista concatenata è la scelta 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++ void LinkedList::Clear() { Node *i=first; while(i!=0) { Node *tmp=i->GetLink(); delete i; i=tmp; } first=0; last=0; } 35 • La pila (stack) è una collezione (un oggetto che raggruppa più dati in una singola struttura) con un comportamento specifico. Lo stack è un ADT. • Lo stack è caratterizzato da: – Interfaccia pubblica. – Implementazione: • Strutture indicizzate (array): array di dimensione variabile. • Strutture collegate (nodi). • Vi sono degli algoritmi che sfruttano comportamento degli oggetti di tipo stack. il Informatica Medica, I semestre, C++ 36 Pile Pile : interfaccia class Stack{ … public: Stack(); ~Stack(); void Push(char x); char Pop(); char Top() const ; bool IsEmpty() const ; void Clear(); }; • Una pila è una sequenza <a1,…,an> di elementi dello stesso tipo, di cui solo l’ultimo elemento inserito, an, è visibile all’utente e la modalità di accesso è “ultimo elemento inserito, primo elemento rimosso” (LIFO: last in, first out). • È una struttura dati lineare a cui si può accedere soltanto mediante uno dei suoi capi per memorizzare e per estrarre dati. • Una pila può essere descritta in termini di operazioni che ne modificano lo stato o che ne verificano lo stato: pertanto è un ADT. Informatica Medica, I semestre, C++ 37 Informatica Medica, I semestre, C++ Pile • Vediamo graficamente alcune operazioni su una pila. Pila vuota push ‘a’ push ‘b’ pop push ‘r’ push ‘s’ pop ‘s’ ‘b’ ‘a’ ‘a’ Informatica Medica, I semestre, C++ ‘a’ ‘r’ ‘r’ ‘r’ ‘a’ ‘a’ ‘a’ 39 Inserisce l'oggetto x in cima alla pila. Rimuove e restituisce l'oggetto in cima alla pila. Restituisce l'oggetto in cima alla pila senza rimuoverlo. Verifica se la pila è logicamente vuota. Svuota la pila. 38