TP 1 : Programmation dynamique#

Coefficient binomial#

On souhaite calculer \(\binom{n}{k}\) par programmation dynamique, en utilisant la formule de Pascal :

\[\binom{n}{k} = \binom{n - 1}{k - 1} + \binom{n - 1}{k}\]

Question

Que peut-on prendre comme cas de base ?

Question

Écrire une fonction récursive binom_rec(n, k) renvoyant \(\binom{n}{k}\) à partir de la formule ci-dessus. Expliquer pourquoi la complexité de cette fonction est très mauvaise.

binom_rec(20, 4)
4845

Question

Écrire une fonction binom_dp(n, k) renvoyant \(\binom{n}{k}\) en utilisant la même formule, mais par programmation dynamique.
Pour cela, on pourra stocker \(\binom{n}{k}\) dans une matrice (ou : un dictionnaire) et la remplir par \(n\) croissant et par \(k\) croissant.

def binom_dp(n, k):
    # définir une matrice M de taille (n+1)x(k+1)
    # M[i][j] contiendra j parmi i
    for i in range(0, n + 1):
        M[i][0] = ... # cas de base
        for j in range(1, k + 1):
            M[i][j] = ... # récurrence
    return ...
binom_dp(20, 4)
4845

Question

Écrire une fonction binom_memo(n, k) renvoyant \(\binom{n}{k}\) en utilisant le même principe, mais avec mémoïsation plutôt que programmation dynamique.

def binom_memo(n, k):
    d = {} # d[(i, j)] contiendra j parmi i
    def aux(i, j): # renvoie j parmi i
        ... # cas de base
        ... # dans le cas général, regarder si (i, j) est dans d : si oui, renvoyer la valeur associée, sinon la calculer et l'ajouter à d
    return aux(n, k)

Rendu de monnaie#

Étant donnée une liste L d’entiers \(a_1,\ldots,a_k\) (des pièces), on veut calculer le nombre minimum \(r(n, k)\) de pièces parmi \(a_1, ..., a_k\) dont la somme vaut \(n\).

Par exemple, si \(k = 3\) et \(a_1 = 1, a_2 = 2, a_3 = 5\) alors \(r(7, 3) = 2\) (car \(7 = 2 + 5\) et c’est la façon de rendre \(7\)€ qui utilise le moins de pièces).

Remarques :

  • On peut utiliser plusieurs fois la même pièce.

  • \(r(0, k)\) revient à rendre \(0\)€, ce qu’on peut faire avec \(0\) pièce : \(r(0, k) = 0\)

  • \(r(n, 0)\) revient à n’utiliser aucune pièce, ce qui est impossible si \(n \neq 0\) : on posera \(r(n, 0)\) = \(\infty\) (float("inf") en Python).

Question

Écrire une relation de récurrence sur \(r(n, k)\). On pourra distinguer deux cas pour rendre \(n\) euros avec les picèes \(a_1\), …, \(a_k\) :

  • soit \(a_k\) n’est pas utilisée (et on a donc \(r(n, k) = r(n, k - 1)\))

  • soit \(a_k\) est utilisée (et on a \(r(n, k) = ...\)).

Comme on ne sait pas si \(a_k\) est utilisée ou non, on a dans le cas général : \(r(n, k) = \min(..., ...)\).

Question

En déduire une fonction rendu(L, n) par programmation dynamique renvoyant le nombre minimum de pièces requises pour rendre n euros, où L est la liste des pièces.
On remplira une matrice M pour que M[i][j] contienne le nombre minimum de pièces pour rendre i euros en utilisant les j premières pièces de L.

rendu([1, 2, 5], 7)
2

Question

Réécrire la fonction précédente par mémoïsation plutôt que par programmation dynamique.

Plus grand carré dans une matrice#

Étant donnée une matrice carrée remplie de 0 ou 1, on souhaite connaître la taille du plus gros carré de 1 dans cette matrice.
Par exemple, ce nombre est 2 pour la matrice \(M\) suivante (correspondant au carré en pointillé) :

La case de coordonnés \((x, y)\) est celle sur la ligne \(x\), colonne \(y\). La case de coordonnées (0, 0) est celle en haut à gauche.
On supposera que les indices en arguments des fonctions ne dépassent pas des tableaux ou matrices correspondants.

Question

Définir M en Python.

Méthode naïve#

Question

Écrire une fonction est_carre telle que est_carre(m, x, y, k) détermine si la sous-matrice de m de taille \(k \times k\) et dont la case en haut à gauche a pour coordonnées (x, y) ne possède que des 1.

assert(est_carre(M, 1, 2, 2) and not est_carre(M, 1, 1, 2))

Question

Écrire une fonction contient_carre telle que contient_carre(m, k) renvoie true si m contient un carré de 1 de taille \(k\), false sinon.

assert(contient_carre(M, 2) and not contient_carre(M, 3))

Question

Écrire une fonction max_carre1 telle que max_carre1(m) renvoie la taille maximum d’un carré de 1 contenu dans m.

max_carre1(M)
2

Question

Quelle est la complexité de max_carre1(m) dans le pire cas ?

On va construire une matrice c telle que c[x][y] est la taille maximum d’un carré de 1 dans m dont la case en bas à droite est m[x][y] (c’est à dire un carré de 1 qui contient m[x][y] mais aucun m[i][j] si \(i > x\) ou \(j > y\)).
Par exemple, c[1][2] = 1 et c[2][3] = 2 pour la matrice \(M\) ci-dessus.

Question

Que vaut c[0][y] et c[x][0] ?

Question

Que vaut c[x][y] si m[x][y] = 0 ?

Question

Montrer que, si m[x][y] = 1, c[x][y] = 1 + min(c[x-1][y], c[x][y-1], c[x-1][y-1]).

Question

En déduire une fonction max_carre2 telle que max_carre2(m) renvoie la taille maximum d’un carré de 1 contenu dans m, ainsi que les coordonnées de la case en haut à gauche d’un tel carré.

max_carre2(M)
2

Question

Quelle est la complexité de max_carre2(m), en fonction des dimensions de m? Comparer avec max_carre1(m).

Pour ceux qui ont fini#

Cette partie n’est pas à rendre, sauf si vous en avez le temps.

Question

S’inscrire sur https://projecteuler.net/ et résoudre ce problème.
On pourra télécharger le fichier triangle.txt demandé avec :

import urllib.request
f = urllib.request.urlopen("https://projecteuler.net/project/resources/p067_triangle.txt")
lignes = list(map(lambda x : list(map(int, x.split())), f.readlines())) # renvoie la liste des lignes du fichier