Épreuve écrite · 2025 · Jour 1

Bac NSI 2025 Amérique du Nord jour 1

Reconnaître un arbre grâce à ses feuilles, remplir un camion, puis organiser des groupes d’enfants : ce sujet relie trois problèmes concrets aux structures de données. Les difficultés intéressantes sont dans les interfaces entre notions : appeler la bonne méthode récursive, conserver une contrainte de référence SQL, ou comprendre ce qu’un choix glouton garantit réellement.

Les trois exercices indépendants valent respectivement 6, 6 et 8 points. Pour travailler en conditions d’épreuve, réservez 3 h 30 sans calculatrice. Vous pouvez ensuite comparer chaque réponse, manipuler les ateliers et retrouver les cours associés.

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

Identifier des végétaux avec un arbre de décision

On identifie des végétaux à partir de leurs folia, leurs feuilles : simples ou complexes, alternées ou non, insérées en hélice, en forme d’ovale ou de cœur, à bord denté. Le tilleul a des folia simples, alternées, non insérées en hélice, en forme de cœur et dentées. Le ficus a des folia simples, alternées, en hélice et ovales. Le robinier a des folia complexes, alternées et non dentées.

Dans l’arbre de décision de la figure 1, un rectangle est une question et un ovale un résultat. Une réponse oui ou non mène à une autre question ou à une liste de végétaux. Cette liste peut avoir zéro, un ou plusieurs éléments. Les points de suspension désignent les parties que le sujet ne représente pas.

ouinonouinonouinonouinonouinonouinonouinonouinonQ1Q2Q3Q4…Q5…Q6Q7SorbierRobinier / NoyerFicus[]Q8…Tilleul[]
Figure 1 : arbre de décision des végétaux (questions détaillées dans le tableau)
Lire les connexions du schéma
  • Q1 relié à Q2 : oui
  • Q1 relié à Q3 : non
  • Q2 relié à Q4 : oui
  • Q2 relié à … : non
  • Q3 relié à Q5 : oui
  • Q3 relié à … : non
  • Q4 relié à Q6 : oui
  • Q4 relié à Q7 : non
  • Q5 relié à Sorbier : oui
  • Q5 relié à Robinier / Noyer : non
  • Q6 relié à Ficus : oui
  • Q6 relié à [] : non
  • Q7 relié à Q8 : oui
  • Q7 relié à … : non
  • Q8 relié à Tilleul : oui
  • Q8 relié à [] : non
RepèreQuestion
Q1Feuilles simples ?
Q2Alternées ? (simples)
Q3Alternées ? (complexes)
Q4Insérées en hélice ?
Q5Bord denté ? (complexes)
Q6En forme d’ovale ?
Q7En forme de cœur ?
Q8Bord denté ? (simples)
Figure 1 : questionBranche ouiBranche non
Feuilles simples ?Disposées de façon alternée ? (branche simple)Disposées de façon alternée ? (branche complexe)
Alternées ? (simples)Insérées en hélice ?…
Insérées en hélice ?En forme d’ovale ?En forme de cœur ?
En forme d’ovale ?FicusAucun végétal
En forme de cœur ?Bord denté ? (simples)…
Bord denté ? (simples)TilleulAucun végétal
Alternées ? (complexes)Bord denté ? (complexes)…
Bord denté ? (complexes)SorbierRobinier, Noyer

Le robinier et le noyer partagent donc un même résultat, puisque les critères décrits ne permettent pas de les séparer.

Question 1

#

On observe un végétal dont les folia sont complexes (non simples), disposées de façon alternée et à bord denté. D’après l’arbre de décision de la figure 1, peut-on identifier ce végétal ? Si oui, quel est-il ?

Comprendre la correction

Oui : le sorbier. On suit successivement les branches non de « Feuilles simples ? », oui de « Disposées de façon alternée ? », puis oui de « Bord denté ? ». La feuille atteinte contient un seul nom.

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

Question 2

#

On observe un végétal dont les folia sont simples, disposées de façon alternée, insérées en hélice et ne sont pas de forme d’ovale. D’après l’arbre de décision de la figure 1, peut-on identifier ce végétal ? Si oui, quel est-il ?

Comprendre la correction

Non. Le chemin oui, oui, oui, non aboutit au résultat vide. L’arbre a bien été parcouru jusqu’à une feuille, mais aucun végétal connu du modèle ne correspond à cette combinaison. Ce n’est ni une erreur d’exécution, ni la preuve qu’un tel végétal ne peut pas exister.

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

On représente un arbre avec les deux classes suivantes.

class Noeud:
    def __init__(self, question, sioui, sinon):
        self.question = question
        self.sioui = sioui
        self.sinon = sinon

class Feuille_resultat:
    def __init__(self, vegetaux):
        self.vegetaux = vegetaux

Noeud.question est une chaîne ; sioui et sinon désignent chacun un nœud ou une feuille. Feuille_resultat.vegetaux est une liste éventuellement vide de noms.

ouinonouinonouinonS[]AD[]SorbierRobinier / Noyer
Figure 2 : arbre simplifié, S = Simples ?, A = Alternées ?, D = Bord denté ?
Lire les connexions du schéma
  • S relié à [] : oui
  • S relié à A : non
  • A relié à D : oui
  • A relié à [] : non
  • D relié à Sorbier : oui
  • D relié à Robinier / Noyer : non
