Concours MP PSI - Exploration d'une grotte souterraine

26 Mar 2026 26 Mar 2026 212 vues ESSADDOUKI Mostafa 18 min de lecture
Complexité algorithmique
1 Introduction à l'analyse des algorithmes 2 Notations asymptotiques 3 Opérations élémentaires et modèles de coût 4 Complexité temporelle et spatiale 5 Méthode de comptage des pas 6 Méthode de comptage des pas pour les boucles
Diviser pour régner & algorithmes de tri
7 Rappel sur l'approche récursive 8 Diviser pour régner 9 la recherche dichotomique 10 Tri par fusion 11 Tri rapide 12 Analyse des fonctions récursives
Les arbres binaires
13 Introduction aux arbres binaires 14 Définitions récursives des arbres 15 Propriétés des arbres binaires 16 Types d'arbre binaire 17 Parcours en profondeur des arbres binaires 18 Parcours en largeur des arbres binaires 19 Exercices corrigés sur les arbres - TD 1 20 Exercices corrigés sur les arbres - TD 2 21 Exercices corrigés sur les arbres - TD 3 22 DS - Arbres binaires de recherche équilibrés (AVL) 23 DS - Codage de Huffman - Compression de données
Algorithmes gloutons
24 Introduction aux algorithmes gloutons 25 Problème de la sélection d'activités 26 Problème de séquencement des tâches 27 Problème du Sac à Dos fraction
Programmation dynamique
28 Introduction à la programmation dynamique 29 Le concept de sous-structure optimale 30 Le concept de sous-problèmes superposés 31 Méthodes de la programmation dynamique 32 Différence entre la programmation dynamique, l'approche diviser pour régner, et les algorithmes gloutons 33 Calculer les nombres de catalan en C++ et Python 34 Calculer le coefficient binomial 35 Le nombre de façons pour construire un mur de dimension 4*N 36 Défi de conversion de mots 37 Décomposition de phrases à partir d'un dictionnaire 38 La collection de pièces dans un labyrinthe 39 Nombre de façons de regrouper les étudiants 40 Compter tous les chemins possibles dans une grille MxN
Méta heuristique
41 Algorithmes heuristiques et métaheuristiques 42 Algorithme de Recuit simulé 43 Algorithme Colonies de fourmis
Théorie des graphes
44 Introduction et notions fondamentales 45 Chemins, cycles et connexité 46 Représentations des graphes 47 Parcours de graphes 48 Algorithme de Dijkstra 49 Algorithme A* 50 Algorithme de Bellman-Ford 51 Algorithme Floyd-Warshall 52 Coloration des graphes
Base de données et SQL
53 Introduction au langage SQL 54 Concepts de base de SGBDR 55 Syntaxes de différentes instructions SQL 56 Création et suppression d'une base de données 57 Opérateurs SQL 58 Les contraintes en SQL 59 Création et suppression des tables en SQL 60 Insertion et modifications des enregistrements 61 Extraction des données - SELECT 62 Filtrer les données - WHERE 63 Recherche de motifs - LIKE 64 Trier les données - ORDER BY 65 Les jointures en SQL - JOIN 66 Fonctions d'agrégation en SQL - SUM, COUNT, AVG, MIN et MAX 67 Organiser des données identiques en groupes - GROUP BY et HAVING 68 Les sous-requêtes en SQL 69 Les fonctions SQL de manipulation de date 70 DS - langage SQL 71 Exercices corrigés de langage SQL
Introduction à l'intelligence artificielle
Introduction à la théorie des jeux
Préparation aux concours
72 Réseau de distribution d'eau 73 Arbre d'expression arithmétique 74 Exploration d'une grotte souterraine
Graphes Grilles BFS / DFS Dijkstra Intermédiaire
4 parties
16 questions
Python CPGE MP - Concurs
~2h30

Exploration de grottes souterraines — Graphes sur grille

Un groupe de spéléologues souhaite explorer un réseau de grottes souterraines. Le réseau est modélisé par un graphe où chaque nœud représente une salle et chaque arête un tunnel reliant deux salles. Chaque tunnel a un poids dépendant du type de salle d'arrivée.

L'entrée de la grotte est la case 'E', la sortie est la case 'S'. Les spéléologues doivent trouver leur chemin, éviter les zones dangereuses et optimiser leur exploration.

  Objectif

