Sommario • Collezioni: • Liste: • Pile (stack) Collezioni Collezioni

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