Figure 2 : questionOuiNon
Simples ?[]Alternées ?
Alternées ?Bord denté ?[]
Bord denté ?['Sorbier']['Robinier', 'Noyer']

Question 3

#

Écrire en langage Python le code permettant de construire l’arbre de décision de la figure 2 et de l’affecter à une variable nommée arbre_2.

Indice 1

Commencez par les feuilles de résultat.

Indice 2

Le premier argument après chaque question est sa branche oui.

Comprendre la correction
arbre_2 = Noeud('Simples ?', Feuille_resultat([]),
    Noeud('Alternées ?',
        Noeud('Bord denté ?', Feuille_resultat(['Sorbier']),
              Feuille_resultat(['Robinier', 'Noyer'])),
        Feuille_resultat([])))

On construit les feuilles, puis les nœuds qui les référencent. L’ordre des arguments est décisif : question, branche oui, branche non. Les deux résultats vides sont des objets Feuille_resultat([]), et non None.

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

On ajoute une méthode est_resultat aux deux classes : elle doit renvoyer True pour une feuille de résultat et False pour un nœud.

Question 4

#

Écrire le code de la méthode est_resultat pour la classe Noeud.

Comprendre la correction
def est_resultat(self):
    return False

Cette méthode s’insère dans la classe Noeud. Un nœud pose une question : ce n’est jamais un résultat, même si ses deux enfants sont des feuilles.

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

Question 5

#

Écrire le code de la méthode est_resultat pour la classe Feuille_resultat.

Comprendre la correction
def est_resultat(self):
    return True

La feuille est un résultat même lorsque sa liste de végétaux est vide. Il ne faut donc pas remplacer cette réponse par len(self.vegetaux) > 0.

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

On souhaite connaître le nombre de végétaux identifiables par un arbre de décision.

Question 6

#

Écrire le code de la méthode nb_vegetaux pour la classe Feuille_resultat.

Comprendre la correction
def nb_vegetaux(self):
    return len(self.vegetaux)

La feuille contenant robinier et noyer contribue pour 2 ; une feuille vide contribue pour 0. On compte les noms enregistrés, pas les feuilles.

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

Question 7

#

Écrire le code de la méthode nb_vegetaux pour la classe Noeud, qui prend en compte tous les végétaux identifiables à partir de ce nœud.

Indice

Un nœud rassemble les résultats de ses deux branches.

Comprendre la correction
def nb_vegetaux(self):
    return self.sioui.nb_vegetaux() + self.sinon.nb_vegetaux()

Chaque sous-arbre sait se compter grâce à la méthode du même nom. On additionne ces résultats ; le nœud-question ne représente lui-même aucun végétal. Pour arbre_2, on obtient 3 noms.

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

La méthode liste_questions doit lister toutes les questions de l’arbre. L’ordre n’importe pas et les doublons sont autorisés. Pour arbre_2, on attend les trois questions « Simples ? », « Alternées ? », « Bord denté ? ».

Question 8

#

Écrire le code de la méthode liste_questions pour la classe Feuille_resultat.

Comprendre la correction
def liste_questions(self):
    return []

Une feuille ne pose aucune question. La liste vide est aussi l’élément neutre de la concaténation utilisée dans le cas récursif.

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

Question 9

#

Écrire le code de la méthode liste_questions pour la classe Noeud, qui prend en compte toutes les questions accessibles à partir de ce nœud. L’opérateur + concatène les listes : [1, 2] + [3, 4, 5] donne [1, 2, 3, 4, 5].

Indice

La question courante doit devenir une liste à un élément.

Comprendre la correction
def liste_questions(self):
    return ([self.question] + self.sioui.liste_questions()
            + self.sinon.liste_questions())

On rassemble la question courante et les listes des deux descendants. Les crochets autour de self.question sont nécessaires : on concatène trois listes, pas une chaîne et des listes.

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

Les caractéristiques sont un dictionnaire dont les clés sont les questions exactes et les valeurs des booléens. Exemple adapté à la figure 2 :

folia_sorbier = {
    'Simples ?': False,
    'Alternées ?': True,
    'Bord denté ?': True
}

Le dictionnaire suivant est insuffisant pour la figure 1 : il manque notamment la clé « Feuilles simples ? ».

folia_tilleul = {
    "En forme d'ovale ?": False,
    "Disposées de façon alternée ?": True,
    "Bord denté ?": True
}

La citation interne de « d’ovale » est écrite ici avec des guillemets compatibles avec Python ; la simple apostrophe imprimée dans le sujet ne doit pas fermer la chaîne.

Question 10

#

Écrire une fonction est_bien_renseigne qui prend un dictionnaire dico_vegetal décrivant les folia et un objet arbre de classe Noeud ou Feuille_resultat, et renvoie True si toutes les questions présentes dans arbre sont des clés du dictionnaire.

Indice

Une clé présente avec la valeur False reste renseignée.

Comprendre la correction
def est_bien_renseigne(dico_vegetal, arbre):
    for question in arbre.liste_questions():
        if question not in dico_vegetal:
            return False
    return True