Modéliser une grotte en graphe, puis implémenter BFS, DFS, Dijkstra et des algorithmes avancés (points d'articulation, collecte de trésors) sur une grille 2D.

Données du problème Grille et constantes
grotte = [
    ['E', '.', '.', '#', '.', '.', '.'],
    ['.', '#', '.', '#', '.', '#', '.'],
    ['.', '#', '.', '.', '.', '#', 'T'],
    ['.', '.', '#', '#', '.', '.', '.'],
    ['#', '.', '.', '.', '#', '.', 'S'],
    ['.', '.', '#', '.', '.', '.', '#'],
    ['.', '#', '.', '.', '#', '.', '.'],
    ['.', '.', '.', '#', '.', 'D', '.'],
]

N = len(grotte)      # 8 lignes
M = len(grotte[0])   # 7 colonnes
# Poids des tunnels selon le type de salle d'arrivée
POIDS = {'.': 1, 'T': 1, 'D': 3, 'E': 1, 'S': 1, 'O': 2}

# Légende des cases :
#   'E' = entrée          '.' = salle accessible
#   'S' = sortie          '#' = paroi (infranchissable)
#   'T' = trésor          'D' = salle dangereuse
#   'O' = obstacle temporaire (rocher instable)

# Directions possibles (haut, bas, gauche, droite)
DIRECTIONS = [(-1, 0), (1, 0), (0, -1), (0, 1)]
Règles de déplacement
  • Deux salles sont reliées si elles sont adjacentes horizontalement ou verticalement (pas en diagonale).
  • Une case '#' est une paroi infranchissable.
  • Le poids d'un tunnel est le poids de la salle d'arrivée selon le dictionnaire POIDS.
0
Modélisation 4 questions

Avant d'appliquer des algorithmes de graphes, on convertit la grille en une structure exploitable : vérification de validité, calcul des voisins, construction du graphe d'adjacence et localisation des cases spéciales.

Q1

Écrire une fonction est_valide(grotte, ligne, col) qui retourne True si la position (ligne, col) est dans les limites de la grotte et n'est pas une paroi '#'.

Exemples

Validation de positions
Entrées
est_valide(grotte, 0, 0)   # 'E'
est_valide(grotte, 0, 3)   # '#'
est_valide(grotte, 9, 0)   # hors limites
Sorties
True
False
False
Solution Q1
def est_valide(grotte, ligne, col):
    N = len(grotte)
    M = len(grotte[0])
    if ligne < 0 or ligne >= N or col < 0 or col >= M:
        return False
    return grotte[ligne][col] != '#'
Sortie
est_valide(grotte, 0, 0)  →  True
est_valide(grotte, 0, 3)  →  False
est_valide(grotte, 9, 0)  →  False
Q2

Écrire une fonction voisins(grotte, ligne, col) qui retourne la liste des positions (l, c) accessibles depuis (ligne, col) (cases valides adjacentes dans les 4 directions), accompagnées de leur poids.

Exemples

Voisins d'une case
Entrées
voisins(grotte, 0, 0)
voisins(grotte, 7, 5)   # 'D'
Sorties
[((0,1), 1), ((1,0), 1)]
[((6,5), 1), ((7,4), 1)]
Solution Q2

On explore les 4 directions et on retourne les cases valides avec leur poids (poids de la case d'arrivée).

DIRECTIONS = [(-1, 0), (1, 0), (0, -1), (0, 1)]

def voisins(grotte, ligne, col):
    resultat = []
    for dl, dc in DIRECTIONS:
        nl, nc = ligne + dl, col + dc
        if est_valide(grotte, nl, nc):
            case = grotte[nl][nc]
            poids = POIDS.get(case, 1)
            resultat.append(((nl, nc), poids))
    return resultat
Sortie
voisins(grotte, 0, 0)  →  [((0,1), 1), ((1,0), 1)]
voisins(grotte, 7, 5)  →  [((6,5), 1), ((7,4), 1)]
Q3

Écrire une fonction construire_graphe(grotte) qui construit et retourne le graphe de la grotte sous forme de dictionnaire d'adjacence : chaque clé est une position (l, c) valide, et la valeur est la liste de tuples ((l', c'), poids) des voisins accessibles.

Exemple

Graphe de la case (0, 0)
Entrée
construire_graphe(grotte)[(0, 0)]
Sortie
[((0,1), 1), ((1,0), 1)]
Solution Q3
def construire_graphe(grotte):
    graphe = {}
    N, M = len(grotte), len(grotte[0])
    for l in range(N):
        for c in range(M):
            if grotte[l][c] != '#':
                graphe[(l, c)] = voisins(grotte, l, c)
    return graphe
Sortie
construire_graphe(grotte)[(0, 0)]
→  [((0,1), 1), ((1,0), 1)]

  Complexité

  • Temps : O(N × M) — on parcourt toutes les cases.
  • Espace : O(N × M) — taille du graphe.
Q4

Écrire une fonction localiser(grotte, symbole) qui retourne la liste de toutes les positions (l, c) contenant le symbole donné.

Exemples

Localisation des symboles spéciaux
Entrées
localiser(grotte, 'E')
localiser(grotte, 'T')
localiser(grotte, 'D')
Sorties
[(0, 0)]
[(2, 6)]
[(7, 5)]
Solution Q4
def localiser(grotte, symbole):
    positions = []
    for l, ligne in enumerate(grotte):
        for c, case in enumerate(ligne):
            if case == symbole:
                positions.append((l, c))
    return positions
Sortie
localiser(grotte, 'E')  →  [(0, 0)]
localiser(grotte, 'T')  →  [(2, 6)]
localiser(grotte, 'D')  →  [(7, 5)]
1
Parcours et accessibilité 4 questions

On implémente les deux parcours fondamentaux (BFS et DFS) pour explorer le graphe, détecter l'accessibilité entre salles et trouver un chemin en nombre minimal de tunnels.

Q5

Écrire une fonction bfs(graphe, depart) qui effectue un parcours en largeur depuis depart et retourne un dictionnaire {sommet: distance} donnant le nombre minimal de tunnels pour atteindre chaque salle accessible.

Exemple

BFS depuis l'entrée
Entrée
bfs(graphe, (0, 0))[(4, 6)]
Sortie
8   # indicatif — nombre minimal de tunnels
Solution Q5 — BFS

Le BFS utilise une file (deque) pour visiter les salles niveau par niveau. Chaque salle est explorée au plus une fois.

from collections import deque

def bfs(graphe, depart):
    distances = {depart: 0}
    file = deque([depart])
    while file:
        courant = file.popleft()
        for (voisin, _) in graphe.get(courant, []):
            if voisin not in distances:
                distances[voisin] = distances[courant] + 1
                file.append(voisin)
    return distances
Sortie
bfs(graphe, (0,0))[(4,6)]  →  8   # indicatif

  Complexité

  • Temps : O(S + T) — S salles, T tunnels (arêtes).
  • Espace : O(S) — dictionnaire des distances + file.
Q6

Écrire une fonction dfs(graphe, depart) qui effectue un parcours en profondeur depuis depart et retourne la liste des salles visitées dans l'ordre de découverte.

Exemple

DFS depuis l'entrée
Entrée
dfs(graphe, (0, 0))[0]
Sortie
(0, 0)   # premier sommet découvert = départ
Solution Q6 — DFS

Le DFS utilise une pile. On propose ici une version itérative pour éviter les débordements de pile sur de grandes grottes.

def dfs(graphe, depart):
    visites = []
    vus = set()
    pile = [depart]
    while pile:
        courant = pile.pop()
        if courant not in vus:
            vus.add(courant)
            visites.append(courant)
            for (voisin, _) in reversed(graphe.get(courant, [])):
                if voisin not in vus:
                    pile.append(voisin)
    return visites
Sortie
dfs(graphe, (0,0))[0]  →  (0, 0)

  Complexité

  • Temps : O(S + T) — identique au BFS.
  • Différence : le DFS explore en profondeur (un chemin jusqu'au bout), le BFS explore niveau par niveau. Le BFS garantit le chemin le plus court en nombre d'arêtes.
Q7

Écrire une fonction est_accessible(graphe, depart, arrivee) qui retourne True si la salle arrivee est atteignable depuis depart. Écrire également salles_accessibles(graphe, depart) qui retourne l'ensemble de toutes les salles atteignables.

Exemple

Accessibilité entrée → sortie
Entrée
entree = localiser(grotte, 'E')[0]   # (0, 0)
sortie = localiser(grotte, 'S')[0]   # (4, 6)
est_accessible(graphe, entree, sortie)
Sortie
True
Solution Q7
def salles_accessibles(graphe, depart):
    return set(bfs(graphe, depart).keys())

def est_accessible(graphe, depart, arrivee):
    return arrivee in salles_accessibles(graphe, depart)
Sortie
est_accessible(graphe, (0,0), (4,6))  →  True
Q8

Écrire une fonction chemin_court_bfs(graphe, depart, arrivee) qui retourne un chemin de longueur minimale (en nombre de tunnels) de depart à arrivee, sous forme de liste de positions. Retourner [] si aucun chemin n'existe. On stockera les prédécesseurs pendant le BFS.

Exemple

Chemin minimal en nombre de tunnels
Entrée
chemin = chemin_court_bfs(graphe, (0,0), (4,6))
len(chemin) - 1   # nombre de tunnels
Sortie
8   # indicatif
Solution Q8 — BFS avec reconstruction

On enregistre le prédécesseur de chaque salle lors de la découverte, puis on remonte de l'arrivée vers le départ.

def chemin_court_bfs(graphe, depart, arrivee):
    predecesseur = {depart: None}
    file = deque([depart])
    while file:
        courant = file.popleft()
        if courant == arrivee:
            break
        for (voisin, _) in graphe.get(courant, []):
            if voisin not in predecesseur:
                predecesseur[voisin] = courant
                file.append(voisin)
    # Reconstruction du chemin
    if arrivee not in predecesseur:
        return []
    chemin = []
    salle = arrivee
    while salle is not None:
        chemin.append(salle)
        salle = predecesseur[salle]
    chemin.reverse()
    return chemin
Sortie
chemin_court_bfs(graphe, (0,0), (4,6))
→  [(0,0), (0,1), (0,2), (1,2), (2,2), (2,3), (2,4), (3,4), (4,4), ...]
   # chemin de longueur minimale (indicatif)
2
Chemins optimaux et pondération 3 questions

Le BFS trouve le chemin le plus court en nombre de tunnels, mais pas en coût total (certaines salles sont plus difficiles à traverser). On implémente Dijkstra pour minimiser le coût pondéré, puis on ajoute la contrainte d'évitement des zones dangereuses.

Q9

Écrire une fonction dijkstra(graphe, depart) qui implémente l'algorithme de Dijkstra en utilisant heapq, avec les poids des tunnels. Retourner le dictionnaire {sommet: cout_minimal} et le dictionnaire des prédécesseurs {sommet: predecesseur}.

Exemple

Coût minimal vers la sortie
Entrée
couts, pred = dijkstra(graphe, (0, 0))
couts[(4, 6)]
Sortie
9   # indicatif selon les poids
Solution Q9 — Dijkstra

On utilise un tas min (heapq). À chaque étape, on extrait la salle de coût minimal non encore finalisée et on met à jour ses voisins.

import heapq

def dijkstra(graphe, depart):
    couts = {depart: 0}
    pred  = {depart: None}
    tas   = [(0, depart)]   # (cout, sommet)
    while tas:
        cout_courant, courant = heapq.heappop(tas)
        if cout_courant > couts.get(courant, float('inf')):
            continue   # entrée obsolète dans le tas
        for (voisin, poids) in graphe.get(courant, []):
            nouveau_cout = cout_courant + poids
            if nouveau_cout < couts.get(voisin, float('inf')):
                couts[voisin] = nouveau_cout
                pred[voisin]  = courant
                heapq.heappush(tas, (nouveau_cout, voisin))
    return couts, pred
Sortie
couts, pred = dijkstra(graphe, (0,0))
couts[(4,6)]  →  9   # indicatif

  Complexité

  • Temps : O((S + T) log S) — avec tas min binaire.
  • Espace : O(S) — dictionnaires + tas.
Q10

Écrire une fonction chemin_optimal(graphe, depart, arrivee) qui utilise dijkstra pour retourner le chemin de coût minimal de depart à arrivee (liste de positions) et son coût total. Retourner ([], float('inf')) si inaccessible.

Exemple

Chemin de coût minimal
Entrée
chemin, cout = chemin_optimal(graphe, entree, sortie)
cout
Sortie
10   # indicatif
Solution Q10 — Chemin optimal
def chemin_optimal(graphe, depart, arrivee):
    couts, pred = dijkstra(graphe, depart)
    if arrivee not in couts:
        return [], float('inf')
    # Reconstruction du chemin via les prédécesseurs
    chemin = []
    salle = arrivee
    while salle is not None:
        chemin.append(salle)
        salle = pred[salle]
    chemin.reverse()
    return chemin, couts[arrivee]
Sortie
chemin, cout = chemin_optimal(graphe, entree, sortie)
cout  →  10   # indicatif
Q11

Écrire une fonction chemin_sans_danger(graphe, grotte, depart, arrivee) qui retourne le chemin de coût minimal évitant les salles dangereuses 'D'. On construira un sous-graphe excluant ces salles, puis on appliquera Dijkstra. Retourner ([], float('inf')) si aucun chemin sûr n'existe.

Exemple

Chemin sûr — sans salles dangereuses
Entrée
chemin, cout = chemin_sans_danger(graphe, grotte, entree, sortie)
Sortie
([...], cout_sans_danger)   # coût potentiellement plus élevé
Remarque
  • On exclut également les voisins des salles dangereuses du sous-graphe, ce qui évite de traverser une case 'D' pour entrer dans une case valide.
  • Le coût peut être plus élevé qu'avec le chemin optimal, car on doit contourner les zones dangereuses.
Solution Q11 — Chemin sans danger
def chemin_sans_danger(graphe, grotte, depart, arrivee):
    # Identifier les salles dangereuses
    dangereuses = set(localiser(grotte, 'D'))
    # Construire un sous-graphe sans les salles dangereuses
    sous_graphe = {}
    for salle, voisins_liste in graphe.items():
        if salle in dangereuses:
            continue
        sous_graphe[salle] = [
            (v, p) for (v, p) in voisins_liste
            if v not in dangereuses
        ]
    return chemin_optimal(sous_graphe, depart, arrivee)
3
Exploration avancée 3 questions

On aborde des problèmes plus complexes : collecte de trésors (problème du voyageur par force brute), détection des points d'articulation (salles critiques dont la suppression déconnecte la grotte), et calcul de chemins alternatifs en cas de blocage.

Q12

Les spéléologues veulent récupérer tous les trésors avant de sortir. Écrire une fonction collecter_tresors(graphe, grotte, entree, sortie) qui retourne un itinéraire complet passant par tous les trésors puis atteignant la sortie, en minimisant le coût total. On utilisera une approche par force brute sur les permutations des trésors (acceptable si leur nombre est faible).

Exemple

Collecte de tous les trésors
Entrée
collecter_tresors(graphe, grotte, entree, sortie)
Sortie
([entree, ..., (2,6), ..., sortie], cout_total)
Principe
  • On teste toutes les permutations de l'ordre de visite des trésors.
  • Pour chaque permutation, on calcule le coût total : entrée → trésor₁ → trésor₂ → … → sortie.
  • On retient la permutation de coût minimal.
  • Complexité : O(T! × Dijkstra) — acceptable pour T ≤ 6.
Solution Q12 — Collecte de trésors
from itertools import permutations

def collecter_tresors(graphe, grotte, entree, sortie):
    tresors = localiser(grotte, 'T')
    if not tresors:
        # Pas de trésors : chemin direct
        chemin, cout = chemin_optimal(graphe, entree, sortie)
        return chemin, cout
    meilleur_cout   = float('inf')
    meilleur_chemin = []
    for perm in permutations(tresors):
        etapes = [entree] + list(perm) + [sortie]
        cout_total = 0
        chemin_total = [etapes[0]]
        valide = True
        for i in range(len(etapes) - 1):
            segment, cout = chemin_optimal(graphe, etapes[i], etapes[i+1])
            if not segment:
                valide = False
                break
            cout_total  += cout
            chemin_total += segment[1:]   # éviter la duplication du point de départ
        if valide and cout_total < meilleur_cout:
            meilleur_cout   = cout_total
            meilleur_chemin = chemin_total
    return meilleur_chemin, meilleur_cout
Sortie
collecter_tresors(graphe, grotte, (0,0), (4,6))
→  ([(0,0), ..., (2,6), ..., (4,6)], cout_total)
Q13

Écrire une fonction zones_coupees(graphe, grotte) qui identifie les salles critiques (points d'articulation) : une salle est critique si sa suppression du graphe déconnecte deux salles précédemment accessibles l'une depuis l'autre. On utilisera l'algorithme DFS avec les temps de découverte et les valeurs low.

Exemple

Points d'articulation de la grotte
Entrée
zones_coupees(graphe, grotte)
Sortie (indicatif)
[(2, 4), (3, 4), ...]
Principe : disc[u] = temps de découverte de u. low[u] = plus petit temps de découverte atteignable depuis le sous-arbre DFS de u. Un nœud non-racine u est point d'articulation si et seulement si il possède un fils v tel que low[v] ≥ disc[u].
Solution Q13 — Points d'articulation
def zones_coupees(graphe, grotte):
    sommets = list(graphe.keys())
    disc = {}     # temps de découverte
    low  = {}     # low-link value
    parent = {}   # parent dans l'arbre DFS
    points = set()
    timer = [0]

    def dfs_articu(u):
        disc[u] = low[u] = timer[0]
        timer[0] += 1
        nb_enfants = 0
        for (v, _) in graphe.get(u, []):
            if v not in disc:
                nb_enfants += 1
                parent[v] = u
                dfs_articu(v)
                low[u] = min(low[u], low[v])
                # Racine avec ≥ 2 enfants → point d'articulation
                if parent.get(u) is None and nb_enfants > 1:
                    points.add(u)
                # Nœud non-racine : low[v] >= disc[u] → point d'articulation
                if parent.get(u) is not None and low[v] >= disc[u]:
                    points.add(u)
            elif v != parent.get(u):
                low[u] = min(low[u], disc[v])

    for s in sommets:
        if s not in disc:
            parent[s] = None
            dfs_articu(s)

    return sorted(points)
Sortie
zones_coupees(graphe, grotte)
→  [(2, 4), (3, 4), ...]   # indicatif

  Complexité

  • Temps : O(S + T) — un seul DFS suffit.
Q14

Écrire une fonction plus_court_contournement(graphe, grotte, depart, arrivee, salle_bloquee) qui retourne le chemin de coût minimal de depart à arrivee en évitant une salle donnée (par exemple un tunnel effondré). Retourner ([], float('inf')) si aucun chemin alternatif n'existe.

Exemple

Contournement d'une salle bloquée
Entrée
plus_court_contournement(graphe, grotte, entree, sortie, (2, 4))
Sortie
([...], cout_alternatif)
Solution Q14 — Contournement

Même principe que Q11 : on construit un sous-graphe sans la salle bloquée et on applique Dijkstra dessus.

def plus_court_contournement(graphe, grotte, depart, arrivee, salle_bloquee):
    # Sous-graphe sans la salle bloquée
    sous_graphe = {}
    for salle, voisins_liste in graphe.items():
        if salle == salle_bloquee:
            continue
        sous_graphe[salle] = [
            (v, p) for (v, p) in voisins_liste
            if v != salle_bloquee
        ]
    return chemin_optimal(sous_graphe, depart, arrivee)
Sortie
plus_court_contournement(graphe, grotte, entree, sortie, (2,4))
→  ([...], cout_alternatif)
4
Visualisation et rapport 2 questions

On finalise l'exploration en produisant une représentation visuelle du chemin dans la grotte et un rapport synthétisant tous les résultats obtenus.

Q15

Écrire une fonction afficher_chemin(grotte, chemin) qui retourne une copie affichable de la grotte (liste de listes de caractères) dans laquelle chaque salle du chemin (hors entrée et sortie) est marquée par '*'.

Exemple de rendu

Grotte avec chemin tracé
Entrée
afficher_chemin(grotte, chemin)
Sortie
E * * # . . .
. # * # . # .
. # * * * # T
. . # # * . .
# . . . # * S
. . # . . . #
. # . . # . .
. . . # . D .
Les cases '*' indiquent le chemin parcouru. L'entrée 'E' et la sortie 'S' conservent leur symbole d'origine.
Solution Q15 — Affichage du chemin
def afficher_chemin(grotte, chemin):
    # Copie profonde de la grotte
    copie = [list(ligne) for ligne in grotte]
    symboles_fixes = {'E', 'S', 'T', 'D', '#'}
    for (l, c) in chemin:
        if copie[l][c] not in symboles_fixes:
            copie[l][c] = '*'
    return copie

def print_grotte(grotte_affichee):
    for ligne in grotte_affichee:
        print(' '.join(ligne))
Affichage
chemin, _ = chemin_optimal(graphe, entree, sortie)
print_grotte(afficher_chemin(grotte, chemin))
# →
# E * * # . . .
# . # * # . # .
# . # * * # T
# . . # # * . .
# # . . . # * S
# ...
Q16

Écrire une fonction rapport_exploration(grotte, graphe) qui retourne un dictionnaire complet synthétisant l'exploration : statistiques des salles, chemin court (BFS), chemin optimal (Dijkstra), chemin sûr, points d'articulation, et itinéraire de collecte des trésors.

Rapport complet

Rapport d'exploration
Entrée
rapport_exploration(grotte, graphe)
Sortie (extrait)
{
  'nb_salles_totales'    : 38,
  'nb_salles_accessibles': 30,
  'nb_tresors'           : 1,
  'nb_salles_dangereuses': 1,
  'entree'               : (0, 0),
  'sortie'               : (4, 6),
  'chemin_court'         : [(0,0), ..., (4,6)],
  'longueur_chemin_court': 9,
  'chemin_optimal'       : [(0,0), ..., (4,6)],
  'cout_chemin_optimal'  : 10,
  'chemin_sans_danger'   : [(0,0), ..., (4,6)],
  'cout_sans_danger'     : 13,
  'points_articulation'  : [(2,4), (3,4)],
  'tresors_collectes'    : [(2,6)],
  'itineraire_complet'   : [(0,0), ..., (2,6), ..., (4,6)],
  'cout_itineraire'      : 18,
}
Solution Q16 — Rapport complet
def rapport_exploration(grotte, graphe):
    N, M = len(grotte), len(grotte[0])
    entree = localiser(grotte, 'E')[0]
    sortie = localiser(grotte, 'S')[0]
    tresors    = localiser(grotte, 'T')
    dangereuses = localiser(grotte, 'D')
    # Salles totales (non '#')
    nb_totales = sum(
        1 for l in range(N) for c in range(M)
        if grotte[l][c] != '#'
    )
    # Salles accessibles depuis l'entrée
    accessibles = salles_accessibles(graphe, entree)
    # Chemins
    chemin_court   = chemin_court_bfs(graphe, entree, sortie)
    ch_opt, c_opt  = chemin_optimal(graphe, entree, sortie)
    ch_sur, c_sur  = chemin_sans_danger(graphe, grotte, entree, sortie)
    # Points d'articulation
    pa = zones_coupees(graphe, grotte)
    # Itinéraire complet (avec trésors)
    itin, c_itin = collecter_tresors(graphe, grotte, entree, sortie)
    return {
        'nb_salles_totales'    : nb_totales,
        'nb_salles_accessibles': len(accessibles),
        'nb_tresors'           : len(tresors),
        'nb_salles_dangereuses': len(dangereuses),
        'entree'               : entree,
        'sortie'               : sortie,
        'chemin_court'         : chemin_court,
        'longueur_chemin_court': len(chemin_court) - 1 if chemin_court else -1,
        'chemin_optimal'       : ch_opt,
        'cout_chemin_optimal'  : c_opt,
        'chemin_sans_danger'   : ch_sur,
        'cout_sans_danger'     : c_sur,
        'points_articulation'  : pa,
        'tresors_collectes'    : tresors,
        'itineraire_complet'   : itin,
        'cout_itineraire'      : c_itin,
    }
Bilan des algorithmes utilisés
rapport = rapport_exploration(grotte, graphe)
# Toutes les fonctions des parties 0-3 sont intégrées
Récapitulatif des algorithmes
Question Algorithme Complexité Usage
Q5 BFS O(S + T) Distance minimale en tunnels
Q6 DFS O(S + T) Exploration exhaustive
Q8 BFS + reconstruction O(S + T) Chemin le plus court
Q9 Dijkstra (heapq) O((S+T) log S) Chemin de coût minimal
Q12 Force brute (permutations) O(T! × Dijkstra) Collecte de trésors
Q13 DFS (low-link) O(S + T) Points d'articulation
Sortie
// La sortie apparaîtra ici…
Prêt · Ctrl+Entrée pour exécuter

Discussion (0)

Soyez le premier à laisser un commentaire !

Laisser un commentaire

Votre commentaire sera visible après modération.