Devoir en groupe 1 : Le flocon de Von Koch Les tours de Hanoï

publicité
Devoir en groupe 1 :
Le flocon de Von Koch
Les tours de Hanoï
Pierre Chatelain
Matthieu Perrin
5 octobre 2009
1
Le flocon de Von Koch
On souhaite réaliser une représentation du flocon de Von Koch en CamL. Après avoir
défini cette fractale, nous présenterons l’algorithme et sa complexité, puis nous chercherons à améliorer la figure obtenue en supprimant les artefacts dus à certaines particularités
d’OCamL. Enfin, nous proposerons quelques variantes de ce problème.
1.1
Définition
Cette figure est une fractale définie à partir du motif de base suivant :
F IG . 1 – motif de base
À la génération 0, on avance simplement de la longueur distance. À la génération n > 0,
on trace quatre morceaux de génération n − 1 en suivant ce motif. Pour obtenir le flocon
complet, il suffit alors de tracer 3 fois le motif, en tournant à chaque fois d’un angle de 2π
3 .
Un exemple est donné figure 2.
1
F IG . 2 – flocon de génération 5
2
1.2
Algorithme
Si on se donne une fonction AVANCE telle que l’appel AVANCE(distance, angle) trace
une ligne de longueur distance dans la direction angle à partir du point courant, la définition
permet de donner directement l’algorithme 1.
Algorithme 1 : dessin du motif
MOTIF(generation, distance, angle) =
if generation = 0 then
AVANCE(distance, angle);
else
MOTIF(generation − 1, distance/3, angle);
MOTIF(generation − 1, distance/3, angle + π/3);
MOTIF(generation − 1, distance/3, angle − π/3);
MOTIF(generation − 1, distance/3, angle);
La complexité de cet algorithme se calcule donc récursivement de la manière suivante,
en notant Cn le nombre de lignes à tracer pour un motif de génération n :
C0 = 1
∀n ≥ 0, Cn+1 = 4 ∗ Cn
D’où Cn = 4n .
1.3
1.3.1
Quelques pièges à éviter
Utilisation du type float
Les points de la fenêtre graphique sont repérés par des couples d’entiers. Or, pour dessiner un flocon d’ordre relativement grand (n > 5), la définition de l’écran est insuffisante, et
on doit dessiner des longueurs inférieures au pixel. Si la conversion f loat → int est effectuée
au mauvais moment, le flocon devient donc rapidement un point...
Une solution est d’utiliser des références sur des flottants pour repérer la position théorique du point courrant, et de n’utiliser des entiers que dans les fonctions graphiques. Le
calcul se faisant entièrement sur des flottants, la déformation du flocon est locale, et il n’y
pas de problème de raccord.
1.3.2
Problème d’arrondis
En regardant à la loupe, nous avons observé une légère déformation locale : le flocon
n’était pas parfaitement symétrique. Cela était dû au fait que la fonction int_of_float donne
la partie entière d’un flottant, et non son arrondi. Il est donc nécessaire de créer une fonction
arr : f loat → int qui, appliquée à un flottant x, renvoie l’entier le plus proche de x.
3
1.4
1.4.1
Quelques variantes...
Flocons tordus
En modifiant l’angle du motif de base, on obtient des formes diverses, plus lisses si
l’angle diminue, et plus découpées si l’angle se rapproche de π2 :
1.4.2
F IG . 3 – angle =
π
10
F IG . 4 – angle =
π
4
F IG . 5 – angle =
3π
7
F IG . 6 – angle =
10π
21
Remplissage du flocon
On peut également chercher à remplir le flocon. Pour cela, une possibilité est de remplir
d’un bloc le triangle interne, puis de dessiner le flocon avec un algorithme similaire à l’algorithme 1, dans lequel on ajoute à chaque appel le remplissage du triangle apparaissant dans
le motif. Plus concrètement, le motif de base devient :
4
F IG . 7 – motif plein
Le résultat n’est pourtant pas totalement satisfaisant : on voit sur la figure 8 qu’il manque
certains pixels. L’algorithme de remplissage reste donc encore à améliorer...
F IG . 8 – flocon plein
5
2
Les tours de Hanoï
2.1
Résolution d’une partie à n disques
Le jeu des tours de Hanoï consiste en un ensemble de trois tiges et n disques de tailles
différentes. Au début de la partie, tous les disques sont rangés par ordre décroissant sur
la première tige. Le but du jeu est de déplacer tous les disques dans le même ordre sur la
dernière tige en n’en déplaçant qu’un seul à la fois et en ne posant jamais un grand disque
sur un plus petit.
On remarque que si l’on sait déplacer n disques d’une tige t1 à une tige t2 , on peut en
déplacer n + 1 en déplaçant les n premiers disques de t1 à t3 puis le disque restant de t1 à t2 ,
et en redéplaçant les n plus petits disques de t3 à t2 . On obtient ainsi l’algorithme 2.
Algorithme 2 : tours de Hanoï
HANOI(nombre, origine, destination) =
if nombre = 1 then
DEPLACE(origine, destination);
else
HANOI(nombre − 1, origine, autretige);
DEPLACE(origine, destination);
HANOI(nombre − 1, autretige, destination);
Soit Cn le nombre de transferts à effectuer pour déplacer n disques d’une tige à une autre
par l’algorithme précédent. Cn vérifie :
C1 = 1
Cn+1 = 2 ∗ Cn + 1
On en déduit que Cn = 2n − 1. En effet, notons un = 2n − 1, on a bien :
u1 = 1
un+1 = un+1 = 2n+1 − 1 = 2 ∗ (2n − 1) + 1 = 2un − 1
2.2
Affichage graphique
La première implémentation de l’algorithme donne une suite de tâches à exécuter pour
résoudre une tour de n disques. Un affichage graphique des tours serait plus éloquante pour
comprendre son fonctionnement. Pour cela, il faut garder en mémoire les disques de chacune
des tiges. Nous avons défini le type tour par :
type tour = { tiges : int list array; nb_disques : int; enleve : int };;
où tiges est le tableau des disques présents sur les trois tiges, nb_disques code le nombre total
de disques dans les tours, et enleve indique si l’un des disques –et lequel– est en cours de
déplacement.
6
F IG . 9 – représentation des tours de Hanoï
Les
fonctions
enleve_disque : tour−>int−>unit,
pose_disque : tour−>int−>unit
et
permettent d’effectuer toutes les opérations de déplacement
des disques d’une tige à une autre.
est_complete : tour−>bool
Enfin, la fonction affiche_tour : tour−>unit permet de dessiner une tour sur l’afficheur graphique de OCamL. Le même algorithme peut alors être implémenté en ralentissant l’affichage à chaque étape grace à une boucle for.
2.3
Un nouvel algorithme moins restrictif
Le problème des tours de Hanoï étant issu d’un jeu, nous avons utilisé les fonctions
décrites ci-dessus pour proposer un jeu interractif dans lequel il est possible de cliquer sur
les tours pour enlever ou poser un disque. Nous utilisons pour cela la gestion d’évènements
de la biblithèque graphique.
Lorsqu’un joueur humain déplace les tours, beaucoup d’états différents peuvent être atteints par les trois tiges. Or il serait intéressant que l’ordinateur puisse terminer seul une
partie dans laquelle le joueur humain s’est fourvoyé.
Voici l’algorithme envisagé : Nous supposerons que tous les disques sont posés sur une
tige. Sinon, il est possible de se ramener à ce cas en reposant le disque enlevé sur une tige où
il est autorisé à aller, celle sur laquelle il était précédement posé par exemple.
Le disque 1 étant le plus petit disque, il est posé au sommet d’une tige culminée par
une suite de la forme 1, ..., n. Si n est le plus grand disque, alors on sait transporter tous
les disques sur la dernière tige. Sinon, le disque n+1 est au sommet d’une autre tige. On
peut y transporter les n premiers disques, et recommencer l’algorithme. A chaque étape, on
transporte un nombre de disque strictement croissant. A une certaine étape, tous les disques
sont donc sur la même tige et l’agorithgme s’arrête.
On peut remarquer que l’algtorithme n’est pas optimal. Par exemple, pour la tour définie par { tiges = [| [n]; [1;...;(n−1)]; [ ] |]; nb_disques = n; enleve = 0 }, les (n-1) premiers disques sont
transportés de la tige 1 à la tige 0, puis de la tige 0 à la tige 1, ce qui est bien sûr inutile.
7
Téléchargement