💻

NSI - Bac 2024

Métropole - Session normale

Epreuve du 15 juin 2024

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 considère une structure de données permettant de représenter un arbre binaire. Un arbre binaire est soit vide, soit un nœud contenant une valeur et deux sous-arbres (gauche et droit).

class Noeud:
    def __init__(self, valeur, gauche=None, droit=None):
        self.valeur = valeur
        self.gauche = gauche
        self.droit = droit

def parcours_prefixe(arbre):
    """Retourne la liste des valeurs de l'arbre dans l'ordre préfixe."""
    if arbre is None:
        return []
    else:
        return [arbre.valeur] + parcours_prefixe(arbre.gauche) + parcours_prefixe(arbre.droit)

# Exemple d'arbre :
#       1
#      / \
#     2   3
#    / \
#   4   5
arbre_exemple = Noeud(1,
                     Noeud(2,
                           Noeud(4),
                           Noeud(5)),
                     Noeud(3))
print(parcours_prefixe(arbre_exemple))  # Affiche [1, 2, 4, 5, 3]
  1. Quelle est la complexité temporelle de la fonction parcours_prefixe en fonction du nombre n de nœuds de l'arbre ? Justifiez votre réponse.

  2. On souhaite maintenant implémenter une fonction hauteur(arbre) qui retourne la hauteur de l'arbre (la hauteur d'un arbre vide est -1, la hauteur d'un arbre réduit à un nœud est 0). Proposez une implémentation récursive de cette fonction.

  3. Complétez la fonction est_equilibre(arbre) ci-dessous qui retourne True si l'arbre est équilibré, c'est-à-dire si pour chaque nœud, les hauteurs de ses sous-arbres gauche et droit diffèrent d'au plus 1, et False sinon.

def est_equilibre(arbre):
    """Retourne True si l'arbre est équilibré, False sinon."""
    if arbre is None:
        return True
    else:
        # À compléter
Notions :Structures de donnéesPythonArbres binairesRécursivitéComplexité
Difficulte : moyen
Mode examen

Methode

Pour résoudre cet exercice sur les arbres binaires, il faut d'abord bien comprendre la structure de données récursive. Un arbre binaire est soit vide (None), soit un nœud avec une valeur et deux sous-arbres. La récursivité est l'outil principal pour manipuler ces structures. Pour la complexité, on analyse le nombre d'appels récursifs en fonction du nombre de nœuds. Pour les fonctions de hauteur et d'équilibre, on utilise une approche récursive qui traite le cas de base (arbre vide) puis combine les résultats des sous-arbres. Il est crucial de bien définir les cas de base et les cas récursifs, et de s'assurer que tous les cas sont couverts. On vérifie toujours les arbres vides pour éviter les erreurs. Pour l'équilibre, on doit à la fois calculer la hauteur et vérifier la condition d'équilibre, ce qui peut se faire en une seule traversée ou avec des appels séparés selon l'implémentation.

