Annexe B

publicité
Annexe B
Algorithmes de la bibliothèque standard STL
Hansel, G., Passeport pour C++ Concepts et mise en pratique. Chap. 23 et annexe A,
1999.
Cay Horstmann & Timothy Budd, La Bible C++. John Wiley & Sons, Inc., 2004, 1230p.
On retrouve aussi dans la bibliothèque standard STL un jeu de fonctions appelées
algorithmes. Pour ces fonctions, l’accès aux données contenues dans un conteneur
s’effectue exclusivement par le biais d’itérateurs. Les algorithmes sont indépendants des
classes mises en jeu dans les conteneurs. De plus, un pointeur usuel pouvant être
considéré comme un cas particulier d’itérateur, les algorithmes sont également utilisables
pour les tableaux usuels.
Pour utiliser un algorithme, il faut préalablement inclure le fichier <algorithm>.
Dans l’annexe A de la référence de Hansel citée plus haut, on retrouve une description
complète des algorithmes de la STL. C’est aussi le cas des deux ouvrages suivants :
Bjarne Stroustrup, The C++ Programming Language, Special Ed., AddisonWesley, 2000.
Stanley B. Lippman et Josée Lajoie, C++ Primer, 3rd ed., Addison-Wesley, 1998.
La bibliothèque standard contient plusieurs douzaines d’algorithmes génériques. Nous
étudierons ici que les plus fréquents et les plus utiles. Pour simplifier la présentation de
ces fonctions, celles-ci sont réparties en plusieurs catégories.
A.
Algorithmes d’initialisation
Ce sont des algorithmes servant principalement à initialiser une séquence.
Un conteneur peut être initialisé par remplissage (initialiser avec une même valeur fixe),
par copie (dupliquer des valeurs depuis un conteneur pour en remplir un second) ou par
génération (exécuter une fonction de façon répétée et employer les valeurs obtenues pour
initialiser un conteneur).
Remplissage :
Deux algorithmes permettent de remplir un conteneur. Le premier utilise comme
arguments une plage d’itérateurs et une valeur, puis affecte à chaque élément de la plage
la valeur spécifiée.
vector<int> a(10);
fill(a.begin(), a.end(), 1);
Le deuxième algorithme reçoit un itérateur, un compteur entier et une valeur. Il initialise
le nombre de positions spécifié par le compteur avec la valeur donnée.
1
fill_n(a.begin(), 5, 2);
// Remplit les 5 premières positions de a avec la
// valeur 2.
Ces deux algorithmes ne peuvent servir à ajouter de nouveaux éléments dans un
conteneur mais, uniquement, à modifier la valeur de certains éléments d’un conteneur.
Copie :
L’algorithme générique copy sert à copier un conteneur dans un autre. Il reçoit 3
arguments, les deux premiers spécifiant la plage du conteneur d’entrée, le troisième
indiquant le point de départ du conteneur de destination. Il est supposé, et non vérifié, que
la destination est suffisamment grande pour accepter l’entrée.
vector<int> b(20);
copy(a.begin(), a.end(), b.begin()); // Initialise les 10 premières positions de b.
Génération :
L’algorithme générique generate permet d’initialiser un conteneur grâce à un générateur.
vector<int> c(20);
generate(c.begin(), c.end(), AleatoireInt(7, 25));
// Remplit le vecteur c de valeurs aléatoires comprises entre 7 et 25.
L’algorithme generate_n reçoit un itérateur, un compteur entier et un générateur.
generate_n(c.begin(), 10, AleatoireInt(10, 30));
// Remplit les 10 premières composantes du vecteur c de valeurs aléatoires
// comprises entre 10 et 30.
B.
Algorithmes de transformations
Cette catégorie d’algorithmes génériques permet de transformer une séquence de valeurs
en une nouvelle séquence.
l’algorithme reverse :
Cet algorithme inverse les éléments d’une séquence, de façon à ce que le dernier élément
devienne le nouveau premier, le premier le nouveau dernier, etc.
vector<int> a(10);
generate(a.begin(), a.end(), Generateur_suite(1)); // Initialement 1, 2, …, 10.
reverse(a.begin(), a.end()); // Maintenant 10, 9, 8, …, 1.
l’algorithme sort :
Pour trier des collections stockées dans un conteneur vector, un deque ou un tableau
ordinaire, nous pouvons recourir à l’algorithme générique sort.
2
vector<int> a(10);
generate(a.begin(), a.end(), AleatoireInt(10, 30));
sort(a.begin(), a.end());
L’algorithme sort ne peut être employé avec des listes, car il nécessite l’emploi
d’itérateurs d’indexation, absents des listes. Toutefois, le conteneur list contient une
fonction membre sort utilisant un autre algorithme.
l’algorithme random_shuffle :
Cet algorithme réarrange de façon aléatoire les éléments d’une collection. Il peut être
utilisé avec des vecteurs, des dèques ou des tableaux ordinaires mais non avec des listes,
des ensembles ou des cartes (map et multimap). Les arguments doivent être des itérateurs
d’accès aléatoire.
vector<int> a(10);
generate(a.begin(), a.end(), Generateur_suite(1));
random_shuffle(a.begin(), a.end());
Il est impossible de prédire ce que pourrait être la séquence résultante. Un résultat
possible serait : 9, 4, 8, 6, 2, 10, 1, 3, 5, 7.
l’algorithme rotate :
L’algorithme effectue dans une séquence une rotation autour d’un élément pivot spécifié
par un itérateur. Cela divise la séquence en deux parties non nécessairement de même
longueur, puis intervertit l’ordre des 2 parties, en conservant l’ordre relatif dans chaque
partie.
vector<int> a(10);
generate(a.begin(), a.end(), Generateur_suite(1));
vector<int>::iterator pivot = find(a.begin(), a.end(), 7);
rotate(a.begin(), pivot, a.end());
La séquence 1 2 3 4 5 6 7 8 9 10 devient 7 8 9 10 1 2 3 4 5 6.
Le pivot doit faire partie de la plage des itérateurs.
l’algorithme partition :
La séquence est formée en déplaçant tous les éléments satisfaisant à un prédicat à un bout
d’une séquence et tous les éléments échouant à ce prédicat à l’autre extrémité.
vector<int> a(10);
generate(a.begin(), a.end(), Generateur_suite(1));
partition(a.begin(), a.end(), paire);
3
Les valeurs paires sont déplacées au début et les valeurs impaires à la fin ce qui donne
comme résultat : 10 2 8 4 6 5 7 3 9 1. Comme on peut le constater, l’ordre relatif des
éléments dans une partition dans le conteneur résultant diffère de celui des valeurs dans la
séquence originale. En substituant partition par stable_partition, cela garantit le maintien
de l’ordre des valeurs résultantes : 2 4 6 8 10 1 3 5 7 9.
l’algorithme transform :
1ière version :
Étant donné une séquence d’entrée spécifiée par des itérateurs de début et de fin, une
destination de sortie spécifiée par un unique itérateur et une fonction unaire, l’algorithme
applique la fonction à chaque élément de l’entrée pour créer les éléments de sortie.
int carre(int n)
{ return n * n; }
vector<int> a(10);
generate(a.begin(), a.end(), Generateur_suite(1));
vector<int> b(10);
transform(a.begin(), a.end(), b.begin(), carre);
2ième version :
Étant donné deux séquences d’entrée dont la première est spécifiée par des itérateurs de
début et de fin, la seconde spécifiée par un itérateur de début, une destination de sortie
spécifiée par un unique itérateur et une fonction binaire, l’algorithme applique la fonction
binaire aux éléments correspondants de l’entrée pour créer les éléments de sortie. On
suppose sans le vérifier que la deuxième séquence d’entrée comporte au moins autant
d’éléments que la première et que la séquence résultat disposera de suffisamment de
place pour accueillir le résultat.
int addition(int a, int b)
{ return a + b; }
vector<int> c(10);
transform(a.begin(), a.end(), b.begin(), c.begin(), addition);
l’algorithme merge :
Étant donné deux séquences d’entrée triées spécifiées par des itérateurs de début et de fin,
une destination de sortie spécifiée par un unique itérateur, l’algorithme fusionne les 2
séquences triées pour obtenir une nouvelle séquence triée.
vector<int> a(10);
vector<int> b(20);
generate_n(a.begin(), 10, Generateur_suite(1));
generate_n(b.begin(), 20, Generateur_suite(5));
vector<int> c(30);
merge(a.begin(), a.end(), b.begin(), b.end(), c.begin());
4
l’algorithme next_permutation :
Une permutation est une réorganisation des valeurs dans une séquence.
Ex. : 6 permutations des valeurs 1 2 3 :
123
132
213
231
312
3 2 1.
Une séquence de n éléments possède en principe n! permutations; la première est telle
que les valeurs apparaissent en ordre croissant, tandis que dans la dernière, les valeurs
figurent en ordre décroissant.
L’algorithme next_permutation est une fonction booléenne qui transforme une séquence à
la prochaine permutation et retourne true si elle existe, false autrement.
Ex. :
vector<int> a(4);
generate(a.begin(), a.end(), Generateur_suite(1));
while (next_permutation(a.begin(), a.end()))
{
cout << “Permutation de sortie “;
vector<int>::iterator p = a.begin();
while (p != a.end())
{
cout << *p << “ “;
++p;
}
cout << endl;
}
Ex. :
char mot[] = "ouf" ;
char * p = mot;
// Itérateur de début.
char * q = mot + strlen(mot);
// Itérateur de fin.
sort(p, q);
// Trie initialement les lettres en ordre croissant.
while (next_permutation(p, q)) cout << mot << endl;
C.
Algorithmes de recherche
Ces algorithmes permettent de localiser des éléments dans une séquence; ils renvoient un
itérateur décrivant une position. Si aucun élément n’est localisé, ils renvoient l’itérateur
de fin.
max_element et min_element :
Ex. :
vector<int> d(10);
generate(d.begin(), d.end(), AleatoireInt(10, 30));
vector<int>::iterator mx = max_element(d.begin(), d.end());
vector<int>::iterator mn = min_element(d.begin(), d.end());
5
if (mx != d.end()) cout << *mx << " " << *mn << endl;
else cout << "vecteur vide " << endl;
find et find_if :
Étant donné une séquence d’entrée spécifiée par des itérateurs de début et de fin, et la
valeur de l’élément recherché, cette fonction find renvoie un itérateur identifiant le
premier élément répondant à la condition de recherche, comme par exemple, pour
localiser la position d’une valeur dans une liste.
Une variante de l’algorithme find est find_if. Le troisième argument est un prédicat au
lieu d’un élément du conteneur. L’algorithme renvoie la position de la première valeur
satisfaisant au prédicat.
Ex. :
La portion de code renvoie la première année bissextile d’une liste d’années :
bool annee_bissextile(int annee)
{
if (0 == annee % 400) return true;
if (0 == annee % 100) return false;
if (0 == annee % 4) return true;
return false;
}
list<int> annees;
…
list<int> ::iterator premiere_annee_bissextile =
find_if(annees.begin(), annees.end(), annee_bissextile);
binary_search :
Étant donné une séquence d’entrée triée spécifiée par des itérateurs de début et de fin, et
la valeur de l’élément recherché, cette fonction booléenne indique si la valeur est ou non
trouvée dans le conteneur.
Ex. :
vector<int> a(10);
generate(a.begin(), a.end(), AleatoireInt(10, 30));
sort(a.begin(), a.end());
if(binary_search(a.begin(), a.end(), 7)) cout << "7 est trouve " << endl;
lower_bound et upper_bound :
La fonction lower_bound renvoie un itérateur indiquant où une valeur peut être insérée
pour conserver la séquence triée; autrement dit, l’itérateur fait référence soit au premier
élément supérieur ou égal à l’élément de recherche, soit à l’itérateur de fin si aucun
élément n’est plus grand ou égal à l’élément de recherche. Par contre, la fonction
upper_bound localise le premier élément strictement supérieur à l’élément de recherche.
Ex. :
vector<int> a(10);
generate(a.begin(), a.end(), AleatoireInt(10, 30));
sort(a.begin(), a.end());
if(binary_search(a.begin(), a.end(), 7))
6
{
vector<int>::iterator p = lower_bound(a.begin(), a.end(), 7);
vector<int>::iterator q = upper_bound(a.begin(), a.end(), 7);
while (p != q)
{
cout << "trouve " << *p << endl;
++p;
}
}
else cout << "non trouve " << endl;
Si une collection contient déjà plusieurs copies de l’élément cherché, le sous-ensemble
décrit par ces 2 valeurs correspond à la portion du conteneur où se trouvent ces valeurs.
adjacent_find :
Renvoie la position du premier élément égal au prochain élément qui suit.
Ex. :
À partir de la séquence des valeurs suivantes, 1 4 2 5 6 6 7 5, l’algorithme renvoie
un itérateur correspondant à la première valeur de 6.
search :
Renvoie la position du premier élément d’un sous-ensemble dans une séquence de plus
grande taille.
equal :
Cette fonction travaille avec 2 séquences de même taille et renvoie true si les éléments
associés sont tous égaux.
mismatch :
Cette fonction travaille avec 2 séquences de même taille et renvoie une paire d’itérateurs
indiquant ensemble les premières positions où les séquences possèdent des éléments
distincts.
D.
Algorithmes de suppression et de remplacement
remove_copy :
Cette fonction reçoit comme argument une valeur spécifique et copie vers un autre
conteneur la séquence du conteneur original à l’exception des instances de la valeur
spécifique.
Ex . : soient a = 1 2 3 2 4 2 5 6,
b = 0 0 0 0 0 0 0 0,
7
L’appel à remove_copy(a.begin(), a.end(), b.begin(), 2) copie toutes les valeurs
rencontrées sauf le 2 aboutissant à :
a = 1 2 3 2 4 2 5 6,
b = 1 3 4 5 6 0 0 0.
La séquence résultante est plus petite que l’original. La fonction renvoie
l’itérateur vers b indiquant la fin de la séquence résultat.
remove_copy_if :
L’appel à remove_copy_if(a.begin(), a.end(), b.begin(), divisible_par(2)) copierait toutes
les valeurs sauf celles divisibles par 2, aboutissant à ceci :
a = 1 2 3 2 4 2 5 6,
b = 1 3 5 0 0 0 0 0.
On peut remarquer que la taille du conteneur résultat, b, n’est pas modifiée même si
seulement une portion initiale a été employée.
remove :
Cette fonction permet de supprimer toutes les instances d’une valeur fixe.
Généralement, les algorithmes génériques ne modifient pas la taille des conteneurs sur
lesquels ils fonctionnent. Cela peut donner lieu à des situations problématiques, comme
dans l’exemple suivant :
remove(a.begin(), a.end(), 2);
Après cet appel, la séquence devient : a = 1 3 4 5 6 2 5 6.

