Épreuve écrite · 2025 · Jour 2

Bac NSI 2025 centres étrangers groupe 1 jour 2

Les trois exercices demandent de lire précisément une relation : joueur-équipe-match, mot-clé de hachage et nombre-diviseur. Les corrections relient les requêtes et algorithmes à leurs contrats, signalent les incohérences de certains exemples fournis et contrôlent les résultats par des programmes indépendants.

Dans ce sujet

Un corrigé pédagogique pour comprendre et justifier vos réponses. Les conseils de rédaction ne constituent pas un barème officiel détaillé.

Exercice 1 · 6 points

Tournoi de volley : suivre les relations entre joueurs, équipes et matchs

Bois-Plage organise en juillet un tournoi de volley par équipes de quatre. Les inscriptions sont stockées dans trois tables. Chaque clé primaire est un identifiant entier ; les clés étrangères relient les lignes indiquées dans le schéma ci-dessous.

TableAttributs et typesClés étrangères
joueurid_joueur INT (primaire), nom TEXT, prenom TEXT,ann_naiss INT, commune TEXT,num_port TEXTAucune
equipeid_equipe INT (primaire), nom TEXT,j_1 INT,j_2 INT,j_3 INT,j_4 INT, points INTj_1,j_2,j_3,j_4 référencent joueur.id_joueur
matchid_match INT (primaire),eq_1 INT,eq_2 INT,eq_gagnante INT, score TEXTeq_1,eq_2,eq_gagnante référencent equipe.id_equipe

Les numéros de téléphone sont textuels pour conserver leur zéro initial. Les extraits de clôture des inscriptions sont :

id_joueurnomprenomann_naisscommunenum_port
25LeclercOcéane2008Bois-Plage0660358945
26RenaultHenri1971Guilland0625597427
27DesousaLaure1980Bois-Plage0746881113
28HernandYves1986Lebrundan0739401689
29GiraudBrigitte1972Saint-Adrien0651936319
30BarbierLaure1979Bois-Plage0787028125
id_equipenomj_1j_2j_3j_4points
8Les Mr Freeze7125330
9Tagadas Winners452367650
10Volley Warriors252730350
11Les Piafs373241280

Question 1

#

Expliquer le rôle des clés primaires dans ces relations.

Indice

Distinguez l’identité d’une ligne de ses informations descriptives.

Comprendre la correction

Une clé primaire identifie sans ambiguïté chaque ligne d’une table. Sa valeur est unique et non nulle. id_joueur distingue les personnes, id_equipe les équipes et id_match les rencontres. Les noms peuvent être partagés ou modifiés ; l’identifiant permet aux autres tables de référencer une ligne précise.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 2

#

Quelles données la table match ne pourrait-elle pas distinguer sans id_match ?

Indice

Deux équipes peuvent-elles se rencontrer plus d’une fois ?

Comprendre la correction

L’identifiant permet notamment d’enregistrer plusieurs rencontres entre les mêmes équipes, éventuellement avec le même gagnant et le même score. Utiliser seulement le couple(eq_1,eq_2) comme clé interdirait une revanche entre les mêmes équipes dans les mêmes rôles. Sans nouvel identifiant ni autre attribut discriminant, des rencontres distinctes mais identiques sur tous les champs restants seraient indiscernables.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 3

#

Donner le résultat de SELECT prenom FROM joueur WHERE ann_naiss<1985 sur l’extrait.

Indice

Le test porte sur l’année de naissance, strictement inférieure à 1985.

Comprendre la correction
prenom
Henri
Laure
Brigitte
Laure

Les identifiants 26,27,29 et 30 satisfont le filtre. Laure apparaît deux fois car deux personnes différentes portent ce prénom. Sans ORDER BY, l’ordre des lignes n’est pas garanti ; le tableau présente l’ordre de l’extrait pour faciliter la lecture.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 4

#

Modifier la requête pour éliminer les doublons.

Indice

Dédupliquer un résultat est différent de supprimer des données.

Comprendre la correction
SELECT DISTINCT prenom FROM joueur WHERE ann_naiss < 1985;

DISTINCT s’applique aux valeurs projetées, ici au prénom seul. Le résultat comporte Henri, Laure etBrigitte une fois chacun. Il ne supprime aucune personne de la table.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 5

#

Obtenir les noms, années de naissance et numéros mobiles des personnes qui habitent Bois-Plage.

SQLSélectionner les joueurs de Bois-PlageÉcrivez votre solution et mettez-la à l’épreuve

Renvoyez le nom, l’année de naissance et le numéro mobile des personnes qui habitent Bois-Plage. Conservez les téléphones sous forme de texte.

SELECT ...
FROM ...
WHERE ...;

Les cas de test proposés :

  • Extrait officiel des inscriptions : Les six personnes viennent du tableau officiel. La commune filtre les lignes et les trois colonnes demandées constituent le résultat.
  • Cas complémentaire : noms et prénoms distincts : Jeu complémentaire pédagogique, distinct des données officielles. Deux personnes ont le même prénom, mais une seule réside dans la commune. Le nom de famille reste la première colonne demandée.