Le test porte sur la présence des clés, pas sur leur valeur : une réponse False est une information valide. On ne renvoie True qu’après avoir vérifié toutes les questions. Un arbre réduit à une feuille n’en exige aucune et satisfait donc la condition.

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

Question 11

#

Écrire une fonction identifier_vegetaux qui prend un dictionnaire dico_vegetal et un arbre de décision arbre, et renvoie la liste, éventuellement vide, des noms correspondant aux caractéristiques. Toutes les questions de l’arbre sont des clés du dictionnaire. L’exemple imprimé est identifier_vegetaux(arbre_2, folia_sorbier) et doit renvoyer ['Sorbier'].

PythonSuivre les bonnes questions jusqu’au végétalÉcrivez votre solution et mettez-la à l’épreuve

Écrivez identifier_vegetaux(arbre, dico_vegetal). L’ordre des paramètres suit l’exemple imprimé, repris par le corrigé. À chaque Noeud, question est une clé du dictionnaire ; True sélectionne sioui et False sélectionne sinon. Une Feuille_resultat contient la liste vegetaux, parfois vide. Renvoyez cette liste, sans parcourir la branche non choisie ni modifier le dictionnaire. Toutes les questions nécessaires sont renseignées.

def identifier_vegetaux(arbre,dico_vegetal):
    # À vous de jouer
    pass

Les cas de test proposés :

  • Une feuille peut déjà être la réponse : Une feuille ne demande aucune information dans le dictionnaire.
  • Une feuille vide est un résultat valide : Vide signifie aucun végétal identifié, pas absence de résultat nécessitant une question supplémentaire.
  • False est une réponse renseignée : La présence de la clé ne suffit pas : sa valeur détermine la branche.
  • Deux questions et plusieurs noms possibles : On renvoie toute la liste du résultat atteint, pas seulement son premier nom.
  • Ne pas concaténer les deux branches : Le dictionnaire décrit un seul chemin, et la feuille de l’autre branche ne doit pas être ajoutée.
Indice 1

Une feuille contient déjà la réponse complète.

Indice 2

À un nœud, la valeur du dictionnaire choisit une seule branche.

Comprendre la correction
def identifier_vegetaux(arbre, dico_vegetal):
    if arbre.est_resultat():
        return arbre.vegetaux
    if dico_vegetal[arbre.question]:
        return identifier_vegetaux(arbre.sioui, dico_vegetal)
    return identifier_vegetaux(arbre.sinon, dico_vegetal)

Nous choisissons l’ordre des paramètres de l’exemple, arbre puis dictionnaire. Le texte les présente dans l’autre ordre : une solution cohérente avec cet autre ordre est également correcte si les appels sont adaptés. Le cas d’arrêt renvoie la liste de la feuille. À chaque nœud, une seule branche est parcourue, celle correspondant à la réponse ; on ne concatène pas les deux sous-arbres.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
Faites parler l’arbre de décisionUn atelier pour expérimenter

Modifiez les trois caractères du végétal. Suivez les seules questions effectivement posées et distinguez résultat vide, identification unique et ambiguïté.

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

Sorbier

Le chemin aboutit à une feuille contenant un seul nom.

Question rencontréeRéponse
Simples ?Non
Alternées ?Oui
Bord denté ?Oui

Une feuille de résultat peut contenir plusieurs noms ou aucun. Identifier suit un chemin ; compter tous les végétaux explore les deux branches.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Organiser les colis et comprendre les limites du glouton

Une entreprise gère des colis ayant un identifiant unique id de type str, un poids en kilogrammes poids de type float, une adresse adresse de type str, et un état etat parmi 'préparé', 'transit', 'livré'. À sa création, un colis est préparé ; les autres valeurs sont passées au constructeur.

class Colis:
    def __init__(self, id, poids, adresse):
        self.id = id
        self.poids = poids
        self.adresse = adresse
        self.etat = 'préparé'

colisA = Colis('AC12', 5.0, '20 rue de la paix 57000 Metz')
colisB = Colis('AF34', 10.25, '32 rue du centre 57000 Metz')

Question 1

#

Écrire la méthode passer_transit de la classe Colis qui permet de mettre l’état du colis à la valeur 'transit'.

Comprendre la correction
def passer_transit(self):
    self.etat = 'transit'

La méthode modifie l’objet sur lequel elle est appelée. Elle n’a pas besoin d’un paramètre etat, puisque la valeur à affecter est imposée, ni de créer un nouveau colis.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)
def ajouter_colis(liste, colis):
    # ajoute le colis à la fin de la liste
    liste.append(colis)

liste_colis = []
ajouter_colis(liste_colis, colisA)
ajouter_colis(liste_colis, colisB)

Question 2

#

Dans cette question uniquement, le transporteur refuse les colis de plus de 25 kg. Recopier et modifier ajouter_colis afin d’ajouter le colis si son poids est inférieur ou égal à 25 kg, et d’afficher « Dépassement du poids maximal autorisé » sinon.

Comprendre la correction
def ajouter_colis(liste, colis):
    if colis.poids <= 25:
        liste.append(colis)
    else:
        print('Dépassement du poids maximal autorisé')

