From b17571761d96a4373cb9e1d51f385db811147d06 Mon Sep 17 00:00:00 2001 From: Quentin Fortier Date: Fri, 10 Nov 2023 16:23:24 +0000 Subject: [PATCH 1/2] Add tp_prog_dyn.ipynb --- .../dl/algo/prog_dyn/tp/tp1/tp_prog_dyn.ipynb | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) diff --git a/files/dl/algo/prog_dyn/tp/tp1/tp_prog_dyn.ipynb b/files/dl/algo/prog_dyn/tp/tp1/tp_prog_dyn.ipynb index cbf7d41e..6ffca0fe 100644 --- a/files/dl/algo/prog_dyn/tp/tp1/tp_prog_dyn.ipynb +++ b/files/dl/algo/prog_dyn/tp/tp1/tp_prog_dyn.ipynb @@ -25,6 +25,18 @@ "**Question** : Que peut-on prendre comme cas de base ?" ] }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "cor" + ] + }, + "source": [ + "$$\\binom{0}{k} = 0$$\n", + "$$\\binom{n}{0} = 1, \\text{si } n \\neq 0$$" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -32,6 +44,24 @@ "**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." ] }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def binom_rec(n, k): # voir cours\n", + " if k == 0:\n", + " return 1\n", + " if n == 0:\n", + " return 0\n", + " return binom_rec(n - 1, k - 1) + binom_rec(n - 1, k)" + ] + }, { "cell_type": "code", "execution_count": 2, @@ -76,6 +106,27 @@ " return ..." ] }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def binom_dp(n, k):\n", + " M = [[0]*(k + 1) for _ in range(n + 1)]\n", + " for i in range(0, n + 1):\n", + " M[i][0] = 1 # cas de base\n", + "\n", + " for i in range(1, n + 1):\n", + " for j in range(1, k + 1):\n", + " M[i][j] = M[i - 1][j - 1] + M[i - 1][j]\n", + " return M[n][k]" + ] + }, { "cell_type": "code", "execution_count": 5, @@ -117,6 +168,27 @@ " return aux(n, k)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def binom(n, k):\n", + " d = {}\n", + " def aux(i, j):\n", + " if j == 0: return 1\n", + " if i == 0: return 0\n", + " if (i, j) not in d:\n", + " d[(i, j)] = aux(i - 1, j - 1) + aux(i - 1, j)\n", + " return d[(i, j)]\n", + " return aux(n, k)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -144,6 +216,24 @@ "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(..., ...)$." ] }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "cor" + ] + }, + "source": [ + "Si $a_k$ est utilisée : il faut encore rendre $n - a_k$ euros avec les pièces $a_1$, ..., $a_k$ (on a le droit d'utiliser plusieurs fois $a_k$), d'où $r(n, k) = r(n - a_k, k) + 1$.\n", + "\n", + "Dans le cas général, on considère les deux possibilités et on conserve le minimum : \n", + "$$\n", + " r(n, k) = min(r(n, k - 1), r(n - a_k, k) + 1)\n", + "$$\n", + "\n", + "Remarque : on ne peut utiliser $a_k$ pour rendre $n$ euros que si $n \\geq a_k$. Si $n < a_k$, on a donc $r(n, k) = r(n, k - 1)$." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -152,6 +242,29 @@ "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`." ] }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def rendu(L, n):\n", + " k = len(L) # nombre de pièces\n", + " M = [[0]*(k + 1) for _ in range(n + 1)]\n", + " for i in range(1, n + 1):\n", + " M[i][0] = float(\"inf\")\n", + " for j in range(1, k + 1):\n", + " if i - L[j - 1] >= 0:\n", + " M[i][j] = min(M[i][j - 1], 1 + M[i - L[j - 1]][j])\n", + " else:\n", + " M[i][j] = M[i][j - 1]\n", + " return M[-1][-1]" + ] + }, { "cell_type": "code", "execution_count": 8, @@ -179,6 +292,46 @@ "**Question** : Réécrire la fonction précédente par mémoïsation plutôt que par programmation dynamique." ] }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def rendu_memo(L, n):\n", + " k = len(L)\n", + " d = {}\n", + " def aux(i, j):\n", + " if (i, j) in d:\n", + " return d[(i, j)]\n", + " if i == 0:\n", + " return 0\n", + " if j == 0:\n", + " return float(\"inf\")\n", + " if i - L[j - 1] >= 0:\n", + " d[(i, j)] = min(aux(i, j - 1), 1 + aux(i - L[j - 1], j))\n", + " else:\n", + " d[(i, j)] = aux(i, j - 1)\n", + " return d[(i, j)]\n", + " return aux(n, k)\n", + "rendu_memo([1, 2, 5], 7)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -207,6 +360,19 @@ "**Question** : Définir `M` en Python." ] }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "M = [[1, 0, 0, 0], [0, 0, 1, 1], [0, 1, 1, 1], [0, 1, 0, 1]]" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -221,6 +387,24 @@ "**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." ] }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def est_carre(M, x, y, k):\n", + " for i in range(x, x + k):\n", + " for j in range(y, y + k):\n", + " if M[i][j] != 1:\n", + " return False\n", + " return True" + ] + }, { "cell_type": "code", "execution_count": 11, @@ -237,6 +421,25 @@ "**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." ] }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def contient_carre(M, k):\n", + " n = len(M)\n", + " for i in range(n - k + 1):\n", + " for j in range(n - k + 1):\n", + " if est_carre(M, i, j, k):\n", + " return True\n", + " return False" + ] + }, { "cell_type": "code", "execution_count": 12, @@ -253,6 +456,24 @@ "**Question** : Écrire une fonction `max_carre1` telle que `max_carre1(m)` renvoie la taille maximum d'un carré de 1 contenu dans `m`." ] }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def max_carre1(M):\n", + " n = len(M)\n", + " for k in range(n, 0, -1):\n", + " if contient_carre(M, k):\n", + " return k\n", + " return 0" + ] + }, { "cell_type": "code", "execution_count": 14, @@ -280,6 +501,19 @@ "**Question** : Quelle est la complexité de `max_carre1(m)` dans le pire cas ?" ] }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "cor" + ] + }, + "source": [ + "- `est_carre(M, x, y, k)` est en $O(k^2)$. \n", + "- `contient_carre(M, k)` appelle O($n$) fois `est_carre`, donc est en $O(n^2 k^2)$. \n", + "- `max_carre1(M)` appelle `contient_carre` pour $k = 1, 2, ..., n$, donc est de complexité $\\sum_{k=1}^n O(n^2 k^2) = O(n^3 \\sum_{k=1}^n k^2)$. Comme $\\sum_{k=1}^n k^2 = \\frac{n(n+1)(2n+1)}{6} = O(n^3)$, la complexité totale est $\\boxed{O(n^6)}$.`" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -295,6 +529,19 @@ "**Question** : Que vaut `c[0][y]` et `c[x][0]` ?" ] }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "cor" + ] + }, + "source": [ + "`c[0][y] = 0` si `m[0][y] = 0` et `c[0][y] = 1` sinon. \n", + "De même pour `c[x][0]`. \n", + "Remarque : `c[0][y]` et `c[x][0]` sont donc les mêmes valeurs que `m[0][y]` et `m[x][0]`, on peut donc initialiser `c` comme une copie de `m`." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -302,6 +549,17 @@ "**Question** : Que vaut `c[x][y]` si `m[x][y] = 0` ?" ] }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "cor" + ] + }, + "source": [ + "`c[x][y] = 0`." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -316,6 +574,25 @@ "**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é." ] }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "tags": [ + "cor" + ] + }, + "outputs": [], + "source": [ + "def max_carre2(m):\n", + " c = m.copy()\n", + " for i in range(len(m)):\n", + " for j in range(len(m[0])):\n", + " if m[i][j] == 1:\n", + " c[i][j] = 1 + min(c[i - 1][j], c[i][j - 1], c[i - 1][j - 1])\n", + " return max(max(l) for l in c)" + ] + }, { "cell_type": "code", "execution_count": 16, @@ -343,6 +620,18 @@ "**Question** : Quelle est la complexité de `max_carre2(m)`, en fonction des dimensions de `m`? Comparer avec `max_carre1(m)`." ] }, + { + "cell_type": "markdown", + "metadata": { + "tags": [ + "cor" + ] + }, + "source": [ + "`max_carre2(m)` est en $\\boxed{O(n^2)}$ à cause des deux boucles `for` imbriquées. \n", + "C'est donc beaucoup mieux que `max_carre1(m)` qui est en $O(n^6)$." + ] + }, { "cell_type": "markdown", "metadata": {}, From 98a25a68d7a70996190673bbf5d24284ded62ba0 Mon Sep 17 00:00:00 2001 From: Quentin Fortier Date: Fri, 10 Nov 2023 16:23:26 +0000 Subject: [PATCH 2/2] Add tp_sac_dos.ipynb --- .../dl/algo/prog_dyn/tp/tp2/tp_sac_dos.ipynb | 308 ------------------ 1 file changed, 308 deletions(-) diff --git a/files/dl/algo/prog_dyn/tp/tp2/tp_sac_dos.ipynb b/files/dl/algo/prog_dyn/tp/tp2/tp_sac_dos.ipynb index 4354f185..20d0fe69 100644 --- a/files/dl/algo/prog_dyn/tp/tp2/tp_sac_dos.ipynb +++ b/files/dl/algo/prog_dyn/tp/tp2/tp_sac_dos.ipynb @@ -35,31 +35,6 @@ "Tester avec l'exemple ci-dessous. Le résultat est-il optimal ?" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [], - "source": [ - "def glouton(c, w, v):\n", - " \"\"\"Renvoie la valeur maximum qu'on peut obtenir avec les objets\n", - " c: capacité du sac\n", - " w: poids des objets\n", - " v: valeur des objets\n", - " \"\"\"\n", - " poids = 0\n", - " valeur = 0\n", - " for i in range(len(w)):\n", - " if poids + w[i] <= c:\n", - " poids += w[i]\n", - " valeur += v[i]\n", - " return valeur" - ] - }, { "cell_type": "code", "execution_count": 2, @@ -94,23 +69,6 @@ "**Question** : Écrire une fonction `combine(L1, L2)` qui renvoie la liste des couples `(L1[i], L2[i])`. On suppose que `L1` et `L2` ont la même longueur." ] }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [], - "source": [ - "def combine(L1, L2):\n", - " L = []\n", - " for i in range(len(L1)):\n", - " L.append((L1[i], L2[i]))\n", - " return L" - ] - }, { "cell_type": "code", "execution_count": 4, @@ -138,25 +96,6 @@ "**Question** : Écrire une fonction `split(L)` telle que si `L` est une liste de couples, `split(L)` renvoie deux listes `L1` et `L2` où `L1` contient les premiers éléments des couples de `L` et `L2` les seconds éléments des couples de `L`." ] }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [], - "source": [ - "def split(L):\n", - " L1 = []\n", - " L2 = []\n", - " for i in range(len(L)):\n", - " L1.append(L[i][0])\n", - " L2.append(L[i][1])\n", - " return L1, L2" - ] - }, { "cell_type": "code", "execution_count": 6, @@ -214,22 +153,6 @@ "**Question** : Écrire une fonction `tri_poids(w, v)` qui renvoie les listes `w2` et `v2` obtenues à partir de `w` et `v` en triant les poids par ordre croissant. On pourra utiliser `L.sort`, `combine` et `split`." ] }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [], - "source": [ - "def tri_poids(w, v):\n", - " L = combine(w, v)\n", - " L.sort()\n", - " return split(L)" - ] - }, { "cell_type": "code", "execution_count": 9, @@ -266,21 +189,6 @@ "Est-ce que cet algorithme est toujours optimal ?" ] }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [], - "source": [ - "def glouton_poids(c, w, v):\n", - " w, v = tri_poids(w, v)\n", - " return glouton(c, w, v)" - ] - }, { "cell_type": "code", "execution_count": 11, @@ -308,27 +216,6 @@ "**Question** : Écrire de même des fonctions `tri_valeur(w, v)` et `glouton_valeur(c, w, v)` qui renvoie la valeur totale des objets choisis par l'algorithme glouton, en considérant les objets dans l'ordre de valeur décroissante (en utilisant `L.sort(reverse=True)`). Est-ce que cet algorithme est toujours optimal ?" ] }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [], - "source": [ - "def tri_valeur(w, v):\n", - " L = combine(v, w)\n", - " L.sort(reverse=True)\n", - " L1, L2 = split(L)\n", - " return L2, L1\n", - "\n", - "def glouton_valeur(c, w, v):\n", - " w, v = tri_valeur(w, v)\n", - " return glouton(c, w, v)" - ] - }, { "cell_type": "code", "execution_count": 13, @@ -356,28 +243,6 @@ "**Question** : De même, écrire une fonction `glouton_ratio(c, w, v)` qui renvoie la valeur totale des objets choisis par l'algorithme glouton, en considérant les objets dans l'ordre de ratio valeur/poids décroissant. On pourra utiliser deux fois `combine`." ] }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [], - "source": [ - "def tri_ratio(v, w):\n", - " L = combine(v, w)\n", - " L = combine([v[i]/w[i] for i in range(len(v))], L)\n", - "\n", - " L.sort(reverse=True)\n", - " return split(split(L)[1])\n", - "\n", - "def glouton_ratio(c, w, v):\n", - " v, w = tri_ratio(v, w)\n", - " return glouton(c, w, v)" - ] - }, { "cell_type": "code", "execution_count": 15, @@ -418,17 +283,6 @@ "**Question** : Que vaut $dp[i][0]$ ?" ] }, - { - "cell_type": "markdown", - "metadata": { - "tags": [ - "cor" - ] - }, - "source": [ - "$dp[i][0] = 0$ : on ne peut pas mettre d'objet dans un sac de capacité $0$." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -436,17 +290,6 @@ "**Question** : Exprimer $dp[i][j]$ en fonction de $dp[i][j-1]$ dans le cas où $w_j > i$." ] }, - { - "cell_type": "markdown", - "metadata": { - "tags": [ - "cor" - ] - }, - "source": [ - "$dp[i][j] = dp[i][j-1]$ : on ne peut pas mettre l'objet $j$ dans le sac de capacité $i$." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -454,17 +297,6 @@ "**Question** : Supposons $w_j \\leq i$. Donner une formule de récurrence sur $dp[i][j]$, en distinguant le cas où l'objet $j$ est choisi et le cas où il ne l'est pas." ] }, - { - "cell_type": "markdown", - "metadata": { - "tags": [ - "cor" - ] - }, - "source": [ - "$$dp[i][j] = \\max(\\underbrace{dp[i][j - 1]}_{\\text{sans prendre } o_j}, \\underbrace{dp[i - w_j][j - 1] + v_j}_{\\substack{\\text{en prenant } o_j}, \\text{si }i - w_j \\geq 0})$$" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -472,28 +304,6 @@ "**Question** : En déduire une fonction `prog_dyn(c, w, v)` qui renvoie la valeur maximum que l'on peut mettre dans un sac de capacité $c$, en ne considérant que les $j$ premiers objets, en remplissant une matrice `dp` de taille $(c+1) \\times (n+1)$." ] }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [], - "source": [ - "def prog_dyn(c, w, v):\n", - " n = len(w)\n", - " dp = [[0 for j in range(c+1)] for i in range(n+1)]\n", - " for i in range(1, n+1):\n", - " for j in range(1, c+1):\n", - " if j < w[i-1]:\n", - " dp[i][j] = dp[i-1][j]\n", - " else:\n", - " dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i-1]] + v[i-1])\n", - " return dp[n][c]" - ] - }, { "cell_type": "code", "execution_count": 17, @@ -529,25 +339,6 @@ "On importera `random` pour utiliser `random.randint(a, b)` qui génère un entier aléatoire entre $a$ et $b$ inclus." ] }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [], - "source": [ - "import random\n", - "\n", - "def genere_instance():\n", - " c = random.randint(1, 1000)\n", - " w = [random.randint(1, 100) for i in range(100)]\n", - " v = [random.randint(1, 100) for i in range(100)]\n", - " return c, w, v" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -556,38 +347,6 @@ "Quelle stratégie gloutonne est la plus efficace ?" ] }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Glouton poids : 0.836161002885315\n", - "Glouton valeur : 0.6230748939403394\n", - "Glouton ratio : 0.995678414824772\n" - ] - } - ], - "source": [ - "gp, gv, gr = 0, 0, 0\n", - "for i in range(100):\n", - " c, w, v = genere_instance()\n", - " sol = prog_dyn(c, w, v)\n", - " gp += glouton_poids(c, w, v)/sol\n", - " gv += glouton_valeur(c, w, v)/sol\n", - " gr += glouton_ratio(c, w, v)/sol\n", - "print(f\"Glouton poids : {gp/100}\")\n", - "print(f\"Glouton valeur : {gv/100}\")\n", - "print(f\"Glouton ratio : {gr/100}\")" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -595,40 +354,6 @@ "**Question** : Comparer le temps total d'exécution de la stratégie gloutonne par ratio et de la programmation dynamique, sur 100 instances générées par `genere_instance()`. On pourra importer `time` et utiliser `time.time()` pour obtenir le temps actuel en secondes." ] }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Glouton poids : 0.008910417556762695 s\n", - "Programmation dynamique : 2.4601693153381348 s\n" - ] - } - ], - "source": [ - "import time\n", - "\n", - "t1, t2 = 0, 0\n", - "for i in range(100):\n", - " c, w, v = genere_instance()\n", - " t = time.time()\n", - " glouton_poids(c, w, v)\n", - " t1 += time.time() - t\n", - " t = time.time()\n", - " prog_dyn(c, w, v)\n", - " t2 += time.time() - t\n", - "print(f\"Glouton poids : {t1} s\")\n", - "print(f\"Programmation dynamique : {t2} s\")" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -646,39 +371,6 @@ "On peut donc construire la liste des objets choisis en remontant la matrice `dp` à partir de la case $(c, n)$." ] }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": { - "tags": [ - "cor" - ] - }, - "outputs": [], - "source": [ - "def prog_dyn(c, w, v):\n", - " n = len(w)\n", - " dp = [[0 for j in range(c+1)] for i in range(n+1)]\n", - " for i in range(1, n+1):\n", - " for j in range(1, c+1):\n", - " if j < w[i-1]:\n", - " dp[i][j] = dp[i-1][j]\n", - " else:\n", - " dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i-1]] + v[i-1])\n", - "\n", - " # reconstruction de la solution\n", - " i, j = n, c\n", - " sol = []\n", - " while i > 0 and j > 0:\n", - " if dp[i][j] == dp[i-1][j]:\n", - " i -= 1\n", - " else:\n", - " sol.append(i-1)\n", - " j -= w[i-1]\n", - " i -= 1\n", - " return sol" - ] - }, { "cell_type": "code", "execution_count": 22,