Épreuve écrite · 2025 · Jour 1 - Remplacement

Bac NSI 2025 Asie remplacement jour 1 : corrigé du sujet 25-NSIPE4

Comment reconnaître une insertion SQL impossible, une boucle de routage et un algorithme qui semble trier mais supprime des valeurs ? Ce sujet fait travailler la précision des modèles autant que la programmation. Les corrections reconstruisent les tables et graphes, explicitent les conventions et testent les solutions sur des contre-exemples.

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

Analyser les résultats d’un club de tennis avec SQL

Le club stocke ses joueurs, les éditions de tournois et leurs participations. Les requêtes demandées sont écrites en SQL. On peut utiliser SELECT, FROM, WHERE avec AND ou OR, JOIN…ON, INSERT, UPDATE, DELETE et ORDER BY. La table joueurs possède une clé primaire entière id ; nom et prenom sont textuels, genre vaut 1 pour un joueur et 2 pour une joueuse.

joueurs.idnomprenomgenre
1DurandEnzo1
2PanaisLise2
3AlpinLucas1
4BenardElsa2
5BenardEmma2

Question 1

#

Expliquer pourquoi nom ne peut pas être choisi comme clé primaire.

Indice

Cherchez un nom présent deux fois dans le tableau.

Comprendre la correction

Le nom Benard apparaît pour deux personnes distinctes, Elsa et Emma. Une clé primaire doit identifier une ligne sans ambiguïté et ses valeurs doivent être uniques et non nulles. Le nom ne satisfait donc pas l’unicité ; l’identifiant id distingue ici les lignes 4 et 5. Une clé primaire doit respecter cette règle pour toute la base, pas seulement pour un extrait qui semblerait ne contenir aucun doublon.

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

Question 2

#

Obtenir les noms et prénoms des joueuses du club.

Indice

Le genre est encodé par un entier, pas par le texte « femme ».

Comprendre la correction
SELECT nom, prenom FROM joueurs WHERE genre = 2;

Le filtre retient le code 2, puis la projection conserve uniquement les deux colonnes demandées. Sur l’extrait, les résultats sont Panais Lise, Benard Elsa et Benard Emma. Sans ORDER BY, SQL ne garantit pas l’ordre de ces lignes ; il n’est pas demandé ici.

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

Question 3

#

Ajouter Nathan Gervais, en choisissant un identifiant cohérent avec la base.

Indice

Séparez le nom de famille du prénom et choisissez une clé inutilisée.

Comprendre la correction
INSERT INTO joueurs (id, nom, prenom, genre) VALUES (6, 'Gervais', 'Nathan', 1);

L’identifiant 6 n’apparaît pas dans l’extrait. Les noms de colonnes rendent explicite l’ordre des valeurs, en particulier nom avant prénom. Comme le tableau est présenté comme un extrait, il faudrait vérifier dans la base complète que 6 est réellement libre ; le choix est cohérent avec les données visibles.

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

La table competitions décrit des éditions : id est une clé primaire entière, nom est textuel et annee entière. Un même nom de tournoi peut apparaître plusieurs années.

competitions.idnomannee
1Open de Tours2022
2Tournoi de Blois2023
3Open de Toums2023
4Open de Nantes2023
5Open de Nantes2021
6Tournoi d’Angers2024

Question 4

#

Corriger le nom de la compétition d’identifiant 3 en Open de Tours.

Indice

La ligne est identifiée par sa clé, pas par sa position affichée.

Comprendre la correction
UPDATE competitions SET nom = 'Open de Tours' WHERE id = 3;

Le WHERE cible la clé primaire de la seule ligne erronée. Sans cette clause, toutes les éditions seraient renommées. L’identifiant est préférable au libellé fautif pour désigner précisément la ligne.

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

Question 5

#

Obtenir les noms des tournois et leur année, en triant par année croissante.

Indice

Le tri porte sur annee, la projection sur nom et annee.

Comprendre la correction
SELECT nom, annee FROM competitions ORDER BY annee ASC;

ASC demande l’ordre croissant et pourrait être omis, car c’est le sens par défaut. Les éditions de 2023 peuvent apparaître dans n’importe quel ordre relatif. Un second critère de tri serait possible, mais n’est pas requis. On ne supprime pas les éditions ayant le même nom : leurs années les distinguent.

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

La table participe relie joueurs et competitions. Tous ses attributs sont entiers. id_joueur référence joueurs.id et id_compet référence competitions.id ; leur couple constitue la clé primaire. nb_fautes compte les fautes directes, nb_gagnant les coups gagnants et aces les services gagnants non touchés.

id_joueurid_competnb_fautesnb_gagnantaces
1116121
311482
137152
331281
2215103
2410170
447184
5411151

Question 6

#

Obtenir nom et prénom des joueurs et joueuses ayant plus de coups gagnants que de fautes lors d’une compétition. Une personne peut apparaître plusieurs fois si elle remplit le critère à plusieurs compétitions.