La borne 25 kg est acceptée : le test utilise <=. Dans la branche refusée, la liste doit rester inchangée. Cette restriction ne s’applique pas aux autres questions.

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

Question 3

#

Écrire une fonction nb_colis qui prend une liste d’objets Colis et renvoie le nombre de colis présents.

Comprendre la correction
def nb_colis(liste):
    return len(liste)

Le nombre d’objets est la longueur de la liste. Une liste vide donne 0, sans traitement particulier.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)
def poids_total(liste):
    total = ...
    for c in liste:
        total = ...
    return total

Question 4

#

Recopier et compléter les lignes 2 et 4 de poids_total, qui renvoie le poids total des colis de la liste.

Comprendre la correction
def poids_total(liste):
    total = 0
    for c in liste:
        total = total + c.poids
    return total

Le cumul commence à 0, l’élément neutre de l’addition. On additionne les attributs poids, pas les objets eux-mêmes. Le retour reste après la boucle, sinon seul le premier colis serait compté.

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

Question 5

#

Écrire liste_colis_etat(liste, statut), où statut vaut 'préparé', 'transit' ou 'livré', qui renvoie une nouvelle liste contenant tous les colis de la liste dont l’état est statut.

Comprendre la correction
def liste_colis_etat(liste, statut):
    resultat = []
    for colis in liste:
        if colis.etat == statut:
            resultat.append(colis)
    return resultat

La nouvelle liste est distincte de la liste source, mais contient les références vers les mêmes colis. Le filtre ne change ni l’état des colis ni leur ordre relatif.

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

Pour charger un camion sans dépasser sa capacité, on considère les colis par poids décroissants, sans tenir compte de leur volume. La fonction suivante trie la liste :

def tri_decroissant(liste):
    n = len(liste)
    for i in range(n - 1):
        min_pos = i
        for j in range(i + 1, n):
            if liste[j].poids > liste[min_pos].poids:
                min_pos = j
        temp = liste[i]
        liste[i] = liste[min_pos]
        liste[min_pos] = temp
    return liste

Question 6

#

Donner le nom du tri utilisé dans tri_decroissant ainsi que son coût dans le pire des cas.

Comprendre la correction

Il s’agit d’un tri par sélection, de coût quadratique O(n²). À chaque position, le programme cherche le colis le plus lourd parmi ceux qui restent, puis l’échange avec celui de la position courante. Il effectue n(n - 1)/2 comparaisons de poids.

Le nom min_pos est trompeur : le test > sélectionne ici un maximum pour obtenir l’ordre décroissant.

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

Question 7

#

Citer un autre algorithme de tri qui aurait pu être utilisé, ainsi que son coût dans le pire des cas.

Comprendre la correction

Le tri fusion a un coût O(n log n) dans le pire des cas. On adapte sa comparaison pour conserver les poids décroissants. Un tri par insertion serait aussi une réponse possible, avec un coût quadratique dans le pire des cas, mais il ne donnerait pas le même gain asymptotique.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
def chargement_glouton(liste, rang, capacite):
    if rang == len(liste):
        return ...
    elif liste[rang].poids <= ...:
        return ... + chargement_glouton(liste, ..., ...)
    else:
        return chargement_glouton(liste, ..., ...)

Question 8

#

Recopier et compléter la fonction récursive chargement_glouton. La liste est triée par poids décroissants ; rang est entre 0 et sa longueur incluses ; capacite est la charge restante. On ne considère que les indices supérieurs ou égaux à rang.

PythonUn colis trop lourd ne bloque pas le camionÉcrivez votre solution et mettez-la à l’épreuve

Complétez chargement_glouton(liste, rang, capacite). La liste de Colis est déjà triée par poids décroissants. À partir de rang, retenez le colis courant si son poids ne dépasse pas la capacité restante, puis poursuivez récursivement. Renvoyez une nouvelle liste contenant les objets retenus, dans leur ordre initial, sans modifier la liste reçue. Ne cherchez pas le chargement optimal : la question demande précisément cette stratégie gloutonne.

def chargement_glouton(liste, rang, capacite):
    # Cas d’arrêt, puis décision sur le colis courant
    pass

Les cas de test proposés :

  • Le premier colis est trop lourd : Refuser 12 kg laisse les 10 kg disponibles pour 7 kg puis 3 kg.
  • La capacité diminue vraiment : Après 8 kg, il reste 2 kg. Choisir ensuite 6 kg ou 4 kg surchargerait le camion. Le glouton ne remplace pas 8 kg par 6 + 4 kg.
  • Un poids exactement admissible : L’égalité doit être acceptée : 5 kg tient dans une capacité de 5 kg.
  • Commencer au milieu de la liste : Le paramètre rang exclut les colis qui le précèdent.
  • Atteindre la fin et ne rien modifier : Le cas d’arrêt doit fonctionner avant tout accès à liste[rang]. Le chargement ne change pas les objets.
Indice

Le rang progresse dans les deux branches, la capacité seulement lors d’un chargement.

