Épreuve écrite · 2026 · Jour 2

Bac NSI 2026 Polynésie jour 2

Ce sujet demande autant de précision sur les données que sur les algorithmes : une mesure d’air possède une clé composée, un taquin contient des cycles, un décalage de recherche doit être justifié, et une route se choisit selon sa métrique. Retrouvez les trois exercices indépendants, leurs figures reconstruites et les corrections après chaque question. Épreuve de 3 h 30 sans calculatrice.

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

Mesures de qualité de l’air : modélisation et requêtes

Dans le contexte décrit par le sujet, le BQA du ministère de la Transition écologique et solidaire mandate le LCSQA pour publier nationalement les mesures de qualité de l’air en temps réel. Les observations sont des moyennes horaires produites par des appareils automatiques sur des stations fixes. Les 18 AASQA citées par l’énoncé gèrent ces stations en métropole et outre-mer et sont responsables des données. Les polluants incluent O3, NO, NO2, SO2, PM10 (particules de diamètre inférieur à 10 µm), PM2.5 (inférieur à 2,5 µm), CO, etc. Une station ne mesure pas nécessairement tous les polluants.

Une AASQA gère zéro, une ou plusieurs stations ; chaque station dépend d’une seule AASQA. Une station réalise zéro, une ou plusieurs mesures ; chaque mesure vient d’une seule station. Les clés primaires sont repérées ci-dessous ; les clés étrangères ne sont volontairement pas repérées dans l’énoncé.

RelationAttributs et types
AASQA[clé primaire : Id] CHAR(7), Nom VARCHAR(50), FuseauHoraire VARCHAR(50), Siteweb VARCHAR(50)
Station[clé primaire : Id] CHAR(7), Nom VARCHAR(50), Latitude DECIMAL(10,6), Longitude DECIMAL(10,6), Altitude INT, TypeImplantation VARCHAR(50), AASQAId CHAR(7)
Mesure[clé primaire : StationId] CHAR(7), [clé primaire : DateDebut] DATETIME, [clé primaire : DateFin] DATETIME, [clé primaire : Polluant] VARCHAR(50), TypeInfluence VARCHAR(50), Valeur DECIMAL(10,1)

Les quatre attributs repérés de Mesure forment ensemble sa clé primaire. Les valeurs des exemples sont reproduites comme dans le sujet, y compris les fuseaux de stockage.

AASQA.IdNomFuseauHoraireSiteweb
FR072AAIR BREIZHUTChttp://www.airbreizh.asso.fr
FR004AAIRPARIFUTChttp://www.airparif.asso.fr
FR061ALIG’AIRUTChttps://www.ligair.fr
FR065AQUALITAIR CORSEUTChttp://www.qualitaircorse.org
FR064AGWAD’AIRUTC-4http://www.gwadair.fr
FR077AHAWA MAYOTTEUTC+3http://www.hawa-mayotte.fr
FR069AATMO NORMANDIEUTChttp://www.atmonormandie.fr/
FR068AATMO OCCITANIEUTChttp://atmo-occitanie.org/
FR071AATMO AUVERGNE-RHONE-ALPESUTChttp://www.atmo-auvergnerhonealpes.fr/
FR076AATMO BOURGOGNE-FRANCHE-COMTEUTChttps://atmo-bfc.org/
FR074AATMO HAUTS DE FRANCEUTChttp://www.atmo-hdf.fr
Station.IdNomLatitudeLongitudeAltitudeTypeImplantationAASQAId
FR37040ABYMES RN116.254086-61.5371313PériurbaineFR064A
FR05083Gonfreville l Orcher49.502730.23248680UrbaineFR069A
FR33203Annecy Rocade45.90976.11825452UrbaineFR071A
FR07056Pays du Mezenc44.983774.2260061191Rurale régionaleFR071A
FR04143Paris Centre48.8592.35137UrbaineFR004A
StationIdDateDebutDateFinPolluantTypeInfluenceValeur (µg/m³)
FR0508315/04/2022 00:0015/04/2022 01:00SO2Industrielle3.3
FR0508315/04/2022 01:0015/04/2022 02:00SO2Industrielle3.3
FR3320315/04/2022 07:0015/04/2022 08:00PM10Trafic66.5
FR3320315/04/2022 08:0015/04/2022 09:00PM10Trafic45
FR0705615/04/2022 13:0015/04/2022 14:00O3Fond101.2
FR0705615/04/2022 14:0015/04/2022 15:00O3Fond102.7

Mots-clés autorisés : SELECT, FROM, WHERE, JOIN ON, INSERT INTO VALUES, UPDATE SET, COUNT, AND, OR, DISTINCT. DISTINCT retire les lignes en double du résultat.

Question 1

#

Expliquer le rôle d’une clé primaire.

Indice

Une clé composée doit être considérée comme un tuple complet.

Comprendre la correction

Elle identifie chaque ligne d’une relation de manière unique. Elle peut comporter plusieurs attributs : c’est alors la combinaison de leurs valeurs qui doit être unique, et ses composantes ne peuvent être NULL. Sans cette identité, deux mesures pourtant différentes pourraient être confondues.

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

Question 2

