Optimiser F1 avec un tri fusion en O(n log n)

This commit is contained in:
rabot
2026-10-21 11:11:28 +02:00
parent 3cdb37276c
commit b6352e063b
4 changed files with 132 additions and 14 deletions
+26 -4
View File
@@ -1,11 +1,11 @@
# Compte rendu du TD de complexité algorithmique # Compte rendu du TD de complexité algorithmique
Ce compte rendu traite les trois étapes du palier 1 du TD Ce compte rendu traite le palier 1 et l'étape 4 du TD
(page 68 de `R5A04_Algorithmique_MM.pdf`) : mesurer la version initiale, (page 68 de `R5A04_Algorithmique_MM.pdf`) : analyser la version initiale,
la profiler, puis calculer les complexités algorithmiques et cyclomatiques. puis atteindre les objectifs avec un commit par optimisation.
Les mesures ont été réalisées le 21 octobre 2026 sur Linux x86_64, avec Les mesures ont été réalisées le 21 octobre 2026 sur Linux x86_64, avec
GCC 16.2.1 et GNU gprof 2.47. Le fichier `banque.c` est dans sa version initiale. GCC 16.2.1 et GNU gprof 2.47. Les sections 1 à 3 décrivent la version initiale.
Les données sont reproductibles grâce à la graine fixe du générateur. Les données sont reproductibles grâce à la graine fixe du générateur.
| Paramètre | Valeur | | Paramètre | Valeur |
@@ -232,3 +232,25 @@ Les décisions d'une fonction appelée se comptent dans cette fonction.
- **F10** : 1 `while` = 1 décision, donc **M = 2**. - **F10** : 1 `while` = 1 décision, donc **M = 2**.
La fonction auxiliaire `longueur` contient elle aussi un `while` et a La fonction auxiliaire `longueur` contient elle aussi un `while` et a
**M = 2**, compté séparément. **M = 2**, compté séparément.
## 4. Atteindre les objectifs
Chaque optimisation est vérifiée avant son commit : compilation avec les
avertissements du Makefile, tests de cas limites via `make test`, exécution
en `-O2`, empreinte **`ff68d260a7a58441`** et comparaison du rapport complet
avec celui de la version initiale. Les constantes, `alea`,
`generer_transactions` et le format du rapport sont conservés.
### F1 — Tri fusion
Le tri à bulles est remplacé par un tri fusion : chaque moitié est triée,
puis les deux moitiés sont fusionnées en un passage. La comparaison porte
sur le montant, puis sur l'identifiant, sans soustraction susceptible de
déborder. La récurrence `T(n) = 2T(n/2) + O(n)` donne **`O(n log n)` au pire
cas**, avec `O(n)` de mémoire auxiliaire et `O(log n)` de pile.
La complexité cyclomatique de F1 passe de **6 à 3** ; les auxiliaires
`comparer_transactions` et `trier_intervalle` ont respectivement **M = 2**
et **M = 6**. Les tests comparent le résultat à un tri de référence pour
les tailles 0 à 128, des doublons, des entrées déjà triées ou inversées,
et les bornes des types `int` et `long`.
+7 -1
View File
@@ -8,7 +8,7 @@ PROGRAMME_PROFIL = build/profil/banque
EMPREINTE_ATTENDUE = ff68d260a7a58441 EMPREINTE_ATTENDUE = ff68d260a7a58441
NOM ?= $(shell whoami) NOM ?= $(shell whoami)
.PHONY: all run check bench profile clean help .PHONY: all run check test bench profile clean help
all: $(PROGRAMME) all: $(PROGRAMME)
@@ -29,6 +29,11 @@ check: $(PROGRAMME)
fi fi
@printf 'Empreinte correcte.\n' @printf 'Empreinte correcte.\n'
test: $(SOURCE) tests/test_banque.c
@programme=$$(mktemp); \
trap 'rm -f "$$programme"' EXIT HUP INT TERM; \
$(CC) $(CPPFLAGS) $(CFLAGS) tests/test_banque.c -o "$$programme" $(LDFLAGS) $(LDLIBS) && "$$programme"
# Le script impose gcc -O2, vérifie l'empreinte et mesure 5 exécutions. # Le script impose gcc -O2, vérifie l'empreinte et mesure 5 exécutions.
bench: $(SOURCE) evaluer.sh bench: $(SOURCE) evaluer.sh
mkdir -p build mkdir -p build
@@ -52,6 +57,7 @@ help:
'make Compiler banque.c avec -O2' \ 'make Compiler banque.c avec -O2' \
'make run Exécuter et produire build/rapport.txt' \ 'make run Exécuter et produire build/rapport.txt' \
"make check Vérifier l'empreinte attendue" \ "make check Vérifier l'empreinte attendue" \
'make test Vérifier les cas limites des fonctions optimisées' \
'make bench NOM=nom Évaluer les performances (médiane de 5 exécutions)' \ 'make bench NOM=nom Évaluer les performances (médiane de 5 exécutions)' \
'make profile Générer le profil gprof avec -O0 -g -pg' \ 'make profile Générer le profil gprof avec -O0 -g -pg' \
'make clean Supprimer build, y compris les mesures et profils' 'make clean Supprimer build, y compris les mesures et profils'
+37 -9
View File
@@ -18,6 +18,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#define NB_TRANSACTIONS 30000 #define NB_TRANSACTIONS 30000
#define NB_CLIENTS 2000 #define NB_CLIENTS 2000
@@ -71,18 +72,45 @@ Transaction *generer_transactions(int n)
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* F1 : trie les transactions par montant croissant, puis par id */ /* F1 : trie les transactions par montant croissant, puis par id */
static int comparer_transactions(const Transaction *a, const Transaction *b)
{
if (a->montant != b->montant)
return (a->montant > b->montant) - (a->montant < b->montant);
return (a->id > b->id) - (a->id < b->id);
}
static void trier_intervalle(Transaction *t, Transaction *tampon, int debut, int fin)
{
if (fin - debut < 2)
return;
int milieu = debut + (fin - debut) / 2;
trier_intervalle(t, tampon, debut, milieu);
trier_intervalle(t, tampon, milieu, fin);
int gauche = debut;
int droite = milieu;
for (int i = debut; i < fin; i++) {
if (gauche < milieu &&
(droite == fin || comparer_transactions(&t[gauche], &t[droite]) <= 0))
tampon[i] = t[gauche++];
else
tampon[i] = t[droite++];
}
memcpy(t + debut, tampon + debut, (size_t)(fin - debut) * sizeof(*t));
}
void trier_par_montant(Transaction *t, int n) void trier_par_montant(Transaction *t, int n)
{ {
for (int i = 0; i < n - 1; i++) { if (n < 2)
for (int j = 0; j < n - 1 - i; j++) { return;
if (t[j].montant > t[j + 1].montant || Transaction *tampon = malloc((size_t)n * sizeof(*tampon));
(t[j].montant == t[j + 1].montant && t[j].id > t[j + 1].id)) { if (tampon == NULL) {
Transaction tmp = t[j]; perror("Allocation du tampon de tri");
t[j] = t[j + 1]; exit(EXIT_FAILURE);
t[j + 1] = tmp;
}
}
} }
trier_intervalle(t, tampon, 0, n);
free(tampon);
} }
/* F2 : nombre de clients ayant au moins une transaction */ /* F2 : nombre de clients ayant au moins une transaction */
+62
View File
@@ -0,0 +1,62 @@
/* Cas limites et comparaisons avec des algorithmes de référence simples. */
#define main banque_main
#include "../banque.c"
#undef main
#include <assert.h>
#include <limits.h>
static int comparer_reference(const void *a, const void *b)
{
const Transaction *ta = a;
const Transaction *tb = b;
if (ta->montant < tb->montant)
return -1;
if (ta->montant > tb->montant)
return 1;
if (ta->id < tb->id)
return -1;
return ta->id > tb->id;
}
static void verifier_tri(Transaction *t, int n)
{
Transaction attendu[128];
assert(n <= 128);
memcpy(attendu, t, (size_t)n * sizeof(*t));
qsort(attendu, (size_t)n, sizeof(*attendu), comparer_reference);
trier_par_montant(t, n);
for (int i = 0; i < n; i++) {
assert(t[i].id == attendu[i].id);
assert(t[i].client == attendu[i].client);
assert(t[i].jour == attendu[i].jour);
assert(t[i].montant == attendu[i].montant);
}
}
static void tester_tri(void)
{
trier_par_montant(NULL, 0);
Transaction limites[] = {
{INT_MAX, 1, 2, LONG_MAX}, {3, 4, 5, 0},
{INT_MIN, 6, 7, LONG_MIN}, {1, 8, 9, 0}, {2, 10, 11, LONG_MAX}
};
verifier_tri(limites, 5);
verifier_tri(limites, 5); /* déjà trié */
Transaction t[128];
for (int n = 1; n <= 128; n++) {
for (int i = 0; i < n; i++)
t[i] = (Transaction){n - i, i, i + 1, (i * 37) % 11 - 5};
verifier_tri(t, n);
for (int i = 0; i < n; i++)
t[i] = (Transaction){i, i, i + 1, n - i};
verifier_tri(t, n); /* ordre inverse */
}
}
int main(void)
{
tester_tri();
puts("Tests banque : OK");
return 0;
}