NSI - Bac 2022
Métropole - Session normale
Epreuve du 15 juin 2022
Consigne officielle
Le candidat traite les trois exercices.
Exercice 1
Enonce
Exercice 1 — Structures de données (6 points)
[Contexte] On considère une structure de données représentant un arbre binaire de recherche (ABR) stockant des entiers. Chaque nœud est représenté par un dictionnaire avec les clés 'valeur', 'gauche' et 'droite'. La valeur None représente un sous-arbre vide.
# Code Python à analyser/compléter
class ABR:
def __init__(self, racine=None):
self.racine = racine
def inserer(self, valeur):
"""Insère la valeur dans l'ABR"""
if self.racine is None:
self.racine = {'valeur': valeur, 'gauche': None, 'droite': None}
else:
self._inserer_rec(self.racine, valeur)
def _inserer_rec(self, noeud, valeur):
if valeur < noeud['valeur']:
if noeud['gauche'] is None:
noeud['gauche'] = {'valeur': valeur, 'gauche': None, 'droite': None}
else:
self._inserer_rec(noeud['gauche'], valeur)
else:
if noeud['droite'] is None:
noeud['droite'] = {'valeur': valeur, 'gauche': None, 'droite': None}
else:
self._inserer_rec(noeud['droite'], valeur)
def recherche(self, valeur):
"""Recherche la valeur dans l'ABR"""
return self._recherche_rec(self.racine, valeur)
def _recherche_rec(self, noeud, valeur):
if noeud is None:
return False
if valeur == noeud['valeur']:
return True
elif valeur < noeud['valeur']:
return self._recherche_rec(noeud['gauche'], valeur)
else:
return self._recherche_rec(noeud['droite'], valeur)
def hauteur(self):
"""Retourne la hauteur de l'arbre"""
return self._hauteur_rec(self.racine)
def _hauteur_rec(self, noeud):
# À compléter
pass
- Expliquez ce que fait la méthode
rechercheet donnez sa complexité dans le pire des cas. - Proposez une méthode itérative (non récursive) pour la recherche dans un ABR.
- Complétez la méthode
_hauteur_recqui doit retourner la hauteur de l'arbre (0 pour un arbre vide, 1 pour un arbre avec uniquement la racine).
Methode
Pour résoudre cet exercice sur les arbres binaires de recherche (ABR), il faut d'abord bien comprendre les propriétés fondamentales d'un ABR : pour tout nœud, les valeurs du sous-arbre gauche sont strictement inférieures à sa valeur, et celles du sous-arbre droit sont supérieures ou égales. Cette propriété permet des algorithmes de recherche et d'insertion efficaces. L'approche méthodologique consiste à : 1) Analyser le code fourni pour comprendre la structure de données (dictionnaires avec clés 'valeur', 'gauche', 'droite'), 2) Comprendre le principe de la récursivité structurelle sur les arbres, 3) Pour la complexité, considérer la forme de l'arbre (équilibré vs dégénéré), 4) Pour la version itérative, remplacer les appels récursifs par une boucle avec un pointeur courant, 5) Pour la hauteur, appliquer la définition récursive classique : hauteur(arbre vide)=0, sinon 1+max(hauteur(gauche), hauteur(droite)). Il faut toujours vérifier les cas limites (arbre vide, feuilles).
Points cles
- 1Propriété fondamentale des ABR : Pour tout nœud N, tous les nœuds du sous-arbre gauche ont une valeur < N.valeur, et ceux du sous-arbre droit ont une valeur ≥ N.valeur. Cette propriété permet une recherche par dichotomie.
- 2Complexité de la recherche : Dans le pire cas (arbre dégénéré en liste chaînée), la complexité est O(n) où n est le nombre de nœuds. Dans le cas moyen (arbre équilibré), elle est O(log n). La hauteur de l'arbre est le facteur déterminant.
- 3Transformation récursif → itératif : Pour un ABR, on peut remplacer la récursivité par une boucle while qui parcourt l'arbre depuis la racine en suivant la propriété des ABR jusqu'à trouver la valeur ou atteindre une feuille (None).
- 4Calcul récursif de la hauteur : La hauteur d'un arbre est définie comme le nombre maximum de nœuds sur un chemin de la racine à une feuille. Formellement : hauteur(None)=0, hauteur(nœud)=1+max(hauteur(gauche), hauteur(droite)).
- 5Gestion des cas limites : Toujours traiter explicitement le cas de l'arbre vide (racine=None) dans les méthodes itératives et récursives pour éviter les erreurs d'accès à des attributs ou clés de dictionnaire.
Exercice 2
Enonce
Exercice 2 — Algorithmique (6 points)
On considère la fonction suivante qui manipule des tableaux d'entiers :
def mystere(tab, x):
'''
tab : tableau d'entiers triés par ordre croissant
x : entier
'''
g = 0
d = len(tab) - 1
while g <= d:
m = (g + d) // 2
if tab[m] == x:
return m
elif tab[m] < x:
g = m + 1
else:
d = m - 1
return -1
- Quel est le nom de l'algorithme implémenté par cette fonction ? Quel est son principe ?
- Quelle est la complexité temporelle de cet algorithme dans le pire des cas ? Justifier.
- On souhaite modifier la fonction pour qu'elle renvoie l'indice de la première occurrence de x dans le tableau (le tableau peut contenir plusieurs fois la même valeur). Compléter la fonction suivante :
def premiere_occurrence(tab, x):
'''
tab : tableau d'entiers triés par ordre croissant
x : entier
Renvoie l'indice de la première occurrence de x dans tab, ou -1 si x n'est pas présent
'''
g = 0
d = len(tab) - 1
resultat = -1
while g <= d:
m = (g + d) // 2
if tab[m] == x:
# À compléter
pass
elif tab[m] < x:
g = m + 1
else:
d = m - 1
return resultat
Methode
Analyse du code puis implémentation
Points cles
- 1Recherche dichotomique
- 2Complexité logarithmique
- 3Modification d'algorithme pour première occurrence
Exercice 3
Enonce
Exercice 3 — Bases de données (6 points)
[Contexte] On considère une base de données relationnelle contenant une table 'Etudiants' avec les attributs suivants :
- id_etudiant (entier, clé primaire)
- nom (texte)
- prenom (texte)
- annee_naissance (entier)
- specialite (texte)
On souhaite implémenter en Python des fonctions pour manipuler cette base de données SQLite.
import sqlite3
def creer_connexion(nom_bd):
"""Crée une connexion à la base de données SQLite spécifiée"""
conn = None
try:
conn = sqlite3.connect(nom_bd)
return conn
except sqlite3.Error as e:
print(e)
return conn
def creer_table(conn):
"""Crée la table Etudiants"""
sql_create_table = """
CREATE TABLE IF NOT EXISTS Etudiants (
id_etudiant INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
prenom TEXT NOT NULL,
annee_naissance INTEGER,
specialite TEXT
);
"""
try:
c = conn.cursor()
c.execute(sql_create_table)
except sqlite3.Error as e:
print(e)
def ajouter_etudiant(conn, etudiant):
"""Ajoute un nouvel étudiant à la table"""
sql = ''' INSERT INTO Etudiants(id_etudiant, nom, prenom, annee_naissance, specialite)
VALUES(?,?,?,?,?) '''
cur = conn.cursor()
cur.execute(sql, etudiant)
conn.commit()
return cur.lastrowid
def rechercher_etudiants_par_specialite(conn, specialite):
"""Recherche tous les étudiants d'une spécialité donnée"""
cur = conn.cursor()
cur.execute("SELECT * FROM Etudiants WHERE specialite=?", (specialite,))
return cur.fetchall()
# Code principal
conn = creer_connexion("universite.db")
if conn is not None:
creer_table(conn)
# Ajout de quelques étudiants
etudiants = [
(1, 'Dupont', 'Jean', 2003, 'NSI'),
(2, 'Martin', 'Marie', 2004, 'Maths'),
(3, 'Durand', 'Pierre', 2003, 'NSI'),
(4, 'Leroy', 'Sophie', 2004, 'Physique')
]
for etudiant in etudiants:
ajouter_etudiant(conn, etudiant)
# Recherche des étudiants en NSI
resultats = rechercher_etudiants_par_specialite(conn, 'NSI')
print("Étudiants en NSI:")
for etudiant in resultats:
print(etudiant)
conn.close()
else:
print("Erreur! impossible de créer la connexion à la base de données.")
-
Expliquez pourquoi l'utilisation des paramètres '?' dans la fonction ajouter_etudiant est importante pour la sécurité des données. Quel risque évite-t-on ?
-
Proposez une fonction Python qui calcule l'âge moyen des étudiants d'une spécialité donnée. Quelle est la complexité algorithmique de votre solution ?
-
Complétez le code pour ajouter une fonction qui permet de mettre à jour la spécialité d'un étudiant donné par son identifiant.
Methode
Pour résoudre ce type d'exercice sur les bases de données en Python, il faut adopter une démarche structurée. Commencez par analyser le code fourni pour comprendre la structure de la base et les fonctions existantes. Pour chaque question, identifiez les concepts théoriques sous-jacents (sécurité des données, complexité algorithmique, opérations CRUD). Lors de l'écriture de code, respectez scrupuleusement la syntaxe SQL et Python, en particulier pour les requêtes paramétrées. Testez mentalement chaque fonction avec des cas limites. Pour les questions théoriques, illustrez vos explications avec des exemples concrets tirés du code. Vérifiez toujours la cohérence entre les spécifications de la table et les opérations réalisées.
Points cles
- 1Injection SQL : L'utilisation des paramètres '?' (requêtes paramétrées) évite l'injection SQL en séparant clairement la structure de la requête SQL des données utilisateur. Cela empêche un attaquant d'injecter du code SQL malveillant via les champs de saisie.
- 2Requêtes paramétrées avec SQLite : En Python avec le module sqlite3, on utilise le marqueur '?' et un tuple pour passer les valeurs. Ce mécanisme échappe automatiquement les caractères spéciaux et valide les types.
- 3Calcul d'âge moyen : Pour calculer l'âge moyen, il faut récupérer les années de naissance, calculer les âges individuels (en fonction de l'année courante), puis en faire la moyenne. La complexité est linéaire O(n) où n est le nombre d'étudiants dans la spécialité.
- 4Opération UPDATE en SQL : La mise à jour d'un enregistrement utilise la commande SQL UPDATE avec une clause WHERE pour cibler l'étudiant par son identifiant. La syntaxe est : UPDATE table SET colonne = nouvelle_valeur WHERE condition.
- 5Gestion des connexions : Il est crucial de gérer proprement la connexion à la base (création, commit des transactions, fermeture) pour éviter les fuites de ressources et garantir la persistance des données.