#

Donner les quatre attributs de la clé primaire de Mesure.

Indice

Lisez les quatre attributs soulignés dans le schéma.

Comprendre la correction

StationId, DateDebut, DateFin et Polluant. Une station peut mesurer plusieurs polluants sur le même intervalle et un même polluant à des heures différentes. Ni la station ni la valeur mesurée ne suffisent seules à identifier une observation.

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

Question 3

#

Expliquer le rôle d’une clé étrangère.

Indice

Une mesure ne doit pas désigner une station inexistante.

Comprendre la correction

Une clé étrangère relie une ligne à une ligne référencée d’une autre relation, ou éventuellement de la même relation. La valeur doit correspondre à une clé existante dans la table cible, sauf NULL si le schéma l’autorise. Cette contrainte évite les références orphelines.

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

Question 4

#

Donner un exemple de clé étrangère dans ce modèle.

Indice

Suivez le lien un-à-plusieurs d’une association vers ses stations.

Comprendre la correction

Station.AASQAId référence AASQA.Id. Autre exemple : Mesure.StationId référence Station.Id. Mentionner la colonne et sa cible rend la relation non ambiguë.

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

Partie B : alimenter la base

De nouvelles mesures doivent être insérées quotidiennement.

Question 5

#

Ajouter la mesure PM10 de valeur 34, influence Trafic, le 20 juin 2022 de 10 h à 11 h, pour Annecy Rocade.

Indice

Cherchez d’abord l’identifiant de la station ; son nom ne va pas dans StationId.

Comprendre la correction
INSERT INTO Mesure
(StationId, DateDebut, DateFin, Polluant, TypeInfluence, Valeur)
VALUES ('FR33203', '2022-06-20 10:00:00', '2022-06-20 11:00:00',
        'PM10', 'Trafic', 34);

La table des stations donne l’identifiant FR33203. Les dates sont écrites au format ISO usuel pour DATETIME, même si le tableau pédagogique les affiche au format jour/mois/année. La mesure vaut 34 µg/m³ ; la valeur numérique ne nécessite pas de guillemets.

La clé composée explique pourquoi on peut conserver deux valeurs identiques de3,3 aux deux premières lignes de l’extrait : leurs heures diffèrent, donc ce sont deux observations distinctes. Inversement, réinsérer exactement la même station, le même intervalle et le même polluant violerait la clé primaire même si la valeur mesurée était différente. Une correction de mesure relèverait alors d’une mise à jour, pas d’un ajout concurrent.

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

Question 6

#

L’ajout d’une mesure O3 de valeur 13,5, influence Fond, le 20 juin 2022 de 00 h à 01 h pour FR41017 produit IntegrityError: FOREIGN KEY constraint failed. Expliquer.

Indice

Le message porte sur une référence, pas sur la valeur du polluant.

Comprendre la correction

La valeur FR41017 ne correspond pas à un identifiant de station présent dans la table Station au moment de l’insertion. La référence de Mesure.StationId serait orpheline ; le SGBD refuse donc l’écriture. Il faut vérifier l’identifiant ou créer préalablement la station avec une association AASQA valide. Le seul extrait des données ne suffirait pas à conclure à son absence ; c’est l’erreur d’intégrité explicitement fournie qui la révèle ici.

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

Partie C : exploiter la base

Question 7

#

Afficher le nom et le site web des AASQA.

Indice

Une seule table contient déjà les deux informations.

Comprendre la correction
SELECT Nom, Siteweb
FROM AASQA;

Aucun filtre n’est demandé : toutes les associations sont concernées. La projection contient seulement les deux attributs utiles.

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

Question 8

#

Afficher le nombre total de stations.

Indice

Ne confondez pas une table complète et son extrait imprimé.

Comprendre la correction
SELECT COUNT(*)
FROM Station;

COUNT(*) compte toutes les lignes de la table, pas seulement celles montrées dans l’extrait. On ne peut donc pas annoncer 5 comme total de la base : l’énoncé ne donne que quelques occurrences.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)
SELECT Station.Nom, Station.TypeImplantation
FROM AASQA JOIN Station ON AASQA.Id = Station.AASQAId
WHERE AASQA.Nom = 'ATMO AUVERGNE-RHONE-ALPES';

Question 9

#

Expliquer la requête donnée.

Indice

Le SELECT décrit les colonnes affichées, le WHERE le groupe de stations retenu.

Comprendre la correction

Elle renvoie le nom et le type d’implantation de toutes les stations gérées par ATMO AUVERGNE-RHONE-ALPES. La jointure associe une station à son AASQA, puis WHERE ne retient que l’association nommée. Dans l’extrait, cela donne Annecy Rocade / Urbaine et Pays du Mezenc / Rurale régionale.

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

Pour les questions 10 à 12, Air Pays de la Loire et la station Mazagran sont connues dans la base, même si elles ne figurent pas dans les extraits.

Question 10

#

Afficher DateDebut, DateFin et Valeur des mesures PM10 de la station Mazagran.

Indice

Reliez Mesure.StationId à Station.Id.

