💻

NSI - Bac 2025

Métropole - Session normale

Epreuve du 15 juin 2025

Duree : 3h30
3 questions
Coef. 16

Consigne officielle

Le candidat traite les trois exercices.

exercice

Exercice 1

6 points

Enonce

Exercice 1 — Structures de données (6 points)

[Contexte] On s'intéresse à la gestion d'une bibliothèque municipale. Les livres sont représentés par des dictionnaires contenant les clés suivantes : 'titre', 'auteur', 'annee', 'isbn' (identifiant unique) et 'emprunte' (booléen).

La bibliothèque utilise une structure de données pour organiser ses livres. On considère le code Python suivant :

class Bibliotheque:
    def __init__(self):
        self.livres = []
        self.index_isbn = {}
    
    def ajouter_livre(self, livre):
        self.livres.append(livre)
        self.index_isbn[livre['isbn']] = len(self.livres) - 1
    
    def rechercher_par_isbn(self, isbn):
        if isbn in self.index_isbn:
            return self.livres[self.index_isbn[isbn]]
        return None
    
    def rechercher_par_titre(self, titre):
        resultats = []
        for livre in self.livres:
            if livre['titre'].lower() == titre.lower():
                resultats.append(livre)
        return resultats
    
    def emprunter_livre(self, isbn):
        livre = self.rechercher_par_isbn(isbn)
        if livre and not livre['emprunte']:
            livre['emprunte'] = True
            return True
        return False
    
    def livres_disponibles(self):
        disponibles = []
        for livre in self.livres:
            if not livre['emprunte']:
                disponibles.append(livre)
        return disponibles
  1. Analyser la complexité temporelle des méthodes rechercher_par_isbn et rechercher_par_titre. Justifier votre réponse.

  2. Proposer une modification de la structure de données pour améliorer la recherche par titre. Décrire l'algorithme de recherche correspondant et analyser sa complexité.

  3. Compléter la classe Bibliotheque en ajoutant une méthode supprimer_livre(isbn) qui retire un livre de la bibliothèque. Cette méthode doit maintenir à jour toutes les structures de données internes. Le code doit gérer le cas où le livre n'existe pas.

Notions :Structures de donnéesPythonComplexité algorithmiqueDictionnairesListes
Difficulte : moyen
Mode examen

Methode

Pour résoudre cet exercice sur les structures de données, il faut d'abord analyser le code fourni en comprenant le rôle de chaque structure (liste et dictionnaire). Pour la question sur la complexité, identifier les opérations dominantes (accès, parcours) et les exprimer en fonction de n (nombre de livres). Pour l'amélioration de la recherche, envisager des structures d'indexation supplémentaires (dictionnaires avec listes) pour éviter les parcours complets. Pour l'implémentation de la suppression, penser à maintenir la cohérence des structures (liste et index) en gérant les cas particuliers (élément en fin de liste ou non). Toujours justifier les choix par des arguments de complexité et d'efficacité.

Points cles

  • 1Complexité algorithmique : La complexité temporelle mesure le nombre d'opérations en fonction de la taille des données (n livres). O(1) pour accès direct, O(n) pour parcours complet.
  • 2Indexation par dictionnaire : Le dictionnaire index_isbn permet un accès en O(1) à un livre via son ISBN, car c'est une table de hachage avec recherche par clé constante en moyenne.
  • 3Parcours de liste : La recherche par titre parcourt toute la liste des livres (O(n)) car il n'y a pas d'index sur le titre. Chaque livre est examiné via une comparaison de chaînes.
  • 4Amélioration par indexation multiple : Pour améliorer la recherche par titre, on peut ajouter un dictionnaire index_titre qui associe à chaque titre une liste d'indices ou de références vers les livres correspondants.
  • 5Suppression avec maintien des index : Supprimer un livre nécessite de le retirer de la liste et de mettre à jour l'index ISBN. Si le livre n'est pas en fin de liste, il faut ajuster les indices dans index_isbn pour les livres décalés.
exercice

Exercice 2

6 points

Enonce

Exercice 2 — Algorithmique (6 points)