Indice

La condition est évaluée par participation, pas sur une somme annuelle.

Comprendre la correction
SELECT joueurs.nom, joueurs.prenom
FROM joueurs JOIN participe ON joueurs.id = participe.id_joueur
WHERE participe.nb_gagnant > participe.nb_fautes;

La jointure relie chaque ligne de statistiques à son joueur grâce à id_joueur. Le test compare deux colonnes de cette même participation. On n’utilise pas DISTINCT, car l’énoncé souhaite conserver les répétitions liées à plusieurs performances. Sur l’extrait : Enzo Durand, Lise Panais, Elsa Benard et Emma Benard remplissent chacun le critère une fois. Le nombre d’aces n’intervient pas dans cette condition.

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

Question 7

#

Obtenir nom et prénom des joueuses et noms des compétitions auxquelles elles ont participé en 2023.

SQLRetrouver les joueuses et leurs compétitions de 2023Écrivez votre solution et mettez-la à l’épreuve

Renvoyez nom et prénom des joueuses ainsi que le nom de chaque compétition à laquelle elles ont participé en 2023. Le genre 2 représente une joueuse.

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

Les cas de test proposés :

  • Participations du tableau officiel : Les joueurs, compétitions et participations proviennent du sujet. Lise apparaît deux fois car elle a joué deux compétitions distinctes.
  • Cas complémentaire : même tournoi sur deux années : Jeu complémentaire pédagogique, distinct des données officielles. Le nom du tournoi ne fixe pas son édition. Il faut filtrer à la fois l’année et le genre.
Indice

La table participe sert de pont entre les personnes et les éditions.

Comprendre la correction
SELECT joueurs.nom, joueurs.prenom, competitions.nom
FROM joueurs
JOIN participe ON joueurs.id = participe.id_joueur
JOIN competitions ON competitions.id = participe.id_compet
WHERE joueurs.genre = 2 AND competitions.annee = 2023;

Les trois tables sont nécessaires : le genre et l’identité proviennent de joueurs, l’année et le tournoi de competitions, et le lien de participe. Deux filtres sont combinés par AND. Lise apparaît pour Blois et Nantes ; Elsa et Emma apparaissent pour Nantes. Préfixer les colonnes nom évite une ambiguïté entre le nom de famille et le nom du tournoi.

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

Question 8

#

Quelle précaution faut-il prendre avant de supprimer Emma Benard de joueurs ? Justifier.

Indice

Repérez les lignes dont la clé étrangère vaut 5.

Comprendre la correction

La participation (id_joueur=5, id_compet=4) référence encore Emma. Il faut traiter cette ligne dépendante avant de supprimer la ligne parent, sinon une contrainte de clé étrangère peut refuser l’opération. Pour une suppression complète de ses participations :

DELETE FROM participe WHERE id_joueur = 5;
DELETE FROM joueurs WHERE id = 5;

Une politique de suppression en cascade pourrait automatiser ce traitement si elle était déclarée, mais le sujet ne l’indique pas. Dans une application qui conserve l’historique sportif, on préférerait souvent marquer une personne comme inactive ; ce serait un autre choix de modèle.

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

Question 9

#

Insérer Agathe Turion, joueuse, ainsi que sa participation au Tournoi de Blois 2024 : 14 fautes, 15 coups gagnants, 2 aces. Le sujet impose id=7 pour joueurs et id=5 pour competitions. Donner les requêtes dans le bon ordre.

Indice

Comparez la clé imposée à celles déjà présentes.

Comprendre la correction

L’intention est de créer d’abord la joueuse et le tournoi, puis leur participation. Mais l’extrait contient déjà la compétition d’id 5, Open de Nantes 2021. Les contraintes données sont incompatibles avec une simple insertion utilisant à nouveau 5 : elle violerait l’unicité de la clé primaire. Il faut signaler cette incohérence, sans écraser l’édition existante.

Si l’identifiant de compétition 5 était libre comme la question semble le supposer, les requêtes attendues seraient :

INSERT INTO joueurs (id,nom,prenom,genre) VALUES (7,'Turion','Agathe',2);
INSERT INTO competitions (id,nom,annee) VALUES (5,'Tournoi de Blois',2024);
INSERT INTO participe (id_joueur,id_compet,nb_fautes,nb_gagnant,aces) VALUES (7,5,14,15,2);

Avec la base réellement reproduite, une correction cohérente consiste à réserver un identifiant de compétition libre, par exemple 7, et à utiliser ce même identifiant dans participe. Les espaces d’identifiants de joueurs et competitions sont indépendants : chacun peut utiliser 7.

INSERT INTO joueurs (id,nom,prenom,genre) VALUES (7,'Turion','Agathe',2);
INSERT INTO competitions (id,nom,annee) VALUES (7,'Tournoi de Blois',2024);
INSERT INTO participe (id_joueur,id_compet,nb_fautes,nb_gagnant,aces) VALUES (7,7,14,15,2);