Comprendre la correction
def chargement_glouton(liste, rang, capacite):
    if rang == len(liste):
        return []
    elif liste[rang].poids <= capacite:
        return [liste[rang]] + chargement_glouton(
            liste, rang + 1, capacite - liste[rang].poids)
    else:
        return chargement_glouton(liste, rang + 1, capacite)

Quand tous les colis ont été examinés, la liste des choix restants est vide. Si un colis entre, on l’ajoute et on retranche son poids. Sinon, on le saute tout en gardant la même capacité. Dans les deux branches, rang augmente : c’est ce qui rapproche du cas d’arrêt.

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

Question 9

#

Expliquer brièvement pourquoi un appel peut produire RecursionError: maximum recursion depth exceeded while calling a Python object.

Comprendre la correction

Un appel est empilé pour chaque colis considéré. Une liste suffisamment longue peut donc dépasser la profondeur de récursion autorisée par Python, même lorsque le programme possède un cas d’arrêt correct. Ce n’est pas nécessairement une récursion infinie : la profondeur peut simplement être trop grande pour l’interpréteur.

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

Question 10

#

Écrire une fonction itérative, sans récursivité, chargement_glouton2(liste, capacite) qui renvoie la liste des colis à charger. La liste est triée par poids décroissants. On pourra créer colis_a_charger puis parcourir les colis en les ajoutant si le poids total n’excède pas la capacité.

Indice 1

Conservez une variable représentant la place restante.

Indice 2

Après un refus, continuez : un colis plus léger peut encore entrer.

Comprendre la correction
def chargement_glouton2(liste, capacite):
    colis_a_charger = []
    for colis in liste:
        if colis.poids <= capacite:
            colis_a_charger.append(colis)
            capacite = capacite - colis.poids
    return colis_a_charger

La capacité représente ici le poids encore disponible. Le programme traite les colis dans le même ordre et prend exactement les mêmes décisions que la version récursive, avec une seule boucle.

Limite importante du libellé : le sujet parle de « maximiser le poids total ». L’algorithme glouton demandé ne garantit pas l’optimum : pour une capacité de 10 kg et les poids 8, 6, 4 kg, il charge 8 kg, alors que 6 + 4 remplit le camion. La solution attendue met en œuvre la procédure imposée ; on ne doit pas lui attribuer une garantie mathématique qu’elle n’a pas.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
Un camion plein… ou un choix trop pressé ?Un atelier pour expérimenter

Choisissez une série de colis et faites varier la capacité. Comparez le parcours glouton à l’optimum calculé en essayant tous les sous-ensembles de cette petite série.

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

Glouton : 8 kg ; optimum : 10 kg

Le premier colis accepté bloque une meilleure combinaison : 6 + 4 = 10 kg. Un choix local ne se corrige pas ensuite.

Colis (kg)DécisionCapacité restante
8Chargé2
6Refusé2
4Refusé2

Le glouton conserve une solution admissible, mais l’admissibilité ne suffit pas à prouver l’optimalité. La version itérative évite seulement l’empilement des appels.

Revoir les notions de cet exercice

Exercice 3 · 8 points

SQL, contraintes et coloration des mésententes

Une association accueille des enfants de 0 à 18 ans et veut former des groupes dont les membres s’entendent. La base contient trois relations. Dans parent, nom est le nom de famille, tel le téléphone et codep le code postal. Dans enfant, id identifie l’enfant, prenom est son prénom, num_parent le téléphone de son unique parent référent et annee son année de naissance. Dans mesentente, enfant1 et enfant2 désignent deux enfants qui ne peuvent pas participer ensemble à une sortie.

Partie A : base de données

Clauses SQL autorisées : SELECT, FROM, WHERE, AND, OR, JOIN ... ON, UPDATE, INSERT, DELETE, DISTINCT et ORDER BY.

RelationAttributs de la figure 1Références
parentnom TEXT ; tel INT ; codep INTtel identifie le parent (clé à proposer en Q4)
enfantid INT, clé primaire ; prenom TEXT ; num_parent INT ; annee, type à précisernum_parent → parent.tel
mesententeenfant 1 INT ; enfant 2 INT, clé composée soulignée dans la figureenfant 1 → enfant.id ; enfant 2 → enfant.id
idprenomnum_parentannee
2Hawa336199112122012
3Adrien336198612322013
6Kian336198345212012
8Gabin336198478522014
12Nakamura336197324532009
14Maya336007821532017
17Olivier336198685642017
21Tess336198358762016
23Rachelle336007854822023

Question 1

#

Donner le type pour l’attribut annee de la table enfant.

Comprendre la correction

INT ou INTEGER : une année de naissance est ici un nombre entier. On ne stocke pas une date complète, puisque seul le millésime est demandé.

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

Question 2

#

Expliquer quelle contrainte de domaine supplémentaire serait pertinente pour l’attribut annee.

Comprendre la correction

Dans le modèle simplifié fondé sur les années, imposer une année de naissance comprise entre l’année courante moins 18 et l’année courante évite les naissances futures et les âges hors du public visé. En 2025, cela correspond à 2007 <= annee <= 2025.

Une année seule ne permet pas de vérifier l’âge exact au jour près : l’intervalle décrit le contrôle cohérent avec cette information limitée. Une application réelle stockerait la date de naissance complète pour appliquer une limite exacte d’âge.

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