On considère la fonction recherche_sequentielle qui prend en paramètre une liste liste d'entiers et un entier valeur, et qui renvoie l'indice de la première occurrence de valeur dans liste, ou -1 si valeur n'est pas présente.

def recherche_sequentielle(liste, valeur):
    '''
    Recherche séquentielle dans une liste non triée.
    '''
    for i in range(len(liste)):
        if liste[i] == valeur:
            return i
    return -1

On souhaite maintenant implémenter une version optimisée pour une liste triée par ordre croissant : la fonction recherche_dichotomique.

  1. Expliquez pourquoi la recherche dichotomique est plus efficace que la recherche séquentielle sur une liste triée. Quelle est la complexité temporelle de chaque algorithme dans le pire des cas ?

  2. Complétez la fonction recherche_dichotomique ci-dessous pour qu'elle renvoie l'indice de valeur dans liste (triée par ordre croissant) ou -1 si valeur n'est pas présente. Utilisez une approche itérative.

def recherche_dichotomique(liste, valeur):
    '''
    Recherche dichotomique dans une liste triée par ordre croissant.
    '''
    gauche = 0
    droite = len(liste) - 1
    while gauche <= droite:
        milieu = (gauche + droite) // 2
        if liste[milieu] == valeur:
            return milieu
        elif liste[milieu] < valeur:
            # À compléter
        else:
            # À compléter
    return -1
  1. Modifiez la fonction recherche_dichotomique pour qu'elle renvoie également le nombre d'itérations effectuées dans la boucle while avant de terminer. La fonction devra renvoyer un tuple (indice, iterations).
Notions :AlgorithmiquePythonRecherche dichotomiqueComplexité
Difficulte : moyen
Mode examen

Methode

Pour résoudre cet exercice sur la recherche dichotomique, il faut d'abord comprendre le principe de l'algorithme : diviser l'espace de recherche par deux à chaque itération. La première question demande une analyse comparative des complexités. Il faut rappeler que la recherche séquentielle examine chaque élément un par un, tandis que la recherche dichotomique élimine la moitié des possibilités restantes à chaque étape. Pour la deuxième question, l'implémentation itérative nécessite de maintenir deux indices (gauche et droite) qui délimitent la zone de recherche, et de les ajuster selon la comparaison avec l'élément central. Il faut veiller à la condition d'arrêt de la boucle (gauche <= droite) et au calcul correct du milieu. Pour la troisième question, il suffit d'ajouter un compteur initialisé à 0 qu'on incrémente à chaque passage dans la boucle while, puis de modifier le return pour renvoyer un tuple. Il est crucial de tester mentalement l'algorithme avec des cas limites : liste vide, valeur absente, valeur aux extrémités.

Points cles

  • 1La recherche dichotomique nécessite une liste triée. Son efficacité vient du fait qu'à chaque étape, la taille de la zone de recherche est divisée par deux, contrairement à la recherche séquentielle qui examine tous les éléments dans le pire des cas.
  • 2Complexité : recherche séquentielle O(n) dans le pire cas (n comparaisons), recherche dichotomique O(log₂ n) dans le pire cas (environ log₂ n comparaisons). La base du logarithme est 2 car on divise par deux à chaque étape.
  • 3Implémentation itérative : on utilise une boucle while avec des indices 'gauche' et 'droite'. La condition 'gauche <= droite' assure qu'on explore toute la zone. Si 'gauche' devient supérieur à 'droite', la valeur n'est pas présente.
  • 4Ajustement des bornes : si la valeur cherchée est supérieure à l'élément du milieu, on restreint la recherche à la moitié droite (gauche = milieu + 1). Si elle est inférieure, on restreint à la moitié gauche (droite = milieu - 1).
  • 5Gestion du retour : la fonction de base renvoie l'indice ou -1. Pour la version avec compteur, on initialise un compteur à 0 avant la boucle, on l'incrémente à chaque itération, et on renvoie un tuple (indice, compteur). Même en cas de retour anticipé (valeur trouvée), il faut inclure le compteur.
exercice

Exercice 3

6 points

Enonce

Exercice 3 — Bases de données (6 points)