Indice

Choisissez les colonnes utiles puis le critère de commune.

Comprendre la correction
SELECT nom, ann_naiss, num_port FROM joueur WHERE commune = 'Bois-Plage';

La projection respecte les trois colonnes demandées. Le filtre textuel compare à la valeur complète de la commune, avec son trait d’union. Le numéro est conservé comme chaîne, pas converti en entier.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 6

#

Obtenir nom et prénom du joueur j_1 de l’équipe Les Kangourous, absente de l’extrait.

Indice

La colonne j_1 contient l’identifiant du joueur recherché.

Comprendre la correction
SELECT j.nom, j.prenom
FROM joueur AS j JOIN equipe AS e ON j.id_joueur = e.j_1
WHERE e.nom = 'Les Kangourous';

La jointure suit uniquement j_1. Joindre aussi les trois autres membres répondrait à une autre question. L’absence de cette équipe dans l’extrait n’empêche pas d’écrire la requête sur la base complète ; on ne peut simplement pas annoncer le nom du joueur à partir des lignes visibles.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 7

#

Mettre à jour les 5 points gagnés par Volley Warriors.

Indice

Utilisez l’identifiant connu de l’équipe.

Comprendre la correction
UPDATE equipe SET points = 5 WHERE id_equipe = 10;

L’identifiant 10 est celui de Volley Warriors dans l’extrait et cible une seule ligne. Une condition sur son nom serait aussi conforme si ce nom identifie bien cette équipe. Sans WHERE, toutes les équipes recevraient 5 points.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 8

#

Écrire la suppression du joueur d’identifiant 35.

Indice

Consultez aussi les clés étrangères qui désignent 35.

Comprendre la correction
DELETE FROM joueur WHERE id_joueur = 35;

La requête demandée est simple, mais le schéma impose une contrainte importante : Volley Warriors référence encore ce joueur dans j_4. Si les clés étrangères sont activées sans cascade adaptée, la suppression sera refusée. Il faut d’abord traiter cette appartenance selon la règle de gestion retenue(remplacement du joueur, annulation d’inscription ou conservation de l’historique). La requête seule ne doit pas être présentée comme forcément exécutable sur les données fournies.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

À la fin du tournoi, voici un extrait de match :

id_matcheq_1eq_2eq_gagnantescore
3238825-20
3339325-15
343101025-7

Question 9

#

Obtenir les identifiants des matchs auxquels l’équipe 12 a participé.

Indice

Cherchez dans les deux colonnes de participants.

Comprendre la correction
SELECT id_match FROM match WHERE eq_1 = 12 OR eq_2 = 12;

L’équipe peut occuper le premier ou le second rôle. Le OR retient les deux cas ; AND exigerait qu’elle soit les deux équipes du même match. L’équipe gagnante seule ne suffit pas, car les matchs perdus sont aussi des participations.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 10

#

Obtenir les identifiants des matchs dont le joueur 1 de l’équipe 1 habite Bois-Plage.

Indice

Suivez les rôles dans l’ordre : match, équipe 1, joueur 1.

Comprendre la correction
SELECT m.id_match
FROM match AS m
JOIN equipe AS e ON e.id_equipe = m.eq_1
JOIN joueur AS j ON j.id_joueur = e.j_1
WHERE j.commune = 'Bois-Plage';

La relation se suit en deux étapes : match.eq_1 désigne equipe.id_equipe, puis equipe.j_1 désigne joueur.id_joueur. Le filtre est posé sur la commune de cette personne. Ni l’équipe 2 ni les joueurs 2 à 4 ne doivent intervenir dans la sélection.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 11

#

Obtenir, par ordre alphabétique, les noms et prénoms des joueurs ayant gagné au moins un match en tant que joueur 1 de l’équipe 1.

Indice

Le gagnant du match doit être la même équipe que celle déjà jointe.

Comprendre la correction
SELECT DISTINCT j.nom, j.prenom
FROM match AS m
JOIN equipe AS e ON e.id_equipe = m.eq_1
JOIN joueur AS j ON j.id_joueur = e.j_1
WHERE m.eq_gagnante = m.eq_1
ORDER BY j.nom, j.prenom;

La jointure suit les mêmes rôles que précédemment, puis le filtre exige que l’équipe gagnante soit eq_1. DISTINCT évite qu’une même personne apparaisse plusieurs fois après plusieurs victoires. Le tri s’effectue d’abord sur le nom, puis sur le prénom. La requête ne sélectionne pas les vainqueurs qui jouaient dans l’équipe 2 : cette restriction est explicitement demandée.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)
Le rôle dans le match change la requêteUn atelier pour expérimenter

Explorez un petit jeu de test complémentaire au sujet. Chaque ligne décrit équipe 1, équipe 2 et gagnante. Choisissez une équipe et comparez participer à gagner dans le rôle équipe 1.

Lire le résultat de l’expérience initiale

4 matchs retenus.

