From b6352e063b02f2e18a1255efb81cdc494f3a7fdc Mon Sep 17 00:00:00 2001 From: rabot Date: Wed, 21 Oct 2026 11:11:28 +0200 Subject: [PATCH] Optimiser F1 avec un tri fusion en O(n log n) --- COMPTE_RENDU.md | 30 +++++++++++++++++++--- Makefile | 8 +++++- banque.c | 46 ++++++++++++++++++++++++++------- tests/test_banque.c | 62 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 tests/test_banque.c diff --git a/COMPTE_RENDU.md b/COMPTE_RENDU.md index 8ccf7a0..933cf1e 100644 --- a/COMPTE_RENDU.md +++ b/COMPTE_RENDU.md @@ -1,11 +1,11 @@ # Compte rendu du TD de complexité algorithmique -Ce compte rendu traite les trois étapes du palier 1 du TD -(page 68 de `R5A04_Algorithmique_MM.pdf`) : mesurer la version initiale, -la profiler, puis calculer les complexités algorithmiques et cyclomatiques. +Ce compte rendu traite le palier 1 et l'étape 4 du TD +(page 68 de `R5A04_Algorithmique_MM.pdf`) : analyser la version initiale, +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 -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. | 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**. La fonction auxiliaire `longueur` contient elle aussi un `while` et a **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`. diff --git a/Makefile b/Makefile index 8b9363a..cc22a6e 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ PROGRAMME_PROFIL = build/profil/banque EMPREINTE_ATTENDUE = ff68d260a7a58441 NOM ?= $(shell whoami) -.PHONY: all run check bench profile clean help +.PHONY: all run check test bench profile clean help all: $(PROGRAMME) @@ -29,6 +29,11 @@ check: $(PROGRAMME) fi @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. bench: $(SOURCE) evaluer.sh mkdir -p build @@ -52,6 +57,7 @@ help: 'make Compiler banque.c avec -O2' \ 'make run Exécuter et produire build/rapport.txt' \ "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 profile Générer le profil gprof avec -O0 -g -pg' \ 'make clean Supprimer build, y compris les mesures et profils' diff --git a/banque.c b/banque.c index 02b0e39..fbb1ea6 100644 --- a/banque.c +++ b/banque.c @@ -18,6 +18,7 @@ #include #include +#include #define NB_TRANSACTIONS 30000 #define NB_CLIENTS 2000 @@ -71,18 +72,45 @@ Transaction *generer_transactions(int n) /* ------------------------------------------------------------------ */ /* 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) { - for (int i = 0; i < n - 1; i++) { - for (int j = 0; j < n - 1 - i; j++) { - if (t[j].montant > t[j + 1].montant || - (t[j].montant == t[j + 1].montant && t[j].id > t[j + 1].id)) { - Transaction tmp = t[j]; - t[j] = t[j + 1]; - t[j + 1] = tmp; - } - } + if (n < 2) + return; + Transaction *tampon = malloc((size_t)n * sizeof(*tampon)); + if (tampon == NULL) { + perror("Allocation du tampon de tri"); + exit(EXIT_FAILURE); } + trier_intervalle(t, tampon, 0, n); + free(tampon); } /* F2 : nombre de clients ayant au moins une transaction */ diff --git a/tests/test_banque.c b/tests/test_banque.c new file mode 100644 index 0000000..13c3dae --- /dev/null +++ b/tests/test_banque.c @@ -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 +#include + +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; +}