[Contexte] On considère une base de données relationnelle contenant deux tables :

  • Etudiants(id_etudiant, nom, prenom, id_classe)
  • Classes(id_classe, nom_classe, niveau)

On souhaite récupérer la liste des étudiants de terminale avec leur classe.

import sqlite3

def obtenir_etudiants_terminale():
    conn = sqlite3.connect('ecole.db')
    cursor = conn.cursor()
    
    requete = """
    SELECT Etudiants.nom, Etudiants.prenom, Classes.nom_classe
    FROM Etudiants
    JOIN Classes ON Etudiants.id_classe = Classes.id_classe
    WHERE Classes.niveau = 'Terminale'
    """
    
    cursor.execute(requete)
    resultats = cursor.fetchall()
    
    conn.close()
    return resultats

# Fonction à compléter
def ajouter_etudiant(nom, prenom, id_classe):
    """Ajoute un nouvel étudiant dans la base de données"""
    conn = sqlite3.connect('ecole.db')
    cursor = conn.cursor()
    
    # À compléter
    
    conn.close()
  1. Expliquez ce que fait la fonction obtenir_etudiants_terminale() et décrivez la requête SQL utilisée.

  2. Proposez une version modifiée de la fonction obtenir_etudiants_terminale() qui retourne les résultats sous forme de liste de dictionnaires plutôt que de tuples.

  3. Complétez la fonction ajouter_etudiant(nom, prenom, id_classe) pour qu'elle insère effectivement un nouvel étudiant dans la table Etudiants. Générez automatiquement l'id_etudiant en prenant le maximum existant + 1.

Notions :Bases de donnéesSQLPythonSQLite
Difficulte : moyen
Mode examen

Methode

Pour résoudre cet exercice sur les bases de données avec Python et SQLite, il faut suivre une approche structurée. D'abord, analyser le code existant pour comprendre le schéma de la base et les opérations réalisées. Ensuite, pour chaque question : 1) Comprendre et expliquer le fonctionnement d'une requête SQL avec JOIN et WHERE, 2) Manipuler les résultats de requêtes SQL en Python pour les transformer dans un format spécifique (liste de dictionnaires), ce qui nécessite de connaître les méthodes des curseurs et la structure des données retournées, 3) Écrire une fonction d'insertion avec génération automatique d'identifiant, ce qui implique d'exécuter deux requêtes SQL séquentielles (SELECT MAX puis INSERT) dans une même transaction. Il est crucial de gérer proprement les connexions à la base (ouverture/fermeture) et de valider les modifications avec commit(). Pour la génération d'id, il faut penser au cas particulier où la table est vide.

Points cles

  • 1JOIN SQL : La clause JOIN permet d'associer des lignes de deux tables sur la base d'une condition de correspondance (ici, l'égalité des id_classe). C'est fondamental pour exploiter les relations entre tables.
  • 2Récupération des résultats : fetchall() retourne une liste de tuples. Chaque tuple correspond à une ligne du résultat de la requête, et l'ordre des éléments dans le tuple correspond à l'ordre des colonnes dans le SELECT.
  • 3Transformation en liste de dictionnaires : Utiliser cursor.description pour obtenir les noms des colonnes et les associer aux valeurs de chaque tuple via zip(). Cela améliore la lisibilité du code en accédant aux données par nom de colonne plutôt que par index.
  • 4Insertion avec ID auto-généré : Pour générer un nouvel id unique, on exécute d'abord une requête SELECT MAX(id_etudiant) FROM Etudiants pour obtenir le plus grand id existant. Il faut gérer le cas où la table est vide (MAX retourne NULL). Ensuite, on incrémente cette valeur et on l'utilise dans l'INSERT.
  • 5Gestion des transactions : Après une modification (INSERT, UPDATE, DELETE), il est impératif d'appeler conn.commit() pour valider les changements dans la base de données. Sinon, ils seront perdus à la fermeture de la connexion.

Informations

MatiereNSI
Session2025
CentreMétropole
Filieregenerale
Coefficient16
Source : Généré par IA (DeepSeek) - Sujet type Bac