Savoir lire la documentation STL
En concours, vous avez le droit à la documentation STL.
Prenez l'habitude de l'utiliser pour ne pas être perdu le jour du concours.
Minimum de C++ à savoir
Cf le doc de Guillaume
- classes
- surcharge du constructeur
- opérateurs
- itérateurs
- functors
Généralités
Pour utiliser les objets de la STL, il faut inclure des fichiers qui portent généralement le nom de l'objet. Exemple:
#include <vector>
Ces objets sont pour la plupart dans un namespace nommé std.
Avant toute chose, on écrira donc
using namespace std;
pour éviter d'écrire std:: devant chaque objet.
Certains objets ne font pas partie du standard de la STL mais sont dans l'extension tr1.
Les headers sont dans le dossier tr1 et les objets sont dans le namespace tr1. Exemple:
#include <tr1/unordered_map>
using namespace std ;
using namespace tr1;
L'indispensable
Paires
La paire permet de créer facilement des structures a deux champs, et définit les opérations <, <=, == pour l'ordre lexicographique.
- Cas d'utilisation: Il est préférable que les noms first et second aient un sens (des dimensions, des limites d'un intervalle) ou aient peu d'importance (valeur de retour d'une fonction, structure crée pour insérer des élements dans un tas)
- Header: utility, mais il est inclus par la plupart des autres headers.
- Construction: pair<Type1, Type2>(valeur1, valeur2) ou make_pair(valeur1,valeur2)
- Utilisation: paire.first et paire.second
Problème : étant données trois nombres s, p et l affichez tous les couples d'entiers positifs (x,y) tels que x+y = s et x*y = p.
pair<int,int> somme_et_produit(int a, int b)
{
return make_pair(a+b,a*b);
}
int maxi( pair<int,int> a )
{
return max(a.first,a.second ) ;
}
int main()
{
pair cherche ;
scanf("%d %d",&cherche.first,&cherche.second);
for( int x = 0 ; x <= maxi(cherche) ; x++ )
for( int y = 0 ; y <= maxi(cherche) ; y++ )
if( somme_et_produit(x,y) == cherche )
printf("%d %d\n",x,y);
}
Vecteurs
vector
- Cas d'utilisation:
- quand on ne connait pas la taille voulue
- pour faire une pile
- Cas de non-utilisation: aucun, mais dans certains cas, il faut faire attention
- Header: vector
- Déclaration: vector<Type> vecteur;
- Insertion en fin (en O(1)): vecteur.push_back(valeur);
- Accès aux éléments: vecteur[index]
- Taille: vecteur.size()
- Nombre d'éléments pré-alloués vecteur.capacity()
- Vide? : vecteur.empty()
- Vider: vecteur.clear();, pour vider aussi la mémoire allouée vecteur.swap(vecteur<type>()) en O(1)
- Pour rétrécir la taille en mémoire (pour que size() == capacity() ) vecteur_a_retrecir.swap(vector<mon_type >() = vecteur_a_retrecir) ; en O(size())
- Suppression en fin (en O(1)): vecteur.pop_back();
- Dernier élément: vecteur.back()
- Insertion à un index quelconque (en O(n)): vecteur.insert(iterateur_avant, valeur);
- Suppression à un index quelconque (en O(n)): vecteur.erase(iterateur_avant);
- Avantages STL:
- pas de souci d'allocation, s'agrandit automatiquement quand on en a besoin
- insertion/suppression simple d'éléments au milieu, en temps linéaire
- opérateurs < et == définis pour l'ordre lexicographique
- Inconvénients STL:
- l'allocation automatique ralentit d'environ 30% par rapport à un simple tableau
- réallocation de x2 à chaque, donc dans le pire cas, à un moment donné, la mémoire allouée peut être jusqu'à 2 fois celle voulue, et même plus si on supprime des éléments
- Attention: quand on a pas beaucoup de place et/ou de temps, utiliser vecteur.reserve(taille_max);
Problème : étant donnés un nombre x et un tableau t trié, écrire une fonction qui insére x dans t de tel sorte que t soit trié.
void insère( vector<int> t , int x )
{
size_t pos = 0 ;
while( pos < t.size() && t[pos] < x )
pos++;
t.insert( t.begin() + pos , x ) ;
}
void insère2 (vector< int > t , int x )
{
vector<int>::iterator pos = t.begin() ;
while( *pos < x )
pos++;
t.insert( pos , x ) ;
}
Tas
priority_queue
Problèmes : voir correction de Parcours le plus court (algorithmes de graphes II) si vous ne maîtrisez pas bien cette structure.
Arbres de recherche
Ils sont implémentés sous forme d'arbres binaires équilibrés (rouges/noirs), ce qui garantit une complexité amortie des opérations en O(log n), les éléments sont stockés dans les noeuds et chaque noeud prend en mémoire, en plus de l'élement qu'il représente, trois pointeurs et un booléen.
set
- Cas d'utilisation:
- structure triée dynamique, vous avec besoin d'accéder à des éléments au mileu de la structure et de passer au suivant, de rechercher l'élément le plus proche avant ou après un élément donné.
- opérations sur les ensembles
- l'étendue des valeurs est vaste et/ou on ne les connait pas à l'avance
- Cas de non-utilisation:
- quand une autre structure est plus adaptée et suffit (tas, arbre max, tableau trié) et que l'on est limité en temps/mémoire
- quand on est vraiment limité en mémoire
- Header: set
- Déclaration: set<Type> ens; où Type implémente l'opérateur <
ou set<Type, FctLess> ens; où FctLess est un functor qui compare de deux objets de type Type
- Insertion (en O(log n)): ens.insert(valeur);
- Suppression (en O(log n)): ens.erase(valeur);
- Recherche (en O(log n)): ens.count(valeur) (retourne 0 ou 1), les puristes préféront : ens.find(valeur)!=ens.end()
ou ens.find(valeur) (retourne un itérateur sur l'élément ; ens.end() si rien n'est trouvé)
- Nombre d'éléments: ens.size()
- Vide? : ens.empty()
- Vider: ens.clear();
- Minimum: *ens.begin() en O(1)
- Trouver l'élément b le plus petit (au sens de la structure) tel que a<b : ens.upper_bound(a).
- Trouver l'élément b le plus petit (au sens de la structure) tel que a<=b : ens.lower_bound(a).
- Note mnémonique : on a toujours *(ens.lower_bound(valeur)) <= *(ens.upper_bound(valeur)) et lower_bound comme upper_bound ne peuvent renvoyer d'éléments inférieurs à celui passé en paramètre car sinon ils ne pourraient rien renvoyer si la valeur était plus petite que toutes celles de la structure, alors que si elle est plus grande ils renvoient ens.end()
- Maximum: *ens.rbegin() en O(1)
- Avantages STL:
- accès aux minimum et maximum en temps constant
- insertion, suppression en temps logarithmique
- structure dynamique, pas besoin de connaître à l'avance les valeurs
- Inconvénient STL:
- utilisation mémoire importante (20 octets pour un int ou un 24 pour long long) NB : les compilateurs alignent par défaut la taille des structures à des multiples de quatre pour des questions d'optimisation.
- Quand on fait iterator++ on est, en moyenne en O(1) mais ça peut être du O( ln(n)) si on est au mauvais endroit. Cependant si on parcourt r cases à la suite on est assuré d'être enO(r+ln(n)) où n=size().
Pour le problème des portes à ouvrir et fermer sur des intervalles
#include
#include
#include
const int INF = 1000*1000*1000 ;
using namespace std ;
using namespace tr1 ;
typedef set > front ;
front barrieres ;
int nb_reqs ;
int est( int deb ,int fin )
{
front::iterator apres = barrieres.upper_bound( make_tuple(deb,fin) );
if(get<0>(*apres) <= deb && get<1>(*apres) >= fin)
return 1 ;
apres--;
return get<0>(*apres) <= deb && get<1>(*apres) >= fin ;
}
void efface( tuple & a , front::iterator & b )
{
get<0>(a) = min( get<0>(a) , get<0>(*b) );
get<1>(a) = max( get<1>(a) , get<1>(*b) );
front::iterator eff = b ;
b++;
barrieres.erase(eff);
}
tuple recupere( tuple ou )
{
front::iterator apres = barrieres.upper_bound( ou );
while( get<1>(ou) + 1 >= get<0>(*apres) )
efface(ou,apres);
while( get<1>(*(--apres)) + 1 >= get<0>(ou) )
efface(ou,apres);
return ou ;
}
int main()
{
barrieres.insert( make_tuple(-INF,-INF) );
barrieres.insert( make_tuple(+INF,+INF) );
scanf("%d",&nb_reqs) ;
for( int req = 0 ; req < nb_reqs ; req ++ )
{
char type[3] ;
int deb , fin ;
scanf("%s %d %d",type,&deb,&fin);
if( type[0] == 'T' )
printf("%d\n",est(deb,fin));
if( type[0] == 'S' )
{
int ndeb,nfin ;
tie(ndeb,nfin) = recupere( make_tuple(deb,fin) );
if( ndeb < deb )
barrieres.insert( make_tuple(ndeb,deb-1) ) ;
if( nfin > fin )
barrieres.insert( make_tuple(fin+1,nfin) ) ;
}
if( type[0] == 'A' )
barrieres.insert( recupere(make_tuple(deb,fin) ) ) ;
}
return 0;
}
map
Même structure que set mais permet d'associer une valeur à une clé.
S'utilise de façon similaire sauf :
- Cas d'utilisation:
- structure triée dynamique
- tableau associatif (dictionnaire, ...)
- certains balayages géométriques
- Header: map
- Déclaration: map<TypeClé, TypeValeur> dictionnaire; où TypeClé implémente l'opérateur <
ou map<TypeClé, TypeValeur, FctLess> dictionnaire; où FctLess est un functor qui compare deux objets de type TypeClé
- Insertion/Modification (en O(log n)): dictionnaire[clé] = valeur;
Remarque: dictionnaire[clé]++; est possible
- Suppression (en O(log n)): dictionnaire.erase(clé);
- Recherche (en O(log n)): dictionnaire.find(clé) (retourne un itérateur sur l'élément ; dictionnaire.end() si rien n'est trouvé)
ou dictionnaire[clé] si on sait que l'élément est dans la map (attention: crée la clé si elle n'existe pas)
- Minimum: dictionnaire.begin()->first pour la clé, et dictionnaire.begin()->second pour la valeur
- Maximum: dictionnaire.rbegin()->first pour la clé, et dictionnaire.rbegin()->second pour la valeur
#include
Tables de hachage
Les tables de hachage de la STL ne sont pas standards et s'appellent unordered, on fera donc attention à utiliser les headers tr1/unordered_set et tr1/unordered_map,
et le namespace tr1.
unordered_set
S'utilise presque comme set.
- Cas d'utilisation: ensemble dont l'étendue des valeurs est vaste et/ou on ne les connait pas à l'avance
- Cas de non-utilisation:
- quand on peut faire une indexation sur les valeurs, utiliser un tableau de booléens
- quand on a besoin d'un ordre sur éléments, utiliser un set
- Déclaration: unordered_set<Type> ens; où Type est, principalement, char*, int ou long long?
ou unordered_set<Type, FctHash> ens; où Type implémente l'opérateur == et FctHash est un functor qui hache un objet de type Type
- Les opérations sont les mêmes que pour set, mais en O(1) (avec une grande constante pour l'insertion). Il n'y a pas de moyen simple d'obtenir le minimum ou le maximum.
- Avantages STL:
- allocation dynamique
- le hachage est prédéfini pour les types de base (par exemple : hash<char*>,...) (attention: il ne l'est pas pour pair)
- simple d'utilisation par rapport à une table hachage codée à la main
- Inconvénients STL:
- allocation de 193 éléments au minimum puis la taille est doublée au besoin sachant que ce sont des listes donc dans à un instant donné, il y a au plus deux fois plus d'éléments et de pointeurs alloués que d'éléments insérés.
- hachage à coder à la main pour les structures
- n'est que deux fois plus rapide que set en pratique
unordered_map
Même structure que unordered_set mais permet d'associer une valeur à une clé. S'utilise presque comme map.
- Cas d'utilisation: structure à accès direct dont l'étendue des valeurs sur lesquels on indexe est vaste et/ou non entier
- Déclaration: unordered_map<TypeClé, TypeValeur> dictionnaire; où TypeClé est, principalement, char*, int ou long long?
ou unordered_map<TypeClé, TypeValeur, FctHash> dictionnaire; où TypeClé implémente l'opérateur == et FctHash est un functor qui hache un objet de type TypeClé
- Les opérations sont les mêmes que pour map, mais en O(1) (avec une grande constante pour l'insertion). Il n'y a pas de moyen simple d'obtenir le minimum ou le maximum.
Comment faire une bonne fonction de hachage ?
Bonne : simple, rapide à exécuter et qui assure une distribution uniforme afin de minimiser les collisions.
Si on a des variables vi, i=1..n avec vi dans [| 0 ; mi - 1 |] alors un bon hachage est
h = ((...(((v1m2 + v2)m3 + v3)m4 + v4) ... )mn + vn) % p
où p est un nombre premier supérieur à maxi mi.
Exemple : TODO
Cas particulier : avec des variables vi, 1=1..n avec vi dans [| 0 ; B - 1 |] alors un bon hachage est
h = ((...(((v1B + v2)B + v3)B + v4) ... )B + vn) % P
avec p premier supérieur à B.
Pour optimiser les multiplications, on peut prendre pour B une puissance de 2.
Exemple : TODO
Nombres premiers utiles : TODO , 10^9+7 , 10^6+3
Savoir utiliser bc et factor.
Autres structures
deque
S'utilise presque comme vector.
- Cas d'utilisation: insertion et suppression à la fois en début et en fin d'un tableau
- Cas de non-utilisation: si une autre structure suffit (file, pile, vecteur)
- Header: deque
- Déclaration: deque<Type> vecteur;
- Les opérations sont les mêmes que pour vector avec en plus :
- Insertion en début (en O(1)): vecteur.push_front(valeur);
- Suppression en début (en O(1)): vecteur.pop_front();
- Premier élément: vecteur.front();
- Attention: il n'est pas possible de réserver de l'espace d'avance comme pour vector.
Remarque: Se prononce [dek].
Minimum et maximum
- Header: algorithm
- Utilisation: min(valeur1, valeur2) où valeur1 et valeur2 sont d'un type commun qui implémente l'opérateur <
queue
Une queue utilise une deque mais restreint les opérations à celles d'une file FIFO.
Elle n'existe que pour faciliter la compréhension.
- Header: queue
- Déclaration: queue<Type> file;
- Utiliser push à la place de push_back.
- Utiliser pop à la place de pop_front.
stack
Une stack utilise une deque mais restreint les opérations à celles d'une pile LIFO.
Elle n'existe que pour faciliter la compréhension.
- Header: stack
- Déclaration: stack<Type> pile;
- Utiliser push à la place de push_back.
- Utiliser pop à la place de pop_back.
Utiliser un vector à la place d'une queue pour une stack est plus efficace. Si on veut se restreindre aux opérations d'une pile, on peut utiliser la déclaration suivante :
stack<Type, vector<Type> > pile;
list
Liste doublement chaînée.
- Header: list
- Déclaration: list<Type> liste;
- Insertion en début, fin, autre position: push_front, push_back, insert
- Suppression en début, fin, autre position: pop_front, pop_back, erase
- Tri (en O(n log n)): liste.sort(); si Type implémente l'opérateur <
ou liste.sort(FctLess); où FctLess est un functor qui compare deux éléments de type Type
- Fusion de deux listes triées (en O(n)): liste.merge(liste2);
- Suppression de tous les doubles consécutifs (en O(n)): liste.unique();
- Suppression des éléments consécutifs qui satisfont une condition (en O(n)): liste.unique(FctComp); où FctComp est un fonctor qui prend deux paramètres de type Type et retourne true si le second paramètre doit être supprimé
- Suppression de tous les éléments égaux à une valeur (en O(n)): liste.remove(valeur);
- Suppression de tous les éléments qui safisfont une condition (en O(n)): liste.remove_if(FctPredicate); où FctPredicate est un functor qui indique si l'élément fournit en paramètre doit être supprimé
- Utilisation mémoire : deux pointeurs par élément
slist
Liste simplement chaînée. À utiliser si on a pas besoin de traverser la liste dans les deux sens.
- Header: slist
- Déclaration: slist<Type> liste;
- Même opérations que list mais il y a ni back ni push_back ni pop_back
Algorithmes
Tris et rangs
sort
- Header: algorithm
- Avantages STL:
- Fonction de comparaison déjà implémentée pour les types de base, les paires, les vecteurs, ...
- Utilise introsort, une amélioration de quicksort. Complexité en O(n log n) en pire cas, ce qui est plus efficace que qsort.
- Utilisation: sort(itérateurDébut, itérateurFin);
ou sort(itérateurDébut, itérateurFin, FctLess);
Exemples :
- Tri d'un tableau: sort(tableau, tableau + N);
- Tri d'un vector ou d'une deque: sort(vecteur.begin(), vecteur.end());
- Tri d'un vector en sens inverse: sort(vecteur.rbegin(), vecteur.rend());
Remarque: pour trier une liste, utiliser liste.sort();.
min_element
- Header: algorithm
- Avantages STL:
- Retourne un itérateur donc à la fois la position et la valeur
- Utilise exactement n - 1 comparaisons
- Utilisation: min_element(itérateurDébut, itérateurFin);
ou min_element(itérateurDébut, itérateurFin, FctLess);
Exemples :
- Minimum d'un tableau: min_element(tableau, tableau + N);
- Minimum d'un vector ou d'une deque: min_element(vecteur.begin(), vecteur.end());
- Minimum d'une list ou d'une slist: min_element(liste.begin(), liste.end());
Remarques :
- Minimum d'une priority_queue: tas.top();
- Minimum d'un set: ens.begin();
-
max_element
Cf min_element.
nth_element
Remplir une structure
generate
fill(debut,fin,valeur);
fin ne sera pas écrit. Exemple:
int tab[100];
fill(tab,tab+100,42);
fill_n
Opérations sur les ensembles triés
inclusion
union
intersection
différence
différence symétrique
Opérations sur les permutations
next_permutation, prev_permutation
bitset
bit_vector
un vector<bool> est un bit_vector.
Avertissements
- C++
- Ne pas mettre les functors dans la classe principale, risque de confondre les champs
Citations
- Tu peux même dire que 26 c'est 2 fois 13. Louis Jachiet, 21/04/2008 18h50
- Et y'a le Lucas-Taureau qui peut se taper plein de vaches en très peu de temps. Lucien Pech, 22/04/2008 12h38