commit f407e96641c7839d6c5db5cc6bed44fef91d6894 Author: Bertrand Benjamin Date: Thu Jun 24 08:13:51 2021 +0200 Feat: import du travail de Cédric diff --git a/1.png b/1.png new file mode 100644 index 0000000..280e627 Binary files /dev/null and b/1.png differ diff --git a/2.png b/2.png new file mode 100644 index 0000000..bffe6df Binary files /dev/null and b/2.png differ diff --git a/3.png b/3.png new file mode 100644 index 0000000..4b61348 Binary files /dev/null and b/3.png differ diff --git a/4.png b/4.png new file mode 100644 index 0000000..be74b78 Binary files /dev/null and b/4.png differ diff --git a/FOAD-bloc5-corrige.py b/FOAD-bloc5-corrige.py new file mode 100644 index 0000000..8447324 --- /dev/null +++ b/FOAD-bloc5-corrige.py @@ -0,0 +1,195 @@ +import matplotlib.pyplot as plt +from random import randint +from time import time + + +### Images de "tests" #################################################################################### + +t1 = [ + [0, 0, 0, 1, 0], + [0, 0, 0, 0, 1], + [1, 0, 0, 0, 0], + [0, 0, 1, 1, 0], + [0, 1, 0, 0, 1], +] + +t2 = [ + [0, 0, 0, 1, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 1, 0], + [1, 1, 1, 1, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 1, 0], + [0, 0, 0, 1, 0, 1, 1, 1, 1, 1], + [0, 0, 0, 1, 0, 0, 1, 0, 1, 0], + [0, 0, 0, 1, 0, 0, 1, 0, 1, 0], +] + +########################################################################################################## + + +def aff(t): + plt.imshow(t, cmap="binary") + # plt.xticks( [i for i in range(len(t[0]))] ) + # plt.yticks( [i for i in range(len(t))] ) + plt.show() + + +def marque_carre(t, lig, col, taille): + for y in range(taille): + for x in range(taille): + t[lig - y][col - x] = 0.25 + t[lig][col] = 0.30 + + +def genere_tab_alea(n, ratio_noir): + t = [] + for lig in range(n): + t.append([]) + for col in range(n): + alea = randint(0, 100) + (t[lig]).append(1 if alea <= ratio_noir else 0) + return t + + +def PlusGrandCarreBlanc_Rec(t, lig, col): + global compteur_naif + compteur_naif += 1 + if t[lig][col] == 1: + return 0 + elif (lig == 0) or (col == 0): + return 1 + else: + # Petit truc pour éviter de calculer toutes les valeurs dans le min si une des trois est déjà à 0 (au min) + h = PlusGrandCarreBlanc_Rec(t, lig - 1, col) + if h == 0: + return 1 + g = PlusGrandCarreBlanc_Rec(t, lig, col - 1) + if g == 0: + return 1 + gh = PlusGrandCarreBlanc_Rec(t, lig - 1, col - 1) + if gh == 0: + return 1 + return min(g, h, gh) + 1 + # return min(PlusGrandCarreBlanc_Rec(t, lig - 1, col), PlusGrandCarreBlanc_Rec(t, lig - 1, col - 1), PlusGrandCarreBlanc_Rec(t, lig, col - 1)) + 1 + + +def PlusGrandCarreBlanc_memo(t, lig, col): + global compteur_memo + compteur_memo += 1 + if t_memo[lig][col] != -1: + return t_memo[lig][col] + if t[lig][col] == 1: + t_memo[lig][col] = 0 + return 0 + elif (lig == 0) or (col == 0): + t_memo[lig][col] = 1 + return 1 + else: + res = ( + min( + PlusGrandCarreBlanc_memo(t, lig - 1, col), + PlusGrandCarreBlanc_memo(t, lig, col - 1), + PlusGrandCarreBlanc_memo(t, lig - 1, col - 1), + ) + + 1 + ) + t_memo[lig][col] = res + return res + + +def RechercheCarreBlanc_naif(t): + l, c = 0, 0 + tmax = 0 + nb_lig, nb_col = len(t), len(t[0]) + for lig in range(nb_lig): # On parcourt les lignes + for col in range(nb_col): # on parcourt les colonnes + taille = PlusGrandCarreBlanc_Rec( + t, lig, col + ) # (le tableau est carré donc toutes les lignes ont la même longueur) + if taille >= tmax: # Si taille est > à tmax + l, c = lig, col # on a lig,col qui sont les coordonnées voulues + tmax = taille # on met à jour le tmax + return l, c, tmax + + +def RechercheCarreBlanc_memo(t): + l, c, tmax = 0, 0, 0 + nb_lig, nb_col = len(t), len(t[0]) + for lig in range(nb_lig): + for col in range(nb_col): + taille = PlusGrandCarreBlanc_memo( + t, lig, col + ) # On utilise PlusGrandCarreBlanc_memo() + if taille >= tmax: + l, c = lig, col + tmax = taille + return l, c, tmax + + +def PlusGrandCarreBlanc_BU(t): + nb_lig, nb_col = len(t), len(t[0]) + res = [[-1 for _ in range(nb_col)] for _ in range(nb_lig)] + for lig in range(nb_lig): + for col in range(nb_col): + if t[lig][col] == 1: # sous-problème de taille "0" + res[lig][col] = 0 + elif lig == 0 or col == 0: # sous-problème de taille "1" + res[lig][col] = 1 + else: + res[lig][col] = 1 + min( + res[lig - 1][col], res[lig][col - 1], res[lig - 1][col - 1] + ) + return res + + +def RechercheCarreBlanc_BU(t): + l, c, tmax = 0, 0, 0 + nb_lig, nb_col = len(t), len(t[0]) + res = PlusGrandCarreBlanc_BU(t) # Avec l'algorithme BOTTOM UP + for lig in range(nb_lig): + for col in range(nb_col): + taille = res[lig][ + col + ] # On utilise le tableau fournit par PlusGrandCarreBlanc_BU() + if taille >= tmax: + l, c = lig, col + tmax = taille + return l, c, tmax + + +############################################# +# Programme principal +############################################# + +compteur_naif = 0 +compteur_memo = 0 +n = 40 +ratio = 50 + +t_memo = [[-1 for i in range(n)] for j in range(n)] +t3 = genere_tab_alea(n, ratio) + +s = time() +res = RechercheCarreBlanc_naif(t3) +e = time() +print("Durée de l'algo naif : ", e - s, "nombre d'appels récursifs : ", compteur_naif) + +s = time() +res = RechercheCarreBlanc_memo(t3) +e = time() +print( + "Durée de l'algo avec memoïsation : ", + e - s, + "nombre d'appels récursifs : ", + compteur_memo, +) + +s = time() +res = RechercheCarreBlanc_BU(t3) +e = time() +print("Durée de l'algo avec BOTTOM UP : ", e - s) + +marque_carre(t3, res[0], res[1], res[2]) +aff(t3) diff --git a/FOAD-bloc5.md b/FOAD-bloc5.md new file mode 100644 index 0000000..dadfe6a --- /dev/null +++ b/FOAD-bloc5.md @@ -0,0 +1,325 @@ +# Recherche du plus grand carré blanc dans une "image" + +## Objectif et déroulement + +On considère le problème suivant : étant donné une image monochrome $n \times n$, déterminer le plus grand carré qui ne contient aucun point noir. + +Le but du TP est d'établir plusieurs algorithmes permettant de trouver le plus grand carré blanc dans une image. + +Déroulement du TP : + +- Conception d'un algorithme récursif naïf : ```RechercheCarreBlanc_naif()``` +- Programmation dynamique - Utilisation de la mémoïsation : ```RechercheCarreBlanc_memoisation()``` +- Programmation dynamique - Optimisation par "Bottom_up" : ```RechercheCarreBlanc_BU()``` + +# Conception d'un algorithme récursif naïf : ```RechercheCarreBlanc_naif()``` + +## Une image c'est quoi ? + +Une image, c'est un tableau de tuples qui représentent les couleurs des pixels. Dans le cadre de ce TP, nous nous limiterons à des images noir et blanc : + +![Image N/B](1.png) + +```python +t=[ + [1,0,0,1,0,0,1,0,0,1], + [0,0,0,0,0,0,1,0,0,0], + ... + [0,0,0,0,1,0,0,0,1,0] +] +``` + +```t[5][1]==1``` car le pixel de la ligne ```5``` et de la colonne ```1``` est noir. (Attention les numéros des lignes/colonnes commencent à 0). + +## Etude du problème + +L'objectif est de trouver le carré blanc le plus grand possible à partir d'un pixel donné (coin inférieur droit). + +- ```PlusGrandCarreBlanc_Rec(lig,col)``` : Retourne la taille du carré blanc à partir du coin inférieur droit (ligne,colonne). + +Par ex : + +- ```PlusGrandCarreBlanc_Rec(1,2) == 2``` +- ```PlusGrandCarreBlanc_Rec(6,4) == 2``` + +![Image N/B](2.png) + +> +> 01. Pour l'image suivante, donner le résultat de : +> +> - ```PlusGrandCarreBlanc_Rec(2,1) == ...``` +> - ```PlusGrandCarreBlanc_Rec(1,2) == ...``` +> - ```PlusGrandCarreBlanc_Rec(1,1) == ...``` +> +>![Image N/B](3.png) +> +> 02. Quelle relation existe-il entre ```PlusGrandCarreBlanc_Rec(2,2)``` et les 3 valeurs précédentes ? +> +> 03. Généraliser ce résultat pour un pixel quelconque : +> +> ```PlusGrandCarreBlanc_Rec(lig,col) = min( ......... , ......... , ......... ) + .........``` +> +> 4. Proposer un algorithme récursif pour ```PlusGrandCarreBlanc_Rec(i,j)``` : +> +> ```python +> def PlusGrandCarreBlanc_Rec(t,lig,col): # t est le tableau représentant l'image noir et blanc +> +> if t(lig, col) == 1: # Si le pixel est noir +> return ... # On retourne ... +> +> # Sinon : +> elif ...... : # Si le pixel est sur la 1ère ligne ou 1ère colonne +> return ... # On retourne ... +> +> else : # Sinon : +> return ... # On retourne 1 + min(PlusGrandCarre des pixels adjacents) +> # Adjacents => à gauche, en haut, en diagonale haut-gauche +> ``` +> +> 05. Tester votre algorithme sur le tableau suivant : +> +> ```python +> t=[ [0,0,0,1,0], +> [0,0,0,0,1], +> [1,0,0,0,0], +> [0,0,1,1,0], +> [0,1,0,0,1] ] +> ``` +> +> ```python +> >>> print(PlusGrandCarreBlanc_Rec(t,2,2)) +> 2 +> ``` +> +> **Remarque :** D'autres tableaux sont présent dans le fichier ```TP.py``` +> +> **Remarque :** Lors de l'évaluation du minimum par ```min()```, l'interpréteur DOIT calculer les 3 valeurs à comparer. Or, nous pouvons lui éviter dans certains cas ce travail (et économiser un nombre d'appels récursifs). En effet, si la première valeur calculée est nulle alors le ```min(... , ... , ...)``` retournera FORCEMENT 0 et la fonction retournera 1. De même pour la seconde et la troisième. +> +> 6. Modifier ```PlusGrandCarreBlanc_Rec(lig,col)``` en tenant compte de la remarque précédente. +> +> ```python +> def PlusGrandCarreBlanc_Rec(t,lig,col): +> ... +> else : +> g = ... # Valeur du pixel de gauche +> if g==0: +> return 1 +> h = ... # Valeur du pixel du haut +> if h==0: +> return 1 +> ... # ... +> return ... # On retourne 1 + min(g,h,...) +> ``` +> +> 7. Ecrire un algorithme simple pour afficher ```t``` à l'aide de ```matplotlib``` et vérifier le bon fonctionnement de votre algorithme. +> +> > **Aide** +> > +> > ```python +> > import matplotlib.pyplot as plt +> > ... +> > plt.imshow(t,cmap='binary') +> > plt.show() +> > ``` +> +> 8. Ecrire un algorithme qui parcourt toute l'image et qui retourne la ligne et la colonne du carré le plus grand ainsi que sa taille. +> +> - ```RechercheCarreBlanc_naif(t)``` : Retourne la ligne et la colonne du coin inférieur droit du carré blanc le plus grand ainsi que sa taille. +> +> ```python +> def RechercheCarreBlanc_naif(t): +> ... +> for lig in range(...): +> for col in range(...): +> ... +> return l,c,tmax +> ``` +> +> 9. Bonus : Faire en sorte d'afficher ce carré lors de l'affichage de l'image. +> +> 10. Ajouter un compteur (variable global) ```compteur_naif``` permetttant de compter le nombre d'appels récursifs +> +> 11. Ecrire une fonction permettant de générer un tableau de $n\times n$ content un ratio de points noir de ```ratio_noir```% +> +> ```python +> def genere_tab_alea(n, ratio_noir): +> ... +> return t +> ``` + +# Programmation dynamique - Utilisation de la mémoïsation : ```RechercheCarreBlanc_memoisation()``` + +Remplacons nous dans le cadre d'un cas simple, on cherche ```PlusGrandCarreBlanc_Rec(2,2)```. + +![Image N/B](3.png) + +> 1. Pendant l'appel de ```PlusGrandCarreBlanc_Rec(2,2)```, quelles sont les 3 appels récursifs qui vont être exécutés ? +> +> - Appel n°1 : ```PlusGrandCarreBlanc_Rec( ... , ... )``` +> - Appel n°2 : ```PlusGrandCarreBlanc_Rec( ... , ... )``` +> - Appel n°3 : ```PlusGrandCarreBlanc_Rec( ... , ... )``` +> +> 2. Compléter le tableau suivant qui liste, pour les appels n°1, n°2 et n°3, les appels récursifs exécutés. +> +> Appel n°1 : ```PGCB_Rec( ... , ... )``` | Appel n°2 : ```PGCB_Rec( ... , ... )``` | Appel n°3 : ```PGCB_Rec( ... , ... )``` +> ---------|----------|--------- +> ```PGCB_Rec( ... , ... )``` | ```PGrCB_Rec( ... , ... )``` | ```PGCB_Rec( ... , ... )``` +> ```PGCB_Rec( ... , ... )``` | ```PGrCB_Rec( ... , ... )``` | ```PGCB_Rec( ... , ... )``` +> ```PGCB_Rec( ... , ... )``` | ```PGrCB_Rec( ... , ... )``` | ```PGCB_Rec( ... , ... )``` +> +> 3. Quels appels sont redondants ? Proposez une solution pour éviter cela. +> + +## Mise en place du tableau de mémoïsation + +Pour économiser des appels récursifs, vous allez stocker **au fur et à mesure** les résultats de ces appels dans un tableau de même taille que l'image. + +Ce tableau sera initialisé entièrement à ```-1```, ce qui signifie qu'aucun résultat n'a déjà été calculé pour ce pixel. Dans le cas contraire, il faudra stocker le résultat dans ce ```t_memo``` + +- ```t_memo``` : Tableau de même dimension que ```t``` qui stocke les valeurs de ```PlusGrandCarreBlanc_memo()``` au fur et à mesure des appels récursifs. Ce tableau est une variable globale. + +> 4. Initialiser la variable globale ```t_memo```. +> +> **Aide :** En une ligne ... ```t_memo = [[-1 for i in ... ] for j ... ]``` +> +> **Remarque :** On peut être malin et faire une copie de ```t``` en remplaceant les ```1``` (pixel noir) par des ```0``` (résultat connu) et les ```0``` (pixels blancs) par des ```-1``` (résultat inconnu) + +## Évolution de ```PlusGrandCarreBlanc_Rec()``` en ```PlusGrandCarreBlanc_memo()``` + +> 5. En vous aidant de ```PlusGrandCarreBlanc_Rec()```, écrire la fonction ```PlusGrandCarreBlanc_memo()``` en stockant et utilisant les résultats précédamment calculés dans le tableau ```t_memo``` + +```python +def PlusGrandCarreBlanc_memo(t, lig, col): + if t_memo[lig][col] != -1: # Utilisation d'une valeur présente dans t_memo + return ... + if t[lig][col] == 1: + ... + return ... +``` + +> 6. Effectuer les tests sur différents pixels d'une image et sur différentes images. Vous pouvez aussi utiliser la fonction ```genere_tab_alea(n, ratio_noir)``` pour valider votre algorithme. + +## Pour aller plus vite ... ```RechercheCarreBlanc_memo()``` ? + +```PlusGrandCarreBlanc_memo()``` est une fonction équivalente à ```PlusGrandCarreBlanc_Rec()``` mais normalement plus rapide. + +Il reste donc à parcourir une image pour trouver les coordonnées du coin inférieur droit du plus grand carré blanc dans l'image. + +> 7. Ecrire la fonction ```RechercheCarreBlanc_memo()``` qui retourne les coordonnées du coin inférieur droit du plus grand carré blanc ainsi que sa taille. +> +> ```python +> def RechercheCarreBlanc_memo(t): +> l, c, tmax = 0, 0, 0 +> ... +> return l, c, tmax +> ``` + +## Complexité + +> 8. A l'aide du module ```time```, mesurer la différence de performance de l'algorithme naïf et de celui qui utilisa le mémoïsation sur des images de tailles et de "ratio de noir" différents. +> +> $~$ | ```n=10``` / ```ratio=50``` | ```n=30``` / ```ratio=50``` | ```n=50``` / ```ratio=40``` | ```n=70``` / ```ratio=35``` +> ---------|----------|---------|----------|---------- +> ```RechercheCarreBlanc_Rec``` | $~$ | $~$| $~$| $~$ +> ```RechercheCarreBlanc_memo``` | $~$ | $~$| $~$| $~$ +> +> 9. De la même manière que pour l'algorithme naïf, implanter un ```compteur_memo``` qui permettra de compter le nombre d'appels récursifs. Effectuer les tests avec les mêmes paramètres que dans la question précédente. +> +> $~$ | ```n=10``` / ```ratio=50``` | ```n=30``` / ```ratio=50``` | ```n=50``` / ```ratio=40``` | ```n=70``` / ```ratio=35``` +> ---------|----------|---------|----------|---------- +> ```RechercheCarreBlanc_Rec``` | $~$ | $~$| $~$| $~$ +> ```RechercheCarreBlanc_memo``` | $~$ | $~$| $~$| $~$ +> +> Uniquement avec ```RechercheCarreBlanc_memo```, mesurer le temps d'exécution de l'algorithme avec ```n=500``` / ```ratio=50``` puis avec ```n=1000``` / ```ratio=50```. +> +> 10. Par combien a été multiplié le temps d'exécution lorsque ```n``` a doublé ? Conjecturer sur la complexité de cet algorithme. +> +> 11. Le gain de performance est-il subtanciel ? +> + +![Roadrunner](https://media1.tenor.com/images/272319d1c44ce6047b25e7f266da00a0/tenor.gif) + +# Programmation dynamique - Optimisation "Bottom Up" : ```RechercheCarreBlanc_BU()``` + +L'optimisation "BOTTOM UP", consiste à traiter les problèmes du plus évident (les "plus petits") au moins évident (les "plus gros") en stockant les résultats au fur et à mesure dans un tableau. + +Il faut toutefois identifier un ordre dans lequel résoudre les sous-problèmes. Dans notre cas l'ordre est : + +- Traiter les problèmes de taille "0" : Les cases noires +- Traiter les problèmes de taille "1" : Les cases sur la première ligne et première colonne +- Traiter les problèmes de taille "2" : ... + +Le tableau de "résultats", qui stocke au fur et à mesure les résultats de ````PlusGrandCarreBlanc(lig,col)````, se nommera ```res```. + +> 1. Compléter, à la main, le tableau ```res``` pour les problèmes de taille "0". +> +> - Si le ```pixel(lig,col)``` est noir alors ```res[lig][col] = 0``` +> + +![Image N/B](4.png) + +> 2. Compléter, à la main, le précédent tableau ```res``` pour les problèmes de taille "1". +> +> - Si le ```pixel(lig,col)``` est sur la première ligne et/ou colonne alors ```res[lig][col] = 1``` +> + +![Image N/B](4.png) + +> 3. En l'état actuel, peut-on, à l'aide de ```res```, calculer ```PlusGrandCarreBlanc(1,1)``` directement (sans calcul intermédiaire) ? et ```PlusGrandCarreBlanc(2,2)``` ? +> +> 4. Comment faut-il parcourir le tableau pour être sûr qu'à chaque appel, l'algorithme dispose des résultats des problèmes de taille inférieur. +> +> 5. Ecrire un algorithme ```PlusGrandCarreBlanc_BU()``` qui : +> +> - Initialise le tableau ```res``` à -1 +> - Complète ```res``` avec les résultats de taille "0" (les cases noires) +> - Complète ```res``` avec les résultats de taille "1" (1ère ligne/colonne) +> - Parcours l'image et calcule les résultats de taille supérieur +> - Et retourne ```res``` + +**Aide :** + +- Remplir un tableau ```res```, qui a la même taille que l'image, et qui est initialisé -1 dans toutes les cases. +- A l'aide de 2 boucles imbriquées sur les lignes et colonnes : + - Si le ```t[lig][col]``` est noir, affectez ```0``` à ```res[lig][col]``` + - Sinon si ```lig==0``` ou ```col==0```, affectez 1 à ```res[lig][col]``` + - Sinon affectez ```min(res[lig][col-1] , res[lig-1][col-1] , res[lig-1][col] ) + 1``` à ```res[lig][col]``` +- Retourner ```res``` + +```python +def PlusGrandCarreBlanc_BU(t): + nb_lig, nb_col = ... + res = [[-1 for i in ...] for j in ...] + for lig in range(nb_lig): + for col in range(nb_col): + ... + return res +``` + +## .... et trouver le carré blanc le plus grand (presque à la vitesse de la lumière) : ```RechercheCarreBlanc_BU()``` + +> 6. Ecrire ```RechercheCarreBlanc_BU()``` qui retourne les coordonnées du coin inférieur droit du plus grand carré blanc ainsi que sa taille à l'aide de l'algorithme précédent. +> +> ``` +> def RechercheCarreBlanc_BU(t): +> l, c, tmax = 0, 0, 0 +> ... +> return l, c, tmax +> ``` + +## Comparaison des algorithmes + +> 7. Comparer le temps d'éxecution des ces algorithmes. +> +> $~$ | ```n=100``` / ```ratio=30``` | ```n=300``` / ```ratio=20``` | ```n=1000``` / ```ratio=20``` | ```n=3000``` / ```ratio=40``` +> ---------|----------|---------|----------|---------- +> ```RechercheCarreBlanc_memo``` | $~$ | $~$| $~$| $~$ +> ```RechercheCarreBlanc_BU``` | $~$ | $~$| $~$| $~$ + +## Conclusion + +Programmation dynamique = Gain en performance + +L'étude théorique d'un problème permet une optimisation précise de l'algorithme qui le résoudra. + +![Programmation dynamique](https://www.artelys.com/wp-content/uploads/2018/11/formation4.jpg) \ No newline at end of file diff --git a/FOAD-bloc5.pdf b/FOAD-bloc5.pdf new file mode 100644 index 0000000..8ed483d Binary files /dev/null and b/FOAD-bloc5.pdf differ diff --git a/ProgrammationDynamique.pdf b/ProgrammationDynamique.pdf new file mode 100644 index 0000000..e9e5c21 Binary files /dev/null and b/ProgrammationDynamique.pdf differ