Comprendre la correction
SELECT m.DateDebut, m.DateFin, m.Valeur
FROM Mesure AS m
JOIN Station AS s ON m.StationId = s.Id
WHERE s.Nom = 'Mazagran' AND m.Polluant = 'PM10';

Le nom de station appartient à Station alors que le polluant et les valeurs appartiennent à Mesure. La jointure est donc nécessaire, sauf à connaître et utiliser l’identifiant exact, qui n’est pas fourni.

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

Question 11

#

Afficher les polluants de Mazagran sans doublons.

Indice

DISTINCT porte ici sur une seule colonne : Polluant.

Comprendre la correction
SELECT DISTINCT m.Polluant
FROM Mesure AS m
JOIN Station AS s ON m.StationId = s.Id
WHERE s.Nom = 'Mazagran';

Un polluant peut être mesuré de nombreuses fois. DISTINCT retire ses répétitions dans la projection afin d’obtenir la liste des polluants observés, pas une ligne par heure de mesure.

L’emplacement de DISTINCT est lié à la projection. SELECT DISTINCT Polluant donne un polluant par ligne ; SELECT DISTINCT Polluant,DateDebut conserverait plusieurs lignes du même polluant à des heures différentes. Le SGBD déduplique les lignes complètes du résultat, pas chaque colonne indépendamment.

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

Question 12

#

Afficher le nom des stations gérées par Air Pays de la Loire ainsi que valeur, début et fin de leurs mesures de NO2.

SQLRelier les stations aux mesures de NO2Écrivez votre solution et mettez-la à l’épreuve

Affichez le nom des stations gérées par Air Pays de la Loire, ainsi que Valeur, DateDebut et DateFin de chacune de leurs mesures de NO2. Ne regroupez pas les mesures distinctes.

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

Les cas de test proposés :

  • Extraits officiels : association demandée non affichée : Les six mesures et les trois stations correspondantes proviennent des extraits officiels. Air Pays de la Loire est annoncée comme présente dans la base complète mais absente des extraits ; ce jeu restreint est donc vide pour la requête.
  • Cas complémentaire : association et polluant : Jeu complémentaire pédagogique, distinct des données officielles. Une mesure du bon polluant dans une autre association est exclue, comme un autre polluant dans la bonne station.
  • Cas complémentaire : deux mesures distinctes de stations homonymes : Jeu complémentaire pédagogique, distinct des données officielles. Les noms de stations ne sont pas des clés. Deux mesures distinctes peuvent donner les mêmes colonnes projetées : DISTINCT les fusionnerait à tort.
Indice

Les trois tables apportent chacune une information nécessaire.

Comprendre la correction
SELECT s.Nom, m.Valeur, m.DateDebut, m.DateFin
FROM AASQA AS a
JOIN Station AS s ON s.AASQAId = a.Id
JOIN Mesure AS m ON m.StationId = s.Id
WHERE a.Nom = 'Air Pays de la Loire'
  AND m.Polluant = 'NO2';

Les deux jointures parcourent la chaîne association → station → mesure. Les stations sans mesure NO2 ne produisent aucune ligne avec cette jointure interne, ce qui correspond aux mesures demandées. Les alias évitent l’ambiguïté entre les différentes colonnes Nom.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)
Une projection suffit-elle à retirer les répétitions ?Un atelier pour expérimenter

Filtrez les six mesures de l’extrait par polluant et choisissez de conserver les observations ou uniquement les polluants distincts. Comparez le nombre de lignes retournées.

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

6 ligne(s) dans le résultat

Le filtre porte sur les lignes de mesures. DISTINCT s’applique ensuite aux colonnes sélectionnées ; deux mesures du même polluant ne deviennent une ligne que si les autres colonnes ne sont pas projetées.

StationDébutPolluantValeur
FR0508300:00SO23.3
FR0508301:00SO23.3
FR3320307:00PM1066.5
FR3320308:00PM1045
FR0705613:00O3101.2
FR0705614:00O3102.7

Une base sépare l’identité d’une mesure, son classement par polluant et les liens vers sa station.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Taquin et recherche de texte : deux parcours à ne pas interrompre trop tôt

Le taquin déplace une tuile voisine dans la case vide jusqu’à ranger les nombres. Pour une grille 3 × 3, le vide vaut ici 0 et la position finale le place en haut à gauche. Les deux parties de l’exercice sont indépendantes.

LignePosition initiale de la figure 1Position finale
13, 1, 2vide, 1, 2
27, 5, vide3, 4, 5
34, 6, 86, 7, 8

La figure 2 présente les douze positions atteignables sur un taquin 2 × 2, en cycle. Chaque ligne ci-dessous est reliée à la suivante, et la dernière à la première. « 0 » désigne le vide :

PositionLigne hauteLigne bassePosition suivante
10, 12, 32
21, 02, 33
31, 32, 04
41, 30, 25
50, 31, 26
63, 01, 27
73, 21, 08
83, 20, 19
90, 23, 110
102, 03, 111
112, 13, 012
122, 10, 31

Question 1

#

Représenter une position des trois tuiles inaccessible depuis la position finale.

Indice

Conservez le vide en place et échangez seulement deux tuiles numérotées.

