Module L04 · Partie F · Coder comme un professionnel
Quatre façons d’attaquer un problème.
Face à un problème nouveau, un informaticien ne cherche pas « l’algorithme » : il reconnaît une stratégie. Diviser pour régner quand les sous-problèmes sont indépendants. Programmation dynamique quand ils se chevauchent. Glouton quand un choix local suffit. Backtracking quand il faut explorer. Ce module apprend à reconnaître, appliquer et prouver.
Durée : 3 séances · Prérequis : récursivité, complexité, L03. Objectifs : maîtriser les quatre paradigmes, savoir passer d’une récursion exponentielle à une table en O(n²), reconnaître les problèmes classiques (sac à dos, distance d’édition, plus longue sous-suite, intervalles, Huffman, n reines, sudoku).
Ce que vous saurez faire à la fin
- Écrire la récurrence d’un problème puis la mémoïser ou la tabuler.
- Prouver qu’un glouton est correct (argument d’échange) ou trouver un contre-exemple.
- Élaguer un backtracking pour qu’il termine en secondes au lieu d’années.
- Résoudre les exercices d’algorithmique des concours X-ENS (épreuve d’informatique).
Références : CLRS chapitres 15-16, Algorithm Design (Kleinberg-Tardos), sujets d’informatique du concours Centrale/Mines/X.
Fiche de cours · Définitions
Les quatre stratégies et leur vocabulaire
Fiche de cours · Formules
Récurrences : le théorème maître et ses cas usuels
| Récurrence | Solution | Algorithme |
|---|---|---|
| T(n) = T(n/2) + O(1) | Θ(log n) | Dichotomie |
| T(n) = 2T(n/2) + O(n) | Θ(n log n) | Tri fusion, tri rapide (moyenne) |
| T(n) = 2T(n/2) + O(1) | Θ(n) | Parcours d’arbre équilibré |
| T(n) = T(n − 1) + O(n) | Θ(n²) | Tri rapide (pire cas), sélection naïve |
| T(n) = T(n − 1) + O(1) | Θ(n) | Récursion linéaire (somme, factorielle) |
| T(n) = 2T(n − 1) + O(1) | Θ(2ⁿ) | Fibonacci naïf, sous-ensembles |
| T(n) = 8T(n/2) + O(n²) ; 7T(n/2) + O(n²) | Θ(n³) ; Θ(n2,81) | Produit matriciel naïf ; Strassen |
Formules de dénombrement pour la force brute : n! permutations, 2ⁿ sous-ensembles, C(n,k) = n!/(k!(n−k)!) parties à k éléments, kⁿ suites de longueur n sur k symboles. Formule du binôme : (a+b)ⁿ = Σ C(n,k)akbn−k.
Fiche de cours · Théorèmes et démonstrations
Démonstrations à savoir refaire (1/2)
Fiche de cours · Théorèmes et démonstrations
Démonstrations à savoir refaire (2/2) : les gloutons
Fiche de cours · Méthodes
Méthodes et pièges
def explorer(partiel): if complet(partiel): enregistrer ; else: for choix in candidats(partiel): if valide(partiel + choix): explorer(partiel + choix). Élaguer tôt (vérifier les contraintes au plus tôt), ordonner les candidats (les plus contraints d’abord), et borner (branch and bound : abandonner si la meilleure extension possible ne bat pas le record).Pièges : mémoïser une fonction dont les arguments sont des listes (non hachables : convertir en tuple) ; récurrence dont un cas fait appel au même sous-problème (boucle infinie) ; oublier que la PD sur les valeurs (sac à dos, sous-somme) est pseudo-polynomiale ; croire qu’un glouton « qui marche sur mes exemples » est prouvé.
Fiche de cours · Exercices corrigés
Exercices corrigés
01 / Diviser pour régner
Couper en deux, résoudre, recoller
Le théorème maître, en pratique
Une récurrence T(n) = a·T(n/b) + O(nd) se résout ainsi : si a < bd → O(nd) ; si a = bd → O(nd log n) ; si a > bd → O(nlog_b a). Tri fusion : a = 2, b = 2, d = 1 → O(n log n). Recherche dichotomique : a = 1, b = 2, d = 0 → O(log n). Multiplication de Karatsuba : 3 produits de taille n/2 au lieu de 4, a = 3, b = 2, d = 1 → O(n1,585) : c’est ainsi que Python multiplie les grands entiers. Strassen fait de même pour les matrices (7 produits au lieu de 8 → O(n2,807)).
01 / Diviser pour régner
Puissance rapide et exponentiation matricielle : Fibonacci en O(log n)
Le même code sert aux entiers, aux entiers modulo p (RSA, Diffie-Hellman) et aux matrices : la seule hypothèse est l’associativité. Passer un paramètre mul est le patron Stratégie du module L02.
02 / Programmation dynamique
Quand les sous-problèmes se recoupent : mémoriser
La recette
- Définir l’état : « escalier(n) = nombre de façons de monter n marches ». L’état doit contenir exactement l’information nécessaire pour la suite.
- Écrire la récurrence : escalier(n) = escalier(n−1) + escalier(n−2) + escalier(n−3), avec les cas de base.
- Choisir : mémoïsation (récursif + cache, simple, ordre automatique) ou tabulation (itératif, plus rapide, mémoire contrôlable).
- Compter : nombre d’états × coût par état = complexité. Ici n états × O(1) = O(n) au lieu de O(3n).
La programmation dynamique s’applique quand le problème a une sous-structure optimale (la solution se construit à partir de solutions de sous-problèmes) et des sous-problèmes qui se chevauchent (sinon, diviser pour régner suffit).
02 / Programmation dynamique
Sac à dos et rendu de monnaie
Le sac à dos est NP-difficile en général, mais O(n·C) quand les poids sont entiers et C raisonnable : on dit pseudo-polynomial. C’est un exemple parfait pour le module L23.
02 / Programmation dynamique
Distance d’édition : ce que fait votre correcteur orthographique
La même table sert à git diff (plus longue sous-suite commune de lignes), à l’alignement de séquences en biologie (Needleman-Wunsch) et à la reconnaissance vocale (DTW). O(n·m) en temps ; la mémoire peut descendre à O(min(n, m)) en ne gardant que deux lignes.
03 / Gloutons
Un choix local suffit… parfois
Prouver un glouton : l’argument d’échange
Pour la planification : soit une solution optimale O qui ne commence pas par l’activité a₁ qui finit le plus tôt. Remplaçons la première activité de O par a₁ : comme a₁ finit plus tôt, elle est compatible avec le reste de O. On obtient une solution optimale qui commence par a₁. Par récurrence sur le reste, le glouton est optimal. Cet « échange » sans perte est la structure de toutes les preuves de gloutons (Huffman, Kruskal, Dijkstra). Quand l’échange peut faire perdre quelque chose — rendu de monnaie avec pièces {1, 3, 4}, sac à dos 0/1 — le glouton échoue et il faut la programmation dynamique.
04 / Backtracking
Explorer, et surtout élaguer
Les trois leviers
- Vérifier tôt : ne pas remplir toute la grille avant de tester ; refuser un candidat dès qu’il viole une contrainte.
- Choisir la variable la plus contrainte (MRV : minimum remaining values) : une case à 1 candidat se remplit sans risque ; une case à 0 candidat fait rebrousser immédiatement.
- Ordonner les valeurs : essayer d’abord celles qui contraignent le moins les voisins (LCV).
Sans ces leviers, un sudoku difficile prend des heures ; avec, des millisecondes. Le backtracking est la base de la résolution de contraintes (CSP), de SAT (module L17) et de la planification. Il est exponentiel dans le pire cas — mais le pire cas est rare quand l’élagage est bon.
04 / Backtracking
Générer : permutations, sous-ensembles, partitions
Les générateurs (yield) rendent l’exploration paresseuse : on peut s’arrêter à la première solution sans avoir calculé les autres. itertools fournit permutations, combinations, product en C.
05 / Reconnaître
Quelle stratégie ? Le guide de décision
| Indice dans l’énoncé | Stratégie | Exemples |
|---|---|---|
| Sous-problèmes indépendants de taille n/b | Diviser pour régner | Tri fusion, dichotomie, Karatsuba, plus proche paire de points |
| « Nombre de façons », « minimum/maximum sur une séquence », sous-problèmes réutilisés | Programmation dynamique | Sac à dos, monnaie, édition, LCS, chemins dans une grille, découpe de tige |
| Un choix « évident » à chaque étape ; structure d’intervalles ou de matroïde | Glouton | Activités, Huffman, Kruskal, Dijkstra, monnaie canonique |
| Contraintes à satisfaire, espace de recherche combinatoire | Backtracking | Sudoku, n reines, coloration, SAT, énumération |
| Optimum d’une fonction continue | Méthodes numériques (L12) | Descente de gradient, Newton |
| Réseau, relations entre entités | Graphes (L05) | Plus court chemin, flot, couplage |
Méthode d’examen : 1) reformuler le problème en une phrase mathématique ; 2) chercher une récurrence ; 3) tester un glouton sur un petit contre-exemple ; 4) si rien ne marche en polynomial, backtracking élagué et se demander si le problème est NP-difficile.
Cours
Cours 1 — Méthode : de l’énoncé à la récurrence
La programmation dynamique se rate presque toujours au même endroit : la définition de l’état. Voici une procédure en cinq questions, à écrire sur papier avant tout code.
- Quel est le sous-problème ? Il doit être paramétré par peu d’entiers (un préfixe, un intervalle, une capacité restante, un sommet). Notation : f(i), f(i, j), f(i, c).
- Quelle est la question posée à f ? « nombre de façons », « valeur maximale », « est-ce possible », « coût minimal ». La question détermine l’opérateur de combinaison : +, max, ou, min.
- Quelle est la dernière décision ? Pour f(i), que se passe-t-il pour l’élément i : pris / pas pris, coupé ici / pas coupé, apparié avec j / sauté. La récurrence énumère ces choix.
- Cas de base ? f(0), f(i, 0) : souvent 0, 1, vrai ou +∞ — le cas où « il n’y a plus rien à décider ».
- Ordre de calcul et coût ? Nombre d’états × coût par état. Si un état dépend d’états « plus petits », la tabulation en ordre croissant marche ; sinon mémoïser.
| Problème | État | Récurrence | Coût |
|---|---|---|---|
| Rendu de monnaie (nombre de façons) | f(m, k) : montant m avec les k premières pièces | f(m, k) = f(m, k−1) + f(m − p_k, k) | O(M·K) |
| Plus longue sous-suite croissante | f(i) : longueur de la meilleure finissant en i | f(i) = 1 + max f(j) pour j < i, t[j] < t[i] | O(n²) (O(n log n) avec dichotomie) |
| Multiplication de matrices en chaîne | f(i, j) : coût minimal pour A_i…A_j | min sur k de f(i,k) + f(k+1,j) + d_{i−1}d_k d_j | O(n³) |
| Chemins dans une grille avec obstacles | f(i, j) | f(i−1, j) + f(i, j−1) si libre | O(n·m) |
Cours
Cours 2 — Exemple travaillé : la plus longue sous-suite croissante, de O(2ⁿ) à O(n log n)
Lecture. La récurrence f(i) répond à la question 3 du cours 1 (« i est le dernier élément »). L’algorithme rapide n’est plus une PD classique : il maintient un invariant (queue est triée et queue[k] est optimal) — la preuve se fait comme au module L09. Reconstruire la sous-suite elle-même demande de mémoriser les prédécesseurs (défi).
Cours
Cours 3 — Prouver un glouton, ou le réfuter : deux exemples rédigés
Exemple 1 (correct) — Ordonnancement pour minimiser la somme des temps d’attente. n tâches de durées d_i ; ordre gloutton : durées croissantes (SPT). Preuve par échange : supposons un ordre optimal où deux tâches consécutives ont d_a > d_b avec a avant b. Échanger a et b ne change les temps d’attente que de ces deux tâches : avant l’échange, b attend d_a de plus ; après, a attend d_b de plus. Comme d_b < d_a, la somme diminue strictement — contradiction avec l’optimalité. Donc l’optimal est trié par durées croissantes. ∎
Exemple 2 (faux) — Sac à dos 0/1 par ratio valeur/poids décroissant. Contre-exemple : capacité 10 ; objets (poids, valeur) = (6, 60), (5, 50), (5, 50). Ratios 10, 10, 10 ; le glouton prend le premier (6) puis ne peut plus rien ajouter : 60. L’optimal prend les deux derniers : 100. Un seul contre-exemple suffit à réfuter ; il faut alors la programmation dynamique (cours L04 §02) — ou accepter une garantie d’approximation (le glouton par ratio, complété par « le meilleur objet seul », donne au moins la moitié de l’optimal : preuve en défi).
TP guidé
TP — Un solveur de sac à dos complet, du naïf à l’industriel (sur PC, 2 h)
- Générateur d’instances.
instances.py: fonctiongenerer(n, C_max, graine); classes d’instances « non corrélées » (poids et valeurs indépendants), « fortement corrélées » (valeur = poids + 10, les plus dures), « sous-ensemble » (valeur = poids). Sauvegarde en JSON. - Quatre solveurs dans
sac.py, même signatureresoudre(objets, C) -> (valeur, choix): force brute (n ≤ 20), PD tabulée O(n·C), PD avec une seule ligne de mémoire O(C), séparation-évaluation (branch and bound : borne par le glouton fractionnaire, exploration des objets par ratio décroissant). - Tests. Force brute comme oracle pour n ≤ 15 ; propriété : les quatre solveurs donnent la même valeur ; le
choixrenvoyé a bien la valeur annoncée et respecte la capacité. - Mesure.
bench.py: temps en fonction de n (PD) et de C (PD : linéaire en C ; B&B : insensible à C mais sensible à la corrélation). Tableau et deux courbes. - Comparer à un solveur industriel.
Jusqu’à quel n votre B&B suit-il OR-Tools ? Sur quelles instances décroche-t-il ?pip install ortools from ortools.algorithms.python import knapsack_solver s = knapsack_solver.KnapsackSolver(knapsack_solver.SolverType.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER, "sac") s.init(valeurs, [poids], [C]); print(s.solve()) - Livrable. Dépôt +
RESULTATS.md: quand utiliser la PD (C petit), le B&B (n grand, C grand), et pourquoi les instances fortement corrélées sont dures pour le B&B (la borne gloutonne est peu informative).
Exercices
Exercices auto-corrigés — récurrences et programmation dynamique
Exercice 1 — Nombre de façons de rendre la monnaie
Écrivez nb_facons(pieces, montant) : nombre de multi-ensembles de pièces totalisant le montant (l’ordre ne compte pas : {1, 2} et {2, 1} sont la même façon). Indice : récurrence f(m, k) du cours 1 ; tabulez en parcourant les pièces à l’extérieur.
Correction
def nb_facons(pieces, montant):
f = [1] + [0] * montant # f[m] : façons avec les pièces vues jusqu'ici
for p in pieces: # pièces à l'extérieur : chaque combinaison comptée une fois
for m in range(p, montant + 1): f[m] += f[m - p]
return f[montant]Inverser les boucles (montant à l’extérieur) compterait les suites ordonnées : 9 au lieu de 4 pour le premier test.
Exercice 2 — Découpe de chaîne en mots du dictionnaire
segmenter(texte, dico) renvoie une segmentation de texte en mots du dictionnaire (liste de mots), ou None. État : « le préfixe de longueur i est segmentable ». Reconstruisez la solution.
Correction
def segmenter(texte, dico):
n = len(texte); prec = [None] * (n + 1); ok = [False] * (n + 1); ok[0] = True
for i in range(1, n + 1):
for j in range(i):
if ok[j] and texte[j:i] in dico: ok[i] = True; prec[i] = j; break
if not ok[n]: return None
mots, i = [], n
while i > 0: mots.append(texte[prec[i]:i]); i = prec[i]
return mots[::-1]Exercices
Exercices auto-corrigés — gloutons, diviser pour régner, backtracking
Exercice 3 — Intervalles : couverture minimale
Des capteurs couvrent chacun un intervalle [a, b] d’une route [0, L]. couverture(intervalles, L) renvoie le nombre minimal de capteurs pour couvrir [0, L], ou −1 si impossible. Glouton : à chaque étape, parmi les intervalles qui commencent avant la position courante, prendre celui qui va le plus loin. Justifiez-le par échange (en commentaire).
Correction
def couverture(intervalles, L):
pos, n, i = 0, 0, 0; I = sorted(intervalles)
while pos < L:
loin = pos
while i < len(I) and I[i][0] <= pos: loin = max(loin, I[i][1]); i += 1
if loin == pos: return -1
pos = loin; n += 1
return nExercice 4 — Compter les inversions et placer 8 reines
a) inversions(t) en O(n log n) par tri fusion (cours L04 §01, à réécrire de mémoire). b) reines(n) renvoie le nombre de solutions du problème des n reines par backtracking avec trois ensembles (colonnes, diagonales) pour tester une case en O(1).
Correction
def inversions(t):
def rec(t):
if len(t) <= 1: return t, 0
m = len(t) // 2; g, a = rec(t[:m]); d, b = rec(t[m:]); r, i, j, inv = [], 0, 0, a + b
while i < len(g) and j < len(d):
if g[i] <= d[j]: r.append(g[i]); i += 1
else: r.append(d[j]); j += 1; inv += len(g) - i
return r + g[i:] + d[j:], inv
return rec(list(t))[1]
def reines(n):
cols, d1, d2 = set(), set(), set()
def _rec(l):
if l == n: return 1
total = 0
for c in range(n):
if c in cols or l + c in d1 or l - c in d2: continue
cols.add(c); d1.add(l + c); d2.add(l - c)
total += _rec(l + 1)
cols.discard(c); d1.discard(l + c); d2.discard(l - c)
return total
return _rec(0)06 / Défis
Défi ★ — Chemins dans une grille
Consigne
Un robot va du coin (0, 0) au coin (n−1, m−1) d’une grille en ne se déplaçant que vers la droite ou le bas. Certaines cases sont des obstacles. 1) Nombre de chemins (programmation dynamique). 2) Chemin de coût minimal si chaque case a un coût. 3) Reconstruire ce chemin et l’afficher.
Correction
n, m = len(grille), len(grille[0])
libre = lambda i, j: grille[i][j] != "#"
N = [[0] * m for _ in range(n)]; N[0][0] = 1
for i in range(n):
for j in range(m):
if not libre(i, j): N[i][j] = 0; continue
if i: N[i][j] += N[i - 1][j]
if j: N[i][j] += N[i][j - 1]
print("chemins :", N[n - 1][m - 1])
INF = float("inf")
C = [[INF] * m for _ in range(n)]; C[0][0] = couts[0][0]
for i in range(n):
for j in range(m):
if not libre(i, j) or (i, j) == (0, 0): continue
C[i][j] = couts[i][j] + min(C[i - 1][j] if i else INF, C[i][j - 1] if j else INF)
i, j, chemin = n - 1, m - 1, []
while (i, j) != (0, 0):
chemin.append((i, j))
if i and C[i - 1][j] <= (C[i][j - 1] if j else INF): i -= 1
else: j -= 1
chemin.append((0, 0)); chemin.reverse()
print("coût min :", C[n - 1][m - 1], chemin)
for i in range(n):
print("".join("*" if (i, j) in chemin else grille[i][j] for j in range(m)))06 / Défis
Défi ★★ — Découpe optimale et mémoire
Consigne
Une barre de longueur n se vend prix[k] pour un morceau de longueur k. Quel découpage maximise le revenu ? 1) Récursif naïf : mesurer le nombre d’appels pour n = 20. 2) Mémoïsé. 3) Tabulé avec reconstruction. 4) Question esprit prépa : montrer que le nombre de découpages possibles est 2n−1, et que la PD en examine seulement O(n²) combinaisons.
Correction
appels = 0
def naif(n):
global appels; appels += 1
if n == 0: return 0
return max(p(k) + naif(n - k) for k in range(1, n + 1))
print(naif(20), "appels :", appels) # 2^20 environ
from functools import lru_cache
@lru_cache(None)
def memo(n):
return 0 if n == 0 else max(p(k) + memo(n - k) for k in range(1, n + 1))
print(memo(20), memo(200))
def tabule(n):
R, choix = [0] * (n + 1), [0] * (n + 1)
for l in range(1, n + 1):
for k in range(1, l + 1):
if p(k) + R[l - k] > R[l]: R[l], choix[l] = p(k) + R[l - k], k
morceaux, l = [], n
while l: morceaux.append(choix[l]); l -= choix[l]
return R[n], morceaux
print(tabule(20))Chaque découpage correspond à un choix « couper ou non » à chacune des n−1 positions : 2n−1. La PD n’a que n états, chacun examinant n choix : O(n²). C’est la différence entre 5·105 et 400 pour n = 20, entre l’âge de l’univers et une milliseconde pour n = 100.
06 / Défis
Défi ★★★ — Esprit prépa : le voyageur de commerce, trois façons
Consigne
n villes à coordonnées aléatoires, un robot doit toutes les visiter et revenir. 1) Backtracking avec élagage par borne (abandonner dès que la longueur partielle dépasse la meilleure trouvée) : jusqu’à n = 10. 2) Programmation dynamique de Held-Karp sur les sous-ensembles (états : (ensemble visité, dernière ville), O(2n·n²)) : jusqu’à n = 15. 3) Heuristique gloutonne « plus proche voisin » puis amélioration 2-opt : n = 200, comparer à l’optimal pour n = 10. Discuter : pourquoi personne ne connaît d’algorithme polynomial ?
Correction
def tsp_backtracking(V):
n = len(V); meilleur = [float("inf"), None]
def rec(chemin, longueur, restants):
if longueur >= meilleur[0]: return # élagage par borne
if not restants:
total = longueur + d(V[chemin[-1]], V[chemin[0]])
if total < meilleur[0]: meilleur[0], meilleur[1] = total, list(chemin)
return
for v in sorted(restants, key=lambda v: d(V[chemin[-1]], V[v])): # les proches d'abord
chemin.append(v); rec(chemin, longueur + d(V[chemin[-2]], V[v]), restants - {v}); chemin.pop()
rec([0], 0.0, set(range(1, n))); return meilleur[0]
def tsp_held_karp(V):
n = len(V); INF = float("inf")
C = {(1, 0): 0.0} # (masque, dernière) → coût
for masque in range(1, 1 << n):
if not masque & 1: continue
for j in range(1, n):
if not masque & (1 << j) or (masque, j) in C: continue
prev = masque ^ (1 << j)
C[(masque, j)] = min((C.get((prev, k), INF) + d(V[k], V[j]) for k in range(n) if prev & (1 << k)), default=INF)
plein = (1 << n) - 1
return min(C[(plein, j)] + d(V[j], V[0]) for j in range(1, n))
def tsp_heuristique(V):
n = len(V); t = [0]; restants = set(range(1, n))
while restants: # plus proche voisin
v = min(restants, key=lambda v: d(V[t[-1]], V[v])); t.append(v); restants.remove(v)
def longueur(t): return sum(d(V[t[i]], V[t[(i + 1) % n]]) for i in range(n))
ameliore = True
while ameliore: # 2-opt : décroiser deux arêtes
ameliore = False
for i in range(1, n - 1):
for j in range(i + 1, n):
if d(V[t[i-1]], V[t[j]]) + d(V[t[i]], V[t[(j+1) % n]]) < d(V[t[i-1]], V[t[i]]) + d(V[t[j]], V[t[(j+1) % n]]):
t[i:j+1] = reversed(t[i:j+1]); ameliore = True
return longueur(t)
V = villes(10)
print("exact (backtracking) :", round(tsp_backtracking(V), 4))
print("exact (Held-Karp) :", round(tsp_held_karp(V), 4))
print("heuristique 2-opt :", round(tsp_heuristique(V), 4))
print("n = 200, 2-opt :", round(tsp_heuristique(villes(200)), 3))Held-Karp est en O(2n n²) : exponentiel, mais infiniment mieux que n! (pour n = 20 : 4·108 contre 2·1018). Le TSP est NP-difficile : on ne connaît pas d’algorithme polynomial et on conjecture qu’il n’en existe pas (P ≠ NP, module L23). En pratique, les heuristiques (2-opt, Lin-Kernighan, recuit simulé) donnent des tours à 2-3 % de l’optimal pour des millions de villes, et les solveurs exacts (Concorde) résolvent des instances de 85 900 villes par séparation-évaluation.
07 / Vérification
« Nombre de façons de rendre 100 € avec des pièces de 1, 2, 5, 10 » : stratégie ?
Deux questions supplémentaires
1. Pourquoi le glouton fonctionne-t-il pour les pièces {1, 2, 5, 10, 20, 50} et pas {1, 3, 4} ? Parce que le premier système est « canonique » : chaque pièce vaut au moins le double… en réalité la condition exacte est plus subtile (test de Kozen-Zaks), mais l’intuition est que rendre la plus grosse pièce n’empêche jamais de finir de façon optimale.
2. Complexité de la mémoïsation de Fibonacci ? O(n) états × O(1) = O(n), contre O(φn) naïf.
Référence
Les mots à retenir
| Mot | Définition |
|---|---|
| Diviser pour régner | Sous-problèmes indépendants, résolus récursivement puis combinés. |
| Théorème maître | Résout T(n) = aT(n/b) + O(nd). |
| Programmation dynamique | Résoudre chaque sous-problème une fois et mémoriser. |
| Mémoïsation | PD récursive avec cache (lru_cache). |
| Tabulation | PD itérative en remplissant une table. |
| Sous-structure optimale | L’optimum contient des optimums de sous-problèmes. |
| Glouton | Choix localement optimal à chaque étape, jamais remis en cause. |
| Argument d’échange | Preuve qu’un glouton est optimal. |
| Backtracking | Explorer en profondeur, défaire, élaguer. |
| MRV | Choisir la variable la plus contrainte. |
| Pseudo-polynomial | Polynomial en la valeur des nombres, pas en leur taille (sac à dos O(nC)). |
Pour continuer
Vous reconnaissez les problèmes
Module suivant : les graphes — la structure qui modélise les réseaux, les cartes, les dépendances, et les algorithmes qui les parcourent (BFS, DFS, Dijkstra, A*, Kruskal, tri topologique).
À faire chez soi
- Résoudre 10 problèmes de programmation dynamique sur un site d’entraînement (Codeforces, LeetCode « DP », France-IOI).
- Lire un sujet d’informatique du concours X-ENS (par exemple 2019, « compression ») et identifier les stratégies demandées.
- Implémenter le problème des n reines avec bitmasks pour n = 14 en moins d’une minute.