Ces deux variantes sont alternatives ; ne pas exécuter les deux séries. Dans une vraie base, les trois insertions forment une transaction afin de ne pas laisser une opération partiellement réalisée.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)
Faire parler la table des participationsUn atelier pour expérimenter

Choisissez une édition et le critère coups gagnants > fautes. Les résultats sont calculés sur les huit participations du sujet ; une personne peut revenir dans plusieurs lignes.

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

4 participations retenues.

Les filtres portent sur une participation et son édition. Le tableau ne dédoublonne pas les personnes.

PersonneTournoiAnnéeFautesGagnants
Enzo DurandOpen de Tours2023715
Lise PanaisOpen de Nantes20231017
Elsa BenardOpen de Nantes2023718
Emma BenardOpen de Nantes20231115

La clé de jointure, les filtres et la gestion des doublons répondent à trois questions différentes.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Corriger un routage puis comparer RIP et OSPF

Le réseau relie sept sous-réseaux : Navigation N (tour de contrôle), Guichets G, Achats A, Sécurité S, Portes P, Bagages B et Commerces C. Les routeurs portent les noms R1 à R7. Le graphe ci-dessous reconstruit les liaisons inter-routeurs ; le tableau précise les connexions vers les sous-réseaux.

R1R2R3R4R5R6R7
Figure 1 : réseau de routeurs de l’aéroport
Lire les connexions du schéma
  • R1 relié à R2
  • R1 relié à R3
  • R2 relié à R3
  • R2 relié à R5
  • R3 relié à R4
  • R3 relié à R6
  • R4 relié à R6
  • R4 relié à R7
  • R5 relié à R6
  • R6 relié à R7
Sous-réseauRouteur associé
Navigation NR1
Bagages BR2
Guichets GR3
Achats AR4
Portes PR5
Sécurité SR6
Commerces CR7

Partie A : Commerces possède déjà 207 machines. Son adresse réseau est 137.254.128.0 et son masque 255.255.255.0, soit /24 : les 24 premiers bits sont communs. À l’exception du routeur, les machines sont numérotées par IP croissante depuis la première adresse disponible.

Question 1

#

Parmi 137.254.128.200 et 137.254.128.210, laquelle correspond à une machine déjà connectée à Commerces ?

Indice

Distinguez le préfixe de réseau du dernier octet qui numérote les hôtes.

Comprendre la correction

L’adresse 137.254.128.200 est déjà attribuée. Les 207 machines sont placées depuis les premières adresses utilisables ; 200 appartient à cette plage, contrairement à 210. La réservation éventuelle d’une adresse pour le routeur ne change pas la distinction entre ces deux propositions.

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

Question 2

#

Peut-on ajouter 132 machines au sous-réseau Commerces ? Justifier.

Indice

Calculez la capacité des 8 bits restants, puis comparez au besoin total.

Comprendre la correction

Non. Un /24 laisse 8 bits d’hôte, soit 256 adresses. En excluant l’adresse réseau et l’adresse de diffusion, on obtient 254 adresses d’hôtes, dont une pour le routeur. Les 207 machines occupent déjà la grande majorité de la capacité ; 207 + 132 = 339 dépasse même les 254 adresses utilisables. Le nombre exact d’adresses restantes dépend du comptage du routeur dans les 207, mais cette ambiguïté ne change pas l’impossibilité.

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

Partie B : le tableau indique, pour chaque source en colonne et chaque destination en ligne, la passerelle à contacter. Une case vide correspond au routeur lui-même. Par exemple R3 envoie vers R2 pour atteindre R5.

Destination / SourceR1R2R3R4R5R6R7
R1-R1R1R6R2R4R4
R2R2-R2R3R2R5R6
R3R3R3-R3R6R3R4
R4R3R3R4-R6R4R4
R5R2R5R2R6-R5R6
R6R2R5R6R6R6-R6
R7R3R3R6R7R6R7-

Question 3

#

Donner les routeurs traversés d’une machine de Navigation à une machine de Commerces.

Indice

La destination reste R7 durant toute la lecture.

Comprendre la correction

Le chemin imposé par les tables est R1 → R3 → R6 → R7. Navigation est reliée à R1 et Commerces à R7. À chaque étape, on lit la ligne de destination R7 dans la colonne du routeur courant : R1 conseille R3, R3 conseille R6, R6 conseille R7. Il faut suivre les tables données, même si un autre chemin est visible sur le graphe.

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

Question 4

#

Quel problème survient de Commerces vers Navigation ?

Indice

Notez les routeurs déjà rencontrés en suivant la destinationR1.

Comprendre la correction