Comprendre la correction
LigneColonne 1Colonne 2
1Vide2
213

Par exemple [0, 2, 1, 3]. Cette position n’apparaît pas parmi les douze sommets du cycle des positions accessibles. Elle échange seulement les tuiles 1 et 2 de la solution. La question peut se résoudre par comparaison exhaustive avec la figure, sans invoquer une formule de parité non donnée.

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

Question 2

#

Quelle structure modélise le mieux les passages d’une position à l’autre ? Justifier.

Indice

Une configuration possède plusieurs voisines et on peut revenir à une configuration déjà visitée.

Comprendre la correction

Un graphe non orienté : un sommet représente une configuration et une arête représente un glissement autorisé. Chaque coup est réversible, ce qui donne les deux sens. Des cycles existent ; une structure d’arbre ne représenterait pas directement tous ces retours.

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

Une position 3 × 3 est lue de gauche à droite et de haut en bas. Les deux positions de la figure 1 deviennent [3, 1, 2, 7, 5, 0, 4, 6, 8] et [0, 1, 2, 3, 4, 5, 6, 7, 8].

Question 3

#

Écrire une assertion qui interrompt le programme si pos ne contient pas neuf éléments.

Indice

assert reçoit une condition qui doit être vraie.

Comprendre la correction
assert len(pos) == 9

Cette vérification porte uniquement sur la longueur. Elle ne prouve pas à elle seule que tous les nombres de 0 à 8 sont présents exactement une fois.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
def est_finale(pos):
    for i in range(len(pos)):
        if ...:
            return ...
    return ...

Question 4

#

Compléter est_finale pour tester si pos est la position finale.

Indice

Cherchez un contre-exemple à l’égalité case par case.

Comprendre la correction
def est_finale(pos):
    for i in range(len(pos)):
        if pos[i] != i:
            return False
    return True

Dans la grille finale, chaque case contient exactement son indice. Une seule différence suffit à conclure False. True est renvoyé seulement après toutes les vérifications ; le placer dans la boucle validerait à tort des listes dont seul le premier élément est correct. On suppose la longueur validée auparavant.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
def case_vide(pos):
    res = ...
    for i in range(9):
        ...
    return res
case_vide([3, 1, 2, 7, 5, 0, 4, 6, 8]) → 5
case_vide([3, 0, 2, 7, 5, 0, 4, 6, 8]) → -1

Question 5

#

Compléter case_vide : renvoyer l’unique indice de 0, ou -1 si zéro est absent ou apparaît plusieurs fois.

Indice

Il faut mémoriser une première découverte sans arrêter trop tôt le parcours.

Comprendre la correction
def case_vide(pos):
    res = -1
    for i in range(9):
        if pos[i] == 0:
            if res != -1:
                return -1
            res = i
    return res

res = -1 signifie qu’aucun zéro n’a encore été trouvé. Au premier zéro, on conserve son indice. Au second, on peut immédiatement refuser la position. Ne pas renvoyer l’indice dès le premier zéro : cela empêcherait de détecter un doublon plus loin.

Trois familles de tests sont nécessaires : un zéro unique, aucun zéro et deux zéros. Une fonction qui renvoie au premier zéro réussit le premier test mais échoue à détecter le troisième. Le stockage temporaire de l’indice permet précisément de continuer à vérifier l’unicité tout en conservant la réponse à renvoyer.

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

Question 6

#

Si le vide est à l’indice i, donner les quatre indices susceptibles de désigner une tuile déplaçable, sans éliminer les indices invalides aux bords.

Indice

La largeur de la grille est trois.

Comprendre la correction

i - 1, i + 1, i - 3, i + 3. Les écarts de 1 correspondent aux colonnes adjacentes, ceux de 3 aux lignes adjacentes. Le sujet demande les candidats seulement ; en code réel, il faudrait rejeter les dépassements et les faux voisins entre la fin d’une ligne et le début de la suivante.

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

La fonction voisins(pos) est disponible et renvoie toutes les positions obtenues en un déplacement.

def est_possible(pos):
    if est_finale(pos):
        return True
    visites = []
    for v in voisins(pos):
        if v not in visites:
            visites.append(v)
            if est_possible(v):
                return True

Question 7

#

Pourquoi est_possible semble-t-elle ne jamais terminer ? Proposer une solution sans la programmer.

Indice

Le mouvement inverse est toujours autorisé : que se passe-t-il dans l’appel suivant ?

Comprendre la correction

Chaque appel recrée sa propre liste vide visites. Le souvenir des positions déjà rencontrées n’est donc pas partagé : un coup peut revenir vers la position précédente puis recommencer indéfiniment. Il faut un ensemble ou une liste de positions visitées commun à tout le parcours, passé en paramètre, et y marquer la position courante avant d’explorer ses voisins. Il faut aussi renvoyer False lorsque toutes les branches échouent. Une version itérative avec pile ou file évite de surcroît la limite de récursion sur ce grand graphe.

Pour un état A ayant pour voisin B, B possède aussi A comme voisin, puisque le déplacement inverse est légal. Avec une nouvelle liste vide à chaque appel, l’appel sur B n’a aucun souvenir de A et repart immédiatement vers lui. Une collection partagée ne suffit pas si l’on oublie d’y placer le sommet courant avant d’explorer : le moment du marquage fait partie de la preuve de terminaison.

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

