Trier des tableaux
Trier des entiers
Avec l' algorithme "qsort"
La librairie C standard offre la fonction qsort() définie dans stdlib.h :
void qsort ( void * base, size_t num, size_t width, int (*fncompare)(const void *, const void *) );
Sort using quicksort algorithm.
Le principe est simple: en lui passant un tableau en paramètre, elle le trie et il n' y a plus qu'a lister le tableau pour voir ce qu'il contient.
Noter que le dernier paramètre de qsort() est un pointeur de fonction: il faut écrire cette fonction et lui passer son nom en paramètre. Ce procédé permet de créer des fonctions pour trier des entiers, des floats, de strings, ...
Gareth McCaughan a réécrit la fonction qsort(): source ici mais d'après mes tests sa fonction qsortG() est deux fois plus lente que qsort de la LibC.
Darel L. Finley a implémenté de façon remarquable l' algorithme quickSort:
void qsort ( void * base, size_t num, size_t width, int (*fncompare)(const void *, const void *) );
Sort using quicksort algorithm.
Le principe est simple: en lui passant un tableau en paramètre, elle le trie et il n' y a plus qu'a lister le tableau pour voir ce qu'il contient.
Noter que le dernier paramètre de qsort() est un pointeur de fonction: il faut écrire cette fonction et lui passer son nom en paramètre. Ce procédé permet de créer des fonctions pour trier des entiers, des floats, de strings, ...
/* GNU example - Sort integers */ #include <stdio.h> #include <stdlib.h> int compare (const void * a, const void * b) { return ( *(int*)a - *(int*)b ); } int main () { int n; int i[15]={102,89,69,9,56,59,85,44,66,12,36,458,965,254,666}; qsort (i, 15, sizeof(int), compare); for (n=0; n<15; n++) printf ("%d ",i[n]); puts(""); return 0; }
Gareth McCaughan a réécrit la fonction qsort(): source ici mais d'après mes tests sa fonction qsortG() est deux fois plus lente que qsort de la LibC.
Darel L. Finley a implémenté de façon remarquable l' algorithme quickSort:
// quickSort // // This public-domain C implementation by Darel R. Finley. // http://alienryderflex.com/quicksort/ // // * Returns true if sort was successful, or false if the nested // pivots went too deep, in which case your array will have // been re-ordered, but probably not sorted correctly. // // * This function assumes it is called with valid parameters. // // * Example calls: // quickSort(&myArray[0],5); // sorts elements 0, 1, 2, 3, and 4 // quickSort(&myArray[3],5); // sorts elements 3, 4, 5, 6, and 7 #include <stdio.h> #include <stdlib.h> char quickSort(int *arr, int elements) { #define MAX_LEVELS 1000 int piv, beg[MAX_LEVELS], end[MAX_LEVELS], i=0, L, R ; beg[0]=0; end[0]=elements; while (i>=0) { L=beg[i]; R=end[i]-1; if (L<R) { piv=arr[L]; if (i==MAX_LEVELS-1) return 0; while (L<R) { while (arr[R]>=piv && L<R) R--; if (L<R) arr[L++]=arr[R]; while (arr[L]<=piv && L<R) L++; if (L<R) arr[R--]=arr[L]; } arr[L]=piv; beg[i+1]=L+1; end[i+1]=end[i]; end[i++]=L; } else { i--; }} return 1; } int main () { int n; int i[15]={102,89,69,9,56,59,85,44,66,12,36,458,965,254,666}; n = quickSort(i, 15); for (n=0; n<15; n++) printf ("%d ",i[n]); puts(""); return 0; }
Avec l' algorithme "Counting sort"
/* Sort integers without qsort, using algorithm "counting sort" source: http://en.wikipedia.org/wiki/Counting_sort */ #include <stdio.h> #include <time.h> #include <stdlib.h> /* end is the last index + 1 */ void csort(int array[], const int end, const int max, const int min) { int i; const int range = max-min+1; int count[range+1], scratch[end]; for(i=0; i<range+1; i++) count[i] = 0; /* Set the value of count[i] to the number of * elements in array with value i+min-1. */ for(i=0; i<end; i++) { int c = array[i]+1-min; count[c]++; } /* Update count[i] to be the number of * elements with value less than i+min. */ for(i=1; i<range; i++) count[i] += count[i-1]; /* Copy the elements of array into scratch in * stable sorted order. */ for(i=(end-1); i>=0; i--) { int c = array[i]-min; int s = count[c]; scratch[s] = array[i]; /* Increment count so that the next element * with the same value as the current element * is placed into its own position in scratch. */ count[c]++; } for(i=0; i<end; i++) array[i] = scratch[i]; } int main(int argc, char* argv[]) { int n; int i[15]={102,89,69,9,56,59,85,44,66,12,36,458,965,254,666}; csort(i, 15, 965, 9); for (n=0; n<15 ; n++) printf("%d ", i[n]); puts(""); return 0; }
Un désavantage pour cette fonction: il faut passer des arguments additionnels comme les valeurs minimales et maximales du tableau (comme si on les connaissait à l' avance)...
Il faut aussi passer le nombre d' éléments du tableau. Aurait pu être évité facilement si la fonction avait inclué un :
#include <stdio.h> int main() { int i[]={102,89,69,9,56,59,85,44,66}; int count = sizeof (i) / sizeof (int); printf("nombre d' éléments: %d ", count); return 0; }
Astucieux non? En effet un tableau d'entiers contient n fois la taille d' un int. Donc connaissant sa taille totale et la taille d'un int, obtient le nombre d' éléments. Que les valeurs soient remplies ou pas! Ce calcul prend en compte la taille allouée au tableau sans se préoccupper de ce qu'il contient!
Avec mon algorithme:
Comme vous vous en doutez je vous invite à écrire votre algorithme de triage
d' entiers. Voici le mien.
Le tableau de valeurs à trier est parcouru une fois afin de remplir un tableau d'index de valeurs triées. Avant d'insérer un élément, trois cas de figure sont possibles:
Partant de ce tableau d' entiers contenant les valeurs suivantes,
Tableau à trier:
Le but consiste à passer les index de ce tableau vers un tableau temporaire qui lui, contiendra les index triés. Au départ, ce tableau est vide.
Tableau temporaire:
L' algorithme commence par mettre le premier élément dans le tableau temporaire.
Tableau temporaire:
A présent une boucle va traiter le tableau de valeurs à trier, en partant du second élément du tableau puisque le premier est déjà inséré.
La seconde valeur à insérer: index=1 valeur=12.
Comme 12 est supérieur à 8, 12 est inséré en tant que dernier élément du tableau de valeurs triées:
Le troisième élément (index: 2 valeur: 6) a une valeur qui est inférieure à la valeur minimale contenue dans le tableau de valeurs triées: chaque élément est décalé vers la droite ...
... Puis l' élément est inséré:
Le quatrième élément à traiter (index: 3 valeur: 10) n'est ni le minimum, ni le maximum: il faut parcourir le tableau de valeurs triées jusqu'à ce qu'un élément qui lui est supérieur soit trouvé. En l' occurence c'est 12, qui a l'index 2. Les éléments ayant un index supérieur ou égal à 2 sont décalés vers la droite ...
Et le nouveau est inséré à la position 2:
Cette explication pas à pas a présenté les trois cas de figure. Et ainsi de suite jusqu'au dernier élément du tableau de valeurs à trier.
Voici le code C qui transpose l' algorithme en syntaxe C. Les trois cas de figures sont traités avec un if ... else if ... else bien qu'un switch aurait pu convenir.
De plus, la fonction de tri prend le tableau à trier en paramètre, pour éviter de déclarer le tableau en variable globale. La fonction trie le tableau comme expliqué ci-dessus, puis substitue les valeurs du tableau à trier avec celles du tableau de valeurs triées:
Le tableau de valeurs à trier est parcouru une fois afin de remplir un tableau d'index de valeurs triées. Avant d'insérer un élément, trois cas de figure sont possibles:
- La valeur de l' index à insérer est supérieure ou égale au dernier élément du tableau de valeurs triées: ce sera le maximum. A ce titre, il est inséré en dernière position.
- La valeur est inférieure ou égale au premier élément du tableau de valeurs triées: ce sera le minimum. A cet effet, décaler les éléments suivants puis insérer le nouvel élément en première position.
- La valeur n'est ni le maximum, ni le maximum: parcourir le tableau de valeurs triées jusqu'à ce qu'une valeur rencontrée lui soit supérieure. Décaler vers la droite les éléments en partant de cette n-ième position puis insérer l' élément à la position n.
Partant de ce tableau d' entiers contenant les valeurs suivantes,
Tableau à trier:
Index | 0 | 1 | 2 | 3 | 4 | 5 |
Valeur | 8 | 12 | 6 | 10 | 14 | 2 |
Le but consiste à passer les index de ce tableau vers un tableau temporaire qui lui, contiendra les index triés. Au départ, ce tableau est vide.
Tableau temporaire:
0 | 1 | 2 | 3 | 4 | 5 | |
index | ||||||
Valeur |
L' algorithme commence par mettre le premier élément dans le tableau temporaire.
Tableau temporaire:
0 | 1 | 2 | 3 | 4 | 5 | |
index | 0 | |||||
Valeur | 8 |
A présent une boucle va traiter le tableau de valeurs à trier, en partant du second élément du tableau puisque le premier est déjà inséré.
La seconde valeur à insérer: index=1 valeur=12.
Comme 12 est supérieur à 8, 12 est inséré en tant que dernier élément du tableau de valeurs triées:
0 | 1 | 2 | 3 | 4 | 5 | |
index | 0 | 1 | ||||
Valeur | 8 | 12 |
Le troisième élément (index: 2 valeur: 6) a une valeur qui est inférieure à la valeur minimale contenue dans le tableau de valeurs triées: chaque élément est décalé vers la droite ...
0 | 1 | 2 | 3 | 4 | 5 | |
index | 0 | 1 | ||||
Valeur | 8 | 12 |
0 | 1 | 2 | 3 | 4 | 5 | |
index | 0 | 1 | ||||
Valeur | 8 | 12 |
... Puis l' élément est inséré:
0 | 1 | 2 | 3 | 4 | 5 | |
index | 2 | 0 | 1 | |||
Valeur | 6 | 8 | 12 |
Le quatrième élément à traiter (index: 3 valeur: 10) n'est ni le minimum, ni le maximum: il faut parcourir le tableau de valeurs triées jusqu'à ce qu'un élément qui lui est supérieur soit trouvé. En l' occurence c'est 12, qui a l'index 2. Les éléments ayant un index supérieur ou égal à 2 sont décalés vers la droite ...
0 | 1 | 2 | 3 | 4 | 5 | |
index | 2 | 0 | 1 | |||
Valeur | 6 | 8 | 12 |
Et le nouveau est inséré à la position 2:
0 | 1 | 2 | 3 | 4 | 5 | |
index | 2 | 0 | 3 | 1 | ||
Valeur | 6 | 8 | 10 | 12 |
Cette explication pas à pas a présenté les trois cas de figure. Et ainsi de suite jusqu'au dernier élément du tableau de valeurs à trier.
Voici le code C qui transpose l' algorithme en syntaxe C. Les trois cas de figures sont traités avec un if ... else if ... else bien qu'un switch aurait pu convenir.
De plus, la fonction de tri prend le tableau à trier en paramètre, pour éviter de déclarer le tableau en variable globale. La fonction trie le tableau comme expliqué ci-dessus, puis substitue les valeurs du tableau à trier avec celles du tableau de valeurs triées:
#include <stdlib.h> #include <stdio.h> #include <string.h> void sort_tableau_int(int *t, int COUNT) { int i, j, k, nb=0; // nombre d' éléments vus int tabi[COUNT]; int tabv[COUNT]; // Ajoute le premier tabi[nb++] = 0; for (i=1; i<COUNT; i++) { if (t[i] >= t[tabi[i-1]]) // Maximum: met en dernier (nb) tabi[nb++]= i; else if (t[i] <= t[tabi[0]] ) // Minimum: décale et met en premier { for (j=nb-1; j>-1; j--) tabi[j+1] = tabi[j]; tabi[0] = nb++; } else // ni min, ni max { for (j=1; j<nb; j++) //du 1er au nb ième { if (t[tabi[j]]>=t[i]) //s'il est supérieur, { for (k=nb-1; k>j-1; k--) //décale tabi[k+1]= tabi[k]; tabi[j] = nb++; //et insère break; } } } } // for // remplit tabv avec les index de tabi et les valeurs de t for(i=0; i<COUNT; i++) tabv[i] = t[tabi[i]]; // pour copier tableau tabv vers original for(i=0; i<COUNT; i++) t[i] = tabv[i]; } int main (void) { int n; int i[15]={102,89,69,9,56,59,85,44,66,12,36,458,965,254,666}; // trie le tableau ... sort_tableau_int(i, 15); // ... vérifie son contenu for (n=0; n<15; n++) printf ("%d ", i[n]); printf ("\n"); return 0; }
Mesure de ces cinq algorithmes/fonctions
Les codes ont été mesurés:
Sur un tableau de dix entiers, bxint.c donne le tempo. qsort() se comporte bien.
Mais sur un grand tableau de mille entiers, Counting Sort brille alors que bxint s'expulse.
C'est la conséquence du logiciel sommaire écrit pour parer à toutes les situations, comme la fonction qsort() de la LibC qui s'en sort moyennement dans toutes les situations, sachant que pour une situation donnée (petit ou grand tableau), certains font mieux.
Comme les programmeurs écrivent du logiciel dédié, qui colle au mieux à son environnement et à une situation donnée, ils préfèrent une fonction plutôt qu'une autre. Imaginez le cas où un programme doit trier dix mille petits tableaux. Employer une fonction générique ralentirait considrablement l'ensemble, dommage si c'est au moment où le robot doit prendre une décision alors qu'il descend un escalier!
- en interne: seule la fonction de tri a été mesurée
- sur le même tableau d'entiers
- selon la stricte même procédure
- toutes les procédures main() sont identiques
Source | Temps | Moyenne | +rapide | +lent | Sortie |
Sur un petit tableau de 10 entiers | |||||
pt-bxint.c | temps | 6.40 | 6 | 7 | sortie |
pt-counting-sort.c | temps | 27.95 | 25 | 31 | sortie |
pt-qsort.c | temps | 14.95 | 14 | 16 | sortie |
pt-qsort-Gareth.c | temps | 45.90 | 44 | 48 | sortie |
pt-quickSort-Finley.c | temps | 10.95 | 8 | 16 | sortie |
Sur un grand tableau de 1000 entiers | |||||
gd-bxint.c | temps | 6021.15 | 5917 | 7377 | sortie |
gd-counting-sort.c | temps | 97.30 | 94 | 102 | sortie |
gd-qsort.c | temps | 762.95 | 745 | 807 | sortie |
gd-qsort-Gareth.c | temps | 326.40 | 323 | 359 | sortie |
gd-quickSort-Finley.c | temps | 268.95 | 261 | 324 | sortie |
outils de test |
Sur un tableau de dix entiers, bxint.c donne le tempo. qsort() se comporte bien.
Mais sur un grand tableau de mille entiers, Counting Sort brille alors que bxint s'expulse.
C'est la conséquence du logiciel sommaire écrit pour parer à toutes les situations, comme la fonction qsort() de la LibC qui s'en sort moyennement dans toutes les situations, sachant que pour une situation donnée (petit ou grand tableau), certains font mieux.
Comme les programmeurs écrivent du logiciel dédié, qui colle au mieux à son environnement et à une situation donnée, ils préfèrent une fonction plutôt qu'une autre. Imaginez le cas où un programme doit trier dix mille petits tableaux. Employer une fonction générique ralentirait considrablement l'ensemble, dommage si c'est au moment où le robot doit prendre une décision alors qu'il descend un escalier!