Points cles

  • 1Complexité des parcours d'arbres : Un parcours récursif comme le parcours préfixe visite chaque nœud exactement une fois. La complexité temporelle est donc linéaire, O(n), où n est le nombre de nœuds. Chaque appel effectue un travail constant (ajout dans une liste) et fait deux appels récursifs, mais le nombre total d'appels est proportionnel à n.
  • 2Hauteur d'un arbre : La hauteur est définie comme la longueur du chemin le plus long de la racine à une feuille. Pour un arbre vide, la hauteur est souvent définie comme -1 (ou parfois 0 pour un arbre à un nœud). La formule récursive est : hauteur(arbre) = 1 + max(hauteur(gauche), hauteur(droit)) pour un nœud non vide, avec -1 pour None.
  • 3Arbre équilibré : Un arbre binaire est équilibré si pour chaque nœud, la différence de hauteur entre ses sous-arbres gauche et droit est au plus 1. Cette propriété doit être vérifiée récursivement sur tous les nœuds. On peut la vérifier en calculant les hauteurs et en testant la condition.
  • 4Récursivité sur les arbres : Les algorithmes sur les arbres binaires suivent un schéma récursif classique : traiter le cas de l'arbre vide, puis pour un nœud, traiter la valeur, et appeler récursivement la fonction sur les sous-arbres gauche et droit. La combinaison des résultats se fait selon l'objectif (liste, hauteur, booléen).
  • 5Implémentation en Python : Utiliser la classe Noeud fournie. Les fonctions prennent en paramètre un arbre (un objet Noeud ou None). Toujours vérifier si l'arbre est None en premier. Pour l'équilibre, on peut soit faire deux fonctions (une pour la hauteur, une pour vérifier l'équilibre en appelant la hauteur), soit une fonction qui retourne à la fois hauteur et équilibre en un seul parcours pour plus d'efficacité.
exercice

Exercice 2

6 points

Enonce

Exercice 2 — Algorithmique (6 points)

On considère l'algorithme suivant qui permet de déterminer si une chaîne de caractères est un palindrome (elle se lit de la même façon de gauche à droite et de droite à gauche).

def est_palindrome(chaine):
    """
    Détermine si une chaîne de caractères est un palindrome.
    """
    n = len(chaine)
    for i in range(n // 2):
        if chaine[i] != chaine[n - 1 - i]:
            return False
    return True
  1. Expliquez ce que fait la fonction est_palindrome et donnez sa complexité temporelle en fonction de la longueur n de la chaîne.

  2. On souhaite maintenant écrire une fonction récursive est_palindrome_rec qui réalise la même tâche. Complétez le code ci-dessous :

def est_palindrome_rec(chaine):
    """
    Version récursive pour déterminer si une chaîne est un palindrome.
    """
    n = len(chaine)
    if n <= 1:
        return True
    if chaine[0] != chaine[n - 1]:
        return False
    # À compléter
  1. Modifiez la fonction est_palindrome_rec pour qu'elle ignore les espaces et la casse (majuscules/minuscules). Par exemple, "Ésope reste ici et se repose" doit être considéré comme un palindrome.
Notions :AlgorithmiquePythonRécursivitéComplexité
Difficulte : moyen
Mode examen

Methode

Pour résoudre cet exercice sur les palindromes, il faut d'abord comprendre le fonctionnement de l'algorithme itératif fourni. Analyser la boucle et les conditions permet de saisir la logique de comparaison symétrique des caractères. Pour la version récursive, identifier le cas de base (chaîne vide ou d'un seul caractère) et le cas récursif (comparer les extrémités puis rappeler la fonction sur la sous-chaîne restante) est essentiel. Ensuite, pour ignorer espaces et casse, il faut prétraiter la chaîne ou adapter la comparaison en utilisant des méthodes de chaîne comme lower() et replace(). La complexité temporelle s'évalue en comptant le nombre d'opérations élémentaires en fonction de la longueur n de la chaîne. Une approche systématique consiste à tester chaque fonction avec des exemples variés (palindromes simples, avec espaces, avec majuscules) pour vérifier son bon fonctionnement.

Points cles

  • 1Principe de vérification d'un palindrome : comparer les caractères symétriquement par rapport au centre de la chaîne. L'algorithme itératif parcourt la moitié de la chaîne en comparant chaque caractère avec son symétrique.
  • 2Complexité temporelle : la fonction itérative a une complexité en O(n) où n est la longueur de la chaîne, car elle effectue au plus n/2 comparaisons. C'est une complexité linéaire.
  • 3Récursivité : pour une version récursive, le cas de base est une chaîne de longueur 0 ou 1 (toujours un palindrome). Le cas récursif compare les premier et dernier caractères, puis rappelle la fonction sur la sous-chaîne intermédiaire (en excluant ces deux caractères).
  • 4Gestion des espaces et de la casse : pour ignorer les différences de casse, convertir la chaîne en minuscules avec la méthode lower(). Pour ignorer les espaces, les supprimer avec replace(' ', ''). Ce prétraitement doit être fait avant la vérification du palindrome.
  • 5Indices et découpage de chaînes : en Python, on accède aux caractères avec chaine[i] et on extrait des sous-chaînes avec le slicing chaine[1:-1]. Attention aux indices négatifs qui permettent d'accéder facilement à la fin de la chaîne.
exercice

Exercice 3

6 points

Enonce

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

Un développeur travaille sur une application de gestion de bibliothèque. Il utilise une base de données relationnelle contenant les tables suivantes :

  • LIVRES (id_livre, titre, auteur, annee_publication, id_genre)
  • GENRES (id_genre, nom_genre)
  • EMPRUNTS (id_emprunt, id_livre, date_emprunt, date_retour)

Il souhaite créer une fonction Python qui permet de récupérer les livres empruntés pendant une période donnée, avec leur genre.

import sqlite3

def livres_empruntes_periode(date_debut, date_fin):
    """
    Retourne la liste des livres empruntés entre date_debut et date_fin
    avec leur titre, auteur et nom du genre.
    """
    conn = sqlite3.connect('bibliotheque.db')
    curseur = conn.cursor()
    
    requete = """
    SELECT L.titre, L.auteur, G.nom_genre
    FROM LIVRES L
    JOIN GENRES G ON L.id_genre = G.id_genre
    JOIN EMPRUNTS E ON L.id_livre = E.id_livre
    WHERE E.date_emprunt BETWEEN ? AND ?
    """
    
    curseur.execute(requete, (date_debut, date_fin))
    resultats = curseur.fetchall()
    
    conn.close()
    return resultats

# Test de la fonction
print(livres_empruntes_periode('2024-01-01', '2024-03-31'))
  1. Expliquez ce que fait la requête SQL dans la fonction, en détaillant chaque clause (SELECT, FROM, JOIN, WHERE).

  2. Le développeur souhaite maintenant modifier la fonction pour qu'elle retourne également le nombre total d'emprunts par livre pendant la période. Proposez une solution algorithmique en expliquant votre démarche.

  3. Complétez le code Python ci-dessous pour implémenter cette nouvelle fonctionnalité :

def livres_empruntes_avec_compteur(date_debut, date_fin):
    """
    Retourne la liste des livres empruntés entre date_debut et date_fin
    avec leur titre, auteur, nom du genre et nombre d'emprunts.
    """
    conn = sqlite3.connect('bibliotheque.db')
    curseur = conn.cursor()
    
    # À compléter : écrire la requête SQL adaptée
    requete = """
    """
    
    curseur.execute(requete, (date_debut, date_fin))
    resultats = curseur.fetchall()
    
    conn.close()
    return resultats
Notions :Bases de donnéesPythonSQLJointures
Difficulte : moyen
Mode examen

Methode

Pour résoudre cet exercice sur les bases de données en Python, il faut adopter une approche méthodique. Commencez par analyser soigneusement la structure des tables et leurs relations (clés primaires et étrangères). Pour la partie SQL, identifiez les données à extraire et les tables à joindre. La compréhension des jointures (INNER JOIN) est cruciale pour relier les informations. Pour les agrégations (comme le comptage), pensez à utiliser la clause GROUP BY et les fonctions d'agrégation (COUNT, SUM). En Python, suivez le pattern standard : connexion à la base, création d'un curseur, exécution d'une requête paramétrée (avec ? pour éviter les injections SQL), récupération des résultats et fermeture de la connexion. Testez toujours votre requête mentalement ou sur un exemple simple avant de l'écrire dans le code.

Points cles

  • 1Compréhension des jointures SQL : La requête utilise JOIN pour combiner les tables LIVRES, GENRES et EMPRUNTS. L'INNER JOIN (implicite avec JOIN) assure que seuls les livres ayant un genre correspondant et un emprunt dans la période sont sélectionnés. La condition ON spécifie comment les tables sont liées (par les clés étrangères).
  • 2Filtrage avec WHERE et BETWEEN : La clause WHERE restreint les résultats aux emprunts dont la date est comprise dans l'intervalle spécifié. BETWEEN ? AND ? est utilisé avec des paramètres pour une requête sécurisée. Les dates doivent être au format compatible avec le champ de la base (généralement 'YYYY-MM-DD').
  • 3Agrégation avec GROUP BY et COUNT : Pour compter le nombre d'emprunts par livre, il faut regrouper les résultats par livre (ou par identifiant de livre) et utiliser la fonction d'agrégation COUNT sur les lignes du groupe. Cela nécessite d'ajouter GROUP BY à la requête.
  • 4Sélection des colonnes dans SELECT : La clause SELECT liste les colonnes à retourner. Pour la version avec compteur, il faut ajouter COUNT(*) ou COUNT(E.id_emprunt) pour obtenir le nombre d'emprunts. Il est essentiel que toutes les colonnes non agrégées soient listées dans GROUP BY.
  • 5Programmation Python avec sqlite3 : L'utilisation du module sqlite3 suit un pattern précis : connexion, curseur, exécution de requête avec paramètres (un tuple), fetchall() pour récupérer tous les résultats sous forme de liste de tuples, et fermeture de la connexion pour libérer les ressources.

Informations

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