Partie B : recherche de mots

Des mots acceptés sont stockés dans un texte, séparés par des espaces. Pour rechercher un mot entier, on cherche le mot encadré d’espaces. Cela suppose que le texte est aussi encadré par des espaces si l’on veut traiter uniformément ses premier et dernier mots.

Question 8

#

Nommer un algorithme de recherche de motif plus efficace que la recherche naïve.

Indice

Les décalages doivent être calculés à partir de ce qui a été comparé.

Comprendre la correction

Boyer-Moore. Il compare le motif depuis la droite et utilise les informations d’une discordance pour justifier des décalages parfois supérieurs à un caractère. Son intérêt ne se réduit pas à décaler systématiquement de la longueur du motif, ce qui peut manquer une occurrence.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)
def recherche(motif, texte):
    """Renvoie le premier indice du motif, ou -1 si absent."""
    n = len(texte)
    m = len(motif)
    i = 0
    while i + m - 1 < n:
        j = m - 1
        while texte[i+j] == motif[j]:
            if j == 0:
                return i
            j = j - 1
        i = i + m
    return -1

Question 9

#

Quel est le résultat de recherche("na", "banana") ?

Indice

Suivez le code fourni, même s’il sera ensuite remis en question.

Comprendre la correction

2. À i = 0, le a final correspond puis n diffère de b, donc échec. Le code décale de m = 2. À i = 2, les caractères texte[3] = a puis texte[2] = n correspondent : la fonction renvoie 2. L’autre occurrence à l’indice 4 n’est pas examinée.

Alignement iComparaison depuis la droiteDécision
0texte[1]=a égale motif[1]=a, puis texte[0]=b diffère de nDécaler de2
2texte[3]=a puis texte[2]=n correspondentRenvoyer2
Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

Question 10

#

Donner un motif et un texte pour lesquels la fonction ne renvoie pas le résultat attendu, en justifiant.

Indice

Placez une occurrence à un indice que les sauts de longueur m ne visiteront jamais.

Comprendre la correction

Avec motif = "ab" et texte = "xaby", la réponse correcte est 1 mais la fonction renvoie -1. Elle teste i = 0 puis i = 2 et ne teste jamais l’alignement i = 1. Décaler de toute la longueur du motif après n’importe quel échec est donc injustifié. Remplacer le décalage par 1 rétablit une recherche correcte pour un motif non vide, mais perd les optimisations de Boyer-Moore. Le code nécessite aussi un traitement séparé du motif vide.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)
Détecter une occurrence sautée par le programmeUn atelier pour expérimenter

Choisissez un cas et comparez les alignements espacés de m caractères aux alignements espacés d’un seul caractère. Les résultats affichent la première occurrence détectée.

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

Le programme renvoie -1

La première occurrence réelle est à l’indice 1. Un saut doit être justifié pour ne pas ignorer une occurrence intermédiaire.

Alignement iFenêtre testéeVerdict
0xaDiscordance
2byDiscordance

L’optimisation ne doit jamais supprimer un cas possible sans preuve. Un contre-exemple court rend une erreur immédiatement visible.

Revoir les notions de cet exercice

Exercice 3 · 8 points

Routes IP et arbre de préfixes par octets

Une IPv4 comporte quatre octets, soit 32 bits. Dans a.b.c.d/n, les n bits de gauche forment la partie réseau, les autres la partie machine. Voici la table de R0 :

$ ip route show scope global table 100
default via 10.0.0.5 dev out2
172.16.0.0/25
    nexthop via 10.0.0.7 dev out3 weight 1
    nexthop via 10.0.0.9 dev out4 weight 1
172.16.0.10 nexthop via 10.0.0.3 dev out1
172.16.0.20 nexthop via 10.0.0.3 dev out1
172.16.0.30 nexthop via 10.0.0.3 dev out1
172.16.0.40 nexthop via 10.0.0.3 dev out1

La figure 1 du calculateur IPv4 pour 172.16.0.0/25 fournit :

PropriétéValeur
CIDR25
Masque255.255.255.128
Masque inverse0.0.0.127
Adresse réseau172.16.0.0
Première adresse hôte172.16.0.1
Dernière adresse hôte172.16.0.126
Broadcast172.16.0.127
Adresses hôtes disponibles126
Plage de filtre incluant réseau et broadcast172.16.0.0 à 172.16.0.127, soit 128 adresses

Question 1

#

Donner le nombre maximal de machines sur 172.16.0.0/25.

Indice

Le nombre d’adresses d’un filtre n’est pas le nombre d’hôtes utilisables.

Comprendre la correction

126 : il reste 32 - 25 = 7 bits machine, donc 2⁷ = 128 adresses, moins réseau et diffusion. La figure distingue à juste titre les 128 adresses du bloc de ses 126 adresses hôtes.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
IP destinationProchain saut
172.16.0.10À compléter
172.16.0.100À compléter
172.16.0.200À compléter

Question 2

#

