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
Puissance 4 : scores, arbre de coups et min-max
Deux joueurs introduisent alternativement un pion blanc ou noir dans l’une des sept colonnes d’une grille verticale de six lignes. Le pion tombe jusqu’à la première case libre en partant du bas. Le premier alignement d’au moins quatre pions de même couleur, horizontal, vertical ou diagonal, gagne. Lignes 0 à 5 de haut en bas ; colonnes 0 à 6 de gauche à droite. La figure 1 montre la victoire du blanc sur une diagonale :
| Ligne | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| 0 | vide | vide | vide | vide | vide | vide | vide |
| 1 | vide | vide | vide | vide | vide | vide | vide |
| 2 | vide | vide | vide | vide | blanc | vide | vide |
| 3 | vide | vide | vide | blanc | noir | vide | vide |
| 4 | vide | noir | blanc | blanc | noir | vide | vide |
| 5 | vide | blanc | noir | noir | noir | blanc | vide |
On veut programmer min-max : simuler des futurs coups des deux joueurs, donner un score aux possibilités puis choisir la meilleure en tenant compte de la réponse adverse. La classe Grille possède un unique attribut grille, une liste de six listes de sept entiers : 0 pour vide, 1 pour le joueur 1, 2 pour le joueur 2.
Question 1
#Écrire le constructeur de Grille, dont le tableau est initialement rempli de 0.
Indice
La grille comporte six lignes indépendantes de sept cases.
Comprendre la correction
def __init__(self):
self.grille = [[0 for _ in range(7)] for _ in range(6)]Chaque itération externe crée une nouvelle ligne. Ne pas écrire [[0]*7]*6 : les six entrées référenceraient alors la même liste et jouer un pion modifierait simultanément plusieurs lignes. La compréhension crée six objets distincts, ce qui est nécessaire pour représenter des cases indépendantes.
def joue(self, colonne, joueur):
ligne = 5
while ligne != -1 and self.grille[ligne][colonne] != 0:
ligne = ...
if ligne != -1:
self.grille[ligne][colonne] = ...
return ...
else:
return ...Question 2
#Compléter joue(colonne, joueur), qui place le pion de bas en haut si la colonne n’est pas pleine et renvoie True, sinon False.
Indice
Une case occupée fait diminuer l’indice de ligne.
Comprendre la correction
def joue(self, colonne, joueur):
ligne = 5
while ligne != -1 and self.grille[ligne][colonne] != 0:
ligne = ligne - 1
if ligne != -1:
self.grille[ligne][colonne] = joueur
return True
else:
return FalseOn part du bas et remonte les cases occupées. Le test ligne != -1 doit être évalué avant l’accès : Python court-circuite AND, donc on ne lit pas accidentellement la dernière ligne via l’indice -1 lorsque la colonne est pleine. L’échec ne modifie aucune case. Les paramètres sont supposés valides : colonne de 0 à 6 et joueur 1 ou 2.
[
[
0,
0,
0,
0,
0,
0,
0
],
[
0,
0,
0,
0,
0,
0,
0
],
[
0,
0,
0,
0,
0,
0,
0
],
[
0,
0,
0,
0,
0,
0,
0
],
[
0,
0,
0,
1,
0,
0,
0
],
[
0,
0,
1,
2,
0,
0,
0
]
]Question 3
#Créer jeu1 correspondant au tableau donné, uniquement avec les méthodes de Grille.
Indice
Le pion en (4,3) ne peut être posé qu’après celui en (5,3).
Comprendre la correction
jeu1 = Grille()
jeu1.joue(2, 1)
jeu1.joue(3, 2)
jeu1.joue(3, 1)Le joueur 1 remplit d’abord la case (5,2). Le joueur 2 joue en (5,3), puis le pion suivant du joueur 1 tombe en (4,3), car le bas de la colonne 3 est déjà occupé. L’ordre de ces trois appels respecte l’alternance des joueurs et la gravité.
valeur_case(ligne,colonne) est fournie et compte les alignements de quatre cases contenant cette position. La figure 2 donne valeur_case(0,1)=4 : deux horizontaux (0,0)-(0,3) et (0,1)-(0,4), un vertical (0,1)-(3,1), une diagonale (0,1),(1,2),(2,3),(3,4). Le score ajoute ces valeurs pour les pions 2 et les soustrait pour les pions 1.
Question 4
#Calculer le score de jeu1 en détaillant les contributions.
Indice
Compte chaque segment de quatre cases qui inclut la case, quelle que soit l’occupation des autres cases.
Comprendre la correction
Score = valeur_case(5,3) - valeur_case(5,2) - valeur_case(4,3) = 7 - 5 - 10 = -8. Les pions du joueur 2 contribuent positivement, ceux du joueur 1 négativement. Les valeurs des cases ne dépendent que de leur position géométrique, pas des pions qui bloquent effectivement des alignements : c’est une heuristique de position, pas un compte de victoires déjà réalisables.
| Case | Alignements horizontaux | Verticaux | Diagonaux | Valeur |
|---|---|---|---|---|
| (5,3) | 4 | 1 | 2 | 7 |
| (5,2) | 3 | 1 | 1 | 5 |
| (4,3) | 4 | 2 | 4 | 10 |
Question 5
#Écrire score(self).
Indice
Le joueur 2 ajoute un poids et le joueur 1 retire le même poids.
Comprendre la correction
def score(self):
total = 0
for ligne in range(6):
for colonne in range(7):
if self.grille[ligne][colonne] == 2:
total += valeur_case(ligne, colonne)
elif self.grille[ligne][colonne] == 1:
total -= valeur_case(ligne, colonne)
return totalLes cases vides n’ajoutent rien. Deux boucles visitent les 42 cases, et l’accumulateur ne change pas la grille. Une grille vide a score 0 ; intervertir partout les joueurs inverse le signe du score. Cette propriété fournit un test utile et explique pourquoi un joueur minimise tandis que l’autre maximise.
Partie B : arbre de coups
La profondeur est limitée par la constante globale niveau_max ; la racine est au niveau 0. Une victoire du joueur 1 vaut -(100 + 10×(niveau_max-niveau)), une victoire du joueur 2 l’opposé positif. À la limite de profondeur, on utilise le score de la grille. Sinon le joueur 1 choisit le minimum, le joueur 2 le maximum. Le bonus de profondeur favorise une victoire plus rapide. La figure 3 est un arbre partiel : racine -1 ; enfants de colonnes 0,1,…,6 au niveau 1 pour le joueur 1 ; sous le premier enfant, colonnes 0 et 1 visibles au niveau 2 pour le joueur 2.
Racine : colonne -1
├─ colonne 0 (joueur 1)
│ ├─ colonne 0 (joueur 2)
│ └─ colonne 1 (joueur 2)
├─ colonne 1 (joueur 1)
├─ …
└─ colonne 6 (joueur 1)Question 6
#Compléter le constructeur de Noeud(colonne), avec score nul et aucun fils.
Indice
Les trois attributs sont explicitement donnés dans le sujet.
Comprendre la correction
class Noeud:
def __init__(self, colonne):
self.colonne = colonne
self.score = 0
self.suivants = []Une liste propre à chaque instance recueille ses coups suivants. La racine utilise la colonne -1, car elle représente la position avant choix et pas un coup réellement joué. Les nœuds enfants stockent les colonnes de 0 à 6 correspondant aux coups autorisés.
On suppose disponible une méthode colonne_score_max analogue pour le maximum.
Question 7
#Écrire colonne_score_min, qui renvoie (colonne, score) pour un fils au score minimal, sachant qu’au moins un fils existe.
Indice
Les coups légaux ne correspondent pas forcément aux indices 0..len(suivants)-1.
Comprendre la correction
def colonne_score_min(self):
meilleur = self.suivants[0]
for fils in self.suivants:
if fils.score < meilleur.score:
meilleur = fils
return (meilleur.colonne, meilleur.score)L’initialisation se fait avec un vrai fils, pas avec un score arbitraire comme 0 qui pourrait être inférieur à tous les scores. Avec un test strict, le premier fils minimal est conservé en cas d’égalité, ce qui respecte « un des fils ». Le couple doit contenir sa colonne réelle, pas son indice dans suivants : des colonnes pleines peuvent manquer dans cette liste.
Grille possède gagnant(), qui renvoie 0, 1 ou 2, et copie_grille(), dont les modifications ne touchent pas la grille d’origine. Le sujet suppose au plus un gagnant.
def calcule_score(self, niveau, joueur, grille):
g = grille.gagnant()
if g == 1:
self.score = ...
elif g == 2:
self.score = ...
elif niveau == niveau_max:
self.score = ...
else:
for colonne in range(7):
grille2 = grille.copie_grille()
if grille2.joue(colonne, joueur):
nouveau_noeud = ...
self.suivants.append(...)
nouveau_noeud.calcule_score(...)
if joueur == 1:
self.score = ...
else:
self.score = ...Question 8
#Compléter calcule_score conformément à min-max.
Indice
Les enfants doivent simuler l’adversaire à la profondeur suivante, pas rejouer toujours le même joueur.
Comprendre la correction
Convention nécessaire : le squelette joue avec joueur pour créer les enfants. Dans la version cohérente ci-dessous, ce paramètre désigne donc le joueur dont c’est le tour dans la grille reçue, et non celui qui vient de jouer comme l’affirme une phrase de la question. Le joueur alterne avec 3-joueur dans les sous-appels. Cette convention rend compatibles le choix min/max et les coups générés.
def calcule_score(self, niveau, joueur, grille):
g = grille.gagnant()
if g == 1:
self.score = -(100 + 10 * (niveau_max - niveau))
elif g == 2:
self.score = 100 + 10 * (niveau_max - niveau)
elif niveau == niveau_max:
self.score = grille.score()
else:
for colonne in range(7):
grille2 = grille.copie_grille()
if grille2.joue(colonne, joueur):
nouveau_noeud = Noeud(colonne)
self.suivants.append(nouveau_noeud)
nouveau_noeud.calcule_score(niveau + 1, 3 - joueur, grille2)
if not self.suivants:
self.score = 0 # grille pleine sans gagnant : nul
elif joueur == 1:
self.score = self.colonne_score_min()[1]
else:
self.score = self.colonne_score_max()[1]On teste une victoire avant la limite de profondeur pour lui donner sa valeur terminale. Pour chaque colonne, une copie indépendante protège la grille du parent : les branches doivent partir de la même position. Seuls les coups légaux créent un enfant. Le score de l’enfant est calculé avant de sélectionner le minimum ou maximum. Le couple renvoyé par colonne_score_min/max fournit le score à l’indice 1.
Le squelette ne traite pas la grille pleine sans gagnant. La branche ajoutée if not self.suivants attribue 0 à cette partie nulle et évite de demander un minimum sur une liste vide. On suppose un nouvel arbre à chaque décision ; réutiliser les mêmes nœuds nécessiterait aussi de vider leurs anciens enfants. Une autre convention « joueur précédent » est possible, mais elle impose de changer la ligne qui joue et le choix du min/max de façon cohérente.
Question 9
#Pourquoi explorer jusqu’à niveau_max = 42 est-il irréaliste ?
Indice
Un niveau supplémentaire multiplie approximativement le travail par le nombre de coups possibles.
Comprendre la correction
Chaque niveau possède jusqu’à sept choix. Le nombre de nœuds peut donc croître comme 1 + 7 + 7² + … + 7⁴², dont le dernier terme dépasse 10³⁵. Les colonnes pleines et les victoires réduisent le nombre réel de branches, mais l’espace à explorer reste immense. Chaque nœud demande aussi une copie de grille et un calcul. Une profondeur limitée avec heuristique est donc nécessaire pour une réponse rapide ; l’élagage alpha-bêta est une amélioration possible sans changer le résultat d’une profondeur donnée.
Question 10
#Écrire choisit_coup(grille,joueur), qui renvoie la colonne à jouer.
Indice
Le score est la deuxième composante, la colonne à renvoyer la première.
Comprendre la correction
def choisit_coup(grille, joueur):
racine = Noeud(-1)
racine.calcule_score(0, joueur, grille)
if not racine.suivants:
return -1 # aucun coup : partie terminée ou profondeur nulle
if joueur == 1:
return racine.colonne_score_min()[0]
return racine.colonne_score_max()[0]L’arbre commence à la position courante au niveau 0, en passant le joueur qui doit agir selon la convention de la correction précédente. On choisit ensuite la colonne de l’enfant optimal, à l’indice 0 du couple. La fonction ne joue pas le coup : elle recommande une colonne. On suppose normalement une partie non terminée et niveau_max ≥ 1 ; -1 exprime explicitement l’absence de coup dans les autres cas.
Faire jouer min-max sur une vraie positionUn atelier pour expérimenter
Choisissez la position, le joueur à jouer et la profondeur. Le tableau montre le score garanti de chaque colonne si l’adversaire répond au mieux ; la grille reste celle du départ.
Lire le résultat de l’expérience initiale
Colonne conseillée : 3
Les branches alternent les joueurs. Les scores négatifs favorisent le joueur 1 ; les positifs le joueur 2. Une victoire plus rapide reçoit un score terminal plus marqué.
| Colonne | Score min-max |
|---|---|
| 0 | -18 |
| 1 | -17 |
| 2 | -13 |
| 3 | -8 |
| 4 | -16 |
| 5 | -17 |
| 6 | -18 |
Min-max anticipe une réponse adverse optimale. Sa correction dépend autant de l’alternance des joueurs que du score choisi.
Exercice 2 · 6 points
Gamerzz : sous-réseaux et files de paquets
Gamerzz propose une salle de jeux en ligne, une salle de réalité virtuelle et un site pour réserver, organiser des événements et suivre les classements. Son architecture comporte sept routeurs A à G. A donne accès à Internet ; le réseau DMZ relie A et B. Les réseaux de service et les liens entre routeurs sont reconstruits ci-dessous.
| Réseau | CIDR | Routeur |
|---|---|---|
| DMZ | 10.42.0.32/27 | A et B ; A = 10.42.0.33 |
| Gaming Online | 10.42.0.64/28 | C |
| Administration | 10.42.0.80/29 | E |
| Application | 10.42.0.88/29 | D |
| Gaming VR | 10.42.0.96/27 | F |
| SGBD | 10.42.0.128/29 | G |
| Liaison | Sous-réseau |
|---|---|
| B-C | 10.42.0.0/30 |
| B-D | 10.42.0.4/30 |
| B-E | 10.42.0.8/30 |
| C-D | 10.42.0.12/30 |
| C-F | 10.42.0.16/30 |
| D-E | 10.42.0.20/30 |
| D-G | 10.42.0.24/30 |
| E-G | 10.42.0.28/30 |
Lire les connexions du schéma
- A relié à B
- B relié à C
- B relié à D
- B relié à E
- C relié à D
- C relié à F
- D relié à E
- D relié à G
- E relié à G
CIDR fixe les bits réseau. Les adresses ayant tous les bits machine à 0 ou tous à 1 sont réservées respectivement au réseau et à la diffusion.
Question 1
#Donner le nombre d’adresses machines du réseau Administration et une adresse possible pour E.
Indice
Le réseau commence au multiple de huit .80.
Comprendre la correction
6 adresses utilisables : un /29 laisse trois bits, donc 2³ - 2 = 6. Le bloc va de 10.42.0.80 à .87 ; les hôtes vont de 10.42.0.81 à 10.42.0.86. E peut recevoir par exemple 10.42.0.81. Les .80 et .87 sont réservées.
Question 2
#Dans quel réseau se trouve la machine 10.42.0.70, à l’origine de téléchargements non autorisés ?
Indice
Un /28 représente un bloc de seize adresses.
Comprendre la correction
Gaming Online : son réseau 10.42.0.64/28 couvre les adresses .64 à .79, avec des hôtes .65 à .78. .70 appartient à cet intervalle. L’adresse ne permet pas à elle seule d’identifier une personne.
Question 3
#Les adresses utilisables des /30 sont attribuées par ordre alphabétique des routeurs. Donner celles de C/F sur 10.42.0.16/30 et C/D sur 10.42.0.12/30.
Indice
Ajoutez 1 et 2 à l’adresse réseau, jamais 0 et 3.
Comprendre la correction
| Liaison | Premier routeur | Second routeur |
|---|---|---|
| C-F | C : 10.42.0.17 | F : 10.42.0.18 |
| C-D | C : 10.42.0.13 | D : 10.42.0.14 |
Un /30 contient quatre adresses : réseau, deux hôtes, diffusion. C reçoit la première adresse hôte puisqu’il précède F comme D dans l’alphabet. Les routeurs ont plusieurs interfaces, donc C possède bien deux adresses distinctes selon le lien.
RIP minimise le nombre de routeurs traversés. Table fournie pour B :
| Réseau | Passerelle | Sauts |
|---|---|---|
| DMZ | connecté | 0 |
| Gaming Online | 10.42.0.2 | 1 |
| Internet | 10.42.0.33 | 1 |
| Gaming VR | 10.42.0.2 | 2 |
| Administration | 10.42.0.10 | 1 |
| Application | 10.42.0.6 | 1 |
| SGBD | 10.42.0.6 | 2 |
Question 4
#Donner une table RIP possible pour C.
Indice
C touche directement B, D et F ; raisonnez ensuite une couche plus loin.
Comprendre la correction
| Réseau | Passerelle depuis C | Sauts |
|---|---|---|
| Gaming Online | connecté | 0 |
| Gaming VR | 10.42.0.18 (F) | 1 |
| DMZ | 10.42.0.1 (B) | 1 |
| Internet | 10.42.0.1 (B) | 2 |
| Administration | 10.42.0.1 (B) ou 10.42.0.14 (D) | 2 |
| Application | 10.42.0.14 (D) | 1 |
| SGBD | 10.42.0.14 (D) | 2 |
La table reprend les mêmes destinations de service que celle de B. La route vers l’Administration a deux choix ex æquo, via B-E ou D-E. Le prochain saut est toujours une interface directement reliée à C. Le routeur ne remplace pas la passerelle par l’adresse de la destination finale.
| Liaison | Débit de la figure 2 |
|---|---|
| A-B | 10 Gbit/s |
| B-C | 1 Gbit/s |
| B-D | 100 Mbit/s |
| B-E | 1 Gbit/s |
| C-F | 10 Gbit/s |
| C-D | 10 Gbit/s |
| D-E | 10 Gbit/s |
| D-G | 10 Gbit/s |
| E-G | 1 Gbit/s |
Question 5
#Calculer le coût pour chaque débit de la figure 2 avec coût = 10¹⁰/d, d en bit/s.
Indice
Convertissez tous les débits en bits par seconde avant la division.
Comprendre la correction
| Débit | En bit/s | Coût |
|---|---|---|
| 10 Gbit/s | 10¹⁰ | 1 |
| 1 Gbit/s | 10⁹ | 10 |
| 100 Mbit/s | 10⁸ | 100 |
Chaque division du débit par dix multiplie le coût par dix. La métrique est sans unité dans ce modèle.
Question 6
#Donner un ordre possible des routeurs suivis depuis A pour joindre Application avec OSPF.
Indice
Le chemin le moins coûteux peut posséder un routeur intermédiaire supplémentaire.
Comprendre la correction
A → B → C → D, de coût 1 + 10 + 1 = 12, ou A → B → E → D, de même coût 12. Le lien direct B-D coûte 100 à lui seul : A-B-D coûterait 101. Ces deux routes ex æquo sont toutes deux optimales ; il n’y a pas de raison de refuser l’une des deux.
Question 7
#Les données TCP sont-elles contenues dans un paquet IP ou l’inverse ?
Indice
La couche transport TCP est portée par la couche réseau IP.
Comprendre la correction
Le segment TCP est encapsulé dans le paquet IP, qui le transporte comme charge utile. Le segment TCP contient à son tour les données applicatives et son propre en-tête. Ce n’est pas le paquet IP qui est à l’intérieur d’un segment TCP.
Question 8
#Pourquoi une file est-elle préférable à une pile pour les paquets à transmettre ?
Indice
Comparez le sort d’un ancien paquet lorsque de nouveaux paquets arrivent constamment.
Comprendre la correction
Une file applique FIFO : le premier paquet arrivé attend le moins et est transmis avant les suivants. Cela conserve l’ordre d’arrivée au niveau de cette file et évite qu’un ancien paquet reste toujours derrière les nouveaux. Une pile LIFO favoriserait les derniers reçus et pourrait retarder indéfiniment les plus anciens si les arrivées continuent. Cela ne signifie pas que tout le réseau garantit un ordre de livraison de bout en bout.
Interface des files : cree_file() crée une file vide ; est_vide(f) teste son état ; enfile(f,x) ajoute ; defile(f) retire et renvoie le plus ancien élément d’une file non vide. Exemple : enfiler 0 puis 1, puis défiler, renvoie 0 et laisse 1. En drop tail, les nouveaux paquets sont ignorés quand la file atteint t_max ; un paquet transmis est défilé.
class Routeur_DROP_TAIL:
def __init__(..., ...):
self.f = ...
self.t_max = ...
self.t = ...Question 9
#Compléter le constructeur de Routeur_DROP_TAIL.
Indice
Une capacité n’est pas la taille actuelle de la file.
Comprendre la correction
def __init__(self, t_max):
self.f = cree_file()
self.t_max = t_max
self.t = 0La file est vide, donc sa taille courante vaut 0. La capacité maximale est reçue en paramètre et conservée. L’invariant à maintenir ensuite est self.t égal au nombre de paquets réellement dans self.f. Le compteur doit donc augmenter à chaque enfile et diminuer à chaque transmission avec defile.
def recoit(self, p):
if ...:
enfile(..., ...)
self.t = ...
return ...
return ...Question 10
#Compléter recoit(p), qui accepte et enfile le paquet seulement si la file n’est pas pleine.
Indice
Les mutations doivent rester dans la branche d’acceptation.
Comprendre la correction
def recoit(self, p):
if self.t < self.t_max:
enfile(self.f, p)
self.t = self.t + 1
return True
return FalseL’inégalité est stricte : avec t = t_max - 1, le dernier emplacement est accepté et le compteur devient t_max. À t = t_max, le paquet est refusé et rien ne change, ni la file ni son compteur. Renvoyer True sans enfiler ou augmenter t lors d’un refus romprait le lien entre état logique et contenu.
Drop tail peut synchroniser les ralentissements des émetteurs. La variante utilise f vide initialement, t=0, t_min et t_max positifs. Organigramme : t
def recoit(self, p):
if ... < ...:
enfile(self.f, p)
self.t = ...
return ...
elif ... <= ... < ...:
if self.tirage_au_sort():
enfile(self.f, p)
self.t = ...
return ...
return ...Question 11
#Compléter recoit de Routeur_ALEA selon les trois zones de taille.
Indice
Découpez l’axe des tailles avec les bornes exactes : < t_min, t_min ≤ t < t_max, puis ≥ t_max.
Comprendre la correction
def recoit(self, p):
if self.t < self.t_min:
enfile(self.f, p)
self.t = self.t + 1
return True
elif self.t_min <= self.t < self.t_max:
if self.tirage_au_sort():
enfile(self.f, p)
self.t = self.t + 1
return True
return FalseSous t_min, l’acceptation est systématique. Entre les deux seuils, True au tirage signifie accepter dans le squelette donné ; False signifie refuser. À partir de t_max, aucun tirage ne doit pouvoir accepter un paquet. Le return False final couvre le tirage défavorable et la saturation. Les égalités sont importantes : t = t_min appartient à la zone aléatoire et t = t_max à la zone refusée.
Le sujet ne donne pas la loi du tirage ; on ne peut donc pas annoncer une probabilité numérique de refus. La méthode illustre une réaction avant saturation afin d’éviter que tous les émetteurs TCP ralentissent simultanément après une perte massive.
Admettre ou rejeter le prochain paquet ?Un atelier pour expérimenter
Faites varier la taille actuelle autour des seuils 3 et 6. Comparez drop tail et la variante aléatoire ; vous fixez le résultat du tirage pour explorer précisément les deux issues.
Lire le résultat de l’expérience initiale
Paquet accepté
Zone intermédiaire. Le tirage n’intervient que pour les tailles 3, 4 ou 5.
| Avant | Condition | Décision | Après |
|---|---|---|---|
| 3 | Zone intermédiaire | Enfiler et incrémenter | 4 |
Une politique de file se vérifie particulièrement aux seuils : juste avant, exactement dessus et juste après.
Exercice 3 · 8 points
Immeubles : SQL et plus longue sous-séquence croissante
Les deux parties sont indépendantes. La première gère les immeubles d’une agence avec deux tables ; la seconde modélise une rue perpendiculaire à la mer par une liste de hauteurs. Le schéma de la figure 1 est :
| Relation | Attributs | Clé primaire | Clé étrangère |
|---|---|---|---|
| immeuble | id_immeuble ; nb_etage_immeuble ; numero_immeuble ; rue_immeuble | id_immeuble | - |
| appartement | id_appart ; etage_appart ; prix_appart ; id_immeuble | id_appart | id_immeuble → immeuble.id_immeuble |
Les attributs indiquent l’identifiant, le nombre d’étages, le numéro dans la rue et son nom pour un immeuble ; l’identifiant, l’étage, le prix et l’immeuble d’appartenance pour un appartement. On peut employer SELECT, FROM, WHERE, AND, OR, JOIN ... ON, INSERT, UPDATE, DELETE, DISTINCT et ORDER BY. « Appartement 603 » et « immeuble 16 » désignent leurs identifiants.
Question 1
#Pourquoi numero_immeuble n’est-il pas la clé primaire ?
Indice
Une clé doit distinguer les immeubles de toutes les rues.
Comprendre la correction
Le même numéro existe dans plusieurs rues : un numéro 13 rue Turing et un numéro 13 rue de la mer sont deux immeubles distincts. Le numéro seul ne garantit donc pas l’unicité. id_immeuble fournit un identifiant indépendant de l’adresse.
Question 2
#Lister les identifiants des immeubles de la rue la mer en ordre croissant.
Indice
La colonne filtrée et la colonne triée ne sont pas nécessairement la même.
Comprendre la correction
SELECT id_immeuble
FROM immeuble
WHERE rue_immeuble = 'la mer'
ORDER BY id_immeuble ASC;Le tri demandé porte sur l’identifiant, pas sur le numéro dans la rue ni sur la hauteur. ASC est facultatif car c’est l’ordre par défaut, mais rend le choix explicite.
Question 3
#Lister les identifiants des appartements de l’immeuble 16 situés au moins au 5e étage.
SQLSélectionner les appartements à partir du cinquième étageÉcrivez votre solution et mettez-la à l’épreuve
Listez les identifiants des appartements de l’immeuble 16 situés au moins au 5e étage. Le cinquième étage est inclus.
SELECT ...
FROM ...
WHERE ...;Les cas de test proposés :
- Jeu pédagogique : autour du seuil : Le schéma et l’immeuble 16 viennent du sujet ; aucune ligne d’appartement n’y est fournie à cette question. Ces valeurs sont des données complémentaires pédagogiques, avec un appartement juste avant la borne.
- Cas complémentaire : le seul candidat est au 5 e : Jeu complémentaire pédagogique, distinct des données officielles. L’identifiant d’un appartement ne doit pas être confondu avec celui de son immeuble. Le test
strict >5ferait perdre le seul résultat attendu.
Indice
Utilisez >= pour inclure exactement 5.
Comprendre la correction
SELECT id_appart
FROM appartement
WHERE id_immeuble = 16 AND etage_appart >= 5;Les deux contraintes doivent être satisfaites simultanément. « Au moins » inclut le cinquième étage. La table appartement contient déjà toutes les informations nécessaires, donc une jointure n’est pas requise.
Question 4
#Pourquoi DELETE FROM immeuble WHERE id_immeuble = 16 risque-t-il de rompre l’intégrité ?
Indice
Le lien de clé étrangère va de appartement vers immeuble.
Comprendre la correction
Des appartements peuvent encore référencer l’immeuble 16. Les supprimer de la table parent sans traiter ces références laisserait des clés étrangères orphelines. Un SGBD qui contrôle cette contrainte refuse normalement l’opération, sauf cascade explicitement configurée. Pour effacer les données d’un immeuble détruit, il faut traiter d’abord les appartements associés puis l’immeuble, dans une transaction si possible.
Question 5
#Ajouter l’immeuble 140, de six étages, au numéro 13 rue Turing.
Indice
L’identifiant et le numéro dans la rue sont deux informations distinctes.
Comprendre la correction
INSERT INTO immeuble
(id_immeuble, nb_etage_immeuble, numero_immeuble, rue_immeuble)
VALUES (140, 6, 13, 'Turing');L’ordre explicite des colonnes évite de confondre identifiant 140 et numéro de rue 13. La clé 140 est supposée disponible pour cette nouvelle construction.
Question 6
#Le prix de l’appartement 603 double grâce à sa nouvelle vue sur mer. Le mettre à jour.
Indice
Le membre droit du SET peut employer la valeur actuelle de la colonne.
Comprendre la correction
UPDATE appartement
SET prix_appart = 2 * prix_appart
WHERE id_appart = 603;La nouvelle valeur dépend de l’ancienne, qu’il n’est pas nécessaire de connaître dans le code de la requête. La condition cible un seul appartement ; elle ne modifie pas les autres prix de l’immeuble.
MAX renvoie le maximum d’un attribut. Exemple fourni :
SELECT MAX(nb_etage_immeuble) FROM immeuble
WHERE id_immeuble <= 23;Question 7
#Obtenir le prix maximal d’un appartement situé dans un immeuble de la rue la mer.
Indice
Reliez le prix à la rue par id_immeuble.
Comprendre la correction
SELECT MAX(a.prix_appart)
FROM appartement AS a
JOIN immeuble AS i ON a.id_immeuble = i.id_immeuble
WHERE i.rue_immeuble = 'la mer';Le prix appartient à appartement, la rue à immeuble. La jointure sélectionne les bonnes lignes avant MAX. La requête renvoie un prix unique, pas l’identifiant de l’appartement qui l’atteint ; plusieurs appartements peuvent avoir ce même prix. S’il n’y a aucun appartement correspondant, MAX renvoie NULL.
Partie B : garder le maximum d’immeubles avec vue
On parcourt une rue depuis la mer. Un immeuble voit la mer si tous ceux placés avant lui ont moins d’étages. Détruire le moins d’immeubles revient à garder une sous-séquence strictement croissante de longueur maximale. Une sous-séquence supprime certains éléments sans déplacer les autres. Pour L1=[10,22,9,33,21,50,41,60], [22,9,50], [10,22] et [33] sont des sous-séquences ; [10] est croissante de longueur 1, [9,33] de longueur 2 et [10,22,33,50,60] est maximale de longueur 5. Pour la suite, L2=[3,1,8,2,5].
Question 8
#Donner toutes les sous-séquences strictement croissantes de longueur 2 de L2 = [3,1,8,2,5].
Indice
Pour chaque paire d’indices i<j, testez L2[i]<L2[j].
Comprendre la correction
[3,8], [3,5], [1,8], [1,2], [1,5], [2,5]. Pour chaque premier élément, on cherche seulement à sa droite une valeur strictement plus grande. [2,8] n’est pas une sous-séquence, car 8 apparaît avant 2 ; [1,3] ne respecte pas non plus l’ordre original.
Question 9
#Déterminer la plus longue sous-séquence strictement croissante de L2.
Indice
Le plus grand nombre de la liste n’appartient pas nécessairement à la meilleure sous-séquence.
Comprendre la correction
[1, 2, 5]Elle a longueur 3. Les longueurs optimales terminant à chaque position sont [1,1,2,2,3]. Aucun élément ne permet de prolonger une chaîne jusqu’à 4 : 8 ne peut être suivi ni de 2 ni de 5, et le 3 initial ne peut pas précéder le 1 ou le 2 dans une suite croissante. Garder ces trois immeubles impose donc d’en retirer deux.
Question 10
#Écrire est_strict_croissante(seq), qui renvoie un booléen.
Indice
La négation de « strictement plus grand » est « inférieur ou égal ».
Comprendre la correction
def est_strict_croissante(seq):
for i in range(1, len(seq)):
if seq[i] <= seq[i - 1]:
return False
return TrueUne violation locale suffit : une égalité doit être refusée autant qu’une diminution. Si toutes les paires consécutives croissent, la transitivité garantit la croissance de toute la liste. Les listes vide ou d’un élément satisfont cette propriété sans comparaison. Placer return True dans la boucle arrêterait le contrôle après la première paire seulement.
def llsc_fin(tab, i):
if ...:
return ...
max_len = 1
for j in range(i):
if tab[j] < ...:
max_len = max(max_len, llsc_fin(tab, j) + 1)
return max_len
def llsc_rec(tab):
n = len(tab)
return max([llsc_fin(tab, i) for i in range(n)])Question 11
#Compléter llsc_fin(tab,i), longueur maximale d’une sous-séquence croissante se terminant exactement à l’indice i.
Indice
L’état « finit en i » est plus précis que « meilleure chaîne parmi les i premières valeurs ».
Comprendre la correction
def llsc_fin(tab, i):
if i == 0:
return 1
max_len = 1
for j in range(i):
if tab[j] < tab[i]:
max_len = max(max_len, llsc_fin(tab, j) + 1)
return max_lenLa sous-séquence réduite à tab[i] justifie l’initialisation à 1. Chaque prédécesseur admissible j est placé avant i et porte une valeur plus petite ; on prolonge sa meilleure chaîne avec tab[i]. Le cas i=0 n’a aucun prédécesseur possible. La fonction globale prend le maximum sur toutes les positions finales : une meilleure chaîne ne se termine pas nécessairement au dernier élément. Le code fourni suppose tab non vide, car max([]) n’est pas défini.
Principe : Compléter llsc_dyn aux lignes 7 et 8. Écrivez Les cas de test proposés : Les cases antérieures de dyn sont déjà terminées lorsque l’on calcule Donner un avantage de cette implémentation par rapport à la version récursive. Comparez les états déjà calculés et le coût de la pile, pas seulement la forme des boucles. Chaque état Choisissez une rue puis faites avancer le calcul. Chaque immeuble affiche la meilleure longueur de chaîne qui se termine à sa position ; le tableau ne calcule que les immeubles déjà traités. Chaîne conservée sur le préfixe : 1 < 2 < 5 Une hauteur égale ne suffit pas : le prochain immeuble doit dépasser strictement tous ceux conservés avant lui. Le résultat porte ici seulement sur le préfixe traité. Le bon état dynamique décrit la meilleure solution avec une fin imposée ; le maximum global se choisit ensuite parmi toutes ces fins.dyn[i] contient la longueur maximale d’une sous-séquence croissante terminée à i. On parcourt tous les jdef llsc_dyn(tab):
n = len(tab)
dyn = [1] * n
for i in range(1, n):
for j in range(i):
if tab[j] < tab[i]:
dyn[i] = max(..., ...)
return ...Question 12
#PythonTrouver une sous-séquence croissante, même avec des sautsÉcrivez votre solution et mettez-la à l’épreuve
llsc_dyn(tab) pour une liste non vide de nombres. Renvoyez la longueur de sa plus longue sous-séquence strictement croissante : les éléments gardent leur ordre, mais ne sont pas obligatoirement consécutifs. dyn[i] mémorise la meilleure longueur qui se termine exactement à i. Le résultat est une longueur, pas une liste d’éléments.def llsc_dyn(tab):
# À vous de jouer
passdyn[-1] donnerait 1, alors que le meilleur sous-problème vaut 4.Indice
dyn[j] + 1 décrit une chaîne terminant par tab[j], prolongée avec tab[i].Comprendre la correction
def llsc_dyn(tab):
n = len(tab)
dyn = [1] * n
for i in range(1, n):
for j in range(i):
if tab[j] < tab[i]:
dyn[i] = max(dyn[i], dyn[j] + 1)
return max(dyn)dyn[i]. Le maximum conserve la meilleure des chaînes qui peuvent précéder tab[i]. Pour L2, dyn devient [1,1,2,2,3]. On renvoie le maximum de toute la table, pas seulement dyn[-1]. Pour accepter aussi une liste vide, ajouter au début if not tab: return 0 ; cette extension évite max([]) sans changer les résultats demandés.Question 13
#Indice
Comprendre la correction
dyn[i] est calculé une seule fois et conservé. Les deux boucles font n(n-1)/2 comparaisons, donc O(n²) temps et O(n) mémoire pour la table. La version récursive sans cache peut recalculer les mêmes états très souvent, jusqu’à un nombre exponentiel d’appels sur une liste croissante. L’itération évite aussi une pile d’appels de profondeur linéaire et sa limite dans Python. Ce sont deux avantages distincts : partage des calculs et absence de récursion.Quels immeubles garder pour laisser voir la mer ?Un atelier pour expérimenter
Lire le résultat de l’expérience initiale
Indice depuis la mer Hauteur Meilleure longueur terminant ici Prédécesseur choisi 0 3 1 Aucun 1 1 1 Aucun 2 8 2 0 3 2 2 1 4 5 3 3 Revoir les notions de cet exercice
Du sujet à la méthode
Votre prochaine séance de révision
- Avant de compléter min-max, annoncez qui doit jouer dans l’état reçu par chaque appel.
- Pour les files, testez toujours les valeurs exactement égales aux seuils.
- En programmation dynamique, nommez précisément le sens d’une case avant d’écrire la transition.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 26-NSIJ1AN1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.