Jeu de test pédagogique distinct de l’extrait officiel. Participer utilise OR ;gagner comme équipe1 ajoute une égalité entre gagnante et équipe1.

MatchÉquipe1Équipe2Gagnante
112312
23123
3101212
4121010

Une jointure doit suivre les rôles précis de la question, pas seulement relier toutes les tables disponibles.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Dictionnaire de mots : hachage, insertion ordonnée et dichotomie

On veut retrouver rapidement des mots composés de lettres majusculesA àZ. Un dictionnaire Python associe à une clé entière une liste de mots rangés alphabétiquement. La clé est la somme des codesASCII des lettres, reduite à son octet de poids faible. Des mots différents peuvent donc partager une clé : c’est la liste associée qui permet de les distinguer.

d = {
    44: ['ABAISSEMENT','ADMINISTRATEUR', ..., 'VERSETS'],
    74: ['ABAISSER','ABLATION', ..., 'TROU'],
    243: ['ABANDON','ALLEGRETTO', ..., 'ZIP'],
    36: ['ABANDONNANT','ABOLITIONNISTE', ..., 'VOULAIT'],
    134: ['ABANDONNE','AGNOSTICISME', ..., 'VOIES'],
    40: ['ABANDONNENT','ACCOUCHEUSE', ..., 'YACK']
}
LettreASCII hexadécimal
A0x41
B0x42
C0x43
D0x44
E0x45
F0x46
G0x47
H0x48
I0x49
J0x4A
K0x4B
L0x4C
M0x4D
N0x4E
O0x4F
P0x50
Q0x51
R0x52
S0x53
T0x54
U0x55
V0x56
W0x57
X0x58
Y0x59
Z0x5A

Les points de suspension désignent des mots non reproduits dans l’extrait, pas des données à insérer. Le préfixe 0x indique l’hexadécimal. Exemple : NSI donne 0x4E+0x53+0x49=0xEA=234 ; ADMINISTRATEUR donne une somme 0x42C, dont on conserve 0x2C=44.

Question 1

#

Calculer en hexadécimal la clé du mot EW.

Indice

Additionnez les codes des deux lettres, pas leurs positions dans l’alphabet.

Comprendre la correction

E vaut 0x45 et W vaut 0x57. Leur somme vaut 0x9C, soit 156 en décimal. Elle tient déjà sur un octet et n’est donc pas modifiée par la réduction modulo 256.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)

Question 2

#

Comparer sans calculer les clés de SAC et CAS. Justifier.

Indice

La fonction choisie tient-elle compte de l’ordre des lettres ?

Comprendre la correction

Elles sont identiques. Les deux mots contiennent les mêmes lettres et la somme est commutative : changer leur ordre ne change ni la somme ni son reste modulo 256. C’est une collision de hachage ; elle ne signifie pas que les mots sont égaux.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
def code_hachage(mot):
    somme = ...
    for caractere in ...:
        somme = ...
    return somme % 0x100

Question 3

#

Compléter code_hachage aux lignes 2,3 et 4.

Indice

Additionner les codesASCII dans un accumulateur entier.

Comprendre la correction
def code_hachage(mot):
    somme = 0
    for caractere in mot:
        somme += ord(caractere)
    return somme % 0x100

L’accumulateur commence à 0, la boucle parcourt les caractères du mot et ajoute leur code renvoyé par ord. Le retour réduit seulement la somme finale. On pourrait aussi réduire l’accumulateur à chaque étape : les deux méthodes donnent le même reste final.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)

Question 4

#

Expliquer somme%0x100 et donner les valeurs possibles de la clé.

Indice

Un octet possède 2⁸ valeurs possibles.

Comprendre la correction

0x100 vaut 256. Le modulo renvoie le reste de la division entière par 256, donc un entier entre 0 et 255 inclus. Cela conserve l’octet de poids faible de la somme. Il existe seulement 256 clés : si l’on stocke davantage de mots, des collisions sont inévitables. Le mot vide, si on l’autorisait, aurait pour clé 0.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)

PartieB : les listes sont triées dans l’ordre alphabétique des majuscules. La fonction fournie modifie la liste en place et la renvoie :

def ajouter_mot_liste(liste, mot):
    i = 0
    while i < len(liste):
        if mot < liste[i]:
            liste.insert(i, mot)
            return liste
        i += 1
    liste.append(mot)
    return liste
ajouter_mot_liste([], 'NSI')  # ['NSI']
ajouter_mot_liste(['NSI'], 'PYTHON')  # ['NSI','PYTHON']
ajouter_mot_liste(['NSI','PYTHON'], 'OBJET')  # ['NSI','OBJET','PYTHON']
ajouter_mot_liste(['NSI','OBJET','PYTHON'], 'RAM')  # ['NSI','OBJET','PYTHON','RAM']

Question 5

#

Dans le pire cas, quel est l’ordre de grandeur du nombre de comparaisons de chaînes de ajouter_mot_liste, pour une liste de n mots ?

Indice

Placez le nouveau mot après tous les mots déjà présents.

Comprendre la correction

