Sujet proposé par David Monniaux. Pale machine X2005.
On se propose ici de calculer, en fonction d'un ensemble P de n points du plan, un sous-ensemble E de ces points formant les sommets de l'enveloppe convexe de P ; c'est-à-dire que le polygone convexe délimité par E englobe tout P. Dans le cours, vous avez vu un algorithme dont le temps d'exécution peut être, dans le pire cas, proportionnel à n² ; nous nous proposons ici d'utiliser un algorithme dont le temps d'exécution dans le pire cas est proportionnel à n log n.
Convexe.java
, que vous rendrez entièrement à chaque dépôt. Mettez import java.util.*;
en tête de ce fichier afin d'accéder facilement à certaines classes livrées avec Java.
1.1 Définissez une classe Vecteur
représentant des points du plan. Afin d'éviter les problèmes d'arrondis dans les calculs, on prendra des coordonnées entières pour l'abscisse et l'ordonnée, respectivement dans les champs x
et y
.
1.2 Définissez des méthodes dynamiques Vecteur moins(Vecteur p)
et int det(Vecteur p)
, calculant respectivement t-p et det(t,p) où t est le vecteur représenté par this
et p est le vecteur représenté par l'argument p
. Nous vous rappelons que det(t,p)=tx.py-ty.px.
Testez votre code avec le programme suivant, à ajouter dans la classe Vecteur
et à exécuter par java Vecteur
:
public String toString() { return("(" + x + "," + y + ")"); } public static void main(String[] args) { Vecteur a = new Vecteur(3, 5), b = new Vecteur(4, 9), c = new Vecteur(1, 4); System.out.println(a.moins(c)); System.out.println(a.moins(c).det(b.moins(c))); }
Vous devez obtenir l'affichage :
(2,1) 7
Vecteur
comme expliqué ci-dessus ! Si vos calculs de base ne fonctionnent pas correctement, vous perdrez un temps précieux par la suite.
Téléchargez le fichier AffichageConvexe.java. Dans votre fichier Convexe.java
, copiez la classe suivante :
class ListeVecteur { Vecteur valeur; ListeVecteur suivant; ListeVecteur(Vecteur valeur, ListeVecteur suivant) { this.valeur = valeur; this.suivant = suivant; } static int longueur(ListeVecteur l) { int i=0; for(; l!=null; l=l.suivant) i++; return i; } }
2.1 Dans la classe Vecteur
, ajoutez une méthode static Vecteur random(Random rng, int maxX, int maxY)
qui devra rendre un vecteur aléatoire d'abscisse comprise entre 0 inclus et maxX
exclu, d'ordonnée comprise entre 0 inclus et maxY
exclu, uniformément distribué. rng.nextInt(n)
permet d'obtenir un entier uniformément distribué entre 0 inclus et n
exclu.
2.2 Dans la classe ListeVecteur
, ajoutez une méthode static ListeVecteur random(Random rng, int largeur, int hauteur, int nbPoints)
qui retournera une liste de nbPoints
vecteurs aléatoires d'abscisses entre 0 inclus et largeur
exclu et d'ordonnée entre 0 inclus et hauteur
exclu.
2.3 Ajoutez également une méthode static int[] getX(ListeVecteur l, int delta)
(respectivement, getY
) donnant le tableau des abscisses+delta
(respectivement, des ordonnées+delta
) des vecteurs de l
, dans le même ordre que dans l
; c'est-à-dire qu'on doit obtenir un tableau { l
1.x+delta
, ..., t
n.x+delta
} si l
1,...,l
n est la suite des vecteurs contenus dans la liste l
.
Compilez Convexe.java
et AffichageConvexe.java
et lancez le programme par java AffichageConvexe
; vous devez obtenir un affichage d'un polygone aléatoire comme dans la figure suivante.
Nous proposons l'algorithme suivant de calcul de l'enveloppe convexe, en trois temps :
Dans cette question, nous nous occuperons des deux premiers points. Le troisième point sera traité dans la question suivante.
Toutes les fonctions de cette question seront à insérer dans la classe ListeVecteur
.
3.1 Programmez une fonction static Vecteur minimumY(ListeVecteur l)
qui retourne un vecteur d'ordonnée minimale dans la liste.
Dans AffichageConvexe.java
, vous trouverez une classe ListeLong
(listes d'entiers longs) et des fonctions de tri fusion sur ces listes. Notez que la séparation en deux sous-liste procède en mettant un élément sur deux dans chaque sous-liste (ceux d'indice pair d'un côté, ceux d'indice impair de l'autre).
Nous vous proposons d'adapter ces fonctions de tri à des listes de vecteurs, avec le critère de comparaison : a < b (respectivement, = et >) si et seulement si det(a-p0,b-p0)<0 (respectivement, = et >).
3.2 Copiez/coller dans la classe ListeVecteur
la procédure de tri des entiers que nous vous avons fournie, et modifiez la, afin d'obtenir static ListeVecteur trier(Vecteur p0, ListeVecteur l)
. Vous devez donc obtenir des fonctions :
static ListeVecteur decoupe2(ListeVecteur l)
,static ListeVecteur decoupe1(ListeVecteur l)
,static ListeVecteur fusionner(Vecteur p0, ListeVecteur l1, ListeVecteur l2)
,static ListeVecteur trier(Vecteur p0, ListeVecteur l)
.3.3 Programmez une fonction static ListeVecteur oter(Vecteur v, ListeVecteur l)
qui, de façon non destructrice, va retourner une liste contenant tous les éléments de l
dans le même ordre à l'exception de la première occurrence de v
.
3.4 Programmez une fonction static ListeVecteur rearrange(ListeVecteur listePoints)
qui retourne une liste avec les mêmes points que son paramètre, mais dans un ordre tel que :
Dans AffichageConvexe
, décommentez AffichageConvexe.rearrange(points);
avant enveloppe = points;
. Si vous avez pu faire la question précédente, remplacez AffichageConvexe.rearrange
par ListeVecteur.rearrange
. Vous devez maintenant obtenir un polygone trié avec le point numéro 0 tout en haut de la figure, comme dans l'exemple suivant :
Nous allons calculer des enveloppes convexes de points[0]
, points[1]
, ..., points[k]
pour k
croissant, de sorte qu'à la fin nous avons l'enveloppe convexe de tous les points. À chaque étape, nous aurons une enveloppe convexe triée, que nous stockerons dans une pile, points[0]
étant au fond de la pile et le dernier point ajouté étant au sommet.
Par souci de simplicité, on utilisera la classe Stack
de la bibliothèque standard de Java :
Stack<Vecteur> pile = new Stack<Vecteur>();
crée une pile de Vecteur
et la met dans la variable pile
.pile.push(a)
pousse le Vecteur
a dans la pile.pile.pop()
retourne le Vecteur
au sommet de la pile et l'ôte de la pile.pile.peek()
retourne le Vecteur
au sommet de la pile sans l'ôter de la pile.pile.empty()
retourne true
si et seulement si la pile est vide.import java.util.*;
en tête de votre fichier.
4.1 Programmez une fonction static ListeVecteur enveloppeConvexe(ListeVecteur points)
qui va calculer l'enveloppe convexe des points, en utilisant une pile dans laquelle vous aurez mis initialement points[0]
puis points[1]
(en notant par abus de langage points[i]
pour le i-ème élément de la liste), suivant l'algorithme suivant :
Si vous prenez b le point au sommet de la pile, a le point dans la pile juste sous le sommet, et p=points[i]
, alors vous avez les deux cas suivants :
points[i]
dans la pile et passer à l'élément suivant de la liste (attention, nous avons bien écrit ≤ et pas <) ;Itérez cet algorithme jusqu'à la fin de la liste.
Dans AffichageConvexe
, remplacez enveloppe = points;
par enveloppe = Convexe.enveloppeConvexe(points);
et testez votre programme.
4.2 Quelle est la complexité de la fonction enveloppeConvexe
? Vous pourrez, pour la déterminer, vous intéresser aux évolutions possibles du couple formé par le nombre d'éléments de la liste points
restant à parcourir et par le nombre d'éléments dans la pile. Justifiez votre réponse. (Mettez la réponse en commentaire dans le programme.)
Voici, pour les curieux, l'explication de l'algorithme, appelé marche de Graham.
Supposons maintenant que nous ayons calculé une enveloppe convexe triée En de points[0]
à points[n]
, avec n>0. Regardons le point points[n+1]
. De deux choses l'une :
points[n-1]
, points[n]
, points[n+1]
forment une « forme convexe » et alors En+1 est En avec points[n+1]
rajouté au bout ;points[0]
à points[6]
.
points[7]
, on s'aperçoit qu'il faut supprimer points[6]
de la pile.
points[7]
, on l'ajoute à la pile.