Le message part de R7 vers R4 pour atteindre R1. Mais R4 envoie vers R6 et R6 renvoie vers R4 : R7 → R4 → R6 → R4 → R6…. Il existe une boucle de routage et le message ne rejoint pas R1. Les liaisons physiques existent ; le défaut vient de l’incohérence des tables. Dans un réseau IP réel, une durée de vie peut limiter la circulation, mais elle ne répare pas les routes.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
R1R2R3R5
Figure 2 : réseau restreint
Lire les connexions du schéma
  • R1 relié à R2
  • R1 relié à R3
  • R2 relié à R3
  • R2 relié à R5

Question 5

#

Donner le dictionnaire du réseau restreint à R1, R2, R3 et R5 de la figure 2.

Indice

Chaque arête doit être enregistrée dans les deux sens.

Comprendre la correction
g = {
    'R1': ['R2', 'R3'],
    'R2': ['R1', 'R3', 'R5'],
    'R3': ['R1', 'R2'],
    'R5': ['R2']
}

Le triangle R1-R2-R3 est complété par la liaison R2-R5. Le graphe est non orienté : chaque liaison apparaît dans les listes des deux extrémités. L’ordre des voisins peut varier sans changer le graphe.

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

Question 6

#

Rappeler le principe d’une fonction récursive.

Indice

Un appel à soi-même ne suffit pas : expliquez aussi comment l’exécution s’arrête.

Comprendre la correction

Une fonction récursive s’appelle elle-même sur un problème lié, généralement plus petit. Un cas de base fournit une réponse sans nouvel appel, et les appels doivent progresser vers ce cas pour terminer. Dans un parcours de graphe, l’ensemble des sommets encore admissibles peut diminuer ; sans contrôle des sommets déjà visités, un cycle pourrait provoquer des appels indéfinis.

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

On dispose de liste_chemins(graphe,r_depart,r_arrivee). Sur le graphe restreint :

liste_chemins(g, 'R1', 'R5')
# [['R1','R2','R5'], ['R1','R3','R2','R5']]

Question 7

#

Écrire plus_court_chemin en utilisant liste_chemins, qui renvoie tous les chemins entre départ et arrivée. Le résultat doit être l’un des plus courts au sens de RIP.

Indice

Comparer les longueurs des listes de routeurs.

Comprendre la correction
def plus_court_chemin(graphe, r_depart, r_arrivee):
    chemins = liste_chemins(graphe, r_depart, r_arrivee)
    if not chemins:
        return None
    meilleur = chemins[0]
    for chemin in chemins[1:]:
        if len(chemin) < len(meilleur):
            meilleur = chemin
    return meilleur

Une liste de k routeurs décrit k−1 liaisons : minimiser sa longueur revient donc à minimiser le nombre de sauts. On initialise le meilleur avec un chemin réel puis on le remplace uniquement lorsqu’une liste plus courte apparaît. La garde sur la liste vide donne None lorsqu’aucun trajet n’existe. En cas d’égalité, la première solution est conservée.

Cette méthode suppose que liste_chemins fournit des chemins sans répétition inutile. Son principal défaut reste le nombre potentiellement très grand de chemins à construire ; la recherche du minimum ne compense pas ce coût.

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

Construire tous les chemins est coûteux. Le sujet propose un parcours en largeur avec dict_chemins, qui associe à chaque sommet découvert son chemin depuis le départ. Le code à compléter est :

def plus_court_chemin_largeur(graphe, r_depart, r_arrivee):
    dict_chemins = {}
    L = [r_depart]
    sommets_marques = [r_depart]
    dict_chemins[r_depart] = [r_depart]
    for r in L:
        for s_r in graphe[r]:
            if not s_r in sommets_marques:
                sommets_marques.append(...)
                dict_chemins[s_r] = dict_chemins[r] + [s_r]
                if s_r == r_arrivee:
                    return ...
                L.append(s_r)

Question 8

#

Compléter plus_court_chemin_largeur : marquer le voisin rencontré et renvoyer le chemin quand ce voisin est l’arrivée.

Indice

Le dictionnaire contient déjà le chemin complet verss_r lorsque le test d’arrivée réussit.

Comprendre la correction
def plus_court_chemin_largeur(graphe, r_depart, r_arrivee):
    if r_depart == r_arrivee:
        return [r_depart]
    dict_chemins = {r_depart: [r_depart]}
    L = [r_depart]
    sommets_marques = [r_depart]
    for r in L:
        for s_r in graphe[r]:
            if s_r not in sommets_marques:
                sommets_marques.append(s_r)
                dict_chemins[s_r] = dict_chemins[r] + [s_r]
                if s_r == r_arrivee:
                    return dict_chemins[s_r]
                L.append(s_r)
    return None

Les deux compléments demandés sont s_r dans append et dict_chemins[s_r] dans le return. La version ci-dessus ajoute les cas départ=arrivée et arrivée inaccessible. La liste L est étendue en fin de parcours et visitée dans son ordre : les sommets à une distance donnée sont traités avant ceux de distance supérieure. Le premier chemin atteignant l’arrivée est donc minimal en nombre de sauts.