L’ordre est linéaire :Θ(n). Si le mot doit être placé à la fin, le test mot<liste[i] est effectué pour chacun des n mots avant append. Il peut être effectué une seule fois si le nouveau mot précède le premier. On compte ici les comparaisons entre chaînes, comme demandé ; le coût interne d’une comparaison dépend aussi de la longueur de leur préfixe commun.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

Question 6

#

Quelle expression teste si la clé c est présente dans dico ?

Indice

L’appartenance à un dictionnaire ne parcourt pas ses valeurs.

Comprendre la correction
c in dico

Pour un dictionnaire, l’opérateur in porte sur les clés. Il ne recherche pas c dans les listes de mots associées.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

Question 7

#

Écrire ajouter_mot_dict(dict_mots, mot), sans valeur de retour, en utilisant code_hachage et ajouter_mot_liste.

Indice

Traiter une clé absente avant d’insérer dans sa liste.

Comprendre la correction
def ajouter_mot_dict(dict_mots, mot):
    cle = code_hachage(mot)
    if cle not in dict_mots:
        dict_mots[cle] = []
    ajouter_mot_liste(dict_mots[cle], mot)

La clé détermine le groupe. Si ce groupe n’existe pas, on crée sa liste ; sinon, on conserve ses mots. L’insertion ordonnée maintient ensuite la précondition nécessaire à la dichotomie. Ne pas remplacer systématiquement la valeur par[mot], car cela supprimerait tous les mots entrés auparavant avec la même clé. Le contrat fourni autorise les doublons de mots ; une politique d’unicité demanderait un test supplémentaire.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

PartieC : le contrat dit que debut est inclus et fin exclu. Le code et l’exemple fournis sont :

def est_present(liste, mot, debut, fin):
    if debut > fin:
        return False
    milieu = (debut + fin) // 2
    if liste[milieu] > mot:
        return est_present(liste, mot, 0, milieu - 1)
    elif liste[milieu] < mot:
        return est_present(liste, mot, milieu + 1, fin)
    else:
        return True
liste_mots = ['FONCTION','NSI','PYTHON','OBJET','RAM']
est_present(liste_mots,'NSI',0,len(liste_mots))  # True

Question 8

#

Pour l’appel fourni recherchant NSI, donner debut et fin à chaque appel récursif.

Indice

Exécutez les paramètres réellement écrits, même lorsqu’ils violent le contrat annoncé.

Comprendre la correction
Appel du code fournidebutfinmilieuValeur lue
1052PYTHON
2010FONCTION
3111NSI