Donner le prochain saut pour 172.16.0.10, 172.16.0.100 et 172.16.0.200.

Indice

Une route hôte exacte l’emporte sur une route de réseau plus générale.

Comprendre la correction
DestinationProchain sautJustification
172.16.0.1010.0.0.3Route exacte, prioritaire sur /25
172.16.0.10010.0.0.7 ou 10.0.0.9Deux prochains sauts de poids égal pour /25
172.16.0.20010.0.0.5Hors du /25 et aucune route spécifique : défaut

Le routeur privilégie la route la plus spécifique, c’est-à-dire le préfixe le plus long. Pour .100, la table autorise deux sorties ; on ne peut en choisir une seule à partir de ces informations.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
PC .10S2R2R0R1R3R4S1R5
Architecture initiale : R0, ses voisins et R5 encore isolé
Lire les connexions du schéma
  • PC .10 relié à S2
  • S2 relié à R2
  • R2 relié à R0
  • R0 relié à R1
  • R0 relié à R3
  • R0 relié à R4
  • R3 relié à S1
  • R4 relié à S1

S2 relie les machines 172.16.0.10, .20 et .30 à R2. R1 dessert Internet. S1 dessert 172.16.0.0/25. R5 dessert déjà 192.168.0.0/24 mais n’a pas encore de lien vers le reste du réseau.

Question 3

#

Donner les adresses des interfaces de R1, R2, R3 et R4 à joindre depuis R0.

Indice

Associez chaque destination de la table à sa branche du dessin.

Comprendre la correction
RouteurAdresse côté R0
R110.0.0.5
R210.0.0.3
R310.0.0.7
R410.0.0.9

R1 donne accès à Internet, donc correspond à la route par défaut. R2 dessert les hôtes .10, .20 et .30 du switch S2 (ainsi que la route hôte .40 de la table). R3 et R4 atteignent le réseau derrière S1 ; les noms out3/out4 permettent l’association représentée dans le schéma.

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

Question 4

#

Compléter le schéma en ajoutant R5-S1, avec R5 à 172.16.0.3, et R5-R2, avec R5 à 10.0.0.1.

Indice

Un routeur possède une adresse distincte sur chaque réseau auquel il est directement connecté.

Comprendre la correction
PC .10S2R2R0R1R3R4S1R5
Architecture initiale : R0, ses voisins et R5 encore isolé
Lire les connexions du schéma
  • PC .10 relié à S2
  • S2 relié à R2
  • R2 relié à R0
  • R0 relié à R1
  • R0 relié à R3
  • R0 relié à R4
  • R3 relié à S1
  • R4 relié à S1
Nouvelle liaisonAdresse de l’interface R5
S1 ↔ R5172.16.0.3
R2 ↔ R510.0.0.1

Ces deux liens s’ajoutent à l’architecture initiale ; la liaison de R5 vers 192.168.0.0/24 est conservée. Les adresses désignent deux interfaces différentes du même routeur.

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

RIP minimise le nombre de routeurs par lesquels les paquets transitent.

Question 5

#

Avec RIP, donner et justifier le coût du chemin de la machine 172.16.0.10 vers 192.168.0.0/24.

Indice

Le sujet exprime ici la métrique comme le nombre de routeurs traversés depuis la machine.

Comprendre la correction

Le chemin passe par machine → S2 → R2 → R5 → réseau 192.168.0.0/24. Il traverse deux routeurs, R2 et R5, donc son coût vaut 2 selon la convention explicitement donnée ici (nombre de routeurs traversés). La portion entre R2 et R5 contient un seul lien inter-routeurs. Les commutateurs ne se comptent pas comme routeurs.

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

OSPF minimise le coût total. Toutes les liaisons sont Ethernet, sauf R0-R2, R0-R4, R4-S1 et S1-R5 qui sont en fibre optique. Ethernet : 1 Gbit/s ; fibre : 10 Gbit/s.

Question 6

#

Calculer le coût Ethernet à 1 Gbit/s et fibre à 10 Gbit/s, avec coût = 10⁹ / BP.

Indice

La bande passante de la fibre est dix fois plus grande.

Comprendre la correction
LiaisonCalculCoût
Ethernet10⁹ / 10⁹1
Fibre optique10⁹ / 10¹⁰0,1

Le débit doit être converti en bit/s. La constante de référence vaut ici 10⁹, pas 10⁸ comme dans d’autres sujets.

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

Question 7

#

Avec OSPF, donner et justifier le coût du chemin de 172.16.0.10 à 192.168.0.0/24.

Indice

Comparez le lien Ethernet R2-R5 à la chaîne de quatre fibres ; les autres liaisons sont communes.

Comprendre la correction

Le chemin favorise la fibre : machine → S2 → R2 → R0 → R4 → S1 → R5 → réseau destination. La partie variable de R2 à R5 utilise quatre liaisons fibre, donc coûte 0,4, contre 1 pour le lien Ethernet direct R2-R5. En additionnant toutes les liaisons physiques dessinées, machine-S2, S2-R2 et R5-réseau destination apportent trois liaisons Ethernet communes, soit un coût total 3,4 contre 4 pour le chemin direct. L’essentiel du choix est la comparaison 0,4 < 1 ; préciser les extrémités évite de confondre coût de transit inter-routeurs et coût physique de bout en bout. Dans un réseau OSPF réel, un segment Ethernet partagé est modélisé au niveau des interfaces de routage, pas comme un routeur supplémentaire S1 : on applique ici le modèle de liaisons du dessin.

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