Le voisin est marqué dès sa découverte, avant son ajout à la liste. Cela empêche que plusieurs routes le mettent plusieurs fois en attente. Le chemin du voisin reprend celui du sommet courant et ajoute seulement ce voisin ; il faut créer une nouvelle liste, et non modifier tous les chemins qui pourraient partager le même objet.

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

Question 9

#

Écrire table_routage(graphe, routeur), qui associe à chaque destination sa passerelle, à l’aide des fonctions précédentes.

Indice

La passerelle est le premier voisin du chemin, pas sa destination finale.

Comprendre la correction
def table_routage(graphe, routeur):
    table = {}
    for destination in graphe:
        if destination != routeur:
            chemin = plus_court_chemin_largeur(graphe, routeur, destination)
            if chemin is not None:
                table[destination] = chemin[1]
    return table

Le chemin commence par le routeur source : la passerelle est donc son deuxième élément, d’indice 1. On omet l’entrée vers soi, qui n’a pas de prochain routeur ; on omet aussi les destinations inaccessibles. La garde destination!=routeur évite un accès à chemin[1] pour une liste d’un seul sommet. Dans ce réseau connecté, toutes les autres destinations sont accessibles.

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

Partie C : le réseau emploie trois types de connexion. La figure 3 associe E, FE ou F à chaque liaison. Le protocole OSPF privilégie le coût total minimal, à la différence du seul nombre de sauts de RIP.

FEFFFEEEFFFFER1R2R3R4R5R6R7
Figure 3 : types de liaison entre routeurs
Lire les connexions du schéma
  • R1 relié à R2 : FE
  • R1 relié à R3 : F
  • R2 relié à R3 : F
  • R2 relié à R5 : FE
  • R3 relié à R4 : E
  • R3 relié à R6 : E
  • R4 relié à R6 : F
  • R4 relié à R7 : F
  • R5 relié à R6 : F
  • R6 relié à R7 : FE

Question 10

#

Calculer le coût OSPF pour Ethernet10Mbit/s, Fast Ethernet100Mbit/s et fibre500Mbit/s, avec coût=10⁹/débit enbit/s.

Indice

Un mégabit vaut 10⁶bits dans les débits du sujet.

Comprendre la correction
LiaisonDébit (bit/s)Coût
E10000000100
FE10000000010
F5000000002

Il faut convertir les mégabits en bits avant la division. Un débit plus grand donne un coût plus faible : OSPF additionne ensuite les coûts des liaisons et minimise ce total. La fibre ne vaut pas zéro, même si elle est la plus rapide.

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

Question 11

#

Donner le chemin de R1 à R7 choisi selon OSPF.

Indice

Les deux liens FE R1-R2 et R6-R7 ont chacun une alternative de deux fibres.

Comprendre la correction

Un chemin minimal est R1 → R3 → R2 → R5 → R6 → R4 → R7, de coût 2+2+10+2+2+2=20. Il comporte plus de sauts qu’un chemin direct par R3-R4, mais évite les liaisons Ethernet de coût 100. Le détour par R3 pour joindre R2 coûte 4 au lieu de 10 ; le détour par R4 pour joindre R7 depuis R6 coûte 4 au lieu de 10.

Il faut comparer des sommes de coûts, pas compter les routeurs ni choisir avidement une liaison rapide sans considérer la suite. Le calcul de plus court chemin sur tous les sommets confirme ici ce trajet.

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

Question 12

#

Compléter la table de routage OSPF de R2 pour les destinations R1 àR7.

Indice

Reconstituez les chemins complets, puis ne gardez que leur deuxième routeur.

Comprendre la correction
DestinationPasserelleCoût total minimal
R1R34
R2- 0
R3R32
R4R514
R5R510
R6R512
R7R516

Vers R4, le chemin R2-R5-R6-R4 coûte 10+2+2=14 ; vers R7, on le prolonge par R4-R7 et obtient 16. Ces deux destinations ont donc la même passerelle R5. Le coût 0 vers soi est une convention d’explication ; aucune passerelle n’est requise sur cette ligne. Une table stocke le prochain saut, pas la totalité du chemin.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
Le moins de sauts ou le moindre coût ?Un atelier pour expérimenter

Changez la destination depuis R1 et le critère. Le calcul utilise exactement les dix liaisons de la figure 3. Comparez le chemin retenu et le coût de chacune de ses arêtes.

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

R1 → R3 → R2 → R5 → R6 → R4 → R7.

RIP compte chaque liaison pour1 ; OSPF utilise son coût. Les deux critères peuvent choisir des trajets différents.

DepuisVersCoût OSPF
R1R32
R3R22
R2R510
R5R62
R6R42
R4R72

Un plus court chemin n’a de sens qu’après avoir défini le coût à minimiser.

Revoir les notions de cet exercice

Exercice 3 · 8 points

Pseudo-tri dictatorial : listes, maillons et piles