La portion initiale (les valeurs avant l’itérateur), est le résultat désiré. Les valeurs depuis
l’itérateur jusqu’à la fin sont uniquement les éléments originaux du conteneur. Pour
éliminer les valeurs superflues, il faut employer la fonction erase fournie par le
conteneur :
vector<int>::iterator p = remove(a.begin(), a.end(), 2);
a.erase(p, a.end()); // Supprime les valeurs restantes.
remove_if :
Cette fonction est semblable à remove mais elle a recours à un prédicat :
vector<int>::iterator p = remove(a.begin(), a.end(), divisible_par(2));
a.erase(p, a.end()); // La collection devient 1, 3, 5.
replace :
Cette fonction permet de remplacer les occurrences d’une valeur fixe par une autre.
8
vector<int> a(10);
generate(a.begin(), a.end(), AleatoireInt(1, 5));
replace(a.begin(), a.end(), 3, 4);
replace_if :
Cette fonction permet de remplacer les éléments d’une séquence vérifiant un prédicat par
une valeur fixe.
vector<int> a(10);
generate(a.begin(), a.end(), AleatoireInt(1, 5));
replace_if(a.begin(), a.end(), paire, 0); // Remplace tous les nombres pairs par 0.
E.
Autres algorithmes
count et count_if :
Ils servent à compter le nombre de valeurs d’une collection ou à compter le nombre de
valeurs satisfaisant un prédicat.
Exemple I:
int count(input_iterator i, input_iterator j, const T &v);
compte le nombre n de fois où une valeur v se rencontre entre les positions i et j
du conteneur et retourne cette valeur n.
#include <iostream>
#include <algorithm>
using namespace std;
int table[] = {12, 321, 34, 13, 12, 15, 12};
…….
int n = 0;
n = count(tab, tab + 7, 12);
cout << n <<endl;
Exemple II :
int count_if(input_iterator i, input_iterator j, fonction booléenne F);
compte le nombre d’occurrences dans l’intervalle [i, j[ qui satisfont à la condition
du prédicat F et incrémente la variable n de ce nombre.
#include <iostream>
#include <algorithm>
#include <vector>
#include <math.h>
using namespace std;
bool premier(unsigned int n)
9
{
for (int i = 2; i <= sqrt(n); i++)
if ((n%i) == 0) return 0;
return 1;
}
class Nombre_premier
{
public:
bool operator()(unsigned int n)
{
for (int i = 2; i <= sqrt(n); i++)
if ((n%i) == 0) return 0;
return 1;
};
};
void main()
{
int n = 0;
int table[100];
for (int i = 2; i < 100; i++) table[i] = i;
cout << "Il y a " << count_if(table+2,table+100, premier)
<< " nombres premiers entre 2 et 100"
<< endl;
vector<int> v(table, table+100);
cout << "Il y a " << count_if(v.begin(), v.end(), premier)
<< " nombres premiers entre 2 et 100"
<< endl;
cout << "Il y a " << count_if(v.begin(), v.end(), Nombre_premier())
<< " nombres premiers entre 2 et 100"
<< endl;
}
Quelques algorithmes numériques sont présents dans le fichier d’en-tête <numeric>,
plutôt que dans <algorithm>.
accumulate :
Ex. :
list<double> D;
10
...
double somme = accumulate(D.begin(), D.end(), 0.0);
// somme contient maintenant la somme des éléments de la liste.
etc.
11
Téléchargement