Le code compare d’abord PYTHON àNSI et appelle(0,1). FONCTION est ensuite inférieur àNSI, donc l’appel suivant est(1,1), où il lit NSI et renvoieTrue. C’est bien la trace du programme fourni, même si elle révèle une incohérence : avec fin exclusive, l’intervalle[1,1[ devrait être vide.

Autre défaut de l’exemple : OBJET est placé après PYTHON, donc la liste annoncée n’est pas triée. Cet appel réussit malgré ces erreurs, il ne les valide pas. Il faut conserver cette trace pour répondre à la question, puis corriger les conventions avant de réutiliser la fonction.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 9

#

Pourquoi la recherche dichotomique demande-t-elle généralement moins d’opérations qu’une recherche mot par mot ?

Indice

La propriété de tri permet d’écarter plusieurs candidats après une seule comparaison.

Comprendre la correction

Dans une liste triée, la comparaison au milieu permet d’éliminer environ la moitié de l’intervalle : si le mot cherché est plus petit, tous les mots à droite du milieu sont trop grands ; s’il est plus grand, tous ceux de gauche sont trop petits. Une recherche séquentielle peut devoir examiner chaque mot. Sans tri, l’élimination d’une moitié n’est pas justifiée et la dichotomie peut manquer un mot présent.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 10

#

Quel est l’ordre de grandeur du nombre de comparaisons en dichotomie sur n mots ?

Indice

Cherchez combien de divisions par 2 réduisent n à 1.

Comprendre la correction

L’ordre est logarithmique : O(log₂ n), et Θ(log n) dans le pire cas pour une recherche binaire standard. Après k divisions par 2, il reste environ n/2ᵏ candidats ; on atteint une taille constante lorsque k est de l’ordre de log₂n. Une entrée vide se traite sans comparaison de mots.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 11

#

Écrire mot_present(dict_mots, mot) en utilisant code_hachage et est_present.

PythonUne collision de hachage n’est pas un mot trouvéÉcrivez votre solution et mettez-la à l’épreuve

Écrivez mot_present(dict_mots, mot). Le dictionnaire associe chaque code de hachage à une liste de mots triée dans l’ordre lexicographique. Calculez la clé avec code_hachage, puis utilisez est_present sur l’intervalle [0, len(liste)[. Le helper de dichotomie fourni ici corrige les erreurs de bornes discutées dans le corrigé. La seule présence de la clé ne prouve pas que mot figure dans sa liste.

def mot_present(dict_mots, mot):
    # À vous de jouer
    pass

Les cas de test proposés :

  • Un dictionnaire vide : Il faut tester la présence de la clé avant de lire la liste.
  • Le seul mot de sa liste : Une borne finale exclusive égale à zéro oublierait ce seul candidat.
  • Deux anagrammes partagent la clé : La somme des codes de lettres crée une collision pour les anagrammes, sans les rendre identiques.
  • Chercher aux deux extrémités : Les deux extrémités de la liste triée appartiennent à l’intervalle de recherche.
  • Une clé connue, une liste sans candidat : La dichotomie doit recevoir un intervalle vide sans être remplacée par un test sur la seule clé.
Indice

La clé choisit un groupe ; la dichotomie choisit un mot dans ce groupe.

Comprendre la correction

La recherche calcule d’abord la clé, puis ne visite que la liste correspondante. Une clé absente suffit à répondreFalse. Une clé présente ne prouve pas que le mot existe, à cause des collisions : il faut chercher dans sa liste triée.

def mot_present(dict_mots, mot):
    cle = code_hachage(mot)
    if cle not in dict_mots:
        return False
    liste = dict_mots[cle]
    return est_present(liste, mot, 0, len(liste))

Pour rendre cette fonction correcte sur tous les cas du contrat, voici la correction nécessaire de est_present :

def est_present(liste, mot, debut, fin):
    if debut >= fin:
        return False
    milieu = (debut + fin) // 2
    if liste[milieu] > mot:
        return est_present(liste, mot, debut, milieu)
    if liste[milieu] < mot:
        return est_present(liste, mot, milieu + 1, fin)
    return True

Trois corrections respectent l’intervalle[debut, fin[ : le cas vide est debut>=fin ; la branche gauche garde debut et utilise milieu comme nouvelle fin exclusive ; la branche droite commence à milieu+1. Le milieu a déjà été comparé, on ne le réintroduit donc pas dans la recherche. Le code initial réinitialisait début à 0 et supprimait un candidat supplémentaire à gauche avec milieu−1.

Un test complet inclut le mot absent après le dernier élément, un mot absent avant le premier, une liste vide et un mot situé à une extrémité. Ces cas font apparaître les erreurs de bornes qu’un succès surNSI ne montre pas.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)
Voir la somme et sa collision de hachageUn atelier pour expérimenter

Saisissez un mot en lettresA àZ. Comparez notamment SAC etCAS, puis ADMINISTRATEUR. Le tableau suit la somme et le reste sur un octet après chaque lettre.

Lire le résultat de l’expérience initiale

Clé : 234 (0xEA).

L’ordre des lettres ne change pas la somme.Une clé identique ne prouve pas que deux mots sont égaux.

LettreASCII décimalSomme cumuléeReste modulo256
N787878
S83161161
I73234234

Le hachage réduit le groupe à examiner ; il ne remplace pas la comparaison des mots.

Revoir les notions de cet exercice

Exercice 3 · 8 points

Diviseurs et multiples : énumérer les chemins sans répétition

Les entiers 1 à 9 sont disposés en cercle. On peut aller d’un nombre à un diviseur ou un multiple différent de lui-même, sans réutiliser un nombre. Le but est d’obtenir un chemin non prolongeable aussi long que possible. Un chemin de six nombres possède cinq arêtes : le sujet mesure sa longueur en arêtes, alors que len(chemin) compte ses sommets.

Depuis 4, on peut aller vers 1,2 ou 8. Le chemin 4,8,2,6 peut être prolongé par 1 puis 7. On s’arrête alors, car le seul voisin de 7, le nombre 1, a déjà été utilisé.

123456789
Figure 1 :4→8→2→6→1→7, chemin non prolongeable de longueur 5
Lire les connexions du schéma
  • 4 vers 8
  • 8 vers 2
  • 2 vers 6
  • 6 vers 1
  • 1 vers 7
Figures 2 et 3 : six autres chemins non prolongeables
123456789
Exemple :1→2→6→3→9
Lire les connexions du schéma
  • 1 vers 2
  • 2 vers 6
  • 6 vers 3
  • 3 vers 9
123456789
Exemple :1→7
Lire les connexions du schéma
  • 1 vers 7
123456789
Exemple :1→3→6→2→4→8
Lire les connexions du schéma
  • 1 vers 3
  • 3 vers 6
  • 6 vers 2
  • 2 vers 4
  • 4 vers 8
123456789
Exemple :3→6→1→7
Lire les connexions du schéma
  • 3 vers 6
  • 6 vers 1
  • 1 vers 7
123456789
Exemple :3→1→5
Lire les connexions du schéma
  • 3 vers 1
  • 1 vers 5
123456789
Exemple :3→6→2→4→8→1→7
Lire les connexions du schéma
  • 3 vers 6
  • 6 vers 2
  • 2 vers 4
  • 4 vers 8
  • 8 vers 1
  • 1 vers 7

Question 1

#

Donner un chemin non prolongeable qui commence par 3→9→1→2.

Indice

Après avoir prolongé, contrôlez tous les voisins du dernier nombre.

Comprendre la correction

Par exemple 3 → 9 → 1 → 2 → 4 → 8. Chaque paire respecte une relation de divisibilité et aucun nombre ne se répète. À8, les seuls voisins sont 1,2 et 4, tous déjà présents : le chemin est donc non prolongeable. La question n’exige pas de prouver qu’il est le plus long parmi tous les chemins.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)

PartieA : chaque Sommet contient sa valeur et deux listes d’objets, diviseurs et multiples. Le graphe complet est :

123456789
Figure 4 : graphe non orienté du jeu à neuf entiers
Lire les connexions du schéma
  • 1 relié à 2
  • 1 relié à 3
  • 1 relié à 4
  • 1 relié à 5
  • 1 relié à 6
  • 1 relié à 7
  • 1 relié à 8
  • 1 relié à 9
  • 2 relié à 4
  • 2 relié à 6
  • 2 relié à 8
  • 3 relié à 6
  • 3 relié à 9
  • 4 relié à 8
class Sommet:
    def __init__(self,valeur):
        self.valeur = valeur
        self.diviseurs = []
        self.multiples = []
def creer_jeu(n):
    jeu = []
    for valeur in range(1,n+1):
        sommet = Sommet(valeur)
        jeu.append(sommet)
    return jeu

jeu_9 = creer_jeu(9)

Question 3

#

Que vaut jeu_9[0].valeur après cette création ?

Indice

Le jeu commence à 1 mais les indicesPython à 0.

Comprendre la correction

La valeur est 1.L’indice 0 désigne le premier objet créé, qui représente l’entier 1. Il ne faut pas confondre l’indice dans la liste et le nombre représenté.

Voir la question dans le sujet PDF, p. 12 (nouvel onglet)
def relier_diviseurs(self,jeu):
    for s in jeu:
        if s != self and self.valeur % s.valeur == 0:
            self.diviseurs.append(s)
            s.multiples.append(self)

Question 4

#

Expliquer le test s!=self and self.valeur%s.valeur==0 de relier_diviseurs.

Indice

Les deux conditions répondent à deux règles différentes du jeu.

Comprendre la correction

s!=self exclut une liaison du sommet vers lui-même. Le reste nul signifie que s.valeur divise exactement self.valeur.La méthode ajoute alors s aux diviseurs de self et self aux multiples de s : les deux objets connaissent leur relation réciproque. Les valeurs du jeu sont positives, donc le calcul du modulo ne divise jamais par zéro.

Voir la question dans le sujet PDF, p. 12 (nouvel onglet)

Question 5

#

Donner le contenu de jeu_9[5].diviseurs après avoir relié ses diviseurs dans le jeu.

Indice

À l’indice 5, on trouve le nombre 6.

Comprendre la correction

jeu_9[5] représente 6. Ses diviseurs propres sont 1,2 et 3, donc la liste contient les objets [jeu_9[0],jeu_9[1],jeu_9[2]], dans cet ordre. Ce ne sont pas les entiers eux-mêmes : on obtient les valeurs par la méthode de la question 7. La consigne écrit parfois jeu au lieu de jeu_9 ; on utilise ici la liste du jeu à neuf sommets.

Voir la question dans le sujet PDF, p. 12 (nouvel onglet)

Question 6

#

Compléter la ligne manquante de creer_jeu avant jeu.append(sommet).

Indice

Les diviseurs propres d’un entier positif sont plus petits que lui.

Comprendre la correction
def creer_jeu(n):
    jeu = []
    for valeur in range(1,n+1):
        sommet = Sommet(valeur)
        sommet.relier_diviseurs(jeu)
        jeu.append(sommet)
    return jeu

La ligne est sommet.relier_diviseurs(jeu). Au moment où l’on crée la valeur v, la liste jeu contient déjà 1 àv−1, donc tous les diviseurs propres possibles de v.L’appel met aussi à jour les listes de multiples des anciens sommets. Le sommet courant est ajouté ensuite. Cette construction utilise l’ordre croissant pour ne tester chaque paire qu’une fois.

Voir la question dans le sujet PDF, p. 12 (nouvel onglet)

Le squelette est return [... for sommet in ...]. Le PDF donne les exemples jeu_9[7].lister_diviseurs() et jeu_9[1].lister_multiples().

Question 7

#

Compléter lister_diviseurs pour renvoyer les valeurs entières des objets diviseurs.

Indice

Extraire l’attribut valeur de chaque objet.

Comprendre la correction
def lister_diviseurs(self):
    return [sommet.valeur for sommet in self.diviseurs]

La compréhension transforme une liste d’objets en liste de nombres sans modifier les liaisons. Le même principe donne lister_multiples avec self.multiples.Dans le jeu complet,8 possède les diviseurs 1,2,4 ;2 possède les multiples 4,6,8.L’exemple du PDF qui affiche seulement[4,8] pour 2 oublie 6 et ne correspond pas au graphe complet.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

Question 8

#

Compléter les tests vérifiant que le sommet représentant 3 est relié à tous ses voisins.

Indice

Trois voisins attendus : un diviseur et deux multiples.

Comprendre la correction
l_div_3 = jeu_9[2].lister_diviseurs()
l_mult_3 = jeu_9[2].lister_multiples()
assert 1 in l_div_3
assert 6 in l_mult_3
assert 9 in l_mult_3

Le nombre 3 est à l’indice 2. Son seul diviseur propre est 1 et ses multiples dans 1..9 sont 6 et 9. Ces assertions prouvent la présence des voisins attendus mais pas l’absence de voisins erronés. Pour renforcer le test, on peut aussi vérifier les listes exactes :

assert l_div_3 == [1]
assert l_mult_3 == [6,9]
Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

PartieB : la file utilise une liste donnees et un indice decalage. La tête logique est à cet indice ; les cases précédentes ne sont pas supprimées mais ignorées. Cela évite de décaler les cases à chaque retrait. Le code fourni, hors méthode taille à compléter, est :

class File:
    def __init__(self):
        self.donnees = []
        self.decalage = 0
    def est_vide(self):
        return self.decalage == len(self.donnees)
    def enfiler(self,element):
        self.donnees.append(element)
    def defiler(self):
        assert not self.est_vide()
        tete = self.donnees[self.decalage]
        self.decalage += 1
        return tete
    def taille(self):
        ...

Question 9

#

Expliquer assert not self.est_vide() dans defiler.

Indice

L’accès à donnees[decalage] exige qu’une tête logique existe.

Comprendre la correction

L’assertion impose que la file contienne un élément avant le retrait. Si elle est vide, une AssertionError interrompt l’appel au lieu de lire une position invalide.C’est une précondition de defiler, qui rappelle que le programme appelant doit contrôler l’état de la file.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 10

#

Compléter taille de la classe File.

Indice

Soustraire la partie ignorée de la liste.

Comprendre la correction
def taille(self):
    return len(self.donnees) - self.decalage

len(donnees) compte aussi les éléments déjà retirés.L’indice decalage est précisément leur nombre. La différence donne donc les éléments encore présents. Par exemple, après trois insertions et deux retraits, la liste physique contient toujours trois cases mais la taille logique vaut 1.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 11

#

Écrire le test complet : créer une file, enfiler 1,2,3, vérifier la taille, défiler dans le bon ordre, puis vérifier le vide.

Indice

Un test de taille et un test d’ordre vérifient des propriétés différentes.

Comprendre la correction
f = File()
assert f.est_vide()
f.enfiler(1)
f.enfiler(2)
f.enfiler(3)
assert f.taille() == 3
assert f.defiler() == 1
assert f.defiler() == 2
assert f.defiler() == 3
assert f.est_vide()
assert f.taille() == 0

Les valeurs de retour vérifient FIFO : le premier entré sort en premier. Une simple vérification de taille ne détecterait pas une structure qui retire les éléments à l’envers. On peut ensuite enfiler 4 et vérifier qu’il ressort correctement malgré decalage déjà avancé ; la file vide ne nécessite pas ici de vider physiquement donnees.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)
def rechercher_chemins(jeu):
    chemins_np = []
    f = File()
    for sommet in jeu:
        f.enfiler([sommet])
    while ...:
        chemin = ...
        dernier = ...
        voisins = dernier.diviseurs + dernier.multiples
        prolongeable = ...
        for voisin in voisins:
            if voisin not in chemin:
                prolongeable = ...
                f.enfiler(...)
        if not prolongeable:
            chemins_np.append(chemin)
    return chemins_np

Question 12

#

Compléter rechercher_chemins pour énumérer tous les chemins non prolongeables depuis tous les sommets.

Indice

Le booléen décrit l’existence d’au moins un prolongement de ce chemin précis.

Comprendre la correction
def rechercher_chemins(jeu):
    chemins_np = []
    f = File()
    for sommet in jeu:
        f.enfiler([sommet])
    while not f.est_vide():
        chemin = f.defiler()
        dernier = chemin[-1]
        voisins = dernier.diviseurs + dernier.multiples
        prolongeable = False
        for voisin in voisins:
            if voisin not in chemin:
                prolongeable = True
                f.enfiler(chemin + [voisin])
        if not prolongeable:
            chemins_np.append(chemin)
    return chemins_np

Chaque départ est placé dans la file comme chemin d’un seul objet. Le booléen prolongeable est remis à False pour chaque chemin, puis passe à True dès qu’au moins un voisin admissible existe. Si aucun prolongement n’a été créé, le chemin courant est terminal et est ajouté au résultat.

Le test voisin not in chemin interdit une répétition dans ce chemin, mais n’interdit pas de réutiliser ce sommet dans un autre candidat. Une visite globale par sommet serait incorrecte : les historiques déjà parcourus changent les prolongements encore autorisés. Chaque branche utilise chemin+[voisin], une nouvelle liste.L’algorithme termine puisque chaque chemin utilise au plus n sommets distincts, mais le nombre de chemins peut être très élevé.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)

Question 13

#

Écrire valeurs_chemin qui transforme un chemin d’objets en liste de leurs valeurs.

Indice

Appliquer la même transformation qu’à la question 7.

Comprendre la correction
def valeurs_chemin(chemin):
    return [sommet.valeur for sommet in chemin]

Les objets restent nécessaires à la recherche pour accéder aux diviseurs et multiples. La projection sur valeur est réservée à l’affichage et à la liste finale. Cette fonction ne modifie pas le chemin d’origine.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)

Question 14

#

Compléter les lignes produisant tous les chemins non prolongeables du jeu à 9 entiers sous forme de listes de valeurs.

Indice

Le résultat doit être une liste de listes.

Comprendre la correction
chemins = []
for chemin in rechercher_chemins(jeu_9):
    chemins.append(valeurs_chemin(chemin))

Le nom choisi à la question 13 est valeurs_chemin ; la mention chemin_valeur dans cette consigne est une variante typographique à harmoniser.L’accumulation utilise append pour conserver un chemin par élément. Utiliser extend fusionnerait tous les nombres en une liste plate et ferait perdre les frontières entre chemins.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)

