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
Pizzayolo : commandes, intégrité et composition des pizzas
Le restaurant Pizzayolo souhaite moderniser ses réservations de pizzas. Un prestataire propose un SGBD relationnel pour remplacer un tableur où chaque nouvelle commande serait une ligne.
Question 1
#Donner deux avantages d’un SGBD par rapport à ce simple tableur.
Indice
Pensez à deux personnes enregistrant des commandes au même moment.
Comprendre la correction
Un SGBD peut faire respecter l’intégrité des données (unicité d’un code de commande, existence du client et de la pizza) et gérer des accès simultanés avec des transactions, pour éviter des modifications incohérentes. Il permet aussi des droits d’accès et des requêtes reliant les tables sans recopier partout les coordonnées. Deux avantages expliqués suffisent ; le SGBD ne supprime pas le besoin d’un schéma bien conçu.
Schéma proposé, clés primaires indiquées :
| Relation | Attributs | Clé primaire |
|---|---|---|
| client | id entier, nom texte, prenom texte | id |
| pizza | num entier, couleur texte, prix flottant | num |
| commande | code entier, id_client entier, num_pizza entier, date texte, livraison entier, paiement entier | code |
| client.id | nom | prenom |
|---|---|---|
| 103 | Esposito | Giulia |
| 106 | Panzini | Raffaele |
| pizza.num | couleur | prix |
|---|---|---|
| 1 | Rossa | 11.5 |
| 2 | Bianca | 11.5 |
| 3 | Nera | 12.5 |
| 4 | Bianca | 15.5 |
| code | id_client | num_pizza | date | livraison | paiement |
|---|---|---|---|---|---|
| 42362 | 103 | 2 | 2025-01-02 | 1 | 1 |
| 42363 | 103 | 3 | 2025-01-02 | 1 | 1 |
| 42364 | 106 | 1 | 2025-01-03 | 1 | 0 |
| 42365 | 103 | 1 | 2025-01-03 | 0 | 1 |
Chaque pizza commandée correspond à une ligne distincte. Un client peut donc avoir plusieurs codes de commande le même jour. livraison vaut 1 si la pizza est livrée et 0 sinon ; paiement vaut 1 si elle est réglée et 0 sinon.
Question 2
#Peut-on avoir deux pizzas avec le même numéro et le même prix, mais une couleur différente ? Justifier.
Indice
La clé primaire num suffit à interdire le doublon.
Comprendre la correction
Non dans cette relation. num est la clé primaire de pizza : deux lignes ne peuvent partager ce numéro, quels que soient leurs prix ou couleurs. Une même couleur et un même prix peuvent au contraire apparaître sur des numéros distincts. La contrainte porte sur la clé, pas sur la totalité des autres attributs.
Question 3
#Donner une clé étrangère, sa relation et la relation/attribut référencé.
Indice
Une valeur étrangère sert à relier les lignes de deux tables.
Comprendre la correction
commande.id_client référence client.id. Autre réponse valable : commande.num_pizza référence pizza.num. Une commande doit désigner un client et une pizza existants ; la valeur étrangère peut apparaître dans plusieurs commandes, contrairement à la clé primaire de la table cible.
On peut employer SELECT, FROM, WHERE, AND, OR, JOIN ... ON, INSERT, UPDATE, DELETE, DISTINCT et ORDER BY. COUNT compte les valeurs non nulles, AVG calcule leur moyenne, MIN et MAX les extrêmes, SUM leur somme. Par exemple MAX(prix) donne le prix le plus élevé, et SUM(livraison) compte les pizzas livrées puisque les valeurs sont 0 ou 1.
Question 4
#Quel résultat donne SELECT prix FROM pizza WHERE couleur = 'Bianca' sur les extraits ?
Indice
Appliquez le filtre sur couleur puis ne conservez que la colonne prix.
Comprendre la correction
| prix |
|---|
| 11.5 |
| 15.5 |
Les pizzas 2 et 4 vérifient la condition. Le résultat contient leurs prix, pas leurs numéros. Aucun ordre n’est garanti en l’absence d’ORDER BY ; ces deux lignes forment le résultat attendu.
Question 5
#Obtenir les couleurs des pizzas dont le prix est strictement supérieur à 15 €.
Indice
« Strictement supérieur » se traduit par >, pas >=.
Comprendre la correction
SELECT couleur
FROM pizza
WHERE prix > 15;Le filtre strict exclut une éventuelle pizza à exactement 15 €. Dans l’extrait, seule la pizza 4 convient, donc le résultat est Bianca. DISTINCT serait possible pour une liste de couleurs sans répétition, mais le sujet ne l’impose pas ici.
Question 6
#Obtenir les prénoms des clients qui n’ont pas payé une pizza pourtant livrée.
SQLRepérer les pizzas livrées mais non payéesÉcrivez votre solution et mettez-la à l’épreuve
Renvoyez les prénoms des clients ayant au moins une commande avec livraison=1 et paiement=0. Les deux conditions doivent concerner la même commande.
SELECT ...
FROM ...
WHERE ...;Les cas de test proposés :
- Commandes de Pizzayolo dans le sujet : Ces lignes reproduisent les trois tables de l’extrait officiel.
- Cas complémentaire : ne pas mélanger deux commandes : Jeu complémentaire pédagogique, distinct des données officielles. Alex a une livraison payée et une autre commande non payée mais non livrée : aucune ligne ne satisfait les deux critères. Béa doit apparaître une fois malgré deux impayés livrés.
Indice
Reliez client et commande puis imposez les deux états simultanément.
Comprendre la correction
SELECT DISTINCT c.prenom
FROM client AS c
JOIN commande AS co ON co.id_client = c.id
WHERE co.livraison = 1 AND co.paiement = 0;Les deux conditions portent sur la même commande : une pizza effectivement livrée et non réglée. La jointure retrouve ensuite le prénom du client. DISTINCT évite de répéter le même prénom dans la projection si plusieurs commandes répondent au filtre. L’extrait renvoie Raffaele. On ne doit pas utiliser OR : cela retiendrait aussi des commandes payées ou encore non livrées.
Question 7
#Calculer le prix moyen des pizzas commandées par un client de nom Esposito.
Indice
Il faut les trois tables : identité du client, choix de pizza et prix.
Comprendre la correction
SELECT AVG(p.prix)
FROM client AS c
JOIN commande AS co ON co.id_client = c.id
JOIN pizza AS p ON p.num = co.num_pizza
WHERE c.nom = 'Esposito';Chaque commande fournit un prix à la moyenne. Dans l’extrait : (11,5 + 12,5 + 11,5) / 3 = 35,5 / 3, soit environ 11,83 €. Ne pas écrire AVG(DISTINCT prix) : les commandes répétées doivent compter. La question filtre un nom, pas un identifiant unique ; si plusieurs clients s’appellent Esposito, leurs commandes sont toutes concernées. Le schéma ne stocke pas le prix au moment de la commande : on calcule avec les prix actuels de pizza, ce qui est la seule information disponible.
Question 8
#La commande 42365 vient d’être livrée. Mettre à jour la base.
Indice
Le code est l’identifiant de la commande à modifier.
Comprendre la correction
UPDATE commande
SET livraison = 1
WHERE code = 42365;On conserve la date, le client et l’état du paiement déjà égal à 1. WHERE cible la commande, pas toutes les commandes du même client.
Question 9
#Le 3 janvier 2025, le nouveau client Emanuele Girasole demande la livraison de la pizza 2. Insérer les données dans le bon ordre, avec id 107 et code 42366 disponibles.
Indice
L’insertion d’une commande dépend de l’existence du client.
Comprendre la correction
INSERT INTO client (id, nom, prenom)
VALUES (107, 'Girasole', 'Emanuele');
INSERT INTO commande
(code, id_client, num_pizza, date, livraison, paiement)
VALUES (42366, 107, 2, '2025-01-03', 0, 0);Le client est créé avant sa commande pour que la clé étrangère 107 ait une cible. La demande n’est pas une livraison déjà réalisée : livraison vaut 0. Aucun règlement n’étant indiqué, on l’enregistre ici en attente avec paiement = 0 ; si le paiement était effectué lors de la réservation, seule cette valeur deviendrait 1. Ces hypothèses doivent être explicites, pas confondues avec les événements futurs.
Question 10
#Quel problème pose DELETE FROM pizza WHERE num = 1 ?
Indice
Retirer de la vente et effacer l’existence passée sont deux opérations différentes.
Comprendre la correction
Les commandes 42364 et 42365 référencent la pizza 1. Avec les clés étrangères activées et sans suppression en cascade, le SGBD refuse sa suppression ; sinon, l’historique contiendrait des références orphelines et perdrait le prix/couleur de la pizza. Une pizza retirée de la vente peut rester dans le catalogue historique avec un indicateur disponible = 0, plutôt que disparaître. Supprimer toutes ses anciennes commandes n’est pas une bonne façon de préserver l’historique.
Question 11
#Ajouter ingredient et composition pour représenter plusieurs ingrédients par pizza et un ingrédient partagé entre plusieurs pizzas ; préciser les clés.
Indice
Une ligne de composition décrit une association précise entre une pizza et un ingrédient.
Comprendre la correction
| Relation | Attributs | Clé primaire | Clés étrangères |
|---|---|---|---|
| ingredient | id_ingredient entier, nom texte | id_ingredient | - |
| composition | num_pizza entier, id_ingredient entier | (num_pizza, id_ingredient) | num_pizza → pizza.num ; id_ingredient → ingredient.id_ingredient |
La relation composition résout l’association plusieurs-à-plusieurs. Sa clé composée empêche d’associer deux fois le même ingrédient à la même pizza, tout en autorisant cet ingrédient sur d’autres pizzas. Un attribut quantite pourrait être ajouté si nécessaire, mais il n’est pas demandé. Stocker tous les ingrédients dans une chaîne séparée par des virgules rendrait les recherches et les contraintes beaucoup moins fiables.
Question 12
#Quels problèmes pose un accès extérieur pour consulter l’historique, et quelles précautions prendre ?
Indice
Distinguez la preuve de l’identité et le droit d’accéder à une commande donnée.
Comprendre la correction
Le principal enjeu est que chaque client ne voie que ses propres commandes. Il faut authentifier le compte, puis vérifier ses autorisations sur le serveur pour chaque requête ; cacher un bouton ou recevoir un id_client dans l’URL ne suffit pas. L’application doit communiquer en HTTPS, utiliser des requêtes paramétrées contre les injections SQL et un compte de base aux droits limités. La base n’a pas besoin d’être directement exposée à Internet : un serveur applicatif contrôle les requêtes. Des sauvegardes, mises à jour et journaux d’accès contribuent à protéger la disponibilité et à détecter les incidents.
Livrée, payée : quelles commandes faut-il relancer ?Un atelier pour expérimenter
Choisissez les états livraison et paiement. Le modèle filtre les quatre commandes puis reconstitue le prénom et le prix par jointure.
Lire le résultat de l’expérience initiale
1 commande(s) sélectionnée(s)
Les deux critères sont appliqués à la même ligne avec AND. La jointure ajoute ensuite les informations des tables client et pizza.
| Code | Prénom | Pizza | Prix | Livrée | Payée |
|---|---|---|---|---|---|
| 42364 | Raffaele | 1 | 11.5 | 1 | 0 |
Un filtre porte sur des lignes précises ; une jointure récupère leurs informations associées sans perdre le lien entre client et commande.
Revoir les notions de cet exercice
Exercice 2 · 6 points
Plus longue sous-séquence commune : de l’exhaustif au dynamique
Une LCS (Longest Common Subsequence) est une plus longue sous-liste commune dont les éléments gardent leur ordre, sans devoir être contigus. Pour L₁ = [9, 3, 7, 5, 8] et L₂ = [9, 7, 8, 3, 7, 3], des sous-listes communes sont [], [9], [3], [9, 7], [9, 3, 7] et [9, 7, 8]. Les deux dernières ont une longueur maximale de 3. Le mot « sous-liste » désigne donc ici une sous-séquence, pas une tranche contiguë.
En Python, + concatène deux listes : [1, 2] + [3, 4] donne [1, 2, 3, 4] et [] + [1] donne [1].
Partie A : recherche exhaustive
Algorithme sous_listes(L, accumulateur), avec accumulateur initial [[]] : si L est vide, renvoyer accumulateur. Sinon retirer le dernier élément e ; créer une liste temporaire ; y ajouter chaque sous-liste de l’accumulateur précédée de e ; rappeler sous_listes avec L raccourcie et la concaténation de l’accumulateur et de la liste temporaire.
Question 1
#Compléter les paramètres des appels de sous_listes([9, 3, 7], [[]]).
Indice
À partir de [[], [7]], préfixez successivement chaque sous-liste par 3.
Comprendre la correction
| Appel | liste | accumulateur |
|---|---|---|
| Initial | [9, 3, 7] | [[]] |
| Premier | [9, 3] | [[], [7]] |
| Deuxième | [9] | [[], [7], [3], [3, 7]] |
| Dernier | [] | [[], [7], [3], [3, 7], [9], [9, 7], [9, 3], [9, 3, 7]] |
À chaque étape, on conserve les sous-listes sans le nouvel élément, puis on ajoute celles qui le contiennent en tête. Comme les éléments sont retirés depuis la fin de liste, le placement en tête préserve l’ordre original. Le nombre de sous-listes double : 1, 2, 4, 8. Il ne s’agit pas de permuter les valeurs.
def sous_listes(liste, accumulateur=[[]]):
if len(liste) == 0:
return accumulateur
element = liste.pop()
tmp = []
for sous_liste in accumulateur:
tmp.append(...)
return sous_listes(...)Question 2
#Compléter les lignes 7 et 8 de sous_listes.
Indice
L’accumulateur doit contenir les choix « sans e » et « avec e ».
Comprendre la correction
tmp.append([element] + sous_liste)
return sous_listes(liste, accumulateur + tmp)[element] transforme l’entier en liste d’un élément avant la concaténation. La concaténation crée de nouvelles listes ; on n’insère pas l’élément dans les sous-listes existantes, qui doivent rester dans l’accumulateur sans cet élément. liste.pop(), en revanche, modifie bien la liste d’entrée. Après l’appel complet, elle est vide. Pour préserver une liste utilisée ensuite, appeler sous_listes(liste.copy()). Cette copie n’est pas indispensable au résultat de l’algorithme fourni, mais évite un effet de bord surprenant.
def est_extraite(sous_liste, liste):
index_ss_liste, index_liste = 0, 0
while index_ss_liste < len(sous_liste) and index_liste < len(liste):
if sous_liste[index_ss_liste] == liste[index_liste]:
index_ss_liste += 1
index_liste += 1
return index_ss_liste == len(sous_liste)def lcs_force_brute(liste_1, liste_2):
tmp = sous_listes(liste_1)
resultat, maxi = [], 0
for element in tmp:
if est_extraite(element, liste_2) and len(element) > maxi:
resultat = element
maxi = len(element)
return resultatQuestion 3
#Pourquoi lcs_force_brute peut-elle devenir très lente quand les listes grandissent ? Justifier.
Indice
Combien de décisions conserver/exclure prenez-vous pour m éléments ?
Comprendre la correction
Pour une première liste de longueur m, chaque élément peut être conservé ou exclu, donc la fonction génère 2ᵐ choix de sous-séquences. Si des valeurs se répètent, certaines listes obtenues sont identiques, mais le programme les génère et les teste tout de même. Pour chaque candidat, est_extraite parcourt la seconde liste avec des indices croissants, en O(n) dans le pire cas. On obtient donc un coût de vérification pouvant atteindre O(n·2ᵐ), auquel s’ajoutent les constructions et copies de listes, de l’ordre de O(m·2ᵐ).
Passer de m à m + 1 double le nombre de candidats ; à m = 30, il dépasse le milliard. Le problème est aussi spatial : tmp conserve toutes les sous-listes simultanément. Le test est_extraite fonctionne pourtant efficacement pour un seul candidat : son indice dans la sous-liste avance seulement lorsqu’une valeur correspond, tandis que l’indice dans la liste cible avance toujours. C’est l’énumération exhaustive qui domine.
Partie B : décomposer le problème
La fonction lcs(L₁, m′, L₂, n′) renvoie une LCS des m′ premiers éléments de L₁ et des n′ premiers éléments de L₂. Exemple avec 0,0 : []. Avec les préfixes de longueurs 3 et 2 des listes ci-dessus : une LCS de [9,3,7] et [9,7] est [9,7].
Question 4
#Que renvoie lcs([9, 3, 7, 5, 8], 1, [9, 7, 8, 3, 7, 3], 1) ?
Indice
Ne confondez pas la longueur d’un préfixe et le dernier indice inclus.
Comprendre la correction
[9]Les paramètres 1 désignent les nombres d’éléments des deux préfixes : chacun vaut [9]. Ils ne désignent pas ici l’indice du dernier élément. La plus longue sous-séquence commune est donc [9], pas [9, 3].
Les listes sont non vides, m et n sont leurs longueurs, et L₁[m-1] = L₂[n-1]. Exemple : m=5, n=3, L₁=[9,3,7,5,8], L₂=[9,7,8]. Si leurs fins sont différentes, on choisit une plus longue liste entre lcs(L₁,m-1,L₂,n) et lcs(L₁,m,L₂,n-1). Avec L₂=[9,7,8,3], le sujet compare notamment [9,7] et [9,7,8], et garde cette dernière.
Question 5
#Si les derniers éléments de L₁ et L₂ sont égaux, exprimer une LCS à partir de lcs(L₁, m-1, L₂, n-1).
Indice
L’élément commun final doit être placé après la solution des deux préfixes.
Comprendre la correction
lcs(L_1, m - 1, L_2, n - 1) + [L_1[m - 1]]Le dernier élément commun peut prolonger une LCS des préfixes privés de leurs derniers éléments. Il doit être ajouté à la fin pour préserver l’ordre. Pour [9,3,7,5,8] et [9,7,8], une LCS des préfixes est [9,7] ; on ajoute 8 et obtient [9,7,8].
L’idée de la récurrence est structurelle : si les derniers éléments diffèrent, une solution commune ne peut pas les utiliser tous les deux comme dernier élément. Il faut alors comparer les solutions obtenues en retirant la fin de l’une ou de l’autre liste, puis garder une plus longue. En cas d’égalité de longueur, plusieurs LCS peuvent être correctes.
def lcs_top_down(liste_x, liste_y):
def aux(liste_x, index_x, liste_y, index_y):
if index_x < 0 or index_y < 0:
return []
if liste_x[index_x] == liste_y[index_y]:
return aux(liste_x, index_x-1, liste_y, index_y-1) + [liste_x[index_x]]
resultat_1 = aux(liste_x, index_x, liste_y, index_y-1)
resultat_2 = aux(liste_x, index_x-1, liste_y, index_y)
if len(resultat_1) >= len(resultat_2):
return resultat_1
return resultat_2
return aux(liste_x, len(liste_x)-1, liste_y, len(liste_y)-1)Question 6
#Justifier que aux dans lcs_top_down est récursive.
Indice
Repérez les trois lignes qui rappellent aux.
Comprendre la correction
La fonction aux s’appelle elle-même dans son propre corps, avec un ou deux indices diminués. Les appels de la branche d’égalité diminuent les deux indices ; ceux de la branche d’inégalité en diminuent un. Quand un indice devient négatif, le préfixe correspondant est vide et le cas de base renvoie []. Le nom top_down ne prouve pas à lui seul la récursivité : ce sont les auto-appels qui la caractérisent.
Question 7
#Quel risque pose ce choix pour de très grandes listes ?
Indice
La profondeur maximale et le nombre total d’appels sont deux mesures différentes.
Comprendre la correction
Les appels non terminés s’empilent. Une branche peut avoir une profondeur proportionnelle à m + n ; pour de grandes listes, elle peut dépasser la limite de récursion et provoquer RecursionError. Les cadres d’appels consomment aussi de la mémoire.
Un second problème s’ajoute : le programme fourni n’a aucune mémoïsation. Plusieurs branches recalculent les mêmes couples d’indices, ce qui peut rendre le temps exponentiel. Le qualifier « top_down » n’en fait donc pas automatiquement un algorithme dynamique efficace. Les copies de listes lors des concaténations ajoutent encore du travail. Il faut distinguer la profondeur de pile et le nombre total de sous-appels : diminuer l’un ne résout pas nécessairement l’autre.
Question 8
#Proposer une solution au problème précédent.
Indice
Pour éviter la pile, calculez d’abord les sous-problèmes les plus petits dans un tableau.
Comprendre la correction
Employer une programmation dynamique itérative : une table D[i][j] contient la longueur d’une LCS des i premiers éléments de L₁ et des j premiers éléments de L₂. Les bords D[0][j] et D[i][0] valent 0. Si les dernières valeurs correspondent, D[i][j] = 1 + D[i-1][j-1] ; sinon D[i][j] = max(D[i-1][j], D[i][j-1]). On remplit la table ligne par ligne sans appels récursifs.
Pour restituer une sous-séquence, on remonte depuis D[m][n] : une égalité des valeurs ajoute cet élément et fait reculer en diagonale ; sinon on va vers une case voisine de même meilleure longueur. Les éléments sont collectés à rebours puis inversés. On remplit (m+1)(n+1) cases, en O(mn) temps et mémoire pour cette version. Une mémoïsation récursive éviterait les recalculs mais conserverait la profondeur de pile ; elle ne répond pas à elle seule au risque de RecursionError. Augmenter la limite de récursion ne change pas la nature du problème.
Construire une LCS dans la table de préfixesUn atelier pour expérimenter
Choisissez deux préfixes des listes du sujet. La table montre leurs longueurs de LCS et le modèle reconstitue une solution. Essayez de prédire si ajouter le prochain élément augmente réellement la longueur.
Lire le résultat de l’expérience initiale
Une LCS : [9, 7, 8]
Chaque case utilise la diagonale en cas d’égalité, sinon le maximum du haut et de la gauche. Des égalités peuvent conduire à plusieurs sous-séquences optimales.
| L1 / L2 | vide | 9 | 7 | 8 | 3 | 7 | 3 |
|---|---|---|---|---|---|---|---|
| vide | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 9 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
| 3 | 0 | 1 | 1 | 1 | 2 | 2 | 2 |
| 7 | 0 | 1 | 2 | 2 | 2 | 3 | 3 |
| 5 | 0 | 1 | 2 | 2 | 2 | 3 | 3 |
| 8 | 0 | 1 | 2 | 3 | 3 | 3 | 3 |
La table ne stocke pas toutes les sous-séquences ; elle partage les résultats des sous-problèmes de préfixes.
Exercice 3 · 8 points
De Bob à Alice : routage, échange de clés et sac supercroissant
Bob désigne le navigateur d’un utilisateur, Alice le serveur web du domaine alice.fr. Lors de la première saisie de http://alice.fr, Bob interroge un serveur DNS pour obtenir l’adresse IP d’Alice avant de lui envoyer sa requête. Bob est relié à R1, Alice à R5 et le DNS à R8. Les coûts des liaisons inter-routeurs sont ceux du schéma :
Lire les connexions du schéma
- R1 relié à R2 : 1
- R2 relié à R3 : 1
- R2 relié à R4 : 10
- R4 relié à R5 : 1
- R4 relié à R6 : 0.01
- R3 relié à R6 : 1
- R3 relié à R7 : 0.1
- R3 relié à R8 : 1
- R6 relié à R7 : 0.1
- R7 relié à R8 : 0.01
Question 1
#Avec RIP, donner uniquement les routeurs traversés pour aller de Bob au DNS.
Indice
Le DNS est raccordé à R8 ; cherchez le moins de routeurs depuis R1.
Comprendre la correction
R1 → R2 → R3 → R8. Ce chemin traverse quatre routeurs et trois liaisons entre eux. Toute route contournant R3 demanderait davantage de routeurs. Les coûts numériques des arêtes ne sont pas la métrique de RIP.
Question 2
#Bob connaît l’IP d’Alice. Donner la route OSPF vers Alice, uniquement avec les routeurs.
Indice
Comparez les deux façons de passer de R3 à R6.
Comprendre la correction
R1 → R2 → R3 → R7 → R6 → R4 → R5. La liaison R2-R4 de coût 10 est évitée ; R3-R6 de coût 1 est aussi moins intéressante que R3-R7-R6 de coût 0,2. Le choix optimal ne minimise donc pas le nombre d’arêtes.
Question 3
#Donner le coût du chemin précédent.
Indice
Écrivez la somme dans le même ordre que les routeurs du trajet.
Comprendre la correction
1 + 1 + 0,1 + 0,1 + 0,01 + 1 = 3,21. On additionne une fois chaque liaison empruntée. Les connexions terminales Bob-R1 et R5-Alice n’ont pas de coût indiqué dans la figure : le total demandé est celui des liaisons pondérées.
Question 4
#Que se passe-t-il si R3 tombe en panne ? Sans écrire les tables de routage.
Indice
Retirez R3 et toutes ses liaisons, puis vérifiez si les autres routeurs restent connectés.
Comprendre la correction
Les routeurs détectent l’indisponibilité des liens vers R3 et mettent à jour leurs informations de routage. Après convergence, les communications peuvent continuer, car le réseau restant relie encore les destinations. Pour Alice, Bob peut suivre R1-R2-R4-R5 ; pour le DNS, R1-R2-R4-R6-R7-R8. Les nouvelles routes peuvent être plus coûteuses et une interruption temporaire peut se produire pendant la convergence.
Question 5
#Citer un protocole permettant de chiffrer les échanges entre navigateur et serveur web.
Indice
Le S de HTTPS correspond à la sécurisation du transport.
Comprendre la correction
HTTPS, c’est-à-dire HTTP transporté dans une connexion TLS. Le chiffrement protège les données en transit ; TLS ajoute aussi des mécanismes d’intégrité et d’authentification du serveur. L’adresse initiale http://alice.fr ne fournit pas à elle seule cette protection.
Bob génère une clé symétrique qu’il doit transmettre à Alice pour qu’ils disposent du même secret.
Question 6
#Quel rôle joue la même clé symétrique possédée par Alice et Bob ?
Indice
Symétrique signifie que le secret est partagé entre les participants.
Comprendre la correction
Elle permet de chiffrer et déchiffrer les messages échangés. L’émetteur utilise le secret pour produire le chiffré ; le destinataire possède le même secret pour retrouver le clair. La clé doit rester inconnue des tiers. Elle ne prouve pas à elle seule lequel des deux détenteurs a produit un message, puisque tous deux la connaissent.
Alice génère un couple clé publique / clé privée. Bob doit envoyer sa clé symétrique sans qu’un tiers puisse l’intercepter et l’utiliser.
Question 7
#Comment employer les clés publique et privée d’Alice pour transmettre cette clé symétrique ? Ne parler ni de signature ni de certificat dans cette réponse.
Indice
La clé publique du destinataire protège le secret qui lui est destiné.
Comprendre la correction
Alice transmet sa clé publique à Bob et garde sa clé privée secrète. Bob chiffre la clé symétrique avec la clé publique d’Alice, puis lui envoie le résultat. Alice le déchiffre avec sa clé privée. La clé publique peut circuler ; elle ne permet pas à un observateur de déchiffrer ce message. La question raisonne dans le modèle d’échange de clés décrit, sans traiter ici la vérification de l’identité du propriétaire de la clé publique.
Le sujet décrit la signature ainsi : Bob envoie sa clé publique à Alice, calcule un condensé de sa clé symétrique, signe ce condensé avec sa clé privée, puis chiffre l’ensemble clé symétrique + signature avec la clé publique d’Alice avant l’envoi.
Question 8
#Pourquoi Alice seule peut-elle accéder à la clé symétrique et vérifier que Bob l’a envoyée ?
Indice
Le chiffrement extérieur protège la confidentialité ; la signature vérifie origine et intégrité.
Comprendre la correction
Le paquet complet est chiffré avec la clé publique d’Alice : sa clé privée permet de l’ouvrir. Alice récupère la clé symétrique et la signature, vérifie cette signature avec la clé publique de Bob, puis compare le condensé vérifié à un nouveau condensé calculé sur la clé reçue. Une modification de la clé provoque un désaccord ; une signature valide établit qu’elle a été produite avec la clé privée correspondant à celle de Bob.
Cette conclusion exige que la clé publique attribuée à Bob soit authentiquement la sienne. Un attaquant qui substituerait sa propre clé pourrait signer lui-même un message : l’envoi d’une clé publique sur un réseau hostile ne prouve pas son identité. La formulation du sujet « chiffrer avec la clé privée » est une image pédagogique de la signature ; les algorithmes de signature réels ne se résument pas tous à inverser un algorithme de chiffrement.
Partie B : module outils
Question 9
#Dans le module outils, écrire somme(tab), somme des entiers d’une liste. Exemple : somme([1,3,5,7,9]) vaut 25.
Indice
L’accumulateur doit être initialisé avant la boucle et renvoyé après elle.
Comprendre la correction
def somme(tab):
total = 0
for valeur in tab:
total += valeur
return totalL’accumulateur vaut la somme des éléments déjà parcourus. Il démarre à 0, élément neutre de l’addition, ce qui rend aussi la réponse correcte pour une liste vide. La liste d’entrée n’est pas modifiée. Le programme utilise une addition par valeur et parcourt la liste une seule fois.
Question 10
#Écrire permuter(tab, i, j), qui échange en place les éléments i et j. Exemple : [1,3,5,7,9] avec i=1,j=3 devient [1,7,5,3,9].
Indice
Il faut conserver une des deux anciennes valeurs avant la première écriture.
Comprendre la correction
def permuter(tab, i, j):
temporaire = tab[i]
tab[i] = tab[j]
tab[j] = temporaireLa variable temporaire sauvegarde la première valeur avant de l’écraser. Sans elle, deux affectations successives naïves copieraient la même valeur aux deux endroits. L’affectation simultanée tab[i], tab[j] = tab[j], tab[i] convient également en Python. Le tableau est modifié sur place ; aucun return n’est nécessaire. Si i = j, l’échange laisse la liste inchangée.
def inverser(tab):
for i in range(len(tab) // 2):
permuter(...)Question 11
#Compléter la ligne de inverser qui inverse tab en place.
Indice
Le premier élément échange avec le dernier, d’indice n - 1.
Comprendre la correction
permuter(tab, i, len(tab) - 1 - i)Les indices symétriques sont i et n - 1 - i. La boucle ne parcourt que n // 2 positions afin de ne pas échanger une deuxième fois les mêmes paires. Pour n impair, la case centrale ne bouge pas, ce qui est correct. Pour n = 0 ou 1, aucun échange n’est nécessaire.
Un sac contient huit entiers : premier entier aléatoire entre 1 et 5 inclus ; à chaque ajout, calculer la somme S des entiers déjà présents, choisir entre S+1 et 2S inclus, puis ajouter. Une fois huit éléments obtenus, inverser la liste pour mettre le plus grand en premier. randint(a,b) inclut ses deux bornes.
from random import randint
def generer_sac():
sac = ...
for i in range(7):
s = ...
nouvel_entier = ...
sac.append(nouvel_entier)
...
return sacQuestion 12
#Compléter generer_sac selon la méthode décrite.
Indice
La boucle complète le premier élément jusqu’à une longueur totale de huit.
Comprendre la correction
from random import randint
def generer_sac():
sac = [randint(1, 5)]
for i in range(7):
s = somme(sac)
nouvel_entier = randint(s + 1, 2 * s)
sac.append(nouvel_entier)
inverser(sac)
return sacUn premier élément est créé, puis sept autres pour un total de huit. À chaque étape, la somme est recalculée sur le sac déjà constitué. Le nouvel entier est strictement supérieur à la somme de tous les précédents : c’est la propriété supercroissante. Après inversion, chaque élément dépasse la somme de ceux qui le suivent. Cette propriété donnera une décision de déchiffrement sans ambiguïté. Écrire randint(s, 2*s) perdrait l’inégalité stricte, et la taille ne doit pas être neuf.
Partie C : chiffrer un octet avec un sac
Un octet est représenté par huit bits en liste. Cle_symetrique possède un seul attribut sac, fourni par outils.generer_sac. Pour chiffrer, multiplier chaque bit par le poids de même indice puis sommer les produits. Avec la clé [568,205,71,32,10,6,2,1], l’octet [1,0,1,1,1,1,0,1] donne 568 + 71 + 32 + 10 + 6 + 1 = 688.
import outils
class Cle_symetrique:
def __init__(self):
... = ...Question 13
#Compléter le constructeur de Cle_symetrique.
Indice
L’attribut de l’objet doit recevoir le résultat de l’appel de fonction.
Comprendre la correction
self.sac = outils.generer_sac()L’appel fabrique une nouvelle clé pour l’instance. outils.generer_sac sans parenthèses conserverait la fonction elle-même au lieu de la liste. Le préfixe outils est nécessaire puisque l’import est import outils.
def chiffrer(self, octet):
"""octet : liste de huit 0 ou 1 ; renvoie un entier."""
tab_produits = []
for i in range(...):
produit = ... * ...
tab_produits.append(produit)
s = ...
return sQuestion 14
#Compléter les lignes 8 à 11 de chiffrer.
Indice
Construisez d’abord la liste des huit contributions puis utilisez le module outils pour les sommer.
Comprendre la correction
for i in range(8):
produit = octet[i] * self.sac[i]
tab_produits.append(produit)
s = outils.somme(tab_produits)Le bit 0 sélectionne un produit nul et le bit 1 sélectionne le poids entier. Les indices des bits et du sac doivent rester alignés. On additionne les produits, pas les bits seuls ni tous les éléments du sac. Sur l’exemple, le bit de poids 205 vaut 0 : ce nombre ne contribue donc pas au total. La précondition impose huit bits ; une validation d’entrée serait une amélioration supplémentaire.
Déchiffrement : pour chaque indice i de 0 à 7, si l’entier restant est supérieur ou égal à sac[i], écrire le bit 1 et soustraire sac[i] ; sinon écrire le bit 0.
Question 15
#Justifier que 688 se déchiffre en [1,0,1,1,1,1,0,1].
Indice
À chaque ligne, comparez le reste au poids puis soustrayez uniquement si le bit vaut 1.
Comprendre la correction
| Poids | Reste avant | Bit | Reste après |
|---|---|---|---|
| 568 | 688 | 1 | 120 |
| 205 | 120 | 0 | 120 |
| 71 | 120 | 1 | 49 |
| 32 | 49 | 1 | 17 |
| 10 | 17 | 1 | 7 |
| 6 | 7 | 1 | 1 |
| 2 | 1 | 0 | 1 |
| 1 | 1 | 1 | 0 |
On examine les poids du plus grand au plus petit. Si le reste atteint le poids, le bit doit valoir 1 : les poids plus petits, tous réunis, ne pourraient atteindre ce poids. Sinon ce bit vaut nécessairement 0. Le reste final nul confirme que tous les termes sélectionnés reconstituent 688. Ce raisonnement utilise la supercroissance, pas simplement l’ordre décroissant.
Question 16
#Écrire dechiffrer, qui reçoit l’entier chiffré et renvoie les huit bits.
PythonRetrouver huit bits avec une clé supercroissanteÉcrivez votre solution et mettez-la à l’épreuve
Complétez la méthode dechiffrer de Cle_symetrique. self.sac contient huit poids en ordre décroissant ; chaque poids dépasse la somme de tous les suivants. entier est garanti issu du chiffrement d’un octet par cette clé. Renvoyez les huit bits dans l’ordre du sac. Pour rendre les tests reproductibles, generer_sac fournit une clé fixe valide, créée pour cet entraînement. La clé ne doit pas être modifiée.
class Cle_symetrique:
def __init__(self):
self.sac = generer_sac()
def dechiffrer(self, entier):
# Construisez les huit bits
passLes cas de test proposés :
- L’octet nul : Il faut renvoyer huit décisions, même si aucune soustraction n’est effectuée.
- Le plus grand poids exactement : L’égalité signifie que ce poids est présent. Une comparaison stricte le refuserait.
- Un bit de poids faible : L’ordre de sortie reste celui du sac décroissant, pas l’ordre des poids sélectionnés.
- Un octet mélangé : 397 = 310 + 70 + 16 + 1. Après chaque choix, le reste doit diminuer.
- Tous les poids et clé inchangée : La somme des huit poids vaut 592. Déchiffrer ne doit pas consommer la liste de la clé.
Indice
La variable reste doit diminuer sans modifier la clé elle-même.
Comprendre la correction
def dechiffrer(self, entier):
octet = []
reste = entier
for poids in self.sac:
if reste >= poids:
octet.append(1)
reste -= poids
else:
octet.append(0)
return octetL’invariant est : entier initial = somme des poids déjà sélectionnés + reste. La boucle garde huit décisions dans l’ordre du sac. Pour un entier effectivement obtenu par chiffrer avec cette clé, le reste final est zéro et l’octet est unique. Tous les entiers entre 0 et la somme du sac ne sont pas forcément représentables ; pour accepter des données arbitraires, on pourrait vérifier reste == 0 et refuser sinon.
Ce mécanisme est un modèle pédagogique, pas une recommandation de cryptographie actuelle : avec seulement 256 octets possibles et une structure aussi visible, il ne fournit pas la sécurité des protocoles réels. L’intérêt de l’exercice est de comprendre pourquoi l’algorithme glouton est correct pour cette suite de poids particulière.
Chiffrer puis vérifier chaque décision du déchiffrementUn atelier pour expérimenter
Choisissez la valeur d’un octet de 0 à 255. Le modèle calcule ses huit bits, le total chiffré et tous les restes de la décomposition gloutonne avec la clé du sujet.
Lire le résultat de l’expérience initiale
10111101 devient 688
Le sac est décroissant et chaque poids dépasse la somme de tous les suivants : la décision gloutonne ne peut pas être compensée par de plus petits poids.
| Poids | Reste avant | Bit choisi | Reste après |
|---|---|---|---|
| 568 | 688 | 1 | 120 |
| 205 | 120 | 0 | 120 |
| 71 | 120 | 1 | 49 |
| 32 | 49 | 1 | 17 |
| 10 | 17 | 1 | 7 |
| 6 | 7 | 1 | 1 |
| 2 | 1 | 0 | 1 |
| 1 | 1 | 1 | 0 |
Un algorithme glouton n’est correct que sous une propriété structurelle : ici, aucun ensemble de petits poids ne remplace un poids plus grand.
Du sujet à la méthode
Votre prochaine séance de révision
- Dans les requêtes, vérifiez quelle table apporte chaque colonne avant de choisir les jointures.
- Pour la récursivité, distinguez le problème de profondeur du problème de recalcul.
- Une décision gloutonne demande une justification : expliquez ici pourquoi les poids restants ne peuvent compenser un poids omis.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 26-NSIJ1AG1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.
