Files
TermNSI/Programmation_Dynamique/Corrige_TP_Donjon.md

6.0 KiB
Raw Blame History

Corrigé — TP Le Donjon du Dragon

Partie 1 — Comprendre le problème

Question 1. Chemins de (0,0) à (2,3) (grille 3×4) en allant uniquement droite/bas : Il faut faire exactement 3 pas vers la droite et 2 pas vers le bas, soit C(5,2) = 10 chemins.

Question 2. Le chemin optimal est (0,0)→(1,0)→(1,1)→(2,1)→(2,2)→(2,3) : 3+5+9+2+8-5 = 22 d'or.

Chemin Or total
Tout droite puis tout bas 3-1+4+1+6-5 = 8
Bas, droite×3, bas 3+5+9-2+6-5 = 16
Bas×2, droite×3 3+5-3+2+8-5 = 10
Bas, droite, bas, droite×2 3+5+9+2+8-5 = 22 ← optimal

Question 3. Pour une grille n×m, le nombre de chemins est C(n+m-2, n-1) (combinaison : choisir quand descendre parmi tous les pas).

Partie 2 — Formulation récursive

Question 4. Cas de base :

  • donjon(i, 0) : on ne peut aller que vers le bas → somme de la colonne 0 jusqu'à i
  • donjon(0, j) : on ne peut aller que vers la droite → somme de la ligne 0 jusqu'à j

Question 5. Relation générale :

donjon(i, j) = grille[i][j] + max(donjon(i-1, j), donjon(i, j-1))

(On arrive en (i,j) depuis le haut ou depuis la gauche, on choisit le meilleur.)

Question 6. Version naïve :

def donjon_naif(grille, i, j):
    if i == 0 and j == 0:
        return grille[0][0]
    if i == 0:
        return grille[0][j] + donjon_naif(grille, 0, j - 1)
    if j == 0:
        return grille[i][0] + donjon_naif(grille, i - 1, 0)
    return grille[i][j] + max(donjon_naif(grille, i - 1, j),
                               donjon_naif(grille, i, j - 1))

grille = [
    [ 3, -1,  4,  1],
    [ 5,  9, -2,  6],
    [-3,  2,  8, -5]
]
n, m = len(grille), len(grille[0])
print(donjon_naif(grille, n - 1, m - 1))   # 22

Partie 3 — Version Top-Down (mémoïsation)

Question 7. Oui, beaucoup de sous-problèmes sont recalculés. Par exemple, donjon(1,1) est appelé depuis donjon(2,1) et depuis donjon(1,2).

compteur = 0

def donjon_naif_compte(grille, i, j):
    global compteur
    compteur += 1
    if i == 0 and j == 0:
        return grille[0][0]
    if i == 0:
        return grille[0][j] + donjon_naif_compte(grille, 0, j - 1)
    if j == 0:
        return grille[i][0] + donjon_naif_compte(grille, i - 1, 0)
    return grille[i][j] + max(donjon_naif_compte(grille, i - 1, j),
                               donjon_naif_compte(grille, i, j - 1))

donjon_naif_compte(grille, 2, 3)
print(f"Nombre d'appels : {compteur}")   # bien plus que les 12 cases de la grille

Question 8. Version mémoïsation :

def donjon_memo(grille):
    n = len(grille)
    m = len(grille[0])
    memo = {}

    def donjon(i, j):
        if (i, j) in memo:
            return memo[(i, j)]
        if i == 0 and j == 0:
            resultat = grille[0][0]
        elif i == 0:
            resultat = grille[0][j] + donjon(0, j - 1)
        elif j == 0:
            resultat = grille[i][0] + donjon(i - 1, 0)
        else:
            resultat = grille[i][j] + max(donjon(i - 1, j), donjon(i, j - 1))
        memo[(i, j)] = resultat
        return resultat

    return donjon(n - 1, m - 1)

print(donjon_memo(grille))   # 22

Question 9. Les deux versions donnent 22 ✓

Partie 4 — Version Bottom-Up (tableau)

Question 10. Tableau t[i][j] = or max depuis (0,0) jusqu'à (i,j) :

grille :              tableau :
[ 3, -1,  4,  1]     [ 3,  2,  6,  7]
[ 5,  9, -2,  6]     [ 8, 17, 15, 21]
[-3,  2,  8, -5]     [ 5, 19, 27, 22]

Question 11. Implémentation :

def donjon_bottom_up(grille):
    n = len(grille)
    m = len(grille[0])
    tableau = [[0] * m for _ in range(n)]

    tableau[0][0] = grille[0][0]

    for j in range(1, m):                          # première ligne
        tableau[0][j] = tableau[0][j - 1] + grille[0][j]

    for i in range(1, n):                          # première colonne
        tableau[i][0] = tableau[i - 1][0] + grille[i][0]

    for i in range(1, n):                          # reste du tableau
        for j in range(1, m):
            tableau[i][j] = grille[i][j] + max(tableau[i - 1][j],
                                                tableau[i][j - 1])

    return tableau[n - 1][m - 1]

print(donjon_bottom_up(grille))   # 22

Question 12. Les trois versions donnent 22 ✓

Partie 5 — Bonus : retrouver le chemin optimal

Question 13. Reconstruction du chemin :

def donjon_chemin(grille):
    n = len(grille)
    m = len(grille[0])
    tableau = [[0] * m for _ in range(n)]

    tableau[0][0] = grille[0][0]
    for j in range(1, m):
        tableau[0][j] = tableau[0][j - 1] + grille[0][j]
    for i in range(1, n):
        tableau[i][0] = tableau[i - 1][0] + grille[i][0]
    for i in range(1, n):
        for j in range(1, m):
            tableau[i][j] = grille[i][j] + max(tableau[i - 1][j],
                                                tableau[i][j - 1])

    # Reconstruction : on remonte depuis (n-1, m-1)
    chemin = []
    i, j = n - 1, m - 1
    while i > 0 or j > 0:
        chemin.append((i, j))
        if i == 0:
            j -= 1
        elif j == 0:
            i -= 1
        elif tableau[i - 1][j] > tableau[i][j - 1]:
            i -= 1
        else:
            j -= 1
    chemin.append((0, 0))
    chemin.reverse()

    return tableau[n - 1][m - 1], chemin


valeur, chemin = donjon_chemin(grille)
print(f"Or maximal : {valeur}")       # 22
print(f"Chemin optimal : {chemin}")   # [(0,0), (1,0), (1,1), (2,1), (2,2), (2,3)]

Auteur : Florian Mathieu

Licence CC BY NC

Licence Creative Commons
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.