NSI · Première
Algorithmique : parcourir, trier, chercher et justifier
Un algorithme résout un problème sous des hypothèses précises. Pour savoir s’il convient, pose quatre questions : que reçoit-il, quel résultat garantit-il, pourquoi s’arrête-t-il et quelles opérations comptes-tu ?
Explications et exemples en accès libre. Ateliers, quiz et cartes avec l’abonnement.
Prévoir mon tempsExplications et exemples en accès libre
Une première étude des explications, schémas, exemples résolus et erreurs expliquées. Les activités, productions, quiz et cartes réservés à l’abonnement ne sont pas comptés ici.
- Comprendre2423 mots d’explication et 4 schémas
- 15 à 24 min
- Étudier les exemples et les erreurs18 cas, exemples et activités guidés
- 36 à 66 min
Étude du cours en accès libre, environ50 min à 1 h 30
Voir le calcul et le temps par étape
Le calcul suit automatiquement les explications et les tâches présentes dans le cours. Ses coefficients sont des repères de planification, pas des temps mesurés auprès d’élèves.
- Lecture active : 2423 mots, à raison de 160 à 220 mots par minute.
- Schémas : 4, avec 1 à 2 min pour lire chacun.
- Exemple guidé : 6 × 2 à 4 min.
- Suivre le code résolu et ses tests : 6 × 3 à 5 min.
- Comprendre une erreur expliquée : 6 × 1 à 2 min.
- Parcourir : ce que les cases déjà lues permettent d’affirmer7 à 13 min
- Tri par insertion : déplacer une clé dans un préfixe ordonné8 à 16 min
- Tri par sélection : fixer le minimum du suffixe7 à 14 min
- Dichotomie : séparer préparation, recherche et preuve d’arrêt8 à 16 min
- k plus proches voisins : voir les distances avant de voter9 à 16 min
- Glouton : solution valide, échec et optimalité9 à 17 min
Les sous-totaux sont arrondis à la minute, puis additionnés. La fourchette totale est élargie aux cinq minutes voisines. Les étapes ci-dessus comprennent leurs explications et exemples ; elles ne s’ajoutent pas une seconde fois au total.
Adapte ce repère à tes acquis et au soin apporté aux exercices. Les pauses, les reprises, la consultation des sources externes et les révisions suivantes s’ajoutent selon tes besoins.
Avec les ateliers, le projet, le quiz et les cartes : environ 2 h 45 à 4 h 45, à répartir sur plusieurs séances.
Objectifs du cours
Ce que tu vas savoir faire
- Implémenter recherche séquentielle, extremum et moyenne en justifiant leur coût linéaire.
- Tracer et prouver les tris par insertion et par sélection avec un invariant de boucle.
- Implémenter une recherche dichotomique avec précondition, invariant et variant de terminaison.
- Exécuter k-NN sur un petit jeu synthétique et analyser l'effet de k, de l'échelle et des égalités.
- Construire une stratégie gloutonne et tester son optimalité par un contre-exemple.
- Comparer des algorithmes par correction, domaine, nombre d'opérations et qualité des données.
Étape du cours · 7 à 13 min
Parcourir : ce que les cases déjà lues permettent d’affirmer
Imagine une rangée de casiers numérotés à partir de zéro. Pour trouver une valeur sans information d’ordre, ouvre les casiers un à un. La recherche séquentielle rend ici l’indice de la première occurrence, ou None si elle est absente. Elle s’applique aussi à des chaînes ou à des tuples dès lors que leur égalité a le sens attendu. Un indice nul est un résultat valide : teste indice is not None, pas simplement if indice.
Avant de tester la case i, l’invariant est : « la cible n’apparaît dans aucune case d’indice strictement inférieur à i ». Au début, le préfixe est vide, donc la propriété est vraie. Si la case i est différente, elle rejoint le préfixe écarté ; sinon le retour donne une occurrence. À la fin, tout le tableau a été écarté : None est justifié. Le nombre de cases restantes diminue, ce qui explique aussi l’arrêt.
Pour un extremum, le candidat est une valeur du tableau et reste le plus petit ou le plus grand élément du préfixe traité. Pour une moyenne, la somme partielle est celle de ce préfixe ; on divise seulement à la fin par l’effectif total. Le tableau vide est accepté par la recherche, mais refusé pour le résumé. Celui-ci accepte des int ou float, pas des booléens ; nombres non finis et calcul flottant non représentable sont refusés. Un résultat décimal peut être approché.
Une recherche lit au mieux une case et au pire n cases ; un résumé complet lit les n valeurs. Le coût du parcours est linéaire si une comparaison et une opération numérique ont un coût unitaire. Ce modèle ne mesure pas les secondes et ne décrit pas le coût en bits de très grands entiers. La trace de recherche stocke au plus n lignes ; elle réutilise le résultat du test au lieu d’effectuer deux égalités.
Exemples résolus et erreurs expliquées
Trouver 7, puis résumer quatre valeurs
- Dans
[4, 7, 4], cherche 7. Avant la première comparaison, aucune case n’a été examinée. Le préfixe vide ne contient donc pas la cible. - La case 0 contient 4 : le test est faux. Le préfixe
[4]est écarté, mais ce seul test ne dit rien des deux cases suivantes. - La case 1 contient 7 : le test est vrai. Renvoie l’indice 1 et deux lignes de trace. Chercher 9 donnerait None après trois tests, et chercher 4 donnerait l’indice 0.
- Pour
[3, -2, 8, 3], les sommes successives sont 3, 1, 9, 12. Le minimum final vaut −2, le maximum 8 et la moyenne 12 / 4 = 3. Vérifie chaque valeur contre le préfixe correspondant.
Conclusion. Le résultat et le nombre de cases examinées répondent à deux questions différentes. Le premier indice vaut parfois zéro.
Laboratoire de code
Languepython
Butsuivre une recherche générique et calculer un résumé numérique avec un contrat explicite
Code solution
import math
def rechercher(valeurs, cible):
trace = []
for indice, valeur in enumerate(valeurs):
trouve = valeur == cible
trace.append((indice, valeur, trouve))
if trouve:
return indice, trace
return None, trace
def resume(valeurs):
if not isinstance(valeurs, (list, tuple)) or not valeurs:
raise ValueError('tableau numérique non vide attendu')
minimum = maximum = valeurs[0]
total = 0
for valeur in valeurs:
if type(valeur) not in (int, float):
raise ValueError('nombre attendu, sans booléen')
if type(valeur) is float and not math.isfinite(valeur):
raise ValueError('nombre non fini')
minimum = min(minimum, valeur)
maximum = max(maximum, valeur)
try:
total += valeur
except OverflowError:
raise ValueError('somme non représentable')
try:
moyenne = total / len(valeurs)
except OverflowError:
raise ValueError('moyenne non représentable')
if not math.isfinite(moyenne):
raise ValueError('moyenne non finie')
return minimum, maximum, moyenneTests
assert rechercher([4, 7, 4], 4)[0] == 0
assert rechercher([4, 7, 4], 7)[0] == 1
assert rechercher([4, 7, 4], 9)[0] is None
assert len(rechercher([4, 7, 4], 9)[1]) == 3
assert resume([3, -2, 8, 3]) == (-2, 8, 3)
assert resume([5]) == (5, 5, 5)
assert rechercher([], 7) == (None, [])
assert rechercher(['bleu', 'vert'], 'vert')[0] == 1
assert rechercher([(1, 2), (3, 4)], (1, 2))[0] == 0Trace
- indice 0 : valeur=4, la cible 7 n'est pas trouvée ; le préfixe [4] est écarté
- indice 1 : valeur=7, la comparaison est vraie et l'indice 1 est rendu
- pour le résumé, chaque valeur met à jour minimum, maximum et somme du préfixe
Clinique de bogue
Indice observémaximum([-8, -3, -12]) renvoie 0, valeur qui n'appartient même pas au tableau
CauseInitialiser avec zéro ajoute une hypothèse cachée selon laquelle une valeur non négative existe. L'invariant correct commence avec le premier élément et exige donc explicitement un tableau non vide. Ce petit correctif travaille sur des entiers et refuse l’entrée vide. Le parcours par indices évite de copier le suffixe.
Étape du cours · 8 à 16 min
Tri par insertion : déplacer une clé dans un préfixe ordonné
Comme pour ranger des cartes dans ta main, prends la première valeur encore à traiter et insère-la parmi celles déjà rangées. À l’étape i, la clé est la valeur de la case i ; les valeurs strictement plus grandes sont décalées vers la droite. La clé, mise à l’abri dans une variable, est écrite dans la place libérée. La solution retourne une nouvelle liste : l’entrée n’est pas modifiée.
Avant l’étape i, les cases d’indices 0 à i−1 sont triées et contiennent exactement les valeurs initiales de ce préfixe, avec leurs répétitions. Ce n’est pas forcément l’ensemble des plus petites valeurs de tout le tableau. Après insertion de la clé, la propriété s’étend à i+1 cases. Elle est vraie au départ pour une seule case et donne tout le tableau trié à la fin. Pendant les décalages, la liste peut temporairement contenir une valeur deux fois : la clé conservée dans la variable et la place à remplir font partie de l’état.
La boucle extérieure parcourt une plage finie. À l’intérieur, j+1 est un entier positif tant que la place n’est pas trouvée ; chaque décalage le diminue d’une unité. La boucle s’arrête donc. Le test strict tableau[j] > cle ne fait pas passer une clé devant une valeur égale. Si l’on adapte les comparaisons à une clé d’enregistrement, cette méthode conserve ainsi l’ordre des ex æquo : c’est la stabilité.
Le laboratoire accepte une liste ou un tuple d’entiers, hors booléens. Il distingue comparaisons entre valeurs et décalages. Une liste déjà triée de n valeurs exige n−1 comparaisons et zéro décalage pour n≥1. En ordre strictement décroissant, les deux compteurs valent 1+2+…+(n−1), soit n(n−1)/2. Les vérifications d’indices et de types ne sont pas incluses dans ces compteurs. Les copies de préfixes de la trace ajoutent un coût quadratique, même sur une entrée triée : utilise tracer=False pour étudier le temps du tri sans ces instantanés. Sans trace, le cas déjà trié est linéaire, validation et copie d’entrée comprises.
Voir pour comprendre
Insertion : agrandir le préfixe trié
Entrée [4, 1, 3, 1]. La clé est conservée pendant les décalages.
| Étape | Clé | Préfixe après insertion |
|---|---|---|
| i = 1 | 1 | [1, 4] |
| i = 2 | 3 | [1, 3, 4] |
| i = 3 | 1 | [1, 1, 3, 4] |
Lis le schéma. Retrouve les éléments décalés pour insérer la dernière clé. Vérifie que les deux valeurs 1 sont encore présentes.
L'invariant associe ordre et conservation des valeurs. Un tableau trié qui perd une occurrence n'est pas une sortie correcte.
Exemples résolus et erreurs expliquées
Insérer sans perdre la clé ni le second 1
- Pars de
[4, 1, 3, 1]. À i = 1, garde la clé 1. Décale 4, puis pose la clé à l’indice 0 : le préfixe devient[1, 4]. - À i = 2, la clé vaut 3. Compare 4 à 3 : il est décalé. Compare ensuite 1 à 3 : le test est faux. Insère 3 pour obtenir
[1, 3, 4]. - À i = 3, garde le second 1. Décale 4 puis 3. Le premier 1 n’est pas strictement plus grand que la clé : arrête les décalages et insère après lui.
- La sortie est
[1, 1, 3, 4]. Les comparaisons entre valeurs sont 1 + 2 + 3 = 6, contre 1 + 1 + 2 = 4 décalages. Vérifie le tri et les deux occurrences de 1 ; ces deux contrôles sont nécessaires.
Conclusion. La trace est prise après chaque insertion complète. Comparaison fausse, décalage et copie de trace ne sont pas la même opération.
Laboratoire de code
Languepython
Butexécuter le tri par insertion avec une trace des préfixes et un compteur de décalages
Code solution
def tri_insertion(valeurs, tracer=True):
if not isinstance(valeurs, (list, tuple)) or any(type(v) is not int for v in valeurs):
raise ValueError('tableau d’entiers attendu, sans booléen')
if type(tracer) is not bool:
raise ValueError('tracer doit être booléen')
tableau = list(valeurs)
trace = []
decalages = comparaisons = 0
for i in range(1, len(tableau)):
cle = tableau[i]
j = i - 1
while j >= 0:
comparaisons += 1
if tableau[j] <= cle:
break
tableau[j + 1] = tableau[j]
decalages += 1
j -= 1
tableau[j + 1] = cle
if tracer:
trace.append((i, cle, tuple(tableau[:i + 1]), decalages))
return tableau, trace, decalages, comparaisonsTests
entree = [4, 1, 3, 1]
sortie, trace, decalages, comparaisons = tri_insertion(entree)
assert sortie == [1, 1, 3, 4] and entree == [4, 1, 3, 1]
assert (decalages, comparaisons) == (4, 6)
assert tri_insertion([1, 2, 3, 4], False)[2:] == (0, 3)
assert tri_insertion([4, 3, 2, 1], False)[2:] == (6, 6)
assert tri_insertion([], False) == ([], [], 0, 0)Trace
- i = 1 : clé 1, préfixe [1, 4], 1 décalage cumulé
- i = 2 : clé 3, préfixe [1, 3, 4], 2 décalages cumulés
- i = 3 : clé 1, préfixe [1, 1, 3, 4], 4 décalages cumulés ; 6 comparaisons au total
Clinique de bogue
Indice observéinsertion([2, 1]) renvoie [2, 1] : dans cette version courte, la clé ne peut jamais franchir l’indice zéro
CauseLa case 0 appartient elle aussi au préfixe : j > 0 l’exclut trop tôt. Le correctif autonome insertion conserve le domaine d’entiers et la copie de l’entrée ; il rend seulement la liste, sans les compteurs du laboratoire.
Étape du cours · 7 à 14 min
Tri par sélection : fixer le minimum du suffixe
La sélection procède autrement : à la position i, cherche le minimum parmi les cases i à n−1, puis échange-le avec la case i s’il n’y est pas déjà. L’indice du candidat minimum commence à i, jamais systématiquement à zéro. Ainsi le préfixe déjà fixé n’est plus déplacé. La solution copie l’entrée avant les échanges.
Avant l’étape i, le préfixe contient i plus petites valeurs du tableau initial, dans l’ordre, en tenant compte des répétitions. L’invariant ajoute que le tableau complet conserve toutes ses valeurs. Le préfixe vide vérifie la propriété ; choisir le minimum restant la prolonge d’une case. Après n−1 étapes, la dernière valeur est aussi à sa place. Dans la boucle intérieure, le candidat est le minimum de la partie du suffixe déjà examinée. Les deux boucles parcourent des plages finies, ce qui établit leur terminaison. Une entrée vide ou réduite à une valeur est déjà triée et ne demande aucune étape.
Pour n valeurs, la boucle intérieure fait n−1 comparaisons, puis n−2, jusqu’à 1 : exactement n(n−1)/2 comparaisons entre valeurs, quel que soit l’ordre initial. Ce sont les nombres de comparaisons qui sont identiques, pas forcément les paires de valeurs comparées. Le nombre d’échanges varie de zéro à n−1 pour n≥1. Une entrée déjà triée évite les échanges mais pas la recherche des minimums. Comme pour l’insertion, la validation de types et les copies de trace sont hors compteur ; tracer=False supprime les instantanés.
Ne confonds pas petit nombre d’échanges et stabilité. Imagine des enregistrements de clés [2A, 2B, 1C], où la lettre identifie l’enregistrement et seule la valeur numérique est comparée. Le premier échange donne [1C, 2B, 2A] : les deux clés 2 ont changé d’ordre. La sélection par échange n’est donc pas stable en général. Les nombres seuls du laboratoire ne permettent pas de voir cette différence d’identité.
Exemples résolus et erreurs expliquées
Même sortie, autres opérations
- Reprends
[4, 1, 3, 1]. À i = 0, le candidat commence à l’indice 0 ; la recherche trouve le premier 1 à l’indice 1 après trois comparaisons. - Échange les cases 0 et 1. Le tableau devient
[1, 4, 3, 1]. Le premier élément est fixé, mais le suffixe n’est pas encore trié. - À i = 1, compare 3 puis 1 au candidat. Le minimum finit à l’indice 3. L’échange donne
[1, 1, 3, 4]. À i = 2, une comparaison suffit et aucun échange n’est nécessaire. - Le total est 3 + 2 + 1 = 6 comparaisons et 2 échanges. L’insertion faisait aussi 6 comparaisons sur cette entrée, mais 4 décalages : ne compare pas directement un échange à un décalage comme s’ils étaient identiques.
Conclusion. L’ordre et la conservation valident la sortie ; les compteurs expliquent comment elle a été obtenue.
Laboratoire de code
Languepython
Buttrier par sélection en conservant une trace de l'invariant et des opérations
Code solution
def tri_selection(valeurs, tracer=True):
if not isinstance(valeurs, (list, tuple)) or any(type(v) is not int for v in valeurs):
raise ValueError('tableau d’entiers attendu, sans booléen')
if type(tracer) is not bool:
raise ValueError('tracer doit être booléen')
tableau = list(valeurs)
comparaisons = echanges = 0
trace = []
for i in range(len(tableau) - 1):
indice_min = i
for j in range(i + 1, len(tableau)):
comparaisons += 1
if tableau[j] < tableau[indice_min]:
indice_min = j
if indice_min != i:
tableau[i], tableau[indice_min] = tableau[indice_min], tableau[i]
echanges += 1
if tracer:
trace.append((i, indice_min, tuple(tableau[:i + 1])))
return tableau, comparaisons, echanges, traceTests
entree = [4, 1, 3, 1]
sortie, comparaisons, echanges, trace = tri_selection(entree)
assert sortie == [1, 1, 3, 4] and entree == [4, 1, 3, 1]
assert (comparaisons, echanges) == (6, 2)
assert tri_selection([1, 2, 3, 4], False)[1:3] == (6, 0)
assert tri_selection([], False) == ([], 0, 0, [])Trace
- i=0 : le minimum du suffixe [4,1,3,1] est une valeur 1 ; le préfixe devient [1]
- i=1 : la recherche repart dans le suffixe restant et place le second 1
- i=2 : 3 est déjà le minimum du suffixe ; le préfixe [1,1,3] est correct
Clinique de bogue
Indice observéaprès avoir fixé le premier élément, la fonction peut encore renvoyer l'indice 0 au lieu d'un indice du suffixe
CauseL'invariant exige que le candidat initial appartienne au suffixe non trié. Initialiser l'indice à zéro viole cette exigence dès que debut est positif, même si la boucle parcourt ensuite le bon intervalle.
Étape du cours · 8 à 16 min
Dichotomie : séparer préparation, recherche et preuve d’arrêt
Dans un tableau trié par ordre croissant, compare la cible à la valeur du milieu. Si elle est plus grande, la cible ne peut être dans la moitié gauche ni au milieu ; sinon, si elle est plus petite, écarte la moitié droite et le milieu. Les bornes gauche et droite sont inclusives. Une égalité rend l’indice trouvé ; avec des répétitions, ce n’est pas nécessairement celui de la première occurrence.
L’invariant est : « si la cible apparaît dans le tableau initial, une occurrence demeure dans la zone encore examinée, tant qu’aucun résultat n’a été rendu ». Il est vrai pour la zone initiale complète. L’ordre justifie chaque exclusion et conserve cette propriété. Quand gauche dépasse droite, la zone est vide : aucune occurrence n’a pu y rester, donc la cible est absente. Ce raisonnement exige que le tableau soit trié et ne soit pas modifié pendant la recherche.
Le variant est la taille de la zone, droite−gauche+1. Elle est positive au début d’un tour et chaque nouvelle borne exclut au moins le milieu. Elle décroît strictement et ne devient pas négative avant la sortie : cela prouve l’arrêt. Pour expliquer le coût logarithmique, il faut plus que la décroissance : chaque tour non concluant garde au plus la moitié des cases. Pour n≥1, au plus floor(log₂(n))+1 positions sont examinées. Un tour peut réaliser un test d’égalité puis un test d’ordre ; tours et comparaisons ne sont donc pas interchangeables.
Le code sépare deux usages. rechercher_dans_tableau_trie suppose une liste ou un tuple d’entiers déjà validé et trié ; il ne parcourt pas toute l’entrée avant de chercher. recherche_dichotomique est la version vérifiée : elle appelle d’abord verifier_tableau_trie, puis le noyau. Vérifier l’ordre exige n−1 comparaisons voisines pour une entrée triée non vide : l’appel vérifié complet est linéaire, pas logarithmique. Pour plusieurs recherches, valide une fois un tableau qui restera inchangé, puis appelle le noyau. Trier préalablement aurait encore un coût distinct.
Voir pour comprendre
Dichotomie : ne jamais garder le milieu écarté
Chercher 7 dans le tableau trié [1, 3, 5, 7, 9]. Les bornes sont inclusives.
- 1Zone 0 à 4, taille 5Milieu 2 : la valeur 5 est inférieure à 7.
- 2Zone 3 à 4, taille 2Gauche devient milieu + 1, soit 3. La case 2 est exclue.
- 3Milieu 3 : valeur 7La cible est trouvée, l'indice renvoyé est 3.
Lis le schéma. Justifie le passage de 0 à 3 avec l'ordre du tableau. Explique pourquoi milieu lui-même peut être exclu.
Le tri autorise l'exclusion d'une moitié ; le déplacement strict de la borne garantit que la zone diminue.
Exemples résolus et erreurs expliquées
Deux positions examinées ne font pas tout le coût
- Prends
[1, 3, 5, 7, 9]et cherche 7. La validation contrôle les types et quatre couples voisins : 1≤3, 3≤5, 5≤7 et 7≤9. - Le noyau commence avec gauche = 0 et droite = 4. Le milieu 2 contient 5. Comme 5<7, la nouvelle borne gauche est 3 : la zone passe de cinq à deux cases.
- Le milieu de [3, 4] vaut 3. Sa valeur est 7 : renvoie 3. La trace contient deux positions examinées, indépendamment des quatre comparaisons préalables de validation.
- Sur
[1, 3], chercher 2 conduit à exclure successivement les cases 0 et 1, puis à rendre None. Gardergauche = milieuconserverait indéfiniment la même zone au premier tour.
Conclusion. Le noyau peut chercher vite parce qu’il reçoit une garantie d’ordre. Construire ou vérifier cette garantie n’est pas gratuit.
Laboratoire de code
Languepython
Butimplémenter la dichotomie avec trace de l'invariant et du variant
Code solution
def verifier_tableau_trie(tableau):
if not isinstance(tableau, (list, tuple)) or any(type(v) is not int for v in tableau):
raise ValueError('tableau d’entiers attendu, sans booléen')
comparaisons = 0
for i in range(len(tableau) - 1):
comparaisons += 1
if tableau[i] > tableau[i + 1]:
raise ValueError('tableau non trié')
return comparaisons
def rechercher_dans_tableau_trie(tableau, cible):
# Précondition : tableau d’entiers déjà trié et non modifié.
# Cette fonction ne vérifie pas cette précondition.
if type(cible) is not int:
raise ValueError('cible entière attendue, sans booléen')
gauche, droite = 0, len(tableau) - 1
trace = []
while gauche <= droite:
milieu = (gauche + droite) // 2
trace.append((gauche, milieu, droite, tableau[milieu], droite - gauche + 1))
if tableau[milieu] == cible:
return milieu, trace
if tableau[milieu] < cible:
gauche = milieu + 1
else:
droite = milieu - 1
return None, trace
def recherche_dichotomique(tableau, cible):
verifier_tableau_trie(tableau)
return rechercher_dans_tableau_trie(tableau, cible)Tests
tableau = (1, 3, 5, 7, 9)
assert verifier_tableau_trie(tableau) == 4
assert rechercher_dans_tableau_trie(tableau, 7) == (3, [(0, 2, 4, 5, 5), (3, 3, 4, 7, 2)])
assert recherche_dichotomique([1, 3], 2)[0] is None
assert recherche_dichotomique([], 2) == (None, [])
assert recherche_dichotomique([2, 2, 2], 2)[0] == 1
try:
recherche_dichotomique([3, 1, 2], 1)
except ValueError:
pass
else:
raise AssertionError('ordre invalide accepté')Trace
- Validation de [1, 3, 5, 7, 9] : quatre comparaisons d’ordre entre cases voisines.
- Recherche de 7 : (gauche, milieu, droite) = (0, 2, 4), puis (3, 3, 4).
- Retour : indice 3 ; deux tours du noyau, qui ne comptent pas le travail de validation.
Clinique de bogue
Indice observécontient([1, 3], 2) ne termine pas, car l'intervalle reste [0, 1] après chaque comparaison
CauseRéaffecter une borne au milieu conserve la case déjà prouvée différente de la cible. Sur un intervalle de deux cases, le milieu peut rester identique ; il faut l'exclure par milieu + 1 ou milieu - 1. Le correctif contient est un noyau autonome : comme le noyau du laboratoire, il suppose le tableau d’entiers déjà trié. Sa sortie est un booléen, pas un indice.
Étape du cours · 9 à 16 min
k plus proches voisins : voir les distances avant de voter
k-NN est une méthode de classification supervisée : chaque exemple possède des caractéristiques et une classe connue. Pour un nouveau point, calcule ses distances aux exemples, retiens les k plus proches, puis compte leurs classes. Ici le point a deux coordonnées numériques finies, la classe est une chaîne non vide et k est un entier de 1 à l’effectif, sans booléen. Le code refuse les coordonnées absentes et les calculs de distance non représentables. Il utilise uniquement des points synthétiques, pas des profils d’élèves.
Dans le plan muni de deux axes de même unité, la distance euclidienne au carré est d² = (x₁−x₂)² + (y₁−y₂)². La racine carrée est croissante sur les nombres positifs : comparer d² donne le même ordre que comparer d, sans la calculer. Il faut bien deux coordonnées par point ; zip seul pourrait ignorer silencieusement une coordonnée manquante. Les carrés empêchent aussi l’annulation d’écarts de signes contraires. Dans le code, les coordonnées sont converties en flottants : les calculs peuvent être arrondis. Une égalité de distances désigne ici l’égalité des résultats calculés.
Deux égalités demandent des règles différentes. À distance égale, le tri stable conserve ici l’ordre des exemples d’entrée, y compris à la frontière du k-ième voisin. À égalité de votes, le code choisit la classe la plus petite selon l’ordre lexicographique Python. Ces conventions rendent le calcul reproductible, pas neutre. Avec plus de deux classes, un k impair n’empêche même pas toutes les égalités de votes. Le résultat fournit les voisins et les effectifs pour que tu puisses expliquer la décision.
L’échelle change la proximité. Pour le point (0, 0), compare un exemple A en (0, 1000) à un exemple B en (2, 0) : leurs d² valent 1 000 000 et 4, donc k=1 propose B. Si tu divises la seconde caractéristique de tous les points par 1000, les d² deviennent 1 et 4 : A est proposé. Ce choix d’échelle doit être justifié par les caractéristiques, pas choisi pour forcer une réponse. La méthode fait ici un parcours des exemples puis un tri de toutes les distances ; un petit k ne signifie pas que seules k distances sont calculées.
Une majorité de voisins n’est pas une certitude sur la classe réelle et son pourcentage n’est pas automatiquement une probabilité fiable. Les étiquettes, les exemples retenus, les unités, k et les égalités influencent le résultat. Pour évaluer une prédiction, il faudrait des cas connus qui n’ont pas servi au réglage. Le petit jeu illustratif ci-dessous apprend à lire le calcul ; il ne mesure aucune performance sur une population réelle.
Voir pour comprendre
Du voisin le plus proche au vote des trois voisins
Point à classer : (0, 0). Quatre exemples fictifs, deux classes. Les deux axes utilisent la même unité.
○ A · △ B · × point à classer (0, 0). Les traits relient les 3 voisins retenus.
| Point (x, y) | d² | Vote |
|---|---|---|
| P1 (1, 0) | 1 | A |
| P2 (0, 2) | 4 | B |
| P3 (2, 1) | 5 | B |
| P4 (4, 3) | 25 | A non retenu |
k = 3 : 1 vote(s) A, 2 vote(s) B. Classe proposée : B.
Lis le schéma. Cache le tableau, retrouve les trois voisins reliés au point à classer puis prédis la classe. Que changerait k=1 ?
Le cercle le plus proche est A, mais deux des trois voisins retenus sont B. La géométrie détermine les voisins ; le vote détermine la proposition de classe.
Exemples résolus et erreurs expliquées
Le voisin le plus proche et la majorité peuvent diverger
- Classe le point (0, 0). Les exemples sont P1=(1, 0), classe A ; P2=(0, 2), B ; P3=(2, 1), B ; P4=(4, 3), A. Ils sont créés pour cet exercice.
- Calcule les distances au carré : P1 donne 1²+0²=1 ; P2 donne 0²+2²=4 ; P3 donne 2²+1²=5 ; P4 donne 4²+3²=25. L’ordre est P1, P2, P3, P4.
- Avec k=1, seul P1 vote : la classe proposée est A. Avec k=3, P1, P2 et P3 votent : un vote A et deux votes B donnent B. Les positions n’ont pas changé, seule la taille du voisinage a changé.
- Avec k=4, on obtient deux votes A et deux votes B. La convention lexicographique rend A. Distingue ce départage d’une égalité de distances, qui serait résolue par l’ordre d’entrée avant le vote.
Conclusion. Lis successivement les coordonnées, l’ordre des distances et les votes. Aucun de ces calculs ne transforme une proposition de classe en certitude.
Laboratoire de code
Languepython
Butimplémenter k-NN sur des points synthétiques et afficher voisins, distances et vote
Code solution
import math
def point_plan(point):
if not isinstance(point, (list, tuple)) or len(point) != 2:
raise ValueError('deux coordonnées attendues')
if any(type(v) not in (int, float) for v in point):
raise ValueError('coordonnées numériques, sans booléen')
try:
x, y = float(point[0]), float(point[1])
except OverflowError:
raise ValueError('coordonnée non représentable')
if not math.isfinite(x) or not math.isfinite(y):
raise ValueError('coordonnées finies attendues')
return x, y
def distance_carree(a, b):
x1, y1 = point_plan(a)
x2, y2 = point_plan(b)
try:
d2 = (x1 - x2) ** 2 + (y1 - y2) ** 2
except OverflowError:
raise ValueError('distance non représentable')
if not math.isfinite(d2):
raise ValueError('distance non finie')
return d2
def predire_knn(exemples, point, k):
if not isinstance(exemples, (list, tuple)) or type(k) is not int or not 1 <= k <= len(exemples):
raise ValueError('effectif ou k invalide')
point_plan(point)
distances = []
for exemple in exemples:
if not isinstance(exemple, (list, tuple)) or len(exemple) != 2:
raise ValueError('coordonnées et classe attendues')
coordonnees, classe = exemple
if not isinstance(classe, str) or not classe.strip():
raise ValueError('classe non vide attendue')
distances.append((distance_carree(coordonnees, point), classe, tuple(coordonnees)))
distances.sort(key=lambda item: item[0])
voisins = distances[:k]
votes = {}
for _, classe, _ in voisins:
votes[classe] = votes.get(classe, 0) + 1
gagnant = min(votes, key=lambda classe: (-votes[classe], classe))
return gagnant, voisins, votesTests
exemples = [((1, 0), 'A'), ((0, 2), 'B'), ((2, 1), 'B'), ((4, 3), 'A')]
assert [distance_carree(p, (0, 0)) for p, _ in exemples] == [1, 4, 5, 25]
assert predire_knn(exemples, (0, 0), 1)[0] == 'A'
assert predire_knn(exemples, (0, 0), 3)[2] == {'A': 1, 'B': 2}
assert predire_knn(exemples, (0, 0), 4)[0] == 'A'
egalite = [((1, 0), 'B'), ((0, 1), 'A')]
assert predire_knn(egalite, (0, 0), 1)[0] == 'B'
assert predire_knn(egalite, (0, 0), 2)[0] == 'A'
bruts = [((0, 1000), 'A'), ((2, 0), 'B')]
reduits = [((x, y / 1000), classe) for (x, y), classe in bruts]
assert predire_knn(bruts, (0, 0), 1)[0] == 'B'
assert predire_knn(reduits, (0, 0), 1)[0] == 'A'Trace
- Distances au carré de P1, P2, P3, P4 : 1, 4, 5, 25.
- k = 1 : A ; k = 3 : B ; k = 4 : égalité de votes, A par convention.
- À distance égale, l’ordre des exemples d’entrée départage avant de compter les votes.
Clinique de bogue
Indice observédistance((0, 10), (10, 0)) vaut 0 et fait croire que deux points très différents coïncident
CauseLes écarts signés peuvent s’annuler. Pour (0, 10) et (10, 0), leur somme vaut zéro alors que la distance au carré vaut 200. Le correctif autonome ci-dessous travaille sur des points du plan à coordonnées entières, sans booléens ; ce domaine réduit le distingue de la fonction numérique du laboratoire. Il refuse aussi une dimension manquante.
Étape du cours · 9 à 17 min
Glouton : solution valide, échec et optimalité
Une stratégie gloutonne choisit à chaque étape une option localement avantageuse, sans revenir sur ses choix. Pour rendre une somme avec des pièces fictives disponibles en quantité illimitée, la règle étudiée prend la plus grande valeur qui ne dépasse pas le reste. L’objectif est de minimiser le nombre de pièces ; obtenir la bonne somme est seulement la condition de validité. La somme est un entier positif ou nul, chaque valeur de pièce un entier strictement positif, sans booléens. Les doublons de valeurs sont sans effet ; l’ordre d’entrée n’impose pas l’ordre des choix.
Après chaque choix, « somme des pièces retenues + reste = somme initiale » est un invariant. Le reste est un entier non négatif qui diminue d’au moins un à chaque pièce ajoutée ; la boucle intérieure termine. La liste des valeurs possibles est finie. Si une pièce de valeur 1 existe, le glouton peut toujours terminer avec un reste nul. Sans elle, il peut échouer alors qu’une autre combinaison fonctionne : pour 6 avec [3, 4], prendre 4 bloque sur un reste de 2, mais 3+3 convient.
La validité ne prouve pas l’optimalité. Pour 6 avec [1, 3, 4], le glouton produit 4+1+1 en trois pièces ; 3+3 en utilise deux. Ce cas réfute l’affirmation « cette règle est optimale pour tous les systèmes ». Il ne prouve pas que tout algorithme glouton est mauvais. Pour [1, 2, 4], on peut au contraire justifier la règle : deux pièces de 1 se remplacent par une de 2 et deux pièces de 2 par une de 4. Un rendu minimal n’en conserve donc au plus qu’une de chaque petite valeur ; le choix glouton construit cette forme. Le reste fourni par ces petites pièces est alors entre 0 et 3 ; la somme fixe donc leur nombre et celui des pièces de 4.
Le laboratoire fournit aussi minimum_par_table, un outil de comparaison pour les petites sommes. Il mémorise le meilleur nombre de pièces pour chaque montant de 0 à la cible, puis reconstruit un rendu. Pour un montant m, toute solution non vide se termine par une pièce p et une solution du montant m−p ; tester chaque p permet de choisir le meilleur total. Cette méthode est de la programmation dynamique, un prolongement fourni, pas une recherche exhaustive de toutes les combinaisons ni un attendu à mémoriser en Première.
Pour une somme S et d valeurs, la table examine au plus S×d transitions et stocke S+1 cases de coût ainsi que les choix. Le glouton trie les d valeurs puis ajoute autant de pièces que son résultat en contient. Cette mesure dépend de la valeur numérique de S, pas seulement du nombre de chiffres qui la représentent. Aucun chronométrage n’est déduit de ces comptes. Le projet limite ses montants et distingue un échec glouton, une impossibilité établie par la table et une solution trop longue.
Voir pour comprendre
Une solution valide n'est pas toujours optimale
Somme 6, valeurs 1, 3 et 4, chacune disponible en quantité illimitée.
| Méthode | Pièces | Nombre |
|---|---|---|
| Choix glouton | 4 + 1 + 1 | 3 |
| Autre solution | 3 + 3 | 2 |
Lis le schéma. Vérifie d'abord que les deux sommes valent 6. Compare ensuite le nombre de pièces, qui est le critère à minimiser.
Ce seul cas suffit à réfuter l'optimalité universelle du glouton. Il ne prouve pas que le glouton échoue sur tous les systèmes.
Exemples résolus et erreurs expliquées
Réfuter précisément, puis chercher une preuve
- Avec les pièces [1, 3, 4], rends 6. Le glouton choisit 4 : reste 2. Il choisit ensuite 1 puis 1 : reste zéro et trois pièces utilisées.
- Vérifie l’autre proposition [3, 3]. Sa somme vaut aussi 6 mais elle utilise deux pièces. Une seule pièce ne suffit pas, car la valeur 6 n’existe pas : deux est bien le minimum.
- Retire maintenant la pièce 1. Le glouton choisit encore 4 mais échoue sur le reste 2. La somme n’est pourtant pas impossible puisque [3, 3] reste disponible.
- Pour [1, 2, 4], le même montant donne [4, 2]. L’argument de remplacement de deux petites pièces par une plus grande justifie ce système particulier ; tester seulement le montant 6 ne suffirait pas à le prouver.
Conclusion. Le contre-exemple attaque une affirmation précise. Un échec de stratégie n’est pas une preuve d’impossibilité.
Laboratoire de code
Languepython
Butproduire un rendu glouton et chercher automatiquement un petit contre-exemple à son optimalité
Code solution
def verifier_pieces(somme, pieces):
if type(somme) is not int or somme < 0:
raise ValueError('somme entière positive ou nulle attendue')
if not isinstance(pieces, (list, tuple)) or not pieces:
raise ValueError('valeurs de pièces attendues')
if any(type(p) is not int or p <= 0 for p in pieces):
raise ValueError('pièces entières strictement positives attendues')
return sorted(set(pieces))
def rendu_glouton(somme, pieces):
valeurs = verifier_pieces(somme, pieces)
reste, rendu = somme, []
for piece in reversed(valeurs):
while piece <= reste:
rendu.append(piece)
reste -= piece
return rendu if reste == 0 else None
def minimum_par_table(somme, pieces):
# Prolongement fourni : programmation dynamique.
valeurs = verifier_pieces(somme, pieces)
cout = [None] * (somme + 1)
choix = [None] * (somme + 1)
cout[0] = 0
for montant in range(1, somme + 1):
for piece in valeurs:
if piece <= montant and cout[montant - piece] is not None:
candidat = cout[montant - piece] + 1
if cout[montant] is None or candidat < cout[montant]:
cout[montant] = candidat
choix[montant] = piece
if cout[somme] is None:
return None
rendu, reste = [], somme
while reste > 0:
piece = choix[reste]
rendu.append(piece)
reste -= piece
return renduTests
assert rendu_glouton(6, [1, 3, 4]) == [4, 1, 1]
assert minimum_par_table(6, [1, 3, 4]) == [3, 3]
assert rendu_glouton(6, [3, 4]) is None
assert minimum_par_table(6, [3, 4]) == [3, 3]
assert minimum_par_table(2, [3, 4]) is None
assert rendu_glouton(0, [1]) == []
assert minimum_par_table(0, [1]) == []
assert rendu_glouton(6, [4, 1, 3, 4]) == [4, 1, 1]Trace
- Glouton pour 6 avec [1, 3, 4] : (pièce, reste) = (4, 2), (1, 1), (1, 0).
- Table : les coûts minimaux pour les montants 0 à 6 sont 0, 1, 2, 1, 1, 2, 2.
- Reconstruction pour 6 : pièce 3, puis pièce 3 ; avec [3, 4], ce rendu existe aussi malgré l’échec du glouton.
Clinique de bogue
Indice observérendu(6, [1, 3, 4]) renvoie six pièces de 1, car l'ordre fourni est pris pour un ordre glouton
CauseLa règle locale exige de considérer d'abord la plus grande pièce admissible. Le contrat ne garantissait pas que la liste d'entrée était déjà décroissante ; l'algorithme doit la trier ou poser cette précondition. Le correctif conserve aussi le contrôle des valeurs : une pièce nulle laisserait le reste inchangé, une pièce négative l’augmenterait. Sans reste nul, il renvoie None et ne prétend pas avoir rendu toute la somme.
Poursuivre avec l’abonnement
Passer de la lecture à la pratique
Explore les six explications, les quatre repères visuels et les exemples résolus.
Entraîne-toi avec six ateliers, dix questions corrigées, douze cartes et un banc d’essai d’algorithmes.
10 questions · 12 cartes. Ta reprise et tes révisions sont enregistrées dans ce navigateur. Elles ne se synchronisent pas entre appareils.
Accéder à l’entraînement Déjà abonné ? Se connecterVérifier et prolonger
Sources du cours
Édition Maxdecours · Vérifié le .
Spécialité NSI, Première générale · programme du BO spécial du 22 janvier 2019
- Programme de l'enseignement de spécialité NSI de premièreMinistère de l'Éducation nationale · consulté le 2026-09-06
- Algorithme des k plus proches voisinsÉduscol · consulté le 2026-09-06
- Recherche dichotomiqueÉduscol · consulté le 2026-09-06
- Algorithmes gloutonsÉduscol · consulté le 2026-09-06
- Sorting HOW TOPython Software Foundation · consulté le 2026-09-06
- Programmes en vigueur et ressources NSIÉduscol · consulté le 2026-09-06
© 2026 Maxdecours.com · Comprendre et progresser