contenitori associativi
TRANSCRIPT
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)
unsignedcharunsigned
int
double
char
int
float
vector hash
search treelist
algo1()algo2()
algo3()
6 tipi elementari
4 strutture dati
3 algoritmi
24 definizioni
72 definizioni
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()algo2()
algo3()
unsignedcharunsigned
int
double
char
int
float
vector hash
search treelist
6 tipi elementari
4 strutture dati
3 algoritmi
Concetto di tipo elementare “T”
Concetto di “contenitore”
4 definizioni
3 definizioni
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
● vector<T>:
● list<T>:
● deque<T>:
10 1 4 12 4
size
capacitybackfront
110 4 4front back
12
4 12 4 10 1
size size
frontback
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.
10 1 4 12 4
i
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
10 1 4 12 4
i
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 1 4 12 4
begin end
Algoritmi e strutture dati Contenitori e Iteratori STL
Iteratori
10 1 4 12 4
begin end
i ++
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}
}
1234567
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 1 4 12 4
begin end
i ++
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}
}
12345678
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
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}
}
12345678
begin end
i ++
110 4 412
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
110 4 112
10 4 10
Algoritmi e strutture dati Contenitori e Iteratori STL
Esempio di programmazione generica
template<class Cont>void azzera_predispari(Cont c){
typename Cont::iterator i;for(i = c.begin(); i != c.end(); i++){
if (* i%2 == 1 && i != c.begin())i--;* i = 0; // azzero l'elemento precedentei++;
}}
}
1234567891011
begin end
i
110 4 112
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)
begin end
i ++
55 4 55
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
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!}
12345
begin end
i ++
55 4 55
Validità degli iteratori
● Alcune operazioni che modificano il contenitore invalidano alcuni (o tutti) gli iteratori.
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); // OKelse i++; }
}
123456
erroreclassico
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
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 << ' ';}
12345
template<class Cont> void stampa_pariOK(const Cont& c){typename Cont::const_iterator i; // okfor(i = c.begin(); i != c.end(); i++)
if (* i % 2 == 0) cout << *c << ' ';}
12345
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.
● vector<int>::reverse_iterator → iteratore inverso● vector<int>::const_reverse_iterator → iteratore inverso a costante
● Ogni contenitore esporta le funzioni:● c.rbegin() → iteratore all'ul<mo elemento● c.rend() → iteratore precedente al primo elemento
● i++ sposta l'iteratore i all'indietro.
rend rbegini
55 4 55
++
Algoritmi e strutture dati Contenitori e Iteratori STL
Iteratori
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}
}
12345678
rend rbegini
55 4 55
++
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'iteratoreif (i != c.end()) * i = 4;i--; // lo riporto indietro
erroreclassico
Algoritmi e strutture dati Contenitori e Iteratori STL
Ranges
● Un range è una sotto-sequenza di elementi delimitata da due iteratori a e b:
● [a, a) è un range vuoto
● [begin(), end()) rappresenta tutta la sequenza
[a, b)
a
21 3 54
b
begin 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.
● 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.
10 1 4 12 4
size
capacitybackfront
Algoritmi e strutture dati Contenitori e Iteratori STL
vector<T> – Funzioni base
● vector<int> vett; → Crea un veEore 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 leEura 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
#include<iostream>#include<vector>using namespace std;int main(){
vector<int> vett; // vettore vuotoint n, c;cin >> n; // attenzione: no controllo errori!vett.reserve(n); // capacità n elementifor(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 sommacout << sum << endl;
}
1234567891011121314151617
Esempio di vector<T>
15 6 3
size
capacityback
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).
● 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.
110 4 4front back
12
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>
#include<list>#include<string>using namespace std;int main(){
list<string> li ; // lista vuotastring 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 finaleli.sort(); // merge sortofstream f("out.txt", ios::out);list<string>::iterator i;for(i = li.begin(); i != li.end(); i++)
f << * i << endl; // stampa su filef.close();
}
123456789101112131415161718
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 sequenzaO(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) O(1)int c.capacity()const; Capacità del buffer O(1) no novoid c.reserve()const; Riserva capacità del buffer O(n) no noT& c[int i]; Accede all'elemento i-esimo O(1) no 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: deque:iterator c.insert(iterator it, T elem);
Inserisce elem prima dell'elemento puntato da it.
O(n) O(1) O(n)
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) O(n)
void c.erase(iterator a, iterator b);
Range erase.Cancella gli elementi nel range [a,b).
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 elementida [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 O(nlogn)
void c.sort(); Esegue merge sort su una lista. no O(nlogn) nobool 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)
!
!
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> → struEura 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 caraEeri. Specializzata per operazioni su sottostringhe.
● valarray<T> → veEore 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.
● Una volta inseriti, non è possibile modificare gli elementi.
6
3 10
9 1241
0 2 14
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>
#include<stream>#include<string>#include<set>using namespace std;
int main(){set<string> parole; // albero vuotostring p;do{
cin >> p;if (parole.find(p) != parole.end())
cout << "Hai gia' inserito: " << p << endl;else
parole.insert(p);} while(p != "fine");
}
12345678910111213141516
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.
● Una volta inserite, le chiavi sono costanti, il relativo valore può essere modificato
Pluto 7
Minnie 2 Topolino 2
Zapotec 13Pippo 4Eta Beta 9
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:
● 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.
map<string, int> m;
m["pippo"] = 4;
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.
Mario Rossi
Gianni Verdi
Paolo Bianchi
Franco Rosi
123 45678925
26
30
27
345 678901
456 789012
234 567890
Nome Età Num. telefono
T
ValueT
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>
#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;}
12345678910111213141516171819
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) noc.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:
che unisce a due a due gli elementi adiacenti, sommandoli fra loro.
template<class Cont> void f(Cont& c);
110 4 412
1611 4
Algoritmi e strutture dati Contenitori e Iteratori STL
Esercizio riassuntivo – Soluzione
template<class Cont>void fondi(Cont& c){
typename Cont::iterator i;for(i = c.begin(); i != c.end(); i++){
int a = * i;i++;if (i != c.end()){
* i += a;i--;i = c.erase(i);
}else
i--;}
}
12345678910111213141516
10 1 4 12 4
begin end
i ++
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/