Le pseudo-tri dictatorial conserve le premier entier, puis parcourt les suivants : un entier plus petit que le dernier conservé est éliminé, les autres sont conservés. Une série vide ou réduite à un élément reste inchangée. Exemple :2,3,1,8 devient 2,3,8. Le but est d’abord de créer une nouvelle liste sans modifier l’originale, puis d’adapter le procédé à une liste chaînée et à une pile. Les trois parties sont indépendantes ; les résultats précédents peuvent être utilisés même sans les avoir trouvés.

Question 1

#

Donner le résultat de tri_dictatorial([31,45,41,28,37,108,127,2,124,421]).

Indice

Gardez en mémoire le dernier entier accepté, pas le précédent dans l’entrée.

Comprendre la correction
[31, 45, 108, 127, 421]

Après 31 et 45, les valeurs 41,28 et 37 sont inférieures au dernier conservé 45.108 puis 127 sont acceptés.2 et 124 sont alors inférieurs à 127, donc rejetés.421 termine la liste. La comparaison porte toujours sur le dernier conservé, même si plusieurs valeurs intermédiaires ont été éliminées.

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

Question 2

#

Pourquoi le tri dictatorial n’est-il pas un algorithme de tri ?

Indice

Un tri doit conserver les données, y compris les doublons.

Comprendre la correction

Un tri réordonne les éléments en conservant leurs occurrences : sa sortie est une permutation de l’entrée. Ici, des valeurs sont supprimées. L’entrée [2,3,1,8] contient 1, mais la sortie ne le contient plus. La liste obtenue est croissante au sens large, mais cela ne suffit pas à en faire le résultat d’un tri.

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

Edgar propose le programme suivant :

def tri_dictatorial(serie):
    serie_triee = [serie[0]]
    for i in range(1, len(serie)):
        if serie[i] >= serie[i - 1]:
            serie_triee.append(serie[i])
    return serie_triee

Question 3

#

Décrire pas à pas la construction de serie_triee par le code d’Edgar pour [8,2,9,6,12].

Indice

Tracez exactement serie[i−1], sans corriger mentalement le programme.

Comprendre la correction
ÉtapeComparaison du codeserie_triee
InitialisationPremier élément 8[8]
i=12>=8 est faux[8]
i=29>=2 est vrai[8,9]
i=36>=9 est faux[8,9]
i=412>=6 est vrai[8,9,12]

Ce test donne par hasard le résultat attendu. Pourtant le programme compare 9 à 2 et 12 à 6, c’est-à-dire aux éléments précédents de l’entrée, même s’ils ont été rejetés. Ce succès local ne valide donc pas sa logique générale.

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

Question 4

#

L’appel tri_dictatorial([]) déclenche IndexError sur serie[0]. Expliquer et corriger ce cas.

Indice

La boucle vide ne protège pas une instruction placée avant elle.

Comprendre la correction

Une liste vide ne possède aucun indice 0. L’erreur se produit dès l’initialisation, avant la boucle. Une garde au début de la fonction respecte le cas vide annoncé :

if not serie:
    return []

La garde doit précéder l’accès à serie[0]. Elle ne répare que ce défaut d’accès ; le problème de comparaison de la question 6 reste présent. L’entrée n’est pas modifiée, conformément au contrat de la partieA.

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

Question 5

#

Pourquoi des tests ne prouvent-ils pas en général l’absence de bugs ?

Indice

Distinguez un exemple qui réussit d’un raisonnement valable pour toute entrée.

Comprendre la correction

Un test examine une entrée particulière et peut révéler un contre-exemple. Un ensemble fini de tests ne couvre généralement pas toutes les entrées possibles, leurs tailles ou leurs combinaisons. Un programme peut donc réussir ces tests et échouer ailleurs, comme celui d’Edgar. Une preuve s’appuie sur les propriétés du programme pour couvrir une classe entière d’entrées ; tests et raisonnement sont complémentaires.

Le sujet illustre cette idée par une citation de Dijkstra sur le rôle des tests : ils sont efficaces pour montrer la présence d’erreurs, mais ne suffisent pas en général à démontrer leur absence. Un domaine fini entièrement testé constitue un cas particulier, pas la situation générale évoquée.

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

Question 6

#

Pour [8,2,3,5,12], le programme renvoie [8,3,5,12], qui n’est pas triée. Expliquer la cause et corriger.

Indice

L’élément précédent dans l’entrée peut avoir été rejeté.

Comprendre la correction

Le 3 est accepté parce qu’il est supérieur à 2, pourtant 2 a été éliminé : il fallait comparer 3 au dernier conservé 8. De même,5 est accepté à tort en étant comparé à 3. Le bon critère utilise la dernière case de la liste résultat.

def tri_dictatorial(serie):
    if not serie:
        return []
    serie_triee = [serie[0]]
    for valeur in serie[1:]:
        if valeur >= serie_triee[-1]:
            serie_triee.append(valeur)
    return serie_triee

