2026-10-21 11:05:36 +02:00
|
|
|
|
# Compte rendu du TD de complexité algorithmique
|
|
|
|
|
|
|
2026-10-21 11:11:28 +02:00
|
|
|
|
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.
|
2026-10-21 11:05:36 +02:00
|
|
|
|
|
|
|
|
|
|
Les mesures ont été réalisées le 21 octobre 2026 sur Linux x86_64, avec
|
2026-10-21 11:11:28 +02:00
|
|
|
|
GCC 16.2.1 et GNU gprof 2.47. Les sections 1 à 3 décrivent la version initiale.
|
2026-10-21 11:05:36 +02:00
|
|
|
|
Les données sont reproductibles grâce à la graine fixe du générateur.
|
|
|
|
|
|
|
|
|
|
|
|
| Paramètre | Valeur |
|
|
|
|
|
|
|---|---:|
|
|
|
|
|
|
| Transactions, `n` | 30 000 |
|
|
|
|
|
|
| Clients, `C` | 2 000 |
|
|
|
|
|
|
| Jours, `J` | 1 000 |
|
|
|
|
|
|
| Requêtes par identifiant, `Q` | 30 000 |
|
|
|
|
|
|
| Somme à rendre, `S` | 400 centimes |
|
|
|
|
|
|
| Types de pièces, `k` | 8 |
|
|
|
|
|
|
|
|
|
|
|
|
## 1. Mesurer la version initiale
|
|
|
|
|
|
|
|
|
|
|
|
Le Makefile compile le programme avec
|
|
|
|
|
|
`gcc -std=c11 -Wall -Wextra -Wpedantic -O2 banque.c -o build/banque`.
|
|
|
|
|
|
La compilation s'est terminée sans avertissement. Les commandes utilisées
|
|
|
|
|
|
pour compiler et enregistrer une mesure sont les suivantes, depuis la racine :
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
make -B all
|
|
|
|
|
|
(cd build && { time -p ./banque > sortie.txt; } 2> temps_avant.txt)
|
|
|
|
|
|
cat build/sortie.txt build/temps_avant.txt
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Résultat de l'exécution mesurée :
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
Clients distincts : 2000
|
|
|
|
|
|
Somme des requêtes : -2624578
|
|
|
|
|
|
Meilleure période : jours 650 à 986 (3548830)
|
|
|
|
|
|
Paires opposées : 5832
|
|
|
|
|
|
Façons de rendre 400 : 1960497
|
|
|
|
|
|
Empreinte : ff68d260a7a58441
|
|
|
|
|
|
real 2.43
|
|
|
|
|
|
user 2.39
|
|
|
|
|
|
sys 0.00
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Le temps réel est de **2,43 s**. Le temps CPU passé dans le programme est de
|
|
|
|
|
|
2,39 s ; le temps système est arrondi à 0,00 s. Cette mesure porte sur une
|
|
|
|
|
|
exécution et peut varier selon la charge de la machine.
|
|
|
|
|
|
|
|
|
|
|
|
L'empreinte obtenue est bien **`ff68d260a7a58441`**, celle attendue dans le TD.
|
|
|
|
|
|
Le rapport produit contient 6 017 lignes, soit 120 348 octets. Les résultats
|
|
|
|
|
|
sont conservés dans [sortie.txt](build/sortie.txt) et
|
|
|
|
|
|
[temps_avant.txt](build/temps_avant.txt).
|
|
|
|
|
|
|
|
|
|
|
|
## 2. Profiler et comparer aux complexités
|
|
|
|
|
|
|
|
|
|
|
|
Le profil a été généré avec les commandes suivantes :
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
make profile
|
|
|
|
|
|
cp build/profil/profil.txt build/profil/profil_avant.txt
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
La cible `profile` utilise une compilation en `-O0 -g -pg`, exécute le programme,
|
|
|
|
|
|
puis lance `gprof -b ./banque gmon.out` dans `build/profil`. Le profil initial
|
|
|
|
|
|
est conservé dans [profil_avant.txt](build/profil/profil_avant.txt).
|
|
|
|
|
|
|
|
|
|
|
|
Cette exécution produit également l'empreinte attendue. Les fichiers
|
|
|
|
|
|
`build/rapport.txt` et `build/profil/rapport.txt` ont été comparés : ils sont
|
|
|
|
|
|
identiques octet par octet.
|
|
|
|
|
|
|
|
|
|
|
|
### Résultats de gprof
|
|
|
|
|
|
|
|
|
|
|
|
Le tableau reprend les temps propres du profil plat : ils correspondent au
|
|
|
|
|
|
temps passé dans chaque fonction, hors fonctions appelées. Les pourcentages
|
|
|
|
|
|
portent sur environ **6,44 s de temps CPU échantillonné**.
|
|
|
|
|
|
|
|
|
|
|
|
| Fonction | Appels | Temps propre (s) | Part du temps |
|
|
|
|
|
|
|---|---:|---:|---:|
|
|
|
|
|
|
| F1 — `trier_par_montant` | 1 | 3,13 | 48,60 % |
|
|
|
|
|
|
| F6 — `rechercher_par_id` | 30 000 | 0,85 | 13,20 % |
|
|
|
|
|
|
| F7 — `nb_paires_opposees` | 1 | 0,76 | 11,80 % |
|
|
|
|
|
|
| F8 — `nb_facons_rendre` | 1 appel initial | 0,66 | 10,17 % |
|
|
|
|
|
|
| `longueur`, appelée par F10 | 6 017 | 0,41 | 6,37 % |
|
|
|
|
|
|
| F5 — `meilleure_periode` | 1 | 0,32 | 4,97 % |
|
|
|
|
|
|
| F2 — `nb_clients_distincts` | 1 | 0,11 | 1,71 % |
|
|
|
|
|
|
| F3 — `total_par_client` | 1 | 0,11 | 1,71 % |
|
|
|
|
|
|
| F4 — `total_par_jour` | 1 | 0,05 | 0,78 % |
|
|
|
|
|
|
| F9 — `categorie` | 30 000 | 0,03 | 0,39 % |
|
|
|
|
|
|
| F10 — `ajouter_ligne` | 6 017 | 0,00 | 0,00 % |
|
|
|
|
|
|
|
|
|
|
|
|
Le graphe d'appels précise que F8 effectue **266 723 738 appels récursifs**,
|
|
|
|
|
|
en plus de l'appel initial. Le compteur du profil plat ne montre que cet
|
|
|
|
|
|
appel initial. Les écarts entre les pourcentages et les temps affichés
|
|
|
|
|
|
s'expliquent par les arrondis ; quelques échantillons sont aussi attribués
|
|
|
|
|
|
à `_init`.
|
|
|
|
|
|
|
|
|
|
|
|
Le temps cumulé de F10, en incluant `longueur`, est d'environ **0,41 s**.
|
|
|
|
|
|
Son temps propre affiché à 0,00 s ne signifie donc pas que la construction
|
|
|
|
|
|
du rapport est gratuite. Plus généralement, une valeur nulle dans ce profil
|
|
|
|
|
|
ne prouve pas l'absence de coût : la période d'échantillonnage est de 0,01 s.
|
|
|
|
|
|
|
|
|
|
|
|
Ces temps ne se comparent pas directement aux 2,43 s de la première étape :
|
|
|
|
|
|
le profil utilise `-O0` et l'instrumentation `-pg`, tandis que la mesure
|
|
|
|
|
|
initiale utilise `-O2`. Le profil décrit le comportement de cette version
|
|
|
|
|
|
instrumentée, pour ces données et sur cette machine.
|
|
|
|
|
|
|
|
|
|
|
|
### Les fonctions les plus coûteuses ont-elles les pires complexités ?
|
|
|
|
|
|
|
|
|
|
|
|
**Non, pas systématiquement.** La complexité décrit la croissance du nombre
|
|
|
|
|
|
d'opérations lorsque la taille des entrées augmente. Le profil mesure le coût
|
|
|
|
|
|
d'une exécution précise. Ce coût dépend aussi des tailles effectivement
|
|
|
|
|
|
utilisées, du nombre d'appels, des données et du travail effectué à chaque
|
|
|
|
|
|
itération.
|
|
|
|
|
|
|
|
|
|
|
|
- **F1 : le tri quadratique domine ici.** Le tri à bulles effectue
|
|
|
|
|
|
`n(n - 1) / 2`, soit **449 985 000 comparaisons de paires voisines**, avec
|
|
|
|
|
|
des échanges de structures lorsque les transactions sont mal ordonnées.
|
|
|
|
|
|
Sa complexité est `O(n²)` et il représente 48,60 % du temps échantillonné.
|
|
|
|
|
|
|
|
|
|
|
|
- **F5 : une complexité cubique ne suffit pas à en faire la fonction la plus
|
|
|
|
|
|
lente.** Ses trois boucles donnent une complexité `O(J³)`, mais elles portent
|
|
|
|
|
|
sur **1 000 jours**, alors que F1 traite 30 000 transactions. F5 effectue
|
|
|
|
|
|
`J(J + 1)(J + 2) / 6`, soit **167 167 000 additions**, et prend environ
|
|
|
|
|
|
0,32 s, contre 3,13 s pour F1. Les variables et les tailles diffèrent :
|
|
|
|
|
|
comparer uniquement les exposants serait trompeur.
|
|
|
|
|
|
|
|
|
|
|
|
- **F6 : le nombre d'appels compte.** Une recherche par identifiant est en
|
|
|
|
|
|
`O(n)` au pire cas, mais `main` l'effectue **30 000 fois**. Le coût de
|
|
|
|
|
|
l'ensemble des recherches est donc `O(Q × n)`, soit `O(n²)` lorsque `Q = n`.
|
|
|
|
|
|
Avec 0,85 s au total, F6 arrive devant F5 malgré sa complexité linéaire
|
|
|
|
|
|
par appel.
|
|
|
|
|
|
|
|
|
|
|
|
- **F7 : une même classe de complexité ne donne pas un même temps.** F7
|
|
|
|
|
|
examine elle aussi **449 985 000 paires**, en `O(n²)`, mais se limite à
|
|
|
|
|
|
additionner deux montants, comparer le résultat à zéro et éventuellement
|
|
|
|
|
|
incrémenter un compteur. Elle prend 0,76 s, environ quatre fois moins que
|
|
|
|
|
|
F1, dont les itérations peuvent déplacer des transactions entières.
|
|
|
|
|
|
|
|
|
|
|
|
- **F8 : les sous-problèmes recalculés multiplient les appels.** Le rendu de
|
|
|
|
|
|
monnaie utilise une récursion sans mémorisation et dépasse 266 millions
|
|
|
|
|
|
d'appels récursifs. Son coût dépend de `S` et de `k`, ici 400 et 8. Malgré
|
|
|
|
|
|
cet arbre d'appels volumineux, son temps de 0,66 s reste inférieur à celui
|
|
|
|
|
|
du tri ; le nombre d'appels ne suffit pas non plus à classer les temps.
|
|
|
|
|
|
|
|
|
|
|
|
- **F10 : il faut tenir compte des fonctions appelées et des appels répétés.**
|
|
|
|
|
|
Chaque ajout parcourt tout le rapport existant avec `longueur`, puis copie
|
|
|
|
|
|
la nouvelle ligne. Pour un préfixe de longueur `p` et une ligne de longueur
|
|
|
|
|
|
`l`, un appel coûte `O(p + l)`. Sur `m` ajouts de lignes de taille bornée,
|
|
|
|
|
|
les parcours successifs coûtent `O(m²)`, soit `O(L²)` dans ce cas, où `L`
|
|
|
|
|
|
est la longueur finale du rapport. Pour le rapport obtenu, les recherches
|
|
|
|
|
|
de fin de chaîne relisent **340 702 496 caractères** de préfixes, ce qui
|
|
|
|
|
|
explique les 0,41 s attribuées à `longueur`.
|
|
|
|
|
|
|
|
|
|
|
|
Pour cette exécution instrumentée, F1, F6 et F7 représentent ensemble
|
|
|
|
|
|
**73,60 %** du temps échantillonné : ce sont les premières pistes
|
|
|
|
|
|
d'optimisation indiquées par le profil.
|
|
|
|
|
|
|
|
|
|
|
|
## 3. Calculer les complexités de F1 à F10
|
|
|
|
|
|
|
|
|
|
|
|
Le tableau reprend l'analyse initiale de `notes.md`, avec la complexité
|
|
|
|
|
|
cyclomatique de chaque fonction. Les complexités temporelles sont données
|
|
|
|
|
|
au pire cas, en faisant varier les tailles des données, même si leurs valeurs
|
|
|
|
|
|
sont fixées par des constantes dans le programme. Les objectifs sont ceux
|
|
|
|
|
|
du tableau de la page 67 du TD ; ils restent à atteindre.
|
|
|
|
|
|
|
|
|
|
|
|
| ID | Fonction | Calcule | Actuelle | M | Objectif |
|
|
|
|
|
|
|:---:|---|---|---|:---:|---|
|
|
|
|
|
|
| F1 | `trier_par_montant` | tri des transactions | `O(n²)` | 6 | `O(n log n)` |
|
|
|
|
|
|
| F2 | `nb_clients_distincts` | clients différents | `O(n²)` | 5 | `O(n + C)` |
|
|
|
|
|
|
| F3 | `total_par_client` | solde par client | `O(n × C)` | 4 | `O(n + C)` |
|
|
|
|
|
|
| F4 | `total_par_jour` | solde par jour | `O(n × J)` | 4 | `O(n + J)` |
|
|
|
|
|
|
| F5 | `meilleure_periode` | période la plus rentable | `O(J³)` | 5 | `O(J)` |
|
|
|
|
|
|
| F6 | `rechercher_par_id` | recherche par identifiant | `O(n)` par requête | 3 | `O(1)` par requête |
|
|
|
|
|
|
| F7 | `nb_paires_opposees` | paires `a + b = 0` | `O(n²)` | 4 | `O(n log n)` |
|
|
|
|
|
|
| F8 | `nb_facons_rendre` | rendu de monnaie | `O(2^(S + k))`, borne générale | 4 | `O(S × k)` |
|
|
|
|
|
|
| F9 | `categorie` | catégorie d'une transaction | `O(1)` | 14 | `M ≤ 10` |
|
|
|
|
|
|
| F10 | `ajouter_ligne` | construction du rapport | `O(p + l)` par ajout ; `O(L²)` au total pour des lignes de taille bornée | 2 | `O(L)` au total |
|
|
|
|
|
|
|
|
|
|
|
|
`n` : transactions ; `C` : clients ; `J` : jours ; `Q` : requêtes ;
|
|
|
|
|
|
`S` : somme à rendre ; `k` : types de pièces ; `L` : longueur finale du
|
|
|
|
|
|
rapport ; `p` : longueur du rapport avant un ajout ; `l` : longueur de la
|
|
|
|
|
|
ligne ajoutée. **F5, F7 et F8** sont signalées comme les plus difficiles
|
|
|
|
|
|
dans le TD.
|
|
|
|
|
|
|
|
|
|
|
|
### Justification des complexités temporelles
|
|
|
|
|
|
|
|
|
|
|
|
- **F1 et F7** : les deux boucles parcourent `n(n - 1) / 2` paires.
|
|
|
|
|
|
Le travail par paire est constant, d'où `O(n²)`.
|
|
|
|
|
|
- **F2** : pour chaque transaction d'indice `i`, la recherche d'un client
|
|
|
|
|
|
déjà rencontré peut parcourir les `i` transactions précédentes.
|
|
|
|
|
|
La somme `0 + 1 + … + (n - 1)` donne la borne `O(n²)`, atteinte si
|
|
|
|
|
|
les clients sont tous différents (`C ≥ n`).
|
|
|
|
|
|
- **F3 et F4** : chaque client, puis chaque jour, provoque un parcours
|
|
|
|
|
|
complet des `n` transactions : respectivement `O(C × n)` et `O(J × n)`.
|
|
|
|
|
|
- **F5** : chaque intervalle de jours est essayé, et sa somme est recalculée
|
|
|
|
|
|
par une troisième boucle. Le nombre d'additions est
|
|
|
|
|
|
`J(J + 1)(J + 2) / 6`, d'où `O(J³)`.
|
|
|
|
|
|
- **F6** : une recherche peut lire les `n` transactions, soit `O(n)`.
|
|
|
|
|
|
Les `Q` recherches de `main` coûtent donc `O(Q × n)` au pire cas.
|
|
|
|
|
|
- **F8** : sans mémorisation, le coût suit la récurrence
|
|
|
|
|
|
`T(S, k) = T(S - pièce[k - 1], k) + T(S, k - 1) + O(1)`.
|
|
|
|
|
|
Chaque appel non terminal engendre deux appels ; chaque branche diminue
|
|
|
|
|
|
`S` ou `k` d'au moins 1. La profondeur est donc au plus `S + k`, ce qui
|
|
|
|
|
|
donne la borne générale `O(2^(S + k))`. Cette borne est large : pour
|
|
|
|
|
|
`k` fixé, on peut affiner en `O(S^k)`. La notation `O(Sⁿ)` des notes
|
|
|
|
|
|
initiales ne convient pas, car `n` désigne les transactions.
|
|
|
|
|
|
- **F9** : le nombre de tests est borné, indépendamment de `n`, d'où `O(1)`.
|
|
|
|
|
|
L'objectif porte ici sur la complexité cyclomatique, pas sur le temps.
|
|
|
|
|
|
- **F10** : `longueur` lit les `p` caractères du rapport, puis la fonction
|
|
|
|
|
|
copie les `l` caractères de la ligne : `O(p + l)` par ajout.
|
|
|
|
|
|
Pour `m` lignes non vides de taille bornée, les préfixes relus ont des
|
|
|
|
|
|
longueurs proportionnelles à `0, 1, …, m - 1` : le coût total est
|
|
|
|
|
|
`O(m²)`, soit `O(L²)` dans ce cas.
|
|
|
|
|
|
|
|
|
|
|
|
### Calcul manuel de la complexité cyclomatique
|
|
|
|
|
|
|
|
|
|
|
|
La convention du cours (pages 31–32) est **`M = nombre de décisions + 1`**.
|
|
|
|
|
|
Chaque `if`, `else if`, `for`, `while`, ainsi que chaque opérateur `&&` ou
|
|
|
|
|
|
`||`, ajoute une décision. `else`, `return` et `break` n'en ajoutent pas.
|
|
|
|
|
|
Les décisions d'une fonction appelée se comptent dans cette fonction.
|
|
|
|
|
|
|
|
|
|
|
|
- **F1** : 2 `for` + 1 `if` + 1 `||` + 1 `&&` = 5 décisions, donc **M = 6**.
|
|
|
|
|
|
- **F2** : 2 `for` + 2 `if` = 4 décisions, donc **M = 5**.
|
|
|
|
|
|
- **F3, F4 et F7** : 2 `for` + 1 `if` = 3 décisions chacune, donc **M = 4**.
|
|
|
|
|
|
- **F5** : 3 `for` + 1 `if` = 4 décisions, donc **M = 5**.
|
|
|
|
|
|
- **F6** : 1 `for` + 1 `if` = 2 décisions, donc **M = 3**.
|
|
|
|
|
|
- **F8** : 2 `if` + 1 `||` = 3 décisions, donc **M = 4**.
|
|
|
|
|
|
- **F9** : 11 `if` ou `else if` + 1 `||` + 1 `&&` = 13 décisions,
|
|
|
|
|
|
donc **M = 14**. C'est la plus grande valeur de F1 à F10.
|
|
|
|
|
|
- **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.
|
2026-10-21 11:11:28 +02:00
|
|
|
|
|
|
|
|
|
|
## 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`.
|
2026-10-21 11:11:56 +02:00
|
|
|
|
|
|
|
|
|
|
### F2 — Tableau des clients rencontrés
|
|
|
|
|
|
|
|
|
|
|
|
Un tableau de `C` indicateurs, initialisé à zéro, remplace la recherche
|
|
|
|
|
|
dans les transactions précédentes. Une transaction marque son client à son
|
|
|
|
|
|
premier passage et incrémente le compteur. Le coût devient **`O(n + C)`**
|
|
|
|
|
|
au lieu de `O(n²)`, avec `O(C)` de mémoire. Les identifiants de clients sont
|
|
|
|
|
|
densement compris entre 0 et `C - 1`, comme le garantit le générateur.
|
|
|
|
|
|
La complexité cyclomatique passe de **5 à 3**. Les tests couvrent l'absence
|
|
|
|
|
|
de transactions, les doublons, les identifiants extrêmes et tous les clients.
|
2026-10-21 11:12:21 +02:00
|
|
|
|
|
|
|
|
|
|
### F3 — Accumulation directe par client
|
|
|
|
|
|
|
|
|
|
|
|
Après la remise à zéro des `C` soldes, un seul parcours des transactions
|
|
|
|
|
|
ajoute chaque montant dans `totaux[t[i].client]`. Les deux boucles sont
|
|
|
|
|
|
successives : **`O(n + C)`** au lieu de `O(n × C)`, avec `O(1)` de mémoire
|
|
|
|
|
|
auxiliaire en dehors du tableau de sortie. La complexité cyclomatique passe
|
|
|
|
|
|
de **4 à 3**. Les tests vérifient les clients sans transaction, les débits,
|
|
|
|
|
|
les crédits, les annulations et la réinitialisation entre deux appels.
|
2026-10-21 11:12:46 +02:00
|
|
|
|
|
|
|
|
|
|
### F4 — Accumulation directe par jour
|
|
|
|
|
|
|
|
|
|
|
|
Le même principe s'applique aux jours : initialisation des `J` soldes,
|
|
|
|
|
|
puis ajout de chaque montant dans `totaux[t[i].jour]`. Le coût passe de
|
|
|
|
|
|
`O(n × J)` à **`O(n + J)`**, avec `O(1)` de mémoire auxiliaire hors sortie.
|
|
|
|
|
|
La complexité cyclomatique passe de **4 à 3**. Les tests comparent chaque
|
|
|
|
|
|
solde à un calcul de référence, y compris aux bornes du tableau, puis
|
|
|
|
|
|
vérifient qu'un appel sans transaction remet tous les soldes à zéro.
|
2026-10-21 11:13:19 +02:00
|
|
|
|
|
|
|
|
|
|
### F5 — Meilleure période en un passage (Kadane)
|
|
|
|
|
|
|
|
|
|
|
|
La somme de la meilleure période se terminant au jour courant est conservée.
|
|
|
|
|
|
Si cette somme est négative, le prochain jour démarre une nouvelle période ;
|
|
|
|
|
|
sinon, elle est prolongée. Le coût passe de `O(J³)` à **`O(J)`**, avec
|
|
|
|
|
|
**`O(1)`** de mémoire auxiliaire, et la complexité cyclomatique de **5 à 4**.
|
|
|
|
|
|
|
|
|
|
|
|
La somme initiale est celle du premier jour : une série entièrement négative
|
|
|
|
|
|
renvoie bien un intervalle non vide. On redémarre uniquement pour une somme
|
|
|
|
|
|
strictement négative et on remplace le meilleur résultat uniquement pour
|
|
|
|
|
|
une somme strictement supérieure. Une somme nulle conserve ainsi le début
|
|
|
|
|
|
le plus ancien ; à égalité, le premier début puis la première fin sont
|
|
|
|
|
|
conservés comme dans les boucles initiales. Les **97 655 séries** de longueur
|
|
|
|
|
|
1 à 7 à valeurs dans `[-2, 2]` sont comparées à la version cubique, sur la
|
|
|
|
|
|
somme et sur les deux bornes : elles couvrent notamment les zéros, les
|
|
|
|
|
|
égalités et les séries entièrement négatives.
|