Question 15

#

Écrire extraire_plus_longs_chemins(L_chemins), qui renvoie tous les chemins de longueur maximale.

Indice

Distinguer remplacement lors d’un nouveau maximum et ajout lors d’une égalité.

Comprendre la correction
def extraire_plus_longs_chemins(L_chemins):
    resultats = []
    longueur_max = -1
    for chemin in L_chemins:
        if len(chemin) > longueur_max:
            longueur_max = len(chemin)
            resultats = [chemin]
        elif len(chemin) == longueur_max:
            resultats.append(chemin)
    return resultats

Un nouveau maximum remplace les candidats précédents ; une égalité ajoute une autre solution. Initialiser le maximum à−1 permet de traiter aussi une liste contenant un chemin vide. La liste d’entrée vide renvoie[]. Le résultat reste toujours une liste de chemins, même s’il n’y en a qu’un : l’exemple du PDF affichant une liste plate dans ce cas contredit ce contrat.

assert extraire_plus_longs_chemins([[1,3,6,2,4,8]]) == [[1,3,6,2,4,8]]
assert extraire_plus_longs_chemins([[1,7],[3,1,5],[4,1,7]]) == [[3,1,5],[4,1,7]]

Comparer len(chemin) suffit pour choisir le plus long, même si la longueur mathématique du chemin vaut len(chemin)−1 : soustraire la même constante ne change pas les comparaisons. Dans le jeu à 9 sommets, le maximum est 8 sommets, soit 7 arêtes, et 16 chemins atteignent cette taille.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)
SommetsChemins non prolongeables
9377
10800
11925
125279
135935
149896