Question 3

#

Donner un exemple d’attribut de la table enfant qui suit une contrainte de référence.

Comprendre la correction

num_parent est une clé étrangère vers parent.tel. Chaque numéro utilisé pour un enfant doit correspondre à un parent présent dans la table parent, sous réserve de la gestion éventuelle de valeurs nulles définie par le schéma.

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

Question 4

#

En expliquant ce choix, proposer une clé primaire pour la table parent.

Comprendre la correction

On choisit tel dans le modèle du sujet : il sert à identifier le parent et est référencé par enfant.num_parent. Il doit donc être unique et non nul. Un nom ou un code postal peut être partagé par plusieurs parents.

Cette unicité est une hypothèse du modèle proposé : dans une vraie famille, plusieurs adultes peuvent partager un numéro. On préférerait alors un identifiant indépendant, mais ce n’est pas le schéma demandé.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)
UPDATE parent SET tel = 33619782812 WHERE tel = 33600782812;

Question 5

#

Le véritable téléphone 33619782812 a été saisi 33600782812. Expliquer pourquoi la requête proposée lève une erreur.

Comprendre la correction
UPDATE parent SET tel = 33619782812 WHERE tel = 33600782812;

Des enfants référencent encore l’ancien numéro. Modifier seulement la clé du parent laisserait leurs références sans ligne correspondante : la contrainte de clé étrangère bloque donc la mise à jour.

Cette explication suppose, comme le scénario du sujet, un ancien numéro effectivement référencé et l’absence de propagation automatique ON UPDATE CASCADE. Avec une autre politique de référence, cette même instruction pourrait réussir.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)
INSERT INTO parent VALUES ('Bauges', 33619782812, 73340);
UPDATE enfant SET num_parent = ... WHERE num_parent = ...;
DELETE FROM parent WHERE tel = ...;

Question 6

#

Compléter la suite de commandes qui corrige le téléphone du parent de nom 'Bauges', de code postal 73340, dont le téléphone erroné est 33600782812 au lieu de 33619782812.

Indice

La nouvelle référence doit exister avant de déplacer les enfants.

Comprendre la correction
INSERT INTO parent VALUES ('Bauges', 33619782812, 73340);
UPDATE enfant SET num_parent = 33619782812
WHERE num_parent = 33600782812;
DELETE FROM parent WHERE tel = 33600782812;

On crée d’abord la destination des nouvelles références. On déplace ensuite toutes les références des enfants, puis on supprime le parent devenu non référencé. L’ordre évite tout état intermédiaire avec une clé étrangère orpheline.

Voir la question dans le sujet PDF, p. 12 (nouvel onglet)
SELECT prenom
FROM enfant
WHERE annee < 2014
ORDER BY annee;

Question 7

#

En considérant la table enfant fournie, donner le résultat de cette requête SQL.

Comprendre la correction
prenom
Nakamura
Hawa
Kian
Adrien

On conserve les naissances strictement antérieures à 2014 et on classe les années par ordre croissant : 2009, 2012, 2012, 2013. Hawa et Kian peuvent être intervertis : la requête ne précise aucun critère de départage pour les années égales.

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

Question 8

#

Proposer une requête qui renvoie les prénoms, par ordre alphabétique, des enfants inscrits pour le parent dont le numéro de téléphone est 3619861122.

Comprendre la correction
SELECT prenom
FROM enfant
WHERE num_parent = 3619861122
ORDER BY prenom;

Le numéro à utiliser est celui de la question, même s’il n’apparaît pas dans l’extrait fourni. Le filtre porte sur num_parent ; le tri sur prenom. Une jointure n’est pas nécessaire puisque ces deux colonnes sont dans enfant.

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

Question 9

#

Proposer une requête qui liste les identifiants et prénoms des enfants dont le parent habite dans la ville de code postal 38520.

SQLRelier les enfants au code postal du parentÉcrivez votre solution et mettez-la à l’épreuve

Listez les identifiants et prénoms des enfants dont le parent habite dans la ville de code postal 38520. Le code postal appartient à parent, pas à enfant.

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

Les cas de test proposés :

  • Enfants du sujet, adresses de test explicites : Les trois enfants viennent du tableau officiel. Le sujet ne donne pas leurs lignes parent : les noms et codes postaux de ces parents sont des compléments pédagogiques déclarés.
  • Cas complémentaire : une référence partagée : Jeu complémentaire pédagogique, distinct des données officielles. Deux enfants partagent le même parent. Un parent sans enfant ne doit créer aucune ligne, et le code voisin 38521 ne doit pas être accepté.
Comprendre la correction
SELECT enfant.id, enfant.prenom
FROM enfant JOIN parent ON enfant.num_parent = parent.tel
WHERE parent.codep = 38520;

On relie l’enfant à son parent par les colonnes de référence, puis on filtre sur le code postal du parent. L’identifiant de l’enfant ne doit pas être comparé au téléphone : une jointure relie des valeurs de même signification.

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

Partie B : graphes et algorithmique

On construit le graphe des mésententes. Un dictionnaire associe à chaque sommet la liste de ses voisins. Voici la figure 2 et sa représentation Python ; Sixtine est un sommet isolé.