Partie B : arbre de préfixes

IP destinationProchain saut
168.16.0.10010.0.0.3
168.16.0.20010.0.0.4
203.18.17.1010.0.0.5
0.0.0.010.0.0.6
168.16.0.1010.0.0.7
203.18.15.3010.0.0.8
203.18.15.2010.0.0.9
203.18.15.1010.0.0.10

Chaque nœud contient un dictionnaire associant une étiquette à un enfant. Le chemin depuis la racine reconstitue une adresse ; si cette adresse est une destination, le nœud contient son prochain saut. Le texte parle de lettres et de mots, mais le code et la figure découpent effectivement les adresses par octets. Un nœud ayant des enfants représente un préfixe de destinations, et non de prochains sauts comme l’indique par erreur une phrase du sujet.

Figure après les quatre premières insertions :

racine
├─ 168 → 16 → 0
│              ├─ 100 : 10.0.0.3
│              └─ 200 : 10.0.0.4
├─ 203 → 18 → 17 → 10 : 10.0.0.5
└─ 0 → 0 → 0 → 0 : 10.0.0.6

Question 8

#

Compléter l’arbre pour les huit entrées de la table de routage.

Indice

Ajoutez les nouveaux suffixes sans recopier les branches de préfixe déjà communes.

Comprendre la correction
racine
├─ 168 → 16 → 0
│              ├─ 100 : saut 10.0.0.3
│              ├─ 200 : saut 10.0.0.4
│              └─ 10  : saut 10.0.0.7
├─ 203 → 18
│        ├─ 17 → 10 : saut 10.0.0.5
│        └─ 15
│           ├─ 30 : saut 10.0.0.8
│           ├─ 20 : saut 10.0.0.9
│           └─ 10 : saut 10.0.0.10
└─ 0 → 0 → 0 → 0 : saut 10.0.0.6

On partage les préfixes d’octets identiques. Les adresses commencent bien par 168 dans cette partie, et non par 172 comme dans la partie A. Le prochain saut est stocké au nœud terminal ; ce n’est pas une étiquette de branche.

Dans cet arbre, une branche 168-16-0 est partagée par les trois destinations terminées par100,200 et10. Les caractères "1" et "0" ne sont pas des niveaux distincts : l’étiquette "100" est un seul octet stocké comme chaîne. Cela distingue un arbre de préfixes par octets d’un arbre binaire par bits et explique pourquoi les adresses IPv4 complètes utilisent quatre niveaux.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)
class Noeud:
    def __init__(self):
        self.saut = ""
        self.dict_adjacence = {}

    def rechercher_enfant(self, octet):
        if not (octet in self.dict_adjacence):
            return ...
        return ...

saut est une chaîne non vide sur un nœud correspondant à une destination. dict_adjacence contient les couples octet/enfant.

Question 9

#

Compléter les lignes 7 et 8 de rechercher_enfant.

Indice

La méthode renvoie le nœud enfant, pas l’octet qui l’identifie.

Comprendre la correction
return None
# Après le if :
return self.dict_adjacence[octet]

Si la clé octet est absente, on renvoie None. Sinon on lit le nœud associé à cette clé. Le test évite une exception KeyError sur un enfant inexistant.

Voir la question dans le sujet PDF, p. 12 (nouvel onglet)
class Arbre:
    def __init__(self):
        self.racine = Noeud()

    def rechercher(self, ip_dest):
        ...

    def mystere(self, ip_dest, saut):
        noeud = self.racine
        octets = ip_dest.split(".")
        i = 0
        while i < len(octets):
            noeud_suivant = noeud.rechercher_enfant(octets[i])
            if noeud_suivant is not None:
                noeud_suivant = Noeud()
                noeud.dict_adjacence[octets[i]] = noeud_suivant
            noeud = noeud_suivant
            i = i + 1
        noeud.saut = saut

split découpe selon un séparateur. Exemple opérationnel : "Bonjour-le-monde".split("-") donne ["Bonjour", "le", "monde"]. Le séparateur doit être exactement le même caractère que celui du texte.

Question 10

#

Compléter le corps de Arbre.rechercher(ip_dest), qui renvoie le nœud de destination ou None.

Indice

Conservez le nœud courant et remplacez-le par son enfant à chaque octet.

Comprendre la correction
noeud = self.racine
for octet in ip_dest.split("."):
    noeud = noeud.rechercher_enfant(octet)
    if noeud is None:
        return None
if noeud.saut == "":
    return None
return noeud

On descend d’un niveau pour chaque octet. Dès qu’une branche manque, l’adresse est absente. Le contrôle final de saut évite de considérer un simple préfixe non terminal comme une destination. Dans le cadre des IPv4 complètes de quatre octets fournies, tous les terminaux insérés sont bien marqués.

