Cadre de ce TP¶
Ce TP a d'abord été proposé dans un notebook Jupyter muni d'un noyau interactif permettant d'écrire du C++ : 98 % de la syntaxe du C est directement utilisable en C++, donc ça ne change pas grand-chose. Ici, on compile de vrais programmes C.
Normalement un programme en C est composé d'un fichier .c (le plus
souvent plusieurs qui sont réunis) contenant une fonction main qui est
le point d'entrée du programme.
Voici un exemple de contenu d'un tel fichier :
// déclaration de fonctions d'impression pour pouvoir les utiliser
#include <stdio.h>
int x = 3; // une variable globale entière x pour tout le programme
// déclaration d'une fonction qui prend deux entiers en entrée et renvoie un entier
int ajoute(int a, int b)
{
return a+b;
}
// la fonction principale
int main(void)
{
int a = 2; // déclaration de variables locales
int b; // non initialisée ici
b = 3; // nécessite que b soit déclarée.
printf("La somme de %d + %d = %d\n",
b, x, ajoute(b, x)); // la fonction principale d'affichage, véritable couteau-suisse
// ici %d signifie un entier qui figure dans la liste juste après
// On retrouve des boucles for
a = 0;
for (int i = 0; i < 10; i++) // mais on doit gérer notre compteur nous-même
{
a = a + i;
}
printf("La somme des entiers de 0 à 9 vaut %d\n", a);
return 0; // pour main signifie que tout va bien
}
qui affiche
La somme de 3 + 3 = 6
La somme des entiers de 0 à 9 vaut 45
On procédera comme en OCaml pour ce TP : on vous demandera d'écrire le
corps de fonctions, puis une série de tests sera effectuée pour vous assurer
que vous avez réussi. Les tests sont écrits avec assert (il faut
#include <assert.h> en tête du fichier) : assert(c); interrompt le
programme avec un message d'erreur si la condition c est fausse. On les
place dans la fonction main.
Question 1
Écrire une fonction int multiplie(int a, int b) qui renvoie le produit de
a et de b (allez voir le corps de ajoute au-dessus et adaptez).
Test :
assert(multiplie(2, 3) == 6);
Corrigé
int multiplie(int a, int b)
{
return a * b;
}
Les bases du C¶
Types et variables¶
Mis à part l'ajout des points-virgules à la fin des instructions, la
différence principale entre OCaml, Python et C dans ce qu'on
vient de voir se situe au niveau des instructions explicites de type et
donc de la notion de déclaration de variable.
On a en C des types de base sur lesquels on reviendra dans les
exercices : int, float, bool, mais une variable doit être
déclarée comme ayant un type donné et, bien qu'on puisse en changer
la valeur, on ne pourra pas changer le type des données qu'elle
contient.
Pour déclarer une variable x ayant le type t on écrit t x;, par
exemple int b; ici. Une fois une variable déclarée, on peut
l'utiliser en lecture et en écriture. Mais il faut écrire dedans AVANT
d'y lire quoi que ce soit. Pour cela, on pourra écrire x = val; pour
que la variable x prenne la valeur val. Il est possible de déclarer
et d'initialiser une variable, comme dans int x = val;.
ATTENTION : si on écrit juste int x; sans valeur, on ne peut pas
supposer que x contienne une valeur spécifique comme 0. On peut
juste dire que x contient un entier quelconque qu'on se gardera bien
d'utiliser dans un calcul.
Ainsi, comme OCaml, C est un langage typé statiquement, mais dont les
types sont explicites.
int x; // x n'est pas initialisé mais c'est un entier
float y = 3.2; // y est un flottant initialisé à 3.2
bool b = true; // b est un booléen qui vaut true (nécessite stdbool.h)
char c = 'a'; // le type des caractères
Un type propre à C est le type de rien, qui se nomme void. Cela
correspond au type unit de OCaml.
Blocs et portée¶
En C on a une notion de bloc : ce sont des instructions ou des
déclarations de variables entourées d'accolades, comme dans :
{
int x = 3;
printf("x vaut %d\n", x);
}
Une variable déclarée dans un bloc n'existe que dans celui-ci. Dès qu'on sort du bloc on ne peut plus y accéder. Les fonctions, les instructions conditionnelles ou les boucles utilisent des blocs.
// exemple avec deux blocs imbriqués où on retrouve la valeur précédente de x
// qui a juste été masquée dans le sous-bloc
{
int x = 3;
printf("x vaut %d\n", x);
{
int x = 4;
printf("x vaut %d\n", x);
}
printf("x vaut %d\n", x);
}
x vaut 3
x vaut 4
x vaut 3
// ATTENTION par contre, le code suivant utilise x dans le bloc pour le changer de
// valeur et comme c'est le x du bloc principal, ça le modifie
{
int x = 3;
printf("x vaut %d\n", x);
{
x = 4;
printf("x vaut %d\n", x);
}
printf("x vaut %d\n", x);
}
x vaut 3
x vaut 4
x vaut 4
Fonctions¶
Une fonction en C a un type de retour et chacun des arguments est
donné avec son type. S'ensuit un bloc d'instructions qui peut
comporter une ou plusieurs instructions return v; qui vont renvoyer la
valeur v. Pour une fonction de type de retour void, on peut omettre le
return ou écrire return; sans valeur.
On définit une fonction ainsi :
type_retour nom_fonction(type_arg1 nom_arg1, ..., type_argn nom_argn)
{
// bloc d'instructions qui constitue le corps
// de la fonction
}
On est donc très proche des fonctions Python mais avec cette
contrainte de type.
int f(int a, char c)
{
printf("a=%d et c=%c\n", a, c);
return a*a;
}
On appelle alors la fonction comme en Python avec
nom_fonction(val1, ..., valn) : int r = f(3, 'z'); affiche
a=3 et c=z et place la valeur 9 dans r.
On voit que la valeur de sortie de la fonction est bien le résultat du
return, le reste c'est juste de l'affichage.
Les arguments sont des variables qu'on peut utiliser directement : cela ne changera pas la valeur passée en paramètre si on les redéfinit. Une fonction peut s'appeler elle-même, avec toutes les réserves vues quant à l'utilisation de la pile.
Instructions conditionnelles¶
Pour effectuer une instruction conditionnelle, on écrit
if (condition)
{
// bloc d'instructions à exécuter quand la condition est vraie
}
else
{
// bloc d'instructions à exécuter quand la condition est fausse
}
Par exemple, on pourra écrire :
if (n % 2 == 0)
{
printf("n est pair\n");
}
else
{
printf("n est impair\n");
}
Ici, comme il n'y a qu'une instruction, on pourrait ne pas faire de bloc et écrire juste l'instruction. Mais on se gardera de faire ça pour le moment, car c'est une source classique d'erreurs.
On note qu'il n'est pas nécessaire d'avoir un else. Ici, comme en
Python et contrairement à OCaml, on n'a pas de notion de type
associé aux branches, ce sont juste des suites d'instructions.
Notamment : en C, ce if est une instruction et pas une
expression. Si on peut écrire
let x = if n mod 2 = 0 then 1 else 2
en OCaml, en C, on devra écrire :
int x;
if (n % 2 == 0)
{
x = 1;
}
else
{
x = 2;
}
Au passage, remarquons qu'on utilise == comme en Python. Par
contre, contrairement à Python, comme les déclarations ne sortent pas
des blocs, on ne peut pas transposer
if n % 2 == 0:
x = 1
else:
x = 2
print("x vaut ", x)
car en C le programme suivant est erroné
if (n % 2 == 0)
{
int x = 1;
}
else
{
int x = 2;
}
printf("x vaut %d\n", x);
puisque x n'est pas définie en dehors des blocs.
Exercice¶
Pour la question suivante, on rappelle la définition de la suite de Syracuse :
Étant donné \(a \in \mathbb{N}^*\), on appelle suite de Syracuse de premier terme \(a\) la suite \((u_n)_{n\in\mathbb{N}}\) définie par \(u_0 = a\) et pour \(n \in \mathbb{N}\) :
Indications :
- pour déterminer si un nombre
aest pair on peut utiliser le modulo et testera % 2 == 0comme enPython; - une fonction peut avoir plusieurs
return, donc quand vous avez calculé une valeur, il suffit de la renvoyer.
Question 2
Écrire une fonction int suivant(int u) qui renvoie la valeur du terme qui
suit u dans la suite.
Tests :
assert(suivant(14) == 7);
assert(suivant(7) == 22);
Corrigé
int suivant(int u)
{
if (u % 2 == 0)
{
return u/2;
}
else
{
return 3*u+1;
}
}
Boucles conditionnelles¶
Pour effectuer une boucle conditionnelle, on écrit
while(condition)
{
// instructions à réaliser
}
Ce sont, comme en Python, les boucles les plus simples mais elles
demandent de gérer plus de choses qu'une boucle inconditionnelle. Par
exemple, pour calculer la somme \(\sum_{k=0}^n k\), on doit nécessairement
gérer les indices à la main :
int somme_entiers(int n)
{
int k = 0; // l'indice
int s = 0; // la somme courante
while (k <= n)
{
s = s + k;
k = k+1;
}
return s;
}
Le cas le plus approprié d'une telle boucle est quand on ne peut maîtriser le nombre d'itérations, comme dans la fonction suivante.
Question 3
Écrire une fonction int sup_log2(int n) qui renvoie, pour \(n \ge 0\), le
plus petit entier naturel \(d\) tel que \(n < 2^d\) (attention, cette
puissance n'existe pas en C).
Tests :
assert(sup_log2(0) == 0);
assert(sup_log2(1) == 1);
assert(sup_log2(15) == 4);
Corrigé
int sup_log2(int n)
{
int d = 0;
int p = 1;
while (p <= n)
{
p = p * 2;
d = d + 1;
}
return d;
}
Boucles for¶
Contrairement à Python, il n'y a aucun mécanisme permettant de
parcourir des valeurs. Mais contrairement aux boucles for de OCaml,
il ne s'agit pas simplement de boucles inconditionnelles. Le mécanisme
est plus complexe.
Commençons par un exemple en reprenant la fonction somme_entiers :
int somme_entiers(int n)
{
int s = 0;
for(int k = 0; k <= n; k++)
{
s = s + k;
}
return s;
}
Déjà, précisons que k++ est une manière d'écrire k = k+1. On parle
d'incrémentation de k.
Ensuite, on peut voir qu'il y a trois parties dans la boucle for :
for (initialisation; condition; miseajour)
{
// instructions
}
L'initialisation est la plupart du temps la déclaration d'une variable de boucle. Ici, on peut utiliser le raccourci permettant de déclarer et définir la variable directement. Par opposition au fait d'écrire :
int k;
for (k = 0; k <= n; k++)
{
s = s+k;
}
Ensuite, vient la condition d'arrêt qui est très proche de celle d'un
while.
Enfin, la mise à jour est une instruction qui sera effectuée à la fin de
chaque itération. Finalement, une boucle for en C n'est qu'un
while déguisé :
initialisation;
while(condition)
{
// instructions
miseajour;
}
Pour autant, la lisibilité d'une boucle for est bien meilleure que
celle d'un while car elle structure plus en mettant en valeur les
vraies instructions du corps de boucle. On ne sera pas surpris de
trouver ainsi des for dès que c'est possible.
Question 4
Écrire une fonction int somme_carres(int n) qui renvoie la somme des
carrés des entiers de 0 à \(n\) : \(0^2 + 1^2 + \dots + n^2\).
Tests :
assert(somme_carres(13) == 819);
assert(somme_carres(0) == 0);
assert(somme_carres(1) == 1);
Corrigé
int somme_carres(int n)
{
int s = 0;
for (int k = 0; k <= n; k++)
{
s = s + k*k;
}
return s;
}
J'aimerais tant (re)voir Syracuse¶
Question 5
Écrire une fonction int syracuse(int a, int n) qui renvoie le \(n\)-ième
terme de la suite de Syracuse de premier terme \(a\).
ATTENTION : on utilisera suivant et on ne fera pas de copier-coller
de code.
Tests :
assert(syracuse(3, 5) == 4);
assert(syracuse(1, 0) == 1);
assert(syracuse(1, 1) == 4);
assert(syracuse(1, 2) == 2);
Corrigé
int syracuse(int a, int n)
{
int u = a;
for (int k = 1; k <= n; k++)
{
u = suivant(u); // on calcule u(k) ici
}
return u;
}
Question 6
Écrire une fonction int temps_de_vol(int a) qui renvoie le temps de vol
pour \(a\), c'est-à-dire le plus petit entier \(n\) tel que \(u_n = 1\) quand
\(u_0 = a\).
Tests :
assert(temps_de_vol(1) == 0);
assert(temps_de_vol(14) == 17);
Corrigé
int temps_de_vol(int a)
{
int n = 0;
int u = a;
while (u != 1)
{
u = suivant(u);
n = n+1;
}
return n;
}
Question 7
Écrire une fonction int altitude_maximale(int a) qui renvoie l'altitude
maximale de \(a\), c'est-à-dire la plus grande valeur prise par la suite
quand \(u_0 = a\).
Tests :
assert(altitude_maximale(1) == 4);
assert(altitude_maximale(2) == 4);
assert(altitude_maximale(14) == 52);
assert(altitude_maximale(16) == 16);
Corrigé
La suite finit par boucler sur \(4, 2, 1\), donc l'altitude maximale vaut au moins 4, et il ne faut pas oublier le premier terme \(u_0 = a\).
int altitude_maximale(int a)
{
int m = 4; // la suite finit par boucler sur 4, 2, 1
if (a > m)
{
m = a;
}
int u = a;
while (u != 1)
{
u = suivant(u);
if (u > m)
{
m = u;
}
}
return m;
}
Les tableaux¶
Déclaration et initialisation¶
La structure de données la plus primitive en C est celle des tableaux.
Attention, ce sont des tableaux de taille fixe comme les 'a array de
OCaml. Pour déclarer un tableau t de n valeurs (où n est une
CONSTANTE et pas une variable) d'un type T on écrit
T t[n];
Par exemple int a[10]; déclare a comme étant un tableau de 10
entiers.
On peut alors accéder en lecture et en écriture à la \(i\)-ème case du
tableau en écrivant t[i]. Ici tout se passe comme si t était une
collection de variables accessibles par des indices. De plus, on a
l'assurance que les cases se suivent linéairement en mémoire, ce qui
sera utile dans la suite.
REMARQUE : C est un langage sans garde-fou, on peut tout à fait
écrire t[100] pour un tableau de taille 3 et ainsi aller écrire
n'importe où en mémoire (voir la page
Travaux Pratiques d'apprentissage du C
et l'explosion d'Ariane 5).
ATTENTION : dans presque tous les cas, les valeurs ne sont pas initialisées, et comme pour les variables, il faut attribuer des valeurs avant de travailler sur le tableau. Notamment, les cases ne sont pas initialisées à zéro.
Il est possible d'initialiser un tableau au moment de sa déclaration. Pour cela, on met les valeurs entre accolades comme ici :
int t[3] = { 1, 3, 5 };
Ce qui équivaut à l'écriture de :
int t[3];
t[0] = 1;
t[1] = 3;
t[2] = 5;
Si jamais on donne moins de n valeurs pour t[n], on va
compléter par des valeurs nulles. Cela permet d'utiliser le cas spécial
{ 0 } pour tout initialiser à zéro. Les trois définitions suivantes sont
alors équivalentes :
int t[3] = { 0, 0, 0 };
int t[3] = { 0, 0 };
int t[3] = { 0 };
(La forme {} est une extension de gcc, normalisée seulement en C23.)
Quand on donne une succession complète de valeurs, on connaît déjà le
nombre d'éléments du tableau. Il est alors assez redondant de le
donner une seconde fois dans la déclaration. On dispose ainsi d'un
mécanisme automatique en écrivant juste T t[] = ...;. On peut
reprendre la déclaration précédente ainsi :
int t[] = { 1, 3, 5 };
Tableaux en arguments¶
Pour passer un tableau en paramètre à une fonction on pourra utiliser,
entre autres, int t[] ou int t[10] avec une taille précise, mais
il n'y a aucune vérification de taille en C. On prendra donc
l'habitude de donner en arguments le nombre d'éléments du tableau.
Ainsi la fonction suivante calcule la somme des éléments d'un tableau.
int somme_tableau(int t[], int nb_elements)
{
int s = 0;
for (int i = 0; i < nb_elements; i++)
{
s = s + t[i];
}
return s;
}
int t[] = { 0, 1, 2, 3 };
printf("%d\n", somme_tableau(t, 4)); // 6
// mais on peut très bien appeler la fonction sur le même tableau avec une taille plus petite
printf("%d\n", somme_tableau(t, 3)); // 3
// ou une taille plus grande mais alors rappelez-vous de la fusée...
printf("%d\n", somme_tableau(t, 1000)); // valeur absurde, ou plantage
Question 8
Écrire une fonction int recherche(int t[], int nb_elements, int x) qui
renvoie le plus petit indice de x dans le tableau t ; si x n'est pas
présent, on renvoie -1 (qui est toujours un indice invalide).
Rappel : on peut faire des return quand on veut.
Tests :
int t[] = { 0, 2, 2, 6 };
assert(recherche(t, 4, 2) == 1);
assert(recherche(t, 1, 2) == -1);
assert(recherche(t, 0, 2) == -1);
assert(recherche(t, 1, 0) == 0);
assert(recherche(t, 4, 7) == -1);
Corrigé
int recherche(int t[], int nb_elements, int x)
{
for (int i = 0; i < nb_elements; i++)
{
if (t[i] == x)
{
return i;
}
}
return -1;
}
Question 9
Écrire une fonction int maximum(int t[], int nb_elements) qui renvoie la
valeur du maximum du tableau t. On considère que nb_elements \(> 0\),
donc il existe toujours.
Tests :
int t[] = { 5, 0, 2, 1, 6 };
assert(maximum(t, 4) == 5);
assert(maximum(t, 5) == 6);
Corrigé
int maximum(int t[], int nb_elements)
{
int m = t[0];
for (int i = 1; i < nb_elements; i++)
{
if (m < t[i])
{
m = t[i];
}
}
return m;
}
Question 10
En déduire une fonction int arg_max(int t[], int nb_elements) qui
renvoie le plus petit indice du maximum de t où nb_elements \(> 0\).
ON NE FERA PAS DE BOUCLES ICI.
Tests :
int t[] = { 5, 0, 5, 1, 6 };
assert(arg_max(t, 4) == 0);
assert(arg_max(t, 5) == 4);
Corrigé
int arg_max(int t[], int nb_elements)
{
return recherche(t, nb_elements, maximum(t, nb_elements));
}
Des tableaux particuliers : les chaînes de caractères¶
Une chaîne de caractères en C est juste un tableau de char dont on
impose que la dernière case soit nulle. Dans le contexte des caractères
on note '\0' le caractère nul, mais cela correspond à la valeur 0.
Ainsi :
char chaine[] = { 'B', 'o', 'n', 'j', 'o', 'u', 'r', '\0' };
est la chaîne contenant Bonjour. Comme écrire ainsi les chaînes serait
très fastidieux, on utilise la notation usuelle :
char chaine[] = "Bonjour";
et tout se passe comme dans la définition ci-dessus : chaine[7] vaut bien
'\0'.
Attention, les caractères en C sont limités : un char est un octet, qui ne
peut contenir qu'un caractère ASCII. On peut écrire « 歩こう! » ou des
caractères accentués dans une chaîne, mais chacun de ces caractères occupe
alors plusieurs char (codage UTF-8). Il y a un certain nombre de caractères
spéciaux comme \n pour effectuer un saut de ligne.
On va maintenant revenir sur la fonction printf vue précédemment.
C'est une fonction spéciale qui peut prendre un nombre variable
d'arguments mais qui en aura toujours au moins un : printf(format, ...)
où format est une chaîne de caractères. Cette chaîne peut comporter
des symboles spéciaux commençant par % et affichant, dans l'ordre de
gauche à droite, les valeurs passées en argument après format.
On a par exemple %d pour afficher un entier signé, %s pour une
chaîne de caractères ou %c pour un caractère. Exemple :
char s[] = "Test";
int i = 2;
int r = printf("Le %deme caractère de la chaine %s est %c\n",
i, s, s[i]); // oui on commence à compter à 0
affiche Le 2eme caractère de la chaine Test est s et r vaut 43.
Le nombre renvoyé par printf est le nombre de caractères (plus
précisément d'octets : le è en compte deux) affichés après ces
substitutions.
On pourra utiliser la fonction suivante pour afficher un tableau :
void print_tableau(int t[], int nb_elements)
{
for(int i = 0; i < nb_elements; i++)
{
char c = ',';
if (i == nb_elements - 1)
c = '\n'; // fin de ligne pour le dernier élément.
printf("%d%c", t[i], c);
}
}
Ainsi, int t[] = {1, 2, 3}; print_tableau(t, 3); affiche 1,2,3.
Tableaux en valeur de retour ?¶
On ne peut pas renvoyer un tableau défini ainsi en C. En effet, il
correspond à une zone mémoire locale à la fonction qui ne peut en
sortir. Comme souvent en C, on peut l'écrire, mais ça va avoir un
comportement erroné pouvant aboutir à une catastrophe (voir les
pointeurs pendants dans le chapitre
Gestion de la mémoire).
La bonne manière d'avoir un tableau en valeur de retour, c'est paradoxalement de le passer en paramètre et d'écrire dedans.
La fonction suivante, par exemple, va renvoyer un tableau à
l'envers. Pour bien identifier le tableau en entrée et le tableau en
sortie, on les a nommés in et out.
void renverse_tableau(int in[], int out[], int nb_elts)
{
for(int i = 0; i < nb_elts; i++)
{
out[i] = in[nb_elts-1-i];
}
}
int t[] = {1,2,3};
int q[3];
renverse_tableau(t, q, 3);
print_tableau(t, 3); // 1,2,3
print_tableau(q, 3); // 3,2,1
Tri par sélection¶
On va maintenant revenir sur un problème classique d'algorithmique : la notion de tri. On considère des tableaux et on va étudier différentes manières de les trier.
On a des méthodes de tri dites en place où on trie un tableau directement en changeant ses valeurs, et des méthodes non en place qui vont placer dans un autre tableau le résultat. Pour effectuer des tris en place, on va considérer une fonction permettant d'échanger deux valeurs dans un tableau.
Question 11
Écrire une fonction void swap(int t[], int i, int j) qui échange dans t
les valeurs des cases d'indices i et j.
Test :
int t[] = { 1, 2, 3 };
swap(t, 0, 2);
assert(t[0] == 3 && t[2] == 1);
Corrigé
void swap(int t[], int i, int j)
{
int temp = t[i];
t[i] = t[j];
t[j] = temp;
}
Le tri par sélection est un tri qui va constituer un sous-tableau trié de plus en plus grand en tête du tableau. C'est une méthode en place. À l'étape \(i\), on considère que les cases d'indices \(0\) à \(i-1\) sont triées et contiennent les \(i\) plus petites valeurs du tableau.
- Si \(i = n\), où \(n\) est la taille du tableau, alors le sous-tableau est en fait le tableau complet : il est trié.
- Sinon, on va chercher le minimum du tableau entre les indices \(i\) et \(n-1\) et on le place à l'indice \(i\). On passe alors à l'étape \(i+1\).
Question 12
Écrire une fonction int indice_minimum_sous_tableau(int t[], int i, int nb_elts)
qui renvoie l'indice du plus petit élément du tableau t à partir de
l'indice i. On appellera toujours cette fonction avec i < nb_elts.
Tests :
int t[] = { 4, 5, 2, 1, 6 };
assert(indice_minimum_sous_tableau(t, 0, 5) == 3);
assert(indice_minimum_sous_tableau(t, 2, 5) == 3);
assert(indice_minimum_sous_tableau(t, 4, 5) == 4);
Corrigé
int indice_minimum_sous_tableau(int t[], int i, int nb_elts)
{
int val_min = t[i];
int indice_min = i;
for (int j = i+1; j < nb_elts; j++)
{
if (val_min > t[j])
{
val_min = t[j];
indice_min = j;
}
}
return indice_min;
}
Question 13
Écrire une fonction void tri_par_selection(int t[], int nb_elts) qui trie
t par sélection à l'aide de la fonction précédente.
Test :
int t[] = { 4, 5, 2, 1, 6 };
int q[] = { 1, 2, 4, 5, 6 };
tri_par_selection(t, 5);
for (int i = 0; i < 5; i++)
{
assert(t[i] == q[i]);
}
Corrigé
void tri_par_selection(int t[], int nb_elts)
{
for (int i = 0; i < nb_elts; i++)
{
int j = indice_minimum_sous_tableau(t, i, nb_elts);
swap(t, i, j);
}
}
L'invariant décrit plus haut est maintenu : après l'itération \(i\), les cases \(0\) à \(i\) contiennent les \(i+1\) plus petites valeurs, triées. Le nombre de comparaisons est \(\sum_{i=0}^{n-1} (n-1-i) = \frac{n(n-1)}{2}\), donc le tri est en \(O(n^2)\) dans tous les cas.