EliseOctaviePierreRaphaelSixtineVirgile
Figure 2 : graphe g1 des mésententes
Lire les connexions du schéma
  • Elise relié à Octavie
  • Elise relié à Virgile
  • Octavie relié à Pierre
  • Octavie relié à Virgile
  • Pierre relié à Raphael
  • Raphael relié à Virgile
g1 = {'Elise': ['Octavie', 'Virgile'],
      'Octavie': ['Elise', 'Pierre', 'Virgile'],
      'Pierre': ['Octavie', 'Raphael'],
      'Raphael': ['Pierre', 'Virgile'],
      'Sixtine': [],
      'Virgile': ['Elise', 'Octavie', 'Raphael']}

Question 10

#

Expliquer pourquoi la situation décrite ne nécessite qu’un graphe non orienté.

Comprendre la correction

La contrainte « ces deux enfants ne peuvent pas être dans le même groupe » est symétrique. Une arête entre A et B interdit le regroupement de A avec B autant que celui de B avec A. Le modèle décrit une incompatibilité de paire, pas l’opinion orientée d’un enfant sur un autre.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)
g2 = {'Adrien': ['Elisabeth', 'Lea'],
      'Elisabeth': ['Adrien', 'Ian', 'Luca'],
      'Ian': ['Elisabeth', 'Joseph', 'Luca'],
      'Joseph': ['Ian'],
      'Lea': ['Adrien'],
      'Luca': ['Elisabeth', 'Ian']}

Question 11

#

Dessiner le graphe g2 défini ci-dessous.

Comprendre la correction
AdrienElisabethIanJosephLeaLuca
Correction : graphe g2
Lire les connexions du schéma
  • Adrien relié à Elisabeth
  • Adrien relié à Lea
  • Elisabeth relié à Ian
  • Elisabeth relié à Luca
  • Ian relié à Joseph
  • Ian relié à Luca

Il comporte six sommets et six arêtes. Chaque paire présente deux fois dans les listes de voisinage ne donne qu’une seule arête non orientée. Le triangle Elisabeth-Ian-Luca est conservé, de même que les deux branches menant à Lea et Joseph.

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

Question 12

#

Écrire degre(g, s), où g représente un graphe et s un sommet, qui renvoie le degré de s. Le degré est le nombre d’arêtes issues du sommet.

Comprendre la correction
def degre(g, s):
    return len(g[s])

Dans cette représentation d’un graphe simple, les voisins sont exactement les extrémités des arêtes incidentes. On renvoie donc la longueur de la liste associée, et non le nombre total de clés du dictionnaire.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)
def sommets_tries(g):
    sommets = [sommet for sommet in g]
    n = len(sommets)
    for i in range(1, n):
        sommet_courant = sommets[i]
        j = i - 1
        while ... and ...:
            sommets[...] = sommets[...]
            j = j - 1
        ...
    return sommets

Question 13

#

Compléter les lignes 7 à 10 de sommets_tries(g), qui renvoie les sommets dans l’ordre décroissant de leur degré.

Comprendre la correction
def sommets_tries(g):
    sommets = [sommet for sommet in g]
    n = len(sommets)
    for i in range(1, n):
        sommet_courant = sommets[i]
        j = i - 1
        while j >= 0 and degre(g, sommets[j]) < degre(g, sommet_courant):
            sommets[j + 1] = sommets[j]
            j = j - 1
        sommets[j + 1] = sommet_courant
    return sommets

On mémorise le sommet courant avant de décaler ceux de degré plus petit vers la droite. La condition j >= 0 doit précéder l’accès à la liste grâce au court-circuit de and. Quand la boucle s’arrête, la place libre est j + 1. Les égalités ne sont pas déplacées : ce tri conserve leur ordre initial.

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

Question 14

#

Préciser le tri utilisé et son coût d’exécution en temps dans le pire des cas selon le nombre n de sommets. On suppose degre de coût constant. Choix proposés : constant, logarithmique, linéaire, quasi-linéaire, quadratique, cubique, exponentiel.

Comprendre la correction

C’est un tri par insertion, de coût quadratique, O(n²), dans le pire des cas. Un sommet peut être déplacé devant tous les sommets déjà traités, donnant jusqu’à 1 + 2 + ... + (n - 1) déplacements. L’hypothèse sur degre rend chaque comparaison constante.

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

Colorer le graphe consiste à donner une couleur à chaque sommet sans relier deux sommets de même couleur. Un groupe est alors constitué de sommets de même couleur. Les couleurs sont numérotées à partir de 0 ; -1 signifie « pas encore coloré ». La figure 3 attribue initialement une couleur distincte à chaque sommet :

Elise (0)Octavie (1)Pierre (2)Raphael (3)Sixtine (4)Virgile (5)
Figure 3 : six couleurs pour g1
Lire les connexions du schéma
  • Elise (0) relié à Octavie (1)
  • Elise (0) relié à Virgile (5)
  • Octavie (1) relié à Pierre (2)
  • Octavie (1) relié à Virgile (5)
  • Pierre (2) relié à Raphael (3)
  • Raphael (3) relié à Virgile (5)
