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
Fibonacci : mesurer le prix des calculs répétés
On définit U₀ = 0, U₁ = 1 et Uₙ = Uₙ₋₁ + Uₙ₋₂ pour n ≥ 2. Les premiers termes sont 0, 1, 1, 2, 3, 5. L’exercice compare trois façons de calculer le même terme : récursion directe, mémorisation et itération.
def suite(n):
if n < 2:
valeur = ...
else:
valeur = suite(n - 1) + suite(...)
return ...Question 1
#Compléter la fonction récursive suite(n) qui renvoie Uₙ.
Indice
Une même instruction peut couvrir les deux cas U₀ et U₁.
Comprendre la correction
def suite(n):
if n < 2:
valeur = n
else:
valeur = suite(n - 1) + suite(n - 2)
return valeurPour n = 0 ou 1, la valeur est n. Pour n ≥ 2, la relation de récurrence est traduite directement. Les appels diminuent jusqu’à l’un des deux cas de base. On suppose n entier naturel, comme pour l’indice de la suite.
Pour suite(4), le premier appel demande suite(3) et suite(2). À son tour, suite(3) redemande suite(2), ce qui illustre déjà un calcul répété. Les cas de base renvoient les valeurs de la suite et ne font pas d’addition entre termes. La terminaison s’appuie sur la diminution stricte de n, pour un indice naturel.
On donne p(0) = p(1) = 0 et p(n) = 1 + p(n - 1) + p(n - 2) pour n ≥ 2.
Question 2
#Écrire nb_additions_suite(n) qui renvoie p(n), le nombre d’additions effectuées par suite.
Indice
Chaque appel non terminal de suite contient une addition.
Comprendre la correction
def nb_additions_suite(n):
if n < 2:
return 0
return 1 + nb_additions_suite(n - 1) + nb_additions_suite(n - 2)L’addition des deux termes de la suite compte pour 1 ; il faut y ajouter les additions des deux sous-appels. Ce programme calcule le décompte p(n). Ses propres additions ne sont pas celles que l’on mesure dans suite : on applique simplement la récurrence fournie.
Question 3
#Calculer p(2), p(4) et p(7).
Indice
Construisez les termes de p dans l’ordre jusqu’à l’indice 7.
Comprendre la correction
| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| p(n) | 0 | 0 | 1 | 2 | 4 | 7 | 12 | 20 |
p(2) = 1, p(4) = 4 et p(7) = 20. Par exemple p(4) = 1 + p(3) + p(2) = 1 + 2 + 1 ; p(7) = 1 + 12 + 7. Calculer les valeurs successives évite de redessiner tout l’arbre d’appels.
def suite2(n, valeurs_calculees=[0, 1]):
if n < len(valeurs_calculees):
valeur = valeurs_calculees[n]
else:
valeurs_calculees.append(
suite2(n - 1, valeurs_calculees)
+ suite2(n - 2, valeurs_calculees))
return valeurs_calculees[n]| n | Nombre d’additions |
|---|---|
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | À compléter |
| 6 | À compléter |
| 7 | À compléter |
Question 4
#Compléter le tableau du nombre d’additions de suite2 pour n = 5, 6, 7.
Indice
Différenciez le coût depuis un cache neuf du coût d’un appel utilisant un cache déjà rempli.
Comprendre la correction
| n | Additions à cache initial [0, 1] |
|---|---|
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | 4 |
| 6 | 5 |
| 7 | 6 |
À partir d’un cache neuf [0, 1], chaque terme d’indice 2 à n n’est calculé qu’une fois, soit n - 1 additions. Les valeurs manquantes sont donc 4, 5, 6. Le paramètre par défaut mutable du code conserve cependant les résultats entre appels : si on lance successivement suite2(2), suite2(3), etc. sans réinitialiser la liste, chaque nouvel indice ne demande qu’une addition. Le tableau du sujet suppose des calculs isolés, chacun avec le cache initial.
Une façon simple de lever l’ambiguïté lors d’un test consiste à appeler suite2(n,[0,1]) avec une nouvelle liste explicitement fournie pour chaque n. Ainsi le tableau compare des conditions initiales identiques. Pour éviter le partage implicite d’un paramètre mutable, une version d’application pourrait utiliser un paramètre par défaut None et créer [0,1] dans la fonction lorsque le cache n’est pas fourni.
Question 5
#Pourquoi suite2 est-elle plus efficace ? Nommer le procédé utilisé.
Indice
La liste évite que suite2(n - 2) refasse le travail accompli dans suite2(n - 1).
Comprendre la correction
La mémoïsation, technique de programmation dynamique, conserve chaque terme calculé dans valeurs_calculees. Un terme déjà connu se lit directement au lieu d’être recalculé. La récursion naïve répète les mêmes sous-problèmes et son coût croît exponentiellement ; le nombre d’additions de la version mémorisée croît linéairement à cache neuf. Par exemple U₇ exige 20 additions naïves, contre 6 mémorisées.
Question 6
#Proposer une version itérative non récursive suite3.
Indice
Deux termes consécutifs suffisent pour produire le suivant.
Comprendre la correction
def suite3(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return aAu début de l’itération k, a = Uₖ et b = Uₖ₊₁. L’affectation simultanée calcule les nouvelles valeurs avec les anciennes. Après n tours, a = Uₙ. Pour n = 0, la boucle ne s’exécute pas et renvoie bien 0. Cette version fait n additions ; on pourrait réduire à n - 1 pour n ≥ 2 avec un traitement séparé des cas de base.
| Avant l’itération | a | b |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 2 | 3 |
| 4 | 3 | 5 |
Une affectation séquentielle a=b puis b=a+b n’aurait pas le même effet, car le second calcul lirait le nouveau a. L’affectation simultanée évalue d’abord les deux expressions avec les anciennes valeurs. Une variable temporaire serait une autre solution correcte. Le tableau montre qu’après quatre tours, a contient bien U₄=3.
Question 7
#Pourquoi préférer la version itérative pour calculer un terme quelconque ?
Indice
Comparer seulement les valeurs renvoyées ne suffit pas : regardez la pile et le stockage.
Comprendre la correction
Elle conserve seulement deux valeurs au lieu d’une liste de tous les termes et évite l’empilement des appels récursifs, donc le risque d’atteindre la limite de récursion de Python. Par rapport à suite2, son ordre de temps reste linéaire en nombre d’additions, mais le nombre de variables de travail est constant. Les entiers eux-mêmes grandissent avec n : « mémoire constante » désigne ici le nombre de cases, pas un nombre constant de bits.
Combien de travail pour un seul nombre ?Un atelier pour expérimenter
Faites varier n puis le plus grand indice déjà mémorisé. Comparez les additions de la récursion naïve au coût réellement restant pour une version avec cache.
Lire le résultat de l’expérience initiale
U7 = 13
La version naïve refait les mêmes appels. Le cache contient tous les indices de 0 jusqu’à la borne sélectionnée : seules les cases manquantes demandent une addition.
| Indice | Terme | Additions naïves | Statut du cache |
|---|---|---|---|
| 0 | 0 | 0 | Déjà connu |
| 1 | 1 | 0 | Déjà connu |
| 2 | 1 | 1 | À calculer |
| 3 | 2 | 2 | À calculer |
| 4 | 3 | 4 | À calculer |
| 5 | 5 | 7 | À calculer |
| 6 | 8 | 12 | À calculer |
| 7 | 13 | 20 | À calculer |
Un coût dépend de l’état initial de la mémoire. Pour comparer deux algorithmes, annoncez si leur cache est neuf ou déjà rempli.
Revoir les notions de cet exercice
Exercice 2 · 6 points
Annuaire téléphonique : construire et parcourir un ABR
Des étudiants développent une application console d’annuaire. Le fichier de contacts fictifs est représenté par la variable suivante ; chaque prénom est associé à un numéro de téléphone. L’application doit ajouter, rechercher et afficher les contacts par ordre alphabétique.
contacts_fictifs = [
{"Olivier": "01 23 45 67 89"},
{"Benjamin": "06 12 34 56 78"},
{"Sophie": "02 34 56 78 90"},
{"Antoine": "09 87 65 43 21"},
{"Eric": "03 45 67 89 01"},
{"Roxanne": "07 45 67 89 01"},
{"Victor": "03 56 78 90 12"},
{"Emilie": "04 23 45 67 89"},
{"Yann": "01 54 32 10 98"},
{"Zoé": "05 23 45 67 89"}
]Un ABR stocke les contacts, sans clés en double. La hauteur d’un arbre réduit à un nœud est 1. Arbre fourni :
Olivier
/ \
Benjamin Sophie
/ \ / \
Antoine Eric Roxanne Victor
/ \
Emilie Yann
\
ZoéQuestion 1
#Quelle est la structure de données de contacts_fictifs ?
Indice
Les crochets extérieurs et les accolades intérieures désignent deux structures distinctes.
Comprendre la correction
C’est une liste de dictionnaires. Chaque dictionnaire contient une seule association : une clé de type chaîne (le prénom) et une valeur de type chaîne (le téléphone). Les numéros sont des textes pour conserver leurs zéros initiaux et leurs espaces.
Question 2
#Justifier que l’arbre est un ABR pour l’ordre alphabétique.
Indice
La propriété porte sur tout le sous-arbre, pas seulement sur un enfant.
Comprendre la correction
Pour chaque nœud, tous les prénoms du sous-arbre gauche le précèdent et tous ceux du sous-arbre droit le suivent. Ainsi Antoine, Benjamin, Emilie et Eric précèdent Olivier ; Roxanne, Sophie, Victor, Yann et Zoé le suivent. La propriété est aussi vraie à l’intérieur : Emilie précède Eric, tandis qu’Eric suit Benjamin. Vérifier seulement les deux enfants de la racine ne suffirait pas.
Question 3
#Donner la racine puis l’ensemble des feuilles.
Indice
Une feuille n’a ni enfant gauche ni enfant droit.
Comprendre la correction
Racine : Olivier. Feuilles : Antoine, Emilie, Roxanne, Zoé, car elles n’ont aucun enfant. Eric et Yann ne sont pas des feuilles même s’ils n’ont qu’un enfant.
Question 4
#Donner hauteur et taille.
Indice
Ne comptez pas les arêtes pour une hauteur dont la feuille vaut 1.
Comprendre la correction
Hauteur 5 ; taille 10. Le plus long chemin est Olivier → Sophie → Victor → Yann → Zoé, soit cinq nœuds avec la convention du sujet. La taille compte tous les nœuds, un par contact.
Question 5
#Quel avantage présente un ABR par rapport à contacts_fictifs ?
Indice
Comparez le nombre de contacts inspectés pour chercher Zoé ou Emilie.
Comprendre la correction
La recherche suit une seule branche, en éliminant à chaque comparaison un sous-arbre entier. Son coût dépend de la hauteur h, soit O(h), contre O(n) pour rechercher un prénom dans la liste non triée de petits dictionnaires. Un ABR équilibré donne O(log n), mais un ABR dégénéré peut encore demander O(n). L’ordre n’assure pas à lui seul l’équilibrage.
Dans cet arbre, chercher Emilie compare Olivier, Benjamin, Eric puis Emilie : quatre nœuds sont visités, alors qu’une recherche séquentielle dans la liste de départ atteint ce contact en huitième position. Cet exemple montre le gain possible sans en faire une garantie universelle. Insérer des clés déjà triées dans un ABR non équilibré créerait une longue chaîne et ferait disparaître le gain logarithmique.
Question 6
#Donner le sous-arbre droit de Benjamin.
Indice
Suivez le lien droit de Benjamin puis conservez tout ce qui en descend.
Comprendre la correction
Eric
/
EmilieIl contient Eric comme racine et Emilie comme enfant gauche. Le sous-arbre inclut les descendants, pas seulement le fils direct.
class Contact:
def __init__(self, v_prenom, v_num_tel):
self.prenom = v_prenom
self.num_tel = v_num_tel
class Noeud:
def __init__(self, v_contact, v_gauche=None, v_droite=None):
self.contact = v_contact
self.gauche = v_gauche
self.droite = v_droiteInstruction A :
abr = Noeud(Contact('Benjamin', '06 12 34 56 78'),
Noeud(Contact('Antoine', '09 87 65 43 21')),
Noeud(Contact('Emilie', '04 23 45 67 89'),
Noeud(Contact('Eric', '03 45 67 89 01'))))Instruction B :
abr = Noeud(Contact('Benjamin', '06 12 34 56 78'),
Noeud(Contact('Antoine', '09 87 65 43 21')),
Noeud(Contact('Eric', '03 45 67 89 01'),
Noeud(Contact('Emilie', '04 23 45 67 89'))))Instruction C :
abr = Noeud(Noeud(Contact('Benjamin', '06 12 34 56 78'),
('Antoine', '09 87 65 43 21')),
Noeud(Contact('Eric', '03 45 67 89 01'),
('Emilie', '04 23 45 67 89')))Question 7
#Parmi A, B et C, choisir l’instruction qui construit le sous-arbre de racine Benjamin.
Indice
Le second argument de Noeud est son enfant gauche ; le troisième son enfant droit.
Comprendre la correction
B. Benjamin reçoit Antoine à gauche et Eric à droite ; Eric reçoit Emilie à gauche. A inverse Eric et Emilie. C mélange des tuples et des nœuds à des emplacements où le constructeur attend un Contact et des sous-arbres.
class ContactManager:
def __init__(self):
self.root = None
def inserer_contact(self, contact):
self.root = self.inserer_recursive(self.root, contact)
def inserer_recursive(self, noeud, contact):
if noeud is None:
return Noeud(contact)
if contact.prenom < noeud.contact.prenom:
noeud.gauche = self.inserer_recursive(noeud.gauche, contact)
else:
...
return noeud
def trouver_contact(self, prenom):
return self.trouver_recursive(self.root, prenom)
def trouver_recursive(self, noeud, prenom):
if noeud is None:
return None
...
return noeud.contact.num_tel
elif prenom < noeud.contact.prenom:
...
else:
return self.trouver_recursive(noeud.droite, prenom)Question 8
#Compléter les lignes 14, 23 et 26 de ContactManager.
Indice
Le résultat d’un appel récursif doit remonter jusqu’à l’appel initial.
Comprendre la correction
noeud.droite = self.inserer_recursive(noeud.droite, contact) # 14
if prenom == noeud.contact.prenom: # 23
return noeud.contact.num_tel
elif prenom < noeud.contact.prenom:
return self.trouver_recursive(noeud.gauche, prenom) # 26L’insertion doit réaffecter le sous-arbre renvoyé, notamment lorsqu’un emplacement vide reçoit un nouveau nœud. La recherche teste l’égalité avant de choisir une branche et transmet avec return le résultat du sous-appel. Le code suppose, comme annoncé, que les prénoms insérés sont distincts ; sans cette précondition, le else d’insertion accepterait un doublon à droite.
Pour insérer Emilie, la comparaison avec Olivier choisit gauche ; avec Benjamin, elle choisit droite ; avec Eric, elle choisit gauche. Cet enfant est None, donc on crée le nœud. Les affectations successives replacent les racines des sous-arbres renvoyés à leur bon emplacement. Pour une recherche absente comme Adrien, on descend jusqu’à un None, puis ce résultat remonte inchangé grâce aux return.
contact_manager = ContactManager()
contact_manager.inserer_contact(Contact("Olivier", "01 23 45 67 89"))
contact_manager.inserer_contact(Contact("Benjamin", "06 12 34 56 78"))
contact_manager.inserer_contact(Contact("Sophie", "02 34 56 78 90"))
contact_manager.inserer_contact(Contact("Antoine", "09 87 65 43 21"))
contact_manager.inserer_contact(Contact("Eric", "03 45 67 89 01"))
contact_manager.inserer_contact(Contact("Roxanne", "07 45 67 89 01"))
contact_manager.inserer_contact(Contact("Victor", "03 56 78 90 12"))
contact_manager.inserer_contact(Contact("Emilie", "04 23 45 67 89"))
contact_manager.inserer_contact(Contact("Yann", "01 54 32 10 98"))
contact_manager.inserer_contact(Contact("Zoé", "05 23 45 67 89"))recherche_contact("Roxanne")
Prénom recherché : Roxanne
Roxanne : 07 45 67 89 01
recherche_contact("Adrien")
Prénom recherché : Adrien
Contact 'Adrien' introuvableQuestion 9
#Écrire recherche_contact(prenom_recherche) qui utilise contact_manager et reproduit les affichages demandés.
Indice
La méthode trouver_contact renvoie soit un numéro, soit None.
Comprendre la correction
def recherche_contact(prenom_recherche):
print("Prénom recherché :", prenom_recherche)
numero = contact_manager.trouver_contact(prenom_recherche)
if numero is None:
print("Contact '" + prenom_recherche + "' introuvable")
else:
print(prenom_recherche + " : " + numero)La recherche est appelée une seule fois et son résultat est conservé. On distingue explicitement None, marqueur d’absence, d’un numéro stocké sous forme de chaîne. Cette fonction affiche ; elle n’a pas besoin de renvoyer le numéro.
print("Liste des contacts triés :")
affichage_contacts_trie(contact_manager.root)Affichage attendu :
Liste des contacts triés :
Antoine : 09 87 65 43 21
Benjamin : 06 12 34 56 78
Emilie : 04 23 45 67 89
Eric : 03 45 67 89 01
Olivier : 01 23 45 67 89
Roxanne : 07 45 67 89 01
Sophie : 02 34 56 78 90
Victor : 03 56 78 90 12
Yann : 01 54 32 10 98
Zoé : 05 23 45 67 89Question 10
#Choisir et justifier le parcours pour afficher les prénoms par ordre alphabétique : largeur, profondeur préfixe, infixe ou postfixe.
Indice
Le nœud doit être affiché entre les deux sous-arbres.
Comprendre la correction
Le parcours en profondeur infixe : sous-arbre gauche, nœud, sous-arbre droit. Les clés de gauche sont plus petites que le nœud et celles de droite plus grandes. En appliquant récursivement le même ordre, on obtient toute la liste triée.
def affichage_contacts_trie(noeud):
"""Afficher les contacts triés par nom."""
if noeud is not None:
...
...
affichage_contacts_trie(noeud.droite)Question 11
#Compléter les lignes 4 et 5 de affichage_contacts_trie.
Indice
Le squelette possède déjà le troisième temps du parcours infixe.
Comprendre la correction
affichage_contacts_trie(noeud.gauche)
print(noeud.contact.prenom + " : " + noeud.contact.num_tel)Le cas None ne produit aucun affichage. Pour chaque nœud non vide, on traite à gauche, affiche son contact puis utilise l’appel à droite déjà fourni. Les numéros restent associés à leurs prénoms : on ne trie pas séparément les deux listes.
Sur le sous-arbre Benjamin, l’exécution descend d’abord sur Antoine, l’affiche, revient afficher Benjamin, puis visite le sous-arbre Eric. Dans celui-ci, Emilie est affichée avant Eric. On obtient donc Antoine, Benjamin, Emilie, Eric. Le cas None se contente de revenir à l’appel précédent : il représente une branche absente et ne doit rien afficher.
Chercher un prénom sans visiter tout l’annuaireUn atelier pour expérimenter
Choisissez un prénom présent ou absent. Le tableau montre chaque comparaison effectuée depuis Olivier et la branche choisie. Comparez cette recherche à un balayage des dix contacts.
Lire le résultat de l’expérience initiale
Contact trouvé
La recherche s’arrête dès l’égalité.
| Nœud | Comparaison | Décision |
|---|---|---|
| Olivier | Emilie < Olivier | Gauche |
| Benjamin | Emilie > Benjamin | Droite |
| Eric | Emilie < Eric | Gauche |
| Emilie | Égalité | Trouvé |
Le coût d’une recherche suit la hauteur de la branche réellement parcourue, pas nécessairement le nombre total de contacts.
Exercice 3 · 8 points
Chatbot : dictionnaire, SQL et recherche dans un arbre
La start-up fictive OpenChat développe un chatbot qui stocke des questions et leurs réponses. On commence par une classe Python munie d’un dictionnaire, puis on utilise une table SQL et enfin un arbre de mots-clés. Ces trois représentations servent le même besoin mais ne s’utilisent pas de la même façon.
Partie 1 : structures de données
Chatbot possède un attribut base_donnees initialement vide. ajouter_question_reponse y mémorise une réponse ; repondre récupère la réponse associée à une question.
class Chatbot:
def __init__(self):
self.base_donnees = ...
def ajouter_question_reponse(self, question, reponse):
self.base_donnees[question] = ...
def repondre(self, question):
return self.base_donnees[question]Question 1
#Compléter les lignes 3 et 6 de Chatbot.
Indice
La question est la clé et la réponse sa valeur.
Comprendre la correction
self.base_donnees = {}
# Dans ajouter_question_reponse :
self.base_donnees[question] = reponseLes accolades vides créent un dictionnaire, pas une liste. Une affectation à une clé ajoute une association ou remplace sa valeur si cette même question existait déjà.
Question 2
#Instancier Bot1 puis ajouter la question « Bonjour, comment vas-tu ? » et la réponse « Bonjour, je vais bien, merci. ».
Indice
Les parenthèses après Chatbot créent réellement un objet.
Comprendre la correction
Bot1 = Chatbot()
Bot1.ajouter_question_reponse(
"Bonjour, comment vas-tu ?",
"Bonjour, je vais bien, merci.")L’appel au constructeur crée l’instance. La méthode agit ensuite sur cette instance : Python transmet automatiquement Bot1 comme self.
Question 3
#Obtenir la réponse à « Bonjour, comment vas-tu ? ».
Indice
Utilisez la méthode qui renvoie une valeur, puis affichez cette valeur si nécessaire.
Comprendre la correction
reponse = Bot1.repondre("Bonjour, comment vas-tu ?")
print(reponse)La réponse renvoyée est Bonjour, je vais bien, merci.. La recherche est exacte : le programme ne comprend pas les variantes de formulation et l’accès à une question absente provoquerait KeyError. Ajouter une réponse par défaut serait une amélioration distincte de la consigne.
Partie 2 : base de données
La table questions_reponses comporte les colonnes id, question, reponse, mot_cle. Les mots-clés disponibles incluent AND, FROM, INSERT, INTO, JOIN, OR, ON, SELECT, SET, UPDATE, VALUES et WHERE.
Question 4
#Associer SELECT, INSERT et UPDATE à : ajouter une donnée, extraire des données, mettre à jour une table.
Indice
Pensez à lire, ajouter, modifier.
Comprendre la correction
| Mot-clé | Action |
|---|---|
SELECT | Extraire des données |
INSERT | Ajouter une donnée |
UPDATE | Mettre à jour les lignes d’une table |
SELECT consulte sans modifier. INSERT crée une ligne. UPDATE modifie des lignes existantes, généralement sélectionnées par WHERE.
Question 5
#Définir une clé primaire.
Indice
L’unicité et l’absence de valeur NULL sont les deux propriétés à citer.
Comprendre la correction
Une clé primaire est un attribut ou un ensemble d’attributs choisi pour identifier de façon unique chaque ligne d’une table. Ses valeurs ne peuvent pas être nulles et deux lignes ne peuvent pas partager la même valeur de clé. Une clé composée s’interprète comme un ensemble, pas comme plusieurs identifiants séparés.
Question 6
#Quel attribut ou ensemble d’attributs convient le mieux comme clé primaire de questions_reponses ? Justifier.
Indice
Le mot-clé sert à classer plusieurs interactions, pas à en identifier une seule.
Comprendre la correction
id, à condition qu’il soit attribué de façon unique et non nulle. Il identifie une interaction indépendamment de son texte. Un même mot-clé peut concerner plusieurs questions ; une réponse comme « Oui » peut aussi se répéter. L’identifiant reste stable si le texte est corrigé.
Question 7
#Récupérer toutes les questions dont le mot-clé est python.
SQLFiltrer le mot-clé du chatbotÉcrivez votre solution et mettez-la à l’épreuve
Renvoyez toutes les questions classées sous le mot-clé exactement égal à python. Le texte de la question peut contenir ou ne pas contenir ce mot.
SELECT ...
FROM ...
WHERE ...;Les cas de test proposés :
- Jeu pédagogique et entrée historique citée : L’entrée 15 sur Marignan est celle demandée plus loin dans le sujet. Les deux autres lignes sont des compléments pédagogiques qui distinguent classement par mot-clé et recherche dans le texte.
- Cas complémentaire : projection avec répétitions : Jeu complémentaire pédagogique, distinct des données officielles. Les identifiants distinguent les lignes même si leurs textes sont identiques. Le mot-clé
Python avancéne vaut paspython.
Indice
La colonne à filtrer et la colonne à afficher ne sont pas les mêmes.
Comprendre la correction
SELECT question
FROM questions_reponses
WHERE mot_cle = 'python';On compare le champ de classement au mot-clé demandé. On ne recherche pas le mot python à l’intérieur du texte de la question, car ce n’est pas ce que demande la consigne.
Le résultat SELECT question est une projection : même si la table contient identifiant, réponse et mot-clé, seuls les textes de questions apparaissent. WHERE est appliqué au champ mot_cle. Une question contenant le mot Python mais classée sous un autre mot-clé ne serait pas retenue par cette requête, conformément au besoin formulé.
Question 8
#Insérer id 15, question « Date de la bataille de Marignan ? », réponse « 1515 », mot-clé « Histoire ».
Indice
Vérifiez qu’aucun des quatre champs n’a été oublié.
Comprendre la correction
INSERT INTO questions_reponses (id, question, reponse, mot_cle)
VALUES (15, 'Date de la bataille de Marignan ?', '1515', 'Histoire');15 identifie l’enregistrement ; « 1515 » est le texte de la réponse. Écrire les colonnes rend explicite l’association de chaque valeur et évite de dépendre implicitement de l’ordre physique du schéma.
Question 9
#Mettre à jour la réponse de l’enregistrement d’id 12 pour qu’elle devienne « Oui ».
Indice
UPDATE modifie une ligne existante ; ce n’est pas une insertion.
Comprendre la correction
UPDATE questions_reponses
SET reponse = 'Oui'
WHERE id = 12;La condition cible une seule interaction par son identifiant. Sans WHERE, toutes les réponses deviendraient « Oui ». On ne modifie ni la question ni son mot-clé.
Partie 3 : arbre de mots-clés
Le sujet fournit cet arbre, présenté comme un ABR pour l’ordre alphabétique :
IA
/ \
bot programmation
/ \ / \
algo chatbot informatique pythonQuestion 10
#Quel parcours donne IA, bot, programmation, algo, chatbot, informatique, python ?
Indice
L’ordre visite d’abord tous les nœuds à une même profondeur.
Comprendre la correction
Le parcours en largeur, de gauche à droite dans chaque niveau : d’abord la racine, ensuite ses enfants, puis les quatre petits-enfants.
Question 11
#Donner l’ordre du parcours en profondeur infixe sur cet arbre.
Indice
Appliquez gauche-nœud-droite récursivement.
Comprendre la correction
algo, bot, chatbot, IA, informatique, programmation, python. On visite gauche, racine, droite à chaque nœud. Le sous-arbre de bot est donc entièrement visité avant IA.
Pour ne pas se perdre, appliquez la règle à un seul sous-arbre : le nœud bot produit algo, bot, chatbot. Le nœud programmation produit informatique, programmation, python. La racine IA se place entre ces deux groupes. Les parcours se déterminent par la structure des liens ; il n’est pas nécessaire de comparer les mots pendant un parcours.
Question 12
#Donner l’ordre du parcours en profondeur postfixe ou suffixe.
Indice
Pour chaque petit arbre, écrivez ses deux feuilles avant sa racine.
Comprendre la correction
algo, chatbot, bot, informatique, python, programmation, IA. Cette fois, chaque racine est visitée après ses deux sous-arbres : gauche, droite, nœud. IA est donc le dernier élément.
Question 13
#Quel parcours donne les mots-clés dans l’ordre alphabétique ?
Indice
La propriété infixe-trié suppose que l’arbre et la recherche utilisent exactement le même ordre.
Comprendre la correction
Le parcours infixe, puisque l’arbre est déclaré ordonné alphabétiquement. Attention à la majuscule de IA : l’ordre alphabétique usuel de l’exercice ignore ici la casse. Python compare directement les chaînes selon les points de code Unicode, où "IA" < "algo". Avec cette comparaison native, le dessin ne serait pas un ABR valide. Pour réaliser le modèle alphabétique annoncé, on peut normaliser les clés avec casefold() au moment des comparaisons.
class Noeud:
def __init__(self, cle):
self.cle = cle
self.gauche = None
self.droit = None
def recherche(arbre, cle):
if arbre is None or arbre.cle == ...:
return arbre
if cle < arbre.cle:
return recherche(arbre. ..., cle)
else:
return recherche(arbre. ..., cle)Question 14
#Compléter la recherche dans l’ABR aux lignes 9, 12 et 14.
PythonTrouver le nœud du chatbot sans explorer tout l’arbreÉcrivez votre solution et mettez-la à l’épreuve
Écrivez recherche(arbre, cle), qui renvoie le nœud portant la clé ou None. La fonction est placée hors de la classe pour être directement exécutable. Pour respecter l’ordre alphabétique sans casse de l’arbre imprimé (racine IA), toutes les comparaisons utilisent casefold, à l’insertion comme à la recherche. Les enfants s’appellent gauche et droit. Renvoyez le nœud lui-même, pas seulement sa chaîne.
def recherche(arbre, cle):
# À vous de jouer
passLes cas de test proposés :
- Un arbre vide : Le cas vide doit être traité avant l’accès à l’attribut cle.
- La racine, quelle que soit la casse : La racine est écrite IA dans le sujet, et le classement retenu ignore la casse.
- La branche gauche puis droite : Le nœud bot guide ensuite la recherche vers son enfant droit.
- La branche droite jusqu’à une feuille : La normalisation s’applique à chaque comparaison, y compris à l’égalité finale.
- Une clé absente entre des clés présentes : La recherche doit aboutir à un enfant vide sans modifier les liens.
Indice
Un nom d’attribut doit correspondre exactement au constructeur.
Comprendre la correction
if arbre is None or arbre.cle == cle:
return arbre
if cle < arbre.cle:
return recherche(arbre.gauche, cle)
else:
return recherche(arbre.droit, cle)Les trois compléments attendus sont cle, gauche, droit. Le premier test arrête la recherche sur un arbre vide ou une égalité. L’attribut s’appelle ici droit, sans e, contrairement à l’exercice précédent.
Le code imprimé place cependant la fonction à l’intérieur de la classe tout en appelant recherche sans qualification. Pour disposer d’une version directement exécutable, on peut définir cette fonction à l’extérieur de la classe, ou employer une méthode statique et appeler Noeud.recherche(...). Pour l’arbre donné avec IA, les comparaisons doivent aussi respecter l’ordre alphabétique sans casse :
def recherche(arbre, cle):
if arbre is None or arbre.cle.casefold() == cle.casefold():
return arbre
if cle.casefold() < arbre.cle.casefold():
return recherche(arbre.gauche, cle)
return recherche(arbre.droit, cle)La recherche sur une clé absente doit aboutir à None, tandis qu’une clé égale à celle du nœud courant renvoie ce nœud lui-même. Les deux tests de base se font avant l’accès aux enfants. La normalisation de casse doit être la même à l’insertion et à la recherche : corriger seulement la comparaison de recherche sur un arbre construit avec un autre ordre ne suffirait pas à restaurer l’invariant d’ABR.
Trois parcours, trois récits du même arbreUn atelier pour expérimenter
Choisissez un parcours puis déplacez le curseur pour n’en révéler que les premières visites. Anticipez le prochain mot-clé avant d’avancer.
Lire le résultat de l’expérience initiale
1 nœud visité
Gauche, nœud, droite : les clés sortent dans l’ordre alphabétique sans tenir compte de la casse.
| Rang | Mot-clé |
|---|---|
| 1 | algo |
Un parcours se définit par l’ordre de visite. Une recherche d’ABR utilise en plus une relation d’ordre cohérente sur les clés.
Revoir les notions de cet exercice
Du sujet à la méthode
Votre prochaine séance de révision
- Annoncez la convention de hauteur avant de compter un chemin.
- Quand un programme est complété, testez un cas absent et un cas de base, pas seulement l’exemple réussi.
- Pour la complexité, distinguez le nombre d’opérations, le volume stocké et l’état initial du cache.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 26-NSIJ1PO1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.