Question 16

#

Peut-on résoudre un jeu de taille aussi grande que souhaité avec ce programme ? Justifier à partir du tableau fourni.

Indice

La file contient des chemins complets, dont le nombre augmente avec les branches possibles.

Comprendre la correction

Pas en pratique.L’énumération de tous les chemins simples entraîne une explosion du travail et de la mémoire, notamment dans la file qui conserve les chemins en attente. Le tableau passe de 377 chemins terminaux pour 9 sommets à 9896 pour 14, avec déjà 5279 à 12. La finitude d’un jeu garantit la terminaison théorique de cette exploration, pas un temps ni une mémoire raisonnables pour une taille arbitraire.

Les quelques valeurs du tableau ne suffisent pas à démontrer une formule asymptotique précise. Mais le mécanisme de branchement explique le risque : à chaque prolongement, plusieurs choix peuvent créer plusieurs listes de candidats. Le coût ne se limite pas au nombre de sommets ni aux seuls chemins finalement conservés.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)
Combien de chemins se cachent dans ce petit jeu ?Un atelier pour expérimenter

Faites varier la taille du jeu de 3 à 10 entiers. Le moteur énumère réellement les chemins sans répétition et compte les chemins non prolongeables. Il affiche un des plus longs, en distinguant sommets et arêtes.

Lire le résultat de l’expérience initiale

377 chemins terminaux ;maximum 8 sommets,soit7 arêtes.

Le parcours interne est exhaustif :un sommet peut réapparaître dans plusieurs chemins,mais jamais deux fois dans le même.

PositionNombrePassage suivant
14Vers un multiple
28Vers un diviseur
32Vers un multiple
46Vers un diviseur
53Vers un multiple
69Vers un diviseur
71Vers un multiple
85Plus de voisin disponible

L’état de recherche est un chemin entier ; sa combinatoire peut être grande même avec peu de sommets.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Suivre les rôles demandés dans les jointures.
  • Un exemple qui réussit ne valide pas les bornes d’une recherche.
  • Pour les chemins, préciser si la longueur compte des sommets ou des arêtes.

Retrouver ces notions dans d’autres sujets

Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027

Énoncé : sujet 25-NSIJ2G11 (PDF) · Publication d’origine (nouvel onglet). Corrigé et explications pédagogiques proposés par Sofien.