Files
2026_TD3_DEV51_Qualite_Algo/banque.c
T
Ryad Mehiri cb7857cd43 F1 : trier les transactions avec qsort (O(n²) → O(n log n))
Le tri à bulles faisait n(n-1)/2 = 450 millions de comparaisons pour
30 000 transactions. qsort trie en O(n log n) avec une fonction de
comparaison (montant, puis id). Les id étant distincts, l'ordre est
total et le résultat identique malgré un tri non stable.

Temps (-O2) : 3,05 s → 1,17 s. Empreinte inchangée (ff68d260a7a58441).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:42:34 +02:00

334 lines
10 KiB
C

/*
* R5.A.04 - Qualité Algorithmique
* TD Complexité algorithmique : analyse des transactions d'une banque
*
* Le programme génère des transactions pseudo-aléatoires (toujours les
* mêmes, grâce à une graine fixe), puis calcule plusieurs indicateurs
* et produit un rapport dans rapport.txt.
*
* L'empreinte affichée à la fin doit rester IDENTIQUE après vos
* optimisations : elle prouve que le résultat n'a pas changé.
*
* Interdit : modifier les constantes, la génération des données,
* le format du rapport, ou écrire les résultats « en dur ».
*
* Compilation : gcc -O2 banque.c -o banque
* Profiling : gcc -O0 -g -pg banque.c -o banque && ./banque && gprof ./banque gmon.out
*/
#include <stdio.h>
#include <stdlib.h>
#define NB_TRANSACTIONS 30000
#define NB_CLIENTS 2000
#define NB_JOURS 1000
#define NB_REQUETES 30000
#define MONTANT_MONNAIE 400
#define GRAINE 20261020UL
typedef struct {
int id;
int client;
int jour;
long montant; /* en centimes, négatif pour un débit */
} Transaction;
/* ------------------------------------------------------------------ */
/* Données (ne pas modifier) */
/* ------------------------------------------------------------------ */
static unsigned long etat_alea = GRAINE;
unsigned long alea(void)
{
etat_alea = etat_alea * 6364136223846793005UL + 1442695040888963407UL;
return etat_alea >> 33;
}
Transaction *generer_transactions(int n)
{
Transaction *t = malloc(n * sizeof(Transaction));
for (int i = 0; i < n; i++) {
t[i].id = i;
t[i].client = alea() % NB_CLIENTS;
t[i].jour = alea() % NB_JOURS;
if (i > 0 && alea() % 20 == 0)
t[i].montant = -t[alea() % i].montant; /* opération inverse */
else
t[i].montant = (long)(alea() % 100001) - 50000;
}
for (int i = n - 1; i > 0; i--) { /* mélange des id */
int j = alea() % (i + 1);
int tmp = t[i].id;
t[i].id = t[j].id;
t[j].id = tmp;
}
return t;
}
/* ------------------------------------------------------------------ */
/* Fonctions à analyser et à optimiser */
/* ------------------------------------------------------------------ */
/* F1 : trie les transactions par montant croissant, puis par id.
* Les id sont tous distincts : l'ordre est total, donc qsort (non stable)
* donne le même résultat que le tri à bulles d'origine. */
int comparer_transactions(const void *pa, const void *pb)
{
const Transaction *a = pa, *b = pb;
if (a->montant != b->montant)
return (a->montant > b->montant) - (a->montant < b->montant);
return (a->id > b->id) - (a->id < b->id);
}
void trier_par_montant(Transaction *t, int n)
{
qsort(t, n, sizeof(Transaction), comparer_transactions);
}
/* F2 : nombre de clients ayant au moins une transaction */
int nb_clients_distincts(const Transaction *t, int n)
{
int nb = 0;
for (int i = 0; i < n; i++) {
int deja_vu = 0;
for (int j = 0; j < i; j++) {
if (t[j].client == t[i].client) {
deja_vu = 1;
break;
}
}
if (!deja_vu)
nb++;
}
return nb;
}
/* F3 : solde total de chaque client */
void total_par_client(const Transaction *t, int n, long *totaux)
{
for (int c = 0; c < NB_CLIENTS; c++) {
totaux[c] = 0;
for (int i = 0; i < n; i++)
if (t[i].client == c)
totaux[c] += t[i].montant;
}
}
/* F4 : solde total de chaque jour */
void total_par_jour(const Transaction *t, int n, long *totaux)
{
for (int j = 0; j < NB_JOURS; j++) {
totaux[j] = 0;
for (int i = 0; i < n; i++)
if (t[i].jour == j)
totaux[j] += t[i].montant;
}
}
/* F5 : période de jours consécutifs dont la somme des soldes est maximale */
long meilleure_periode(const long *totaux, int nb_jours, int *debut, int *fin)
{
long meilleur = totaux[0];
*debut = 0;
*fin = 0;
for (int i = 0; i < nb_jours; i++) {
for (int j = i; j < nb_jours; j++) {
long somme = 0;
for (int k = i; k <= j; k++)
somme += totaux[k];
if (somme > meilleur) {
meilleur = somme;
*debut = i;
*fin = j;
}
}
}
return meilleur;
}
/* F6 : retrouve une transaction à partir de son id */
const Transaction *rechercher_par_id(const Transaction *t, int n, int id)
{
for (int i = 0; i < n; i++)
if (t[i].id == id)
return &t[i];
return NULL;
}
/* F7 : nombre de paires de transactions de montants opposés (a + b = 0) */
long nb_paires_opposees(const Transaction *t, int n)
{
long nb = 0;
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n; j++)
if (t[i].montant + t[j].montant == 0)
nb++;
return nb;
}
/* F8 : nombre de façons de rendre une somme avec les pièces disponibles */
static const int PIECES[] = {1, 2, 5, 10, 20, 50, 100, 200};
#define NB_PIECES 8
long nb_facons_rendre(int somme, int nb_pieces)
{
if (somme == 0)
return 1;
if (somme < 0 || nb_pieces == 0)
return 0;
return nb_facons_rendre(somme - PIECES[nb_pieces - 1], nb_pieces) +
nb_facons_rendre(somme, nb_pieces - 1);
}
/* F9 : catégorie d'une transaction (pour les statistiques) */
int categorie(const Transaction *t)
{
if (t->montant < 0) {
if (t->montant < -20000) {
if (t->jour % 7 == 5 || t->jour % 7 == 6)
return 1;
else
return 2;
} else if (t->montant < -5000) {
return 3;
} else {
if (t->client % 10 == 0)
return 4;
return 5;
}
} else if (t->montant == 0) {
return 0;
} else {
if (t->montant > 30000) {
if (t->client % 10 == 0 && t->jour % 7 < 5)
return 6;
return 7;
} else if (t->montant > 10000) {
return 8;
} else if (t->montant > 1000) {
if (t->jour % 30 == 0)
return 9;
return 10;
}
return 11;
}
}
/* F10 : ajoute une ligne à la fin du rapport */
int longueur(const char *s)
{
int n = 0;
while (s[n] != '\0')
n++;
return n;
}
void ajouter_ligne(char *rapport, const char *ligne)
{
int fin = longueur(rapport);
int i = 0;
while (ligne[i] != '\0') {
rapport[fin + i] = ligne[i];
i++;
}
rapport[fin + i] = '\0';
}
/* ------------------------------------------------------------------ */
/* Programme principal (à adapter si vous changez les signatures) */
/* ------------------------------------------------------------------ */
unsigned long empreinte(const char *s)
{
unsigned long h = 1469598103934665603UL;
for (int i = 0; s[i] != '\0'; i++) {
h ^= (unsigned char)s[i];
h *= 1099511628211UL;
}
return h;
}
int main(void)
{
Transaction *t = generer_transactions(NB_TRANSACTIONS);
char *rapport = malloc(1 << 20);
char ligne[128];
rapport[0] = '\0';
/* recherches par id, dans l'ordre d'origine */
long somme_requetes = 0;
unsigned long etat = 12345;
for (int q = 0; q < NB_REQUETES; q++) {
etat = etat * 1103515245UL + 12345UL;
const Transaction *r = rechercher_par_id(t, NB_TRANSACTIONS, (int)((etat >> 8) % NB_TRANSACTIONS));
if (r != NULL)
somme_requetes += r->montant;
}
int nb_clients = nb_clients_distincts(t, NB_TRANSACTIONS);
static long totaux_clients[NB_CLIENTS];
total_par_client(t, NB_TRANSACTIONS, totaux_clients);
static long totaux_jours[NB_JOURS];
total_par_jour(t, NB_TRANSACTIONS, totaux_jours);
int debut, fin;
long meilleure = meilleure_periode(totaux_jours, NB_JOURS, &debut, &fin);
long nb_paires = nb_paires_opposees(t, NB_TRANSACTIONS);
int categories[12] = {0};
for (int i = 0; i < NB_TRANSACTIONS; i++)
categories[categorie(&t[i])]++;
trier_par_montant(t, NB_TRANSACTIONS);
long nb_facons = nb_facons_rendre(MONTANT_MONNAIE, NB_PIECES);
/* rapport */
snprintf(ligne, sizeof(ligne), "clients distincts;%d\n", nb_clients);
ajouter_ligne(rapport, ligne);
snprintf(ligne, sizeof(ligne), "somme des requetes;%ld\n", somme_requetes);
ajouter_ligne(rapport, ligne);
snprintf(ligne, sizeof(ligne), "meilleure periode;%d;%d;%ld\n", debut, fin, meilleure);
ajouter_ligne(rapport, ligne);
snprintf(ligne, sizeof(ligne), "paires opposees;%ld\n", nb_paires);
ajouter_ligne(rapport, ligne);
snprintf(ligne, sizeof(ligne), "facons de rendre %d;%ld\n", MONTANT_MONNAIE, nb_facons);
ajouter_ligne(rapport, ligne);
for (int c = 0; c < 12; c++) {
snprintf(ligne, sizeof(ligne), "categorie;%d;%d\n", c, categories[c]);
ajouter_ligne(rapport, ligne);
}
for (int c = 0; c < NB_CLIENTS; c++) {
snprintf(ligne, sizeof(ligne), "client;%d;%ld\n", c, totaux_clients[c]);
ajouter_ligne(rapport, ligne);
}
for (int j = 0; j < NB_JOURS; j++) {
snprintf(ligne, sizeof(ligne), "jour;%d;%ld\n", j, totaux_jours[j]);
ajouter_ligne(rapport, ligne);
}
for (int i = 0; i < NB_TRANSACTIONS; i += 10) {
snprintf(ligne, sizeof(ligne), "rang;%d;%d;%ld\n", i, t[i].id, t[i].montant);
ajouter_ligne(rapport, ligne);
}
FILE *f = fopen("rapport.txt", "w");
if (f != NULL) {
fputs(rapport, f);
fclose(f);
}
printf("Clients distincts : %d\n", nb_clients);
printf("Somme des requêtes : %ld\n", somme_requetes);
printf("Meilleure période : jours %d à %d (%ld)\n", debut, fin, meilleure);
printf("Paires opposées : %ld\n", nb_paires);
printf("Façons de rendre %d : %ld\n", MONTANT_MONNAIE, nb_facons);
printf("Empreinte : %016lx\n", empreinte(rapport));
free(rapport);
free(t);
return 0;
}