dc1 = {'Elise': 0, 'Octavie': 1, 'Pierre': 2,
       'Raphael': 3, 'Sixtine': 4, 'Virgile': 5}

Question 15

#

Recopier et colorer le graphe g1 en n’utilisant que trois couleurs 0, 1 et 2.

Comprendre la correction
Elise (0)Octavie (1)Pierre (0)Raphael (1)Sixtine (0)Virgile (2)
Une coloration valide avec trois couleurs
Lire les connexions du schéma
  • Elise (0) relié à Octavie (1)
  • Elise (0) relié à Virgile (2)
  • Octavie (1) relié à Pierre (0)
  • Octavie (1) relié à Virgile (2)
  • Pierre (0) relié à Raphael (1)
  • Raphael (1) relié à Virgile (2)

Vérifiez chaque arête : ses deux couleurs sont différentes. Sixtine, isolée, peut prendre n’importe laquelle des couleurs. Le triangle Elise-Octavie-Virgile impose au moins trois couleurs ; cette coloration est donc minimale pour ce graphe précis.

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

On parcourt les sommets en donnant à chacun le plus petit numéro non utilisé par ses voisins. Les fonctions fournies sont :

def couleurs_voisins(g, dc, s):
    return [dc[v] for v in g[s]]

def plus_petite_couleur_hors_voisins(g, dc, s):
    couleur = 0
    n = len(g)
    cvoisins = couleurs_voisins(g, dc, s)
    while couleur < n:
        if couleur not in cvoisins:
            return couleur
        couleur = couleur + 1
    return couleur  # au cas où len(dc) = 0
def colorer_graphe(g, dc):
    # Les clés de dc sont les sommets de g, valeurs à -1
    for s in dc:
        couleur = ...
        ... = couleur

Question 16

#

Compléter colorer_graphe(g, dc), qui modifie le dictionnaire dc pour associer à chaque sommet sa couleur. Au départ ses clés sont les sommets de g et toutes ses valeurs sont -1.

Comprendre la correction
def colorer_graphe(g, dc):
    for s in dc:
        couleur = plus_petite_couleur_hors_voisins(g, dc, s)
        dc[s] = couleur

On choisit la plus petite couleur absente des voisins, puis on l’enregistre immédiatement. Ainsi les sommets suivants voient ce nouveau choix. Modifier les valeurs du dictionnaire pendant le parcours est autorisé : on ne change pas son ensemble de clés.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)
def welsh_powell(g):
    # Initialisation à -1 pour tous les sommets
    dc = ...
    # Coloration par degré décroissant
    for ...
        ...
        ...
    return dc

Question 17

#

Compléter welsh_powell(g), qui renvoie le dictionnaire des couleurs en parcourant les sommets par degré décroissant. On pourra utiliser sommets_tries et s’inspirer de colorer_graphe.

Indice

Tous les voisins doivent déjà avoir une entrée dans dc, même non colorés.

Comprendre la correction
def welsh_powell(g):
    dc = {s: -1 for s in g}
    for s in sommets_tries(g):
        couleur = plus_petite_couleur_hors_voisins(g, dc, s)
        dc[s] = couleur
    return dc

Tous les sommets doivent être présents dans dc avant la première recherche de couleur, car couleurs_voisins lit aussi les voisins encore non traités. Leur valeur -1 ne bloque aucune couleur positive ou nulle. L’ordre décroissant vient de la liste renvoyée par sommets_tries, et non de l’ordre initial du dictionnaire.

Sur g1, avec le tri stable ci-dessus, on traite Octavie, Virgile, Elise, Pierre, Raphael, Sixtine et on obtient respectivement 0, 1, 2, 1, 0, 0. Cette stratégie produit une coloration valide ; sa minimalité ne découle pas du seul caractère glouton.

Voir la question dans le sujet PDF, p. 15 (nouvel onglet)
Construisez les groupes, une couleur à la foisUn atelier pour expérimenter

Choisissez le graphe et l’ordre de traitement, puis avancez le nombre de sommets colorés. Les voisins déjà colorés interdisent leur couleur au prochain sommet.

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

6/6 sommets colorés, 3 couleur(s)

Les sommets de même couleur peuvent partager un groupe. Un sommet isolé ne force jamais une nouvelle couleur.

SommetDegréCouleurs interdites au moment du choixCouleur choisie
Octavie3Aucune0
Virgile301
Elise20, 12
Pierre201
Raphael210
Sixtine0Aucune0

Une couleur interdit seulement les voisins immédiats. Trier par degré change les décisions locales ; toute arête doit toujours relier deux couleurs différentes.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Pour les méthodes récursives, rédigez d’abord le comportement d’une feuille : il fixe le type du résultat et le cas d’arrêt.
  • Dans les tris, observez le mécanisme et le sens de la comparaison plutôt que le nom de la variable min_pos.
  • En SQL, vérifiez la projection, le filtre et la jointure séparément. Justifiez les contraintes avec les valeurs qu’elles relient.
  • Pour une coloration, contrôlez chaque arête. Pour un glouton, distinguez une solution admissible d’une solution optimale.

Retrouver ces notions dans d’autres sujets

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

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