forked from menault/2026_TD3_DEV51_Qualite_Algo
Optimiser F1 avec un tri fusion en O(n log n)
This commit is contained in:
+26
-4
@@ -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`.
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user