Après chaque étape, la sortie contient exactement les éléments déjà lus que la règle autorise, dans leur ordre initial. Ajouter une valeur seulement si elle est au moins égale à la dernière préserve la croissance. La garde traite le vide, et le parcours lit l’entrée sans la modifier. Les égalités sont conservées : [2,2,1,3] devient [2,2,3].

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

PartieB : on modifie désormais la liste chaînée initiale. Les classes fournies sont :

class Maillon:
    def __init__(self, val, suiv):
        self.valeur = val
        self.suivant = suiv

class Liste:
    def __init__(self, tete):
        self.tete = tete
m1:1m0:0m8:8None
Figure 1 : liste chaînée m1, m0, m8
Lire les connexions du schéma
  • m1:1 vers m0:0
  • m0:0 vers m8:8
  • m8:8 vers None

Question 7

#

Construire les maillons m1, m0, m8 et la liste ma_liste représentés par 1→0→8→None.

Indice

Le dernier maillon peut être créé sans dépendre d’un autre.

Comprendre la correction
m8 = Maillon(8, None)
m0 = Maillon(0, m8)
m1 = Maillon(1, m0)
ma_liste = Liste(m1)

Construire depuis la fin permet de disposer de chaque successeur au moment de créer le maillon précédent. L’attribut tete référence m1 ; il ne contient pas directement l’entier 1. Chaque suivant contient une référence à un autre Maillon ou la valeur None.

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

Question 8

#

Donner les résultats de m1.valeur==1 ; m1.suivant.valeur==8 ; m1.suivant.suivant==None ; m1.suivant.suivant.suivant==None.

Indice

Comptez les liaisons traversées, une par accès à suivant.

Comprendre la correction
ExpressionRésultatJustification
m1.valeur == 1Truem1 contient 1
m1.suivant.valeur == 8FalseLe suivant estm 0, de valeur 0
m1.suivant.suivant == NoneFalseOn atteintm 8, qui existe
m1.suivant.suivant.suivant == NoneTrueLe suivant de m8 estNone

Chaque accès à suivant traverse une liaison. Il faut distinguer le maillon, sa valeur et l’absence de maillon. Les expressions de cette question se lisent avant la modification de la question 9.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)
m1:1m0:0m8:8None
Figure : m1 pointe directement vers m8 ; m0 reste un objet séparé
Lire les connexions du schéma
  • m1:1 vers m8:8
  • m0:0 vers m8:8
  • m8:8 vers None

Question 9

#

Donner une instruction pour que ma_liste passe directement de 1 à 8, en contournant le maillon 0.

Indice

Modifier la référence du maillon précédent, pas la valeur du maillon à éliminer.

Comprendre la correction
ma_liste.tete.suivant = ma_liste.tete.suivant.suivant

La flèche issue de m1 pointe maintenant vers m8. L’objet m0 existe toujours si une variable le référence, et son propre suivant reste m8 ; il n’est simplement plus dans la chaîne accessible depuis tete. Aucun déplacement de données n’est nécessaire.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)
def tri_dictatorial_chaine(chaine):
    maillon = chaine.tete
    while maillon.suivant ...:
        if maillon.valeur ...:
            maillon = ...
        else:
            maillon.suivant = ...

Question 10

#

Compléter tri_dictatorial_chaine, qui modifie la liste chaînée sans rien renvoyer.

PythonSupprimer les maillons sans perdre le dernier conservéÉcrivez votre solution et mettez-la à l’épreuve

Écrivez tri_dictatorial_chaine(chaine). En partant de la tête, conservez le premier maillon, puis seulement ceux dont la valeur est supérieure ou égale à celle du dernier conservé. Modifiez les liens suivant en place, sans reconstruire de maillons, sans trier les valeurs et sans rien renvoyer. La classe Chaine fournit l’attribut tete, un Maillon fournit valeur et suivant. La garde pour une chaîne vide est une extension explicite du squelette.

def tri_dictatorial_chaine(chaine):
    # À vous de jouer
    pass

Les cas de test proposés :

  • Le contre-exemple du faux tri : Après avoir rejeté 2, la référence de comparaison reste 8 ; 3 et 5 sont donc rejetés aussi.
  • Les égalités sont conservées : Le critère est supérieur ou égal, pas strictement supérieur.
  • Une série décroissante : Plusieurs suppressions consécutives doivent être possibles sans avancer le maillon conservé.
  • Les objets conservés restent les mêmes : La question demande de modifier les liens, pas de créer une autre chaîne contenant des valeurs identiques.
  • Une chaîne minimale ou vide : La garde évite d’accéder à suivant sur None ; un maillon seul est déjà conservé.
Indice

Après un rejet, le dernier maillon conservé ne change pas.

Comprendre la correction
def tri_dictatorial_chaine(chaine):
    maillon = chaine.tete
    if maillon is None:
        return
    while maillon.suivant is not None:
        if maillon.valeur <= maillon.suivant.valeur:
            maillon = maillon.suivant
        else:
            maillon.suivant = maillon.suivant.suivant