Rechercher 203.18.15.20 visite successivement les enfants "203", "18", "15", "20", puis renvoie le nœud dont saut vaut10.0.0.9. Rechercher 203.18.15.99 partage trois niveaux puis échoue sur "99". Les clés de dict_adjacence sont des chaînes issues de split ; utiliser l’entier203 comme clé ne retrouverait pas la branche "203".

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

Question 11

#

Expliquer et justifier le rôle de mystere.

Indice

Une branche doit être créée quand elle manque, pas quand elle existe déjà.

Comprendre la correction

Son intention est d’insérer une destination et son prochain saut : parcourir les octets, créer les branches manquantes, puis affecter saut au terminal. Mais la condition imprimée est inversée : if noeud_suivant is not None remplace les branches existantes et laisse None quand une branche manque, ce qui provoque une erreur. Il faut la corriger en if noeud_suivant is None:. Avec cette correction, les préfixes existants sont conservés ; réinsérer une même destination met à jour son prochain saut.

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

Question 12

#

Proposer un algorithme qui inverse la table de routage stockée dans l’arbre.

PythonRetrouver toutes les destinations derrière un même prochain sautÉcrivez votre solution et mettez-la à l’épreuve

Implémentez en Python l’algorithme d’inversion demandé : inverser(arbre) renvoie un dictionnaire associant chaque saut à la liste de ses adresses de destination. La racine n’a pas d’octet ; chaque arête est étiquetée par un octet sous forme de chaîne dans dict_adjacence. Un saut non vide désigne une route enregistrée. Le regroupement en listes conserve toutes les destinations si plusieurs utilisent le même saut. Les exemples complémentaires sont construits avec la représentation du sujet, sans modifier l’arbre.

def inverser(arbre):
    # À vous de jouer
    pass

Les cas de test proposés :

  • Un arbre sans route : Les nœuds sans saut ne créent pas d’entrée vide dans le résultat.
  • La destination 203.18.15.20 du sujet : Les quatre octets sont réunis avec des points ; le saut devient la clé de la réponse.
  • Partager un préfixe sans mélanger les branches : 100, 200 et 10 sont chacun un octet, pas des niveaux composés de chiffres.
  • Deux destinations partagent un saut : Une affectation simple inverse[saut] = destination écraserait la première route.
  • Des branches éloignées gardent leur propre chemin : Le préfixe doit être indépendant pour chaque branche sœur. L’arbre reste intact après la lecture.
Indice

Il faut conserver le chemin de la racine au terminal pour retrouver l’adresse complète.

Comprendre la correction

Effectuer un parcours en profondeur en mémorisant la liste des octets du chemin. Lorsqu’un nœud a un saut non vide, reconstituer la destination avec ".".join(prefixe), puis l’ajouter au dictionnaire inverse sous la clé saut. Continuer récursivement pour chaque enfant en ajoutant son octet au chemin. Comme plusieurs destinations peuvent partager un même prochain saut, utiliser une liste de destinations par saut afin de ne rien perdre ; dans la table du sujet, les huit sauts sont distincts.

def inverser(arbre):
    inverse = {}
    def visiter(noeud, prefixe):
        if noeud.saut != "":
            destination = ".".join(prefixe)
            if noeud.saut not in inverse:
                inverse[noeud.saut] = []
            inverse[noeud.saut].append(destination)
        for octet, enfant in noeud.dict_adjacence.items():
            visiter(enfant, prefixe + [octet])
    visiter(arbre.racine, [])
    return inverse

L’algorithme visite chaque nœud une fois. Il ne suffit pas d’échanger aveuglément une clé et une valeur dans un dictionnaire si les valeurs peuvent se répéter.

Pour la table fournie, l’entrée inverse 10.0.0.9 contient la destination203.18.15.20. Si un second réseau utilisait le même prochain saut, sa destination serait ajoutée à cette liste plutôt que d’écraser la première. Le préfixe transmis à chaque appel est construit avec prefixe+[octet], une nouvelle liste : les branches sœurs n’altèrent donc pas réciproquement leurs chemins.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)
Suivre les octets dans la table de routageUn atelier pour expérimenter

Choisissez une destination existante ou absente, puis avancez dans ses quatre octets. Repérez précisément où deux adresses partagent leurs branches et où une recherche échoue.

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

Préfixe partagé : 203

Le dictionnaire de chaque nœud choisit le prochain octet. Plusieurs adresses partagent une branche tant que leurs préfixes sont identiques.

Destination encore compatibleProchain saut
203.18.17.1010.0.0.5
203.18.15.3010.0.0.8
203.18.15.2010.0.0.9
203.18.15.1010.0.0.10

Un arbre de préfixes n’est pas un ABR : chaque octet choisit une branche par clé de dictionnaire, sans comparaison gauche/droite.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Distinguez une valeur absente d’un préfixe existant mais non terminal.
  • Une figure de routage et sa table se lisent ensemble : reliez chaque destination à la bonne branche.
  • Pour prouver qu’un programme est incorrect, donnez une entrée minimale et tracez son exécution exacte.

Retrouver ces notions dans d’autres sujets

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

Énoncé : sujet 26-NSIJ2PO1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.