Algoritmi e strutture dati
Contenitori e Iteratori STL
Contenitori
●
La STL fornisce una serie di classi standard che realizzano dei
contenitori.
●
Esempi di contenitori sono le liste, i vettori, gli alberi, etc.
●
Un contenitore (container) è una collezione di oggetti di tipo
omogeneo, organizzati in un certo modo.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Programmazione tradizionale (C)
algo1()
algo2()
vector
list
algo3()
hash
search tree
unsigned
char
unsigned
int
int
3 algoritmi
72 definizioni
4 strutture dati
24 definizioni
char
float
6 tipi elementari
double
Fare una libreria di contenitori con la programmazione tradizionale del C porta ad
un esplosione di definizioni di strutture dati e di algoritmi. È necessario definire una
struttura list per ogni tipo elementare (char, unsigned char, etc.). Inoltre è necessario
definire un algoritmo algo1 per ogni struttura list definita. Se E è il numero di tipi
elementari, C il numero di contenitori e A il numero di algoritmi, bisogna definire
E*C strutture dati ed E*C*A algoritmi.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Programmazione generica
●
La STL del C++ utilizza la programmazione generica.
●
La classe “list” viene implementata una volta sola. Il tipo degli
elementi è un generico tipo T.
●
Non importa sapere a priori quale sarà T, basta che soddisfi
alcuni requisiti (constraints).
●
●
Per esempio: il T dei search tree dev'essere ordinabile (<)
Programmazione generica significa programmare utilizzando dei
concetti. Un concetto è una famiglia di astrazioni che hanno un
insieme comune di operazioni.
Per risolvere questo problema, la libreria STL (Standard Template Library) usa la
programmazione generica. La classe list viene implementata una volta sola. Il
tipo degli elementi è un generico tipo T. La programmazione generica non va
confusa con la programmazione a oggetti, tipica di linguaggi a più alto livello come
il Java. In Java il problema si risolve con l'ereditarietà. Ovvero si definisce un tipo
object di cui tutti gli altri tipi sono derivati. In questo modo basta definire una
lista di object, e automaticamente abbiamo definito tutte le possibili liste di tutti i
possibili tipi elementari. L'approccio a oggetti è più semplice e porta a meno errori
di programmazione. Tuttavia è poco efficiente, perché bisogna fare continue
conversioni di tipo da object a int (nel caso di lista di interi).
Nella programmazione generica invece non si definisce nessuna classe object.
Basta che il tipo T fornisca alcuni requisiti (constraints), necessari per stare in una
lista. L'approccio generico è più flessibile ed efficiente, ma può portare ad errori di
programmazione se usato in modo sbagliato.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Programmazione generica (C++)
algo1()
Concetto di
“contenitore”
algo2()
vector
list
Concetto di tipo
elementare “T”
algo3()
hash
search tree
unsigned
char
unsigned
int
int
3 algoritmi
3 definizioni
4 strutture dati
4 definizioni
char
float
6 tipi elementari
double
Con l'approccio generico si racchiudono tutti i possibili tipi elementari nel
concetto di tipo elementare T. Quindi si definisce una sola struttura dati list
generica di elementi di tipo T. Poi si racchiudono tutti i possibili contenitori nel
concetto di contenitore. Quindi si programmano algoritmi che operano su un
generico contenitore.
Per rendere generico il concetto di tipo elementare basta ricorrere al meccanismo
dei template. Per rendere generico il concetto di contenitore si ricorre al
meccanismo dei template più al concetto di iteratore.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Contenitori
●
Contenitori si dividono in:
●
●
●
●
Sequenze
Adattatori
Quasi-contenitori
Contenitori associativi
I contenitori (containers) in C++ si dividono in quattro famiglie: (1) le sequenze
sono i contenitori che impongono un ordinamento lineare nelle posizioni degli
elementi, ovvero dati due elementi si può sempre definire quale sta prima e quale
sta dopo (esempio: list); (2) gli adattatori non sono contenitori indipendenti ma
si appoggiano ad un altro container e ne estendono le funzionalità (esempio:
priority_queue); (3) i quasi-contenitori non esportano tutte le funzionalità di
un contenitore propriamente detto (esempio: string); (4) i contenitori associativi
sono contenitori che non impongono un ordinamento lineare nelle posizioni degli
elementi (esempio: map).
Algoritmi e strutture dati
Contenitori e Iteratori STL
Sequenze
size
●
vector<T>:
10
1
4
front
12
4
back
capacity
●
list<T>:
10
1
4
12
4
front
back
size
●
deque<T>:
4
12
size
4
10
back
front
1
Il vector è un semplice array dinamico di grandezza “size”, memorizzato in un
buffer di capacità “capacity”. La capacità del buffer è sempre maggiore o uguale
alla grandezza del vettore. L'allocazione del buffer è gestita: se il vettore cresce
oltre la capacità del buffer, viene automaticamente riallocato in un buffer più
capiente, e tutti gli elementi copiati nel nuovo buffer.
La list è una lista doppia. Ogni elemento contiene non solo il puntatore
all'elemento successivo ma anche a quello precedente. Sono poi definiti il puntatore
alla testa della lista ed il puntatore alla coda della lista.
La deque (pron. “deck”, sta per double-ended queue) è un vettore circolare.
Il vector, la list e la deque sono sequenze, in quanto organizzano la
collezione di elementi secondo un ordine lineare. Per “front” si intende l'elemento
di testa, cioè il primo della sequenza. Per “back” quello di coda, cioè l'ultimo della
sequenza.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Iteratori
●
Un iteratore è un oggetto che punta ad un elemento del
contenitore. Sono un'astrazione del concetto di puntatore.
i
10
1
4
12
4
Un iteratore è un oggetto che punta ad un elemento di un contenitore. Può essere
pensato come un'astrazione di un puntatore. Il vantaggio degli iteratori è che
offrono un set di operazioni comuni con un'interfaccia che non dipende dal tipo di
contenitore iterato.
La tecnica degli iteratori è usata dalle librerie standard di quasi tutti i linguaggi
moderni (Java, C#, perl, etc.) Gli iteratori del C++ sono particolarmente potenti in
quanto permetto non solo di accedere agli elementi in lettura e scrittura ma anche di
modificare il contenitore stesso (inserire elementi, cancellare elementi, etc.)
Algoritmi e strutture dati
Contenitori e Iteratori STL
Iteratori
●
Su ogni iteratore i sono ridefinite le operazioni di:
●
●
●
Dereferenziazione (accesso all'elemento puntato): *i
Scorrimento all'elemento successivo/precedente nella sequenza:
i++, i-Test di (dis)uguaglianza: i==j, i!=j
i
10
1
4
12
4
Algoritmi e strutture dati
Contenitori e Iteratori STL
Iteratori
●
Su ogni contenitore c sono definiti due metodi:
●
●
c.begin() restituisce un iteratore che punta al primo elemento
di c
c.end() restituisce un iteratore che punta all'elemento
successivo dell'ultimo
10
begin
1
4
12
4
end
Algoritmi e strutture dati
Contenitori e Iteratori STL
Iteratori
10
begin
1
2
3
4
5
6
7
1
i
++
4
12
4
end
void stampa_vector_int(vector<int> vett){
vector<int>::iterator i;
// scorro gli elementi in ordine:
for(i = vett.begin(); i != vett.end(); i++){
cout << *i; // stampo l'elemento
}
}
Questo algoritmo stampa gli elementi di un vettore di interi. La funzione
vett.begin() restituisce un iteratore all'elemento di testa (front element). La funzione
vett.end() restituisce un iteratore che punta ad un immaginario elemento successivo
all'ultimo.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Iteratori
10
begin
1
2
3
4
5
6
7
8
1
i
++
4
12
4
end
template<class T>
void stampa_vector(vector<T> vett){
typename vector<T>::iterator i;
// scorro gli elementi in ordine:
for(i = vett.begin(); i != vett.end(); i++){
cout << *i; // stampo l'elemento
}
}
Questo algoritmo stampa gli elementi di un vettore di tipo generico. Quando si
usa un'espressione generica a sinistra del “::”, si rende necessaria la parola chiave
typename.
Constraints: Il tipo T deve avere il vincolo di essere stampabile (definito
l'operatore << su ostream). Quindi la funzione può essere invocata su vettori di
interi, caratteri, stringhe, etc. ma non su vettori di classi, a meno che di non
ridefinire il <<.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Iteratori
i
10
1
4
++
12
begin
1
2
3
4
5
6
7
8
4
end
template<class Cont>
void stampa(Cont c){
typename Cont::iterator i;
// scorro gli elementi in ordine:
for(i = c.begin(); i != c.end(); i++){
cout << *i; // stampo l'elemento
}
}
Questo è lo stesso algoritmo ma generico sul tipo di contenitore.
Constraints: L'unico vincolo è che il tipo Cont deve supportare gli iteratori.
Quindi funziona con qualsiasi contenitore STL (vector, list, etc.)
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esempio di programmazione generica
●
Funzione generica che azzera gli elementi precedenti agli
elementi dispari di una sequenza
10
1
4
12
1
0
1
4
0
1
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esempio di programmazione generica
1
template<class Cont>
2
void azzera_predispari(Cont c){
3
typename Cont::iterator i;
4
for(i = c.begin(); i != c.end(); i++){
5
if(*i%2 == 1 && i != c.begin())
6
i--;
7
*i = 0; // azzero l'elemento precedente
8
i++;
9
}
10
}
11 }
i
10
begin
1
4
12
1
end
Constraints: Il tipo Cont deve supportare gli iteratori. In più gli elementi devono
avere definito l'operatore %. Quindi l'algoritmo è applicabile a vettori, liste, etc. di
elementi interi (char, int, unsigned int, etc.)
Algoritmi e strutture dati
Contenitori e Iteratori STL
Validità degli iteratori
●
Un iteratore può essere:
●
●
Valido: se punta ad un elemento (può essere dereferenziato)
Non valido: se non punta ad un elemento (non può essere
dereferenziato)
i
5
begin
5
4
++
5
5
end
Un iteratore è valido se punta ad un elemento. L'iteratore restituito da end() è un
po' speciale, in quanto non è valido (non punta a nessun elemento) ma su di esso
può essere applicato ugualmente l'operatore di spostamento all'indietro (i--).
Algoritmi e strutture dati
Contenitori e Iteratori STL
Validità degli iteratori
●
Alcune operazioni che modificano il contenitore invalidano
i
++
alcuni (o tutti) gli iteratori.
5
5
4
5
5
begin
1
2
3
4
5
template<class Cont> void cancella_pari(Cont& c){
typename Cont::iterator i;
for(i = c.begin(); i != c.end(); i++)
if(*i % 2 == 0) c.erase(i); // errore!
}
1
2
3
4
5
6
template<class Cont> void cancella_pariOK(Cont& c){
typename Cont::iterator i;
for(i = c.begin(); i != c.end(); ) {
if(*i % 2 == 0) i = c.erase(i); // OK
else i++; }
}
end
errore
classico
La funzione c.erase(iterator i) cancella dalla sequenza l'elemento puntato da i.
L'iteratore i viene invalidato (non punta più ad un elemento valido). Un errore
comune (esempio 1) consiste nello scorrere una sequenza cancellando o inserendo
elementi, senza fare attenzione all'invalidazione degli iteratori. Per mantenere
l'iteratore i valido bisogna programmare come nell'esempio 2. Notare che la
funzione c.erase() restituisce un iteratore alla posizione successiva a quella
cancellata. In questo caso non ho bisogno di incrementare l'iteratore. L'istruzione
i++ va messa quindi nella parte else (riga 5) e non nel passo del for (riga 3).
Algoritmi e strutture dati
Contenitori e Iteratori STL
Iteratori a costante
●
Un iteratore a costante è un iteratore che non può modificare il
container
●
●
●
vector<int>::iterator → iteratore a mutabile
vector<int>::const_iterator → iteratore a costante
Con contenitori costanti è obbligatorio usare iteratori a costante
1
2
3
4
5
template<class Cont> void stampa_pari(const Cont& c){
typename Cont::iterator i; // errore! Il contenitore è costante.
for(i = c.begin(); i != c.end(); i++)
if(*i % 2 == 0) cout << *c << ' ';
}
1
2
3
4
5
template<class Cont> void stampa_pariOK(const Cont& c){
typename Cont::const_iterator i; // ok
for(i = c.begin(); i != c.end(); i++)
if(*i % 2 == 0) cout << *c << ' ';
}
Algoritmi e strutture dati
Contenitori e Iteratori STL
Iteratori inversi
●
Gli iteratori inversi vedono il contenitore come se fosse
“rovesciato”. Sono utili per visitare in ordine inverso.
●
●
●
Ogni contenitore esporta le funzioni:
●
●
●
vector<int>::reverse_iterator → iteratore inverso
vector<int>::const_reverse_iterator → iteratore inverso a costante
c.rbegin() → iteratore all'ulƟmo elemento
c.rend() → iteratore precedente al primo elemento
i++ sposta l'iteratore i all'indietro.
++
rend
5
5
i
4
rbegin
5
5
Algoritmi e strutture dati
Contenitori e Iteratori STL
Iteratori
++
rend
5
1
2
3
4
5
6
7
8
5
i
4
rbegin
5
5
template<class Cont>
void stampa_contrario(Cont c){
typename Cont::reverse_iterator i;
// scorro gli elementi in ordine:
for(i = c.rbegin(); i != c.rend(); i++){
cout << *i; // stampo l'elemento
}
}
Questo algoritmo stampa dall'ultimo al primo gli elementi di un contenitore.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Mobilità degli iteratori
●
Un iteratore può essere:
●
●
●
Forward: permettono di avanzare in una sequenza (i++)
Bidirectional: permettono di scorrere in entrambi i sensi (i++, i--)
Random Access: permettono di saltare da un elemento all'altro
(i++, i--, i+=5, i-=5)
●
list<T> esporta iteratori Bidirectional
●
vector<T> e deque<T> esportano iteratori Random Access
Algoritmi e strutture dati
Contenitori e Iteratori STL
Mobilità degli iteratori
●
L'operatore + è implementato solamente dagli iteratori Random
Access.
●
Per accedere all'elemento successivo nel caso di iteratori
Bidirectional non si può fare *(i+1).
●
Bisogna fare:
i++; // sposto l'iteratore
if(i != c.end()) *i = 4;
i--; // lo riporto indietro
errore
classico
Algoritmi e strutture dati
Contenitori e Iteratori STL
Ranges
●
Un range è una sotto-sequenza di elementi delimitata da due
iteratori a e b:
[a, b)
a
1
2
b
3
4
begin
●
[a, a) è un range vuoto
●
[begin(), end()) rappresenta tutta la sequenza
5
end
Un concetto molto usato nella STL è quello di range. Il range è una sottosequenza
di elementi delimitata da due iteratori a e b. Si indica con [a, b). Per convenzione
la sottosequenza include l'elemento puntato da a ma non quello puntato da b.
Algoritmi e strutture dati
Contenitori e Iteratori STL
vector<T>
●
Array dinamico con riallocazione automatica.
size
10
front
1
4
12
4
back
capacity
●
La grandezza può aumentare e diminuire.
●
Quando supera la capacità, il buffer viene automaticamente
riallocato.
●
Si possono accedere direttamente gli elementi.
Invalidazione iteratori:
●
●
●
Se si elimina un elemento, si invalidano gli iteratori all'elemento
ed ai successivi.
Se si inserisce un elemento, si invalidano gli iteratori agli elementi
successivi, o tutti in caso di riallocazione.
Algoritmi e strutture dati
Contenitori e Iteratori STL
vector<T> – Funzioni base
●
vector<int> vett; → Crea un veƩore vuoto di interi.
●
vett.push_back(int e); → Inserisce “e” come ulƟmo
elemento.
●
int vett.pop_back(); → Cancella l'ulƟmo elemento.
●
int vett.size(); → ResƟtuisce il numero di elemenƟ.
●
int& vett[5]; → Accede all'elemento di indice 5 (in leƩura
e/o scrittura). Non esiste per il contenitore “list”.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esempio di vector<T>
●
Programma che stampa la somma di N numeri.
●
●
●
Prende il numero N da tastiera.
Prende N numeri da tastiera.
Stampa la somma.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esempio di vector<T>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
size
#include<iostream>
#include<vector>
15 6
3
using namespace std;
back
capacity
int main(){
vector<int> vett;
// vettore vuoto
int n, c;
cin >> n;
// attenzione: no controllo errori!
vett.reserve(n);
// capacità n elementi
for(int i = 0; i < n; i++){
cin >> c;
// prendo da tastiera n elementi
vett.push_back(c);
}
int sum = 0;
for(vector<int>::iterator j = vett.begin(); j!=vett.end(); j++)
sum += *j;
// calcolo la somma
cout << sum << endl;
}
La funzione vett.reserve() server ad allocare un buffer di capacità n. Non cambia
il comportamento del programma, ma ne aumenta l'efficienza in quanto evita inutili
riallocazioni automatiche. L'uso di vett.reserve() diminuisce la genericità del
programma, in quanto è una funzione specifica di vector e non è definita per gli altri
contenitori.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Regola aurea per programmare con contenitori
●
Quando avete bisogno di un contenitore, ma non sapete quali
saranno le operazioni più comuni su di esso, usate vector<T>.
●
Scorrete il vector<T> con iteratori e programmate in modo
generico.
●
Quando avete identificato le operazioni più comuni, sostituite il
vector con il contenitore più adatto (vector, list, deque, set, etc.)
Quando si programma, capita molte volte di aver bisogno di una collezione di
dati, ma non sappiamo ancora quale è la struttura dati più efficiente per realizzarla
(vector, list, etc.). Si consiglia di usare inizialmente la struttura dati vector e
programmare in modo generico, usando iteratori ed evitando metodi specifici del
vector. Poi quando si sono identificate le operazioni più comuni, cambiare
eventualmente la struttura dati. Se sono frequenti le operazioni di accesso diretto, è
meglio lasciare vector; se invece sono frequenti gli inserimenti e le cancellazioni
a metà, è più efficiente una list; se infine sono frequenti inserimenti e
cancellazioni in testa e in coda, è meglio usare la deque. In ogni caso, la
programmazione generica renderà minimi i cambiamenti da effettuare nel codice.
Algoritmi e strutture dati
Contenitori e Iteratori STL
list<T>
●
Lista doppia: ogni elemento ha il puntatore al successivo e al
precedente.
●
C'è una puntatore di testa (front) ed uno di coda (back).
10
1
front
4
12
4
back
●
Non si possono accedere direttamente gli elementi, ma solo
attraverso iteratori.
●
Invalidazione degli iteratori:
●
●
Se si elimina un elemento, si invalidano gli iteratori che puntano
all'elemento.
Se si inserisce un elemento, non si invalida nessun iteratore.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esempio di list<T>
●
Programma che prende N righe da tastiera (fino ad una riga
nulla) e le scrive ordinate su un file.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esempio di list<T>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<list>
#include<string>
using namespace std;
int main(){
list<string> li;
// lista vuota
string s;
do{
getline(cin, s);
// input di una riga (no controllo errori!)
li.push_back(s);
}while(!s.empty());
li.pop_back();
// tolgo la riga vuota finale
li.sort();
// merge sort
ofstream f("out.txt", ios::out);
list<string>::iterator i;
for(i = li.begin(); i != li.end(); i++)
f << *i << endl;
// stampa su file
f.close();
}
La funzione li.sort() ordina la lista per mezzo dell'algoritmo merge sort. È
specifica per il tipo list<T>. Per usarla, T deve essere ordinabile.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Operazioni su sequenze
Operazione:
Funzione:
vector:
list:
deque:
vector<T> c;
Default constructor.
Crea un contenitore vuoto.
O(1)
O(1)
O(1)
vector<T> c(int n,
T elem);
Fill constructor.
Crea un contenitore di n elementi
uguali a elem
O(n)
O(n)
O(n)
vector<T> c(iterator a,
iterator b);
Range contructor.
Crea un contenitore copiando gli
elementi da [a,b)
O(n)
O(n)
O(n)
operator==(vector A,
vector B);
Restituisce vero sei i contenitori
sono uguali (stessi elementi nello
stesso ordine).
O(n)
O(n)
O(n)
~vector<T>;
Distruttore.
Invoca delete su ogni elemento.
O(n)
O(n)
O(n)
!
Il distruttore di un contenitore invoca automaticamente la delete su ogni elemento.
Questo comportamento è essenziale per definire liste di oggetti di tipo class.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Operazioni su sequenze
Operazione:
Funzione:
vector:
list:
deque:
iterator c.begin();
iterator c.end();
Iteratori di inizio e fine sequenza
O(1)
O(1)
O(1)
int c.size()const;
Numero di elementi
O(1)
O(n)
O(1)
void c.resize(int m,
T elem);
Diminuisce o aumenta il numero
di elementi
O(|m-n|)*
O(|m-n|)
O(|m-n|)
bool c.empty()const;
size()==0
O(1)
O(1)
int c.capacity()const;
Capacità del buffer
O(1)
no
no
void c.reserve()const;
Riserva capacità del buffer
O(n)
no
no
T& c[int i];
Accede all'elemento i-esimo
O(1)
no
O(1)**
!
O(1)
* O(n) se provoca riallocazione
** Meno efficiente del vector<T>
La funzione c.empty() equivale a fare c.size()==0 ma ha complessità O(1).
Algoritmi e strutture dati
Contenitori e Iteratori STL
Operazioni su sequenze
Operazione:
Funzione:
vector:
list:
iterator c.insert(iterator
it, T elem);
Inserisce elem prima
dell'elemento puntato da it.
O(n)
O(1)
deque:
vector::insert(iterator it,
int n, T elem);
Fill insert.
Inserisce n copie di elem prima
dell'elemento puntato da it.
O(n)
O(n)
O(n)
vector::insert(iterator it,
iterator a, iterator b);
Range insert.
Inserisce gli elementi in [a,b)
prima dell'elemento puntato da it.
O(n)
O(n)
O(n)
iterator c.erase(iterator
it);
Cancella l'elemento puntato da it.
Restituisce un iteratore valido
all'elemento successivo.
O(n)
O(1)
void c.erase(iterator a,
iterator b);
Range erase.
Cancella gli elementi nel range
[a,b).
O(n)
O(n)
!
!
O(n)
O(n)
O(n)
Le funzioni insert ed erase hanno complessità O(1) per la lista, e complessità O(n)
per gli altri tipi di sequenze. Questo perché nel vector e nella deque, ogni volta che
si inserisce o cancella un elemento nel mezzo, bisogna spostare gli altri elementi per
ricompattare la sequenza.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Operazioni su sequenze
Operazione:
Funzione:
vector:
list:
deque:
T front();
Accede al primo elemento.
O(1)
O(1)
O(1)
push_front(T e);
Aggiunge e come primo elemento.
O(n)
O(1)
O(1)
!
T pop_front();
Restituisce e cancella il primo
elemento.
O(n)
O(1)
O(1)
!
T back();
Accede all'ultimo elemento.
O(1)
O(1)
O(1)
push_back(T elem);
Aggiunge elem come ultimo
elemento.
O(1)*
O(1)
O(1)
T pop_back();
Restituisce e cancella l'ultimo
elemento.
O(1)
O(1)
O(1)
*
O(n) se provoca riallocazione
La push_front() e la pop_front() hanno complessità O(n) per il vector, ma solo
O(1) per la deque. Questo è dovuto alla sua natura di array circolare.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Operazioni su sequenze
Operazione:
Funzione:
vector:
list:
deque:
void swap(container c1,
container c2);
Scambia due contenitori in modo
efficiente.
O(1)
O(1)
O(1)
iterator copy(iterator a,
iterator b, iterator to);
Copia gli elementi
da [a,b)
a [to,to+(b-a)).
O(n)
O(n)
O(n)
void sort(iterator a,
iterator b);
Esegue quick sort sul range [a,b).
O(nlogn)
no
void c.sort();
Esegue merge sort su una lista.
no
O(nlogn)
no
bool is_sorted(iterator a,
iterator b);
Restituisce true se la sequenza
[a,b) è ordinata.
O(n)
O(n)
O(n)
void make_heap(iterator
a, iterator b);
Trasforma [a,b) in uno heap.
O(n)
no
O(n)
iterator
max_element(iterator a,
iterator b);
Restituisce l'elemento massimo in
[a,b).
O(n)
O(n)
O(n)
iterator
min_element(iterator a,
iterator b);
Restituisce l'elemento minimo in
[a,b).
O(n)
O(n)
O(n)
!
!
O(nlogn)
La funzione globale sort(iterator a, iterator b) esegue quick sort sul range [a,b).
Può essere applicata soltanto a contenitori che esportano iteratori random access.
Quindi non su “list”, ma su “vector” e “deque”.
Per ordinare una “list” si ricorre alla funzione c.sort() (dove c è una list), che
esegue merge sort.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Adattatori
●
Estendono le funzionalità di una sequenza (vector, list, deque)
●
queue<T> → coda Last In – First Out (estende deque<T>)
●
stack<T> → pila First In – First Out (estende deque<T>)
●
priority_queue<T> → struƩura heap (estende vector<T>)
Gli adattatori si appoggiano ad una sequenza, estendendone le funzionalità.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Quasi-contenitori
●
Non hanno tutte le funzionalità di un contenitore ordinario.
Sono specializzati per usi specifici.
●
string → stringa di caraƩeri. Specializzata per operazioni su
sottostringhe.
●
valarray<T> → veƩore matemaƟco. Specializzato per
operazioni di calcolo su molti dati. T dev'essere un tipo
matematico.
●
bitset<N> → insieme di N flag (N è un intero). Viene
memorizzato in modo compatto (1 bit per flag). Specializzato
per operazioni bit a bit.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Contenitori associativi
●
Contenitori che offrono una ricerca efficiente basata sul valore
di una chiave.
●
La chiave può essere di un qualsiasi tipo T (ordinabile).
●
set<T> → albero binario di ricerca di chiavi T.
●
map<T, ValueT> → albero binario di ricerca di coppie
chiave-valore (pair<T, ValueT>).
Le visita tramite iteratore esegue una visita simmetrica
dell'albero, quindi gli elementi vengono visitati in modo
ordinato (crescente)
●
Algoritmi e strutture dati
Contenitori e Iteratori STL
set<T>
●
Realizza un albero binario di ricerca di elementi di tipo T. Gli
elementi sono ordinati e non ci sono ripetizioni.
6
3
1
0
●
10
4
2
9
12
14
Una volta inseriti, non è possibile modificare gli elementi.
Algoritmi e strutture dati
Contenitori e Iteratori STL
set<T> – Funzioni base
●
set<int> s; → Crea un set vuoto di interi.
●
s.insert(int e); → Inserisce “e” nel set.
●
iterator s.find(int e); → Cerca “e” nel set.
Restituisce un iteratore che punta ad “e”, o s.end() se “e” non
esiste.
●
int s.size(); → ResƟtuisce il numero di elemenƟ.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esempio di set<T>
●
Programma che prende da tastiera delle parole (fino alla parola
“fine”) e stampa un messaggio se la parola è già stata inserita.
Dopodiché stampa le parole in modo ordinato.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esempio di set<T>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stream>
#include<string>
#include<set>
using namespace std;
int main(){
set<string> parole;
// albero vuoto
string p;
do{
cin >> p;
if(parole.find(p) != parole.end())
cout << "Hai gia' inserito: " << p << endl;
else
parole.insert(p);
}while(p != "fine");
}
Algoritmi e strutture dati
Contenitori e Iteratori STL
map<T, ValueT>
●
Realizza un dizionario di coppie chiave-valore, ottimizzato per la
ricerca sulla chiave.
●
È implementato con un albero binario di ricerca ordinato sulle
chiavi. Ogni nodo contiene una coppia chiave-valore. Non ci
sono ripetizioni sulle chiavi.
Pluto 7
Minnie 2
Eta Beta 9
●
Topolino 2
Pippo 4
Zapotec 13
Una volta inserite, le chiavi sono costanti, il relativo valore può
essere modificato
Algoritmi e strutture dati
Contenitori e Iteratori STL
map<T, ValueT>
●
È possibile accedere ad un elemento tramite indicizzazione
diretta (operator[]), con indice di tipo T (non necessariamente
intero). Per esempio:
map<string, int> m;
m["pippo"] = 4;
●
L'operatore m["pippo"] cerca un elemento con chiave “pippo”.
●
●
Se tale elemento esiste, accede al campo valore.
Altrimenti lo crea (con chiave “pippo” e valore 0), e accede al
campo valore.
L'operatore di indicizzazione diretta (parentesi quadre) serve sia ad accedere (in
lettura o in scrittura) il campo valore associato ad una chiave, sia ad inserire un
nuovo elemento nell'albero.
Algoritmi e strutture dati
Contenitori e Iteratori STL
map<T, ValueT>
●
Con map<T, ValueT> è possibile costruire dei semplici database
in memoria.
ValueT
T
Nome
Età
Num. telefono
Mario Rossi
25
123 456789
Gianni Verdi
26
234 567890
Paolo Bianchi
30
345 678901
Franco Rosi
27
456 789012
struct info{
int eta;
long numTelefono;
};
map<string, info> db;
...
db["Mario Rossi"].eta = 26;
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esempio di map<T, ValueT>
●
Programma che legge una lista di contatti da file, ci aggiunge un
contatto preso da tastiera, e poi li stampa.
●
Ogni contatto è su una riga ed ha il formato:
email numero-di-telefono
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esempio di map<T, ValueT>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<map>
#include<string>
using namespace std;
int main(){
map<string, long int> contatti;
string email;
long int telefono;
ifstream f("contatti.txt", ios::in);
while(!f.eof()){
f >> email >> telefono;
contatti[email] = telefono; // inserimento di un elemento
}
f.close();
cin >> email >> telefono;
contatti[email] = telefono;
map<string, long int>::iterator i;
for(i = contatti.begin(); i != contatti.end(); i++) // visita ordinata
cout << i->first << ' ' << i->second << endl;
}
Algoritmi e strutture dati
Contenitori e Iteratori STL
Contenitori associativi
Operazione:
Funzione:
set:
map:
set<T> c;
Default constructor.
Crea un contenitore vuoto.
O(1)
O(1)
set<T> c(iterator a,
iterator b);
Range constructor.
Crea un contenitore
copiando dal range [a,b).
O(nlogn)
O(nlogn)
~set<T>;
Destructor.
Invoca delete su tutti gli
elementi.
O(n)
O(n)
operator==(set A, set B)
Restituisce vero se i set
sono uguali (contengono
gli stessi elementi).
O(n)
O(n)
!
Algoritmi e strutture dati
Contenitori e Iteratori STL
Contenitori associativi
Operazione:
Funzione:
set:
map:
iterator c.begin();
iterator c.end();
Iteratori di inizio e fine
sequenza.
O(1)
O(1)
int c.size();
Numero di elementi.
O(n)
O(n)
bool c.empty();
c.size()==0
O(1)
O(1)
ValueT& c[T k];
Restituisce l'elemento con
chiave k.
Lo crea se non esiste.
no
O(logn)
iterator c.find(T k);
Trova l'elemento k.*
O(logn)
O(logn)
iterator c.lower_bound(T
k);
Trova il più piccolo
elemento > k.*
O(logn)
O(logn)
iterator c.upper_bound(T
k);
Trova il più grande
elemento <= k.*
O(logn)
O(logn)
*
!
Restituisce c.end() se tale elemento non esiste.
L'accesso tramite parentesi quadra ad un elemento di una map, crea l'elemento se
non esiste. L'elemento creato assume un valore iniziale di default. Se si vuole
testare la presenza di un elemento senza crearlo, si deve utilizzare la funzione find().
Algoritmi e strutture dati
Contenitori e Iteratori STL
Contenitori associativi
Operazione:
Funzione:
set:
map:
c.insert(T elem);
Inserisce elem.
O(logn)
no
c.insert(iterator a,
iterator b);
Range insert.
Inserisce gli elementi in
[a,b).
O(logn) per ogni
elemento
O(logn) per ogni
elemento
void c.erase(T elem);
Cancella l'elemento elem.
O(logn)
O(logn)
void c.erase(iterator i);
Cancella l'elemento
puntato da i.
O(logn)
O(logn)
void c.erase(iterator a,
iterator b);
Range erase.
Cancella gli elementi nel
range [a,b).
O(logn) per ogni
elemento
O(logn) per ogni
elemento
*
La funzione make_pair(T k, ValueT elem) crea una struttura dati pair<T,ValueT>.
Algoritmi e strutture dati
Contenitori e Iteratori STL
Altri contenitori associativi
●
multiset<T>: come set, ma sono ammessi valori ripetuti.
●
multimap<T, ValueT>: come map, ma sono ammessi valori
ripetuti delle chiavi.
●
hash_set<T>: come set, ma gli elementi sono memorizzati con
struttura ad hash.
●
●
Più efficiente per la ricerca
Non garantisce ordinamento
●
hash_map<T, ValueT>: come map, ma gli elementi sono
memorizzati con struttura ad hash.
●
hash_multiset<T>
●
hash_multimap<T, ValueT>
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esercizio riassuntivo
●
Scrivere una funzione generica:
template<class Cont> void f(Cont& c);
che unisce a due a due gli elementi adiacenti, sommandoli fra
loro.
10
1
4
11
16
4
12
4
Algoritmi e strutture dati
Contenitori e Iteratori STL
Esercizio riassuntivo – Soluzione
1
template<class Cont>
2
void fondi(Cont& c){
3
typename Cont::iterator i;
4
for(i = c.begin(); i != c.end(); i++)
5
{
6
int a = *i;
7
i++;
8
if(i != c.end()){
9
*i += a;
10
i--;
11
i = c.erase(i);
i
12
}
13
else
10 1
4
14
i--;
15
}
begin
16 }
++
12
4
end
Algoritmi e strutture dati
Contenitori e Iteratori STL
Riferimenti
●
Matthew H. Austern, “Generic programming and the STL,”
Addison-Wesley
●
Bjarne Stroustrup, “The C++ Programming Language,” Addison
Wesley
●
Guida di riferimento sul C/C++: http://en.cppreference.com/w/