Si le prochain maillon est admissible, il devient le dernier conservé et on avance. Sinon, on le contourne en reliant le maillon courant au successeur du prochain ; on reste sur le même dernier conservé pour examiner le nouveau voisin. Avancer après une suppression ferait sauter une comparaison, notamment avec plusieurs petites valeurs consécutives.

La garde ajoutée traite une chaîne vide, absente du squelette fourni mais autorisée par le principe général. Chaque tour avance ou supprime un maillon, donc réduit le travail restant sur une chaîne finie. La liste est modifiée en place et la fonction renvoie implicitement None.

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

Question 11

#

Rappeler le principe de fonctionnement d’une pile.

Indice

Identifiez quel élément sort après plusieurs empilements.

Comprendre la correction

Une pile suit LIFO : le dernier élément empilé est le premier dépilé. L’ajout et le retrait se font au sommet. On ne retire pas directement un élément au milieu de la pile. Ici, l’ordre de la série à traiter est donc l’ordre dans lequel les éléments sont dépilés.

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

Les lignes à ordonner sont : tester p non vide ; créer p 2 vide ; dépiler le premier élément dans dernier_conservé et l’empiler dans p 2 ; tant que p non vide ; dépiler dans candidat ; tester candidat>=dernier_conservé ; mettre à jour dernier_conservé et empiler candidat ; tant que p 2 non vide ; dépiler p 2 et empiler dans p.

Question 12

#

Remettre dans l’ordre les instructions fournies, avec l’indentation nécessaire aux blocs si et tant que.

Indice

Séparez la phase de filtrage et la phase de restitution.

Comprendre la correction
si p n'est pas vide :
    créer une pile intermédiaire p2 vide
    dépiler p dans dernier_conservé, puis empiler cette valeur dans p2
    tant que p n'est pas vide :
        dépiler p dans candidat
        si candidat >= dernier_conservé :
            affecter candidat à dernier_conservé et l'empiler dans p2
    tant que p2 n'est pas vide :
        dépiler p2 et empiler la valeur dans p

Le premier dépilement initialise toujours un élément conservé. La première boucle filtre les suivants dans l’ordre de lecture. La seconde boucle est placée après la première, et non dedans : elle restitue la pile initiale une fois tout le filtrage terminé. Le passage par p 2 inverse l’ordre, puis le retour vers p l’inverse une seconde fois. La pile finale se dépile donc dans l’ordre croissant des valeurs retenues.

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

Question 13

#

Écrire tri_dictatorial_pile(p), en utilisant la classe Pile fournie : constructeur sans argument, depiler, empiler(elt), est_vide et __str__. La pile d’entrée doit être modifiée.

Indice

Traduisez les deux boucles du pseudo-code en respectant l’interface.

Comprendre la correction
def tri_dictatorial_pile(p):
    if not p.est_vide():
        p2 = Pile()
        dernier_conserve = p.depiler()
        p2.empiler(dernier_conserve)
        while not p.est_vide():
            candidat = p.depiler()
            if candidat >= dernier_conserve:
                dernier_conserve = candidat
                p2.empiler(candidat)
        while not p2.est_vide():
            p.empiler(p2.depiler())

On utilise seulement l’interface annoncée de Pile. Aucun accès à une liste interne supposée n’est nécessaire. La garde empêche de dépiler une pile vide. Le candidat est retiré de p dans tous les cas ; il n’est ajouté à p 2 que s’il satisfait le critère. Après restitution, les éléments rejetés ont disparu, et les conservés ont retrouvé leur ordre de lecture.

Par exemple, si p se dépile initialement en 2,3,1,8, p 2 se dépile après filtrage en 8,3,2. Le transfert final donne une pile p se dépilant en 2,3,8. L’expression « triée » doit être accompagnée de cette convention de lecture, car un dessin de pile peut être lu dans l’autre sens.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)
Le précédent ou le dernier conservé ?Un atelier pour expérimenter

Choisissez une série puis le critère. Chaque ligne montre le seuil réellement utilisé et la liste construite. Le cas adversaire révèle le défaut que le premier test ne montrait pas.

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

Résultat : [8, 12].

La règle correcte compare au dernier conservé. Le critère du programme initial peut accepter une valeur inférieure au début du résultat.

CandidatComparé àDécisionRésultat courant
8-Conserver8
28Éliminer8
38Éliminer8
58Éliminer8
128Conserver8, 12

Un test qui réussit peut cacher une mauvaise référence de comparaison ; cherchez un contre-exemple ciblé.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • En SQL, confronter les valeurs imposées aux clés déjà présentes.
  • Pour le routage, distinguer la table donnée du meilleur chemin théorique.
  • Pour les structures, préciser si les données sont copiées ou modifiées en place.

Retrouver ces notions dans d’autres sujets

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

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