École Polytechnique Promotion 2000
Corrigé du contrôle hors-classement d'Informatique Fondamentale
Georges Gonthier
23 Avril 2002
Cet examen est composé de deux parties indépendantes.Tous les documents du cours sont autorisés. Le correcteur
attachera une grande importance à la concision la clarté, et la
précision de la rédaction.
Partie I : Modélisation de scènes 3D
L'objet de cette partie est l'écriture
d'une mini-bibliothèque de rendu de scènes 3D, limitée à un lancer de
rayon simpliste sur des scènes polyhédriques. On identifiera les
points à des vecteurs de R3, et on les représentera par
des objets de la classe Vec3 ci-dessous; ainsi, le produit
scalaire de deux vecteurs v1 et v2 s'écrira
v1.prod(v2).
class Vec3 {
float x, y, z;
Vec3 (float x0, float y0, float z0) { x = x0; y = y0; z = z0; }
float prod (Vec3 v) { return x * v.x + y * v.y + z * v.z; }
final static Vec3 NUL = new Vec3 (0, 0, 0);
}
Un rayon, c'est-à-dire une demi-droite ouverte, sera représenté par
deux vecteurs org et dir donnant
respectivement l'origine et la direction de la demi-droite (la norme
de dir importe peu).
Question 1.
Écrivez la classe Rayon correspondante, avec des champs
org et dir, en y incluant un constructeur et une
méthode pos(float l) qui retourne le point de
paramètre l > 0 du rayon, pos(l) =
org+ldir.
Corrigé.
class Rayon {
Vec3 org, dir;
Rayon (Vec3 o, Vec3 d) { org = o; dir = d; }
Vec3 pos (float a) {
return new Vec3 (org.x + a * dir.x, org.y + a * dir.y, org.z + a * dir.z);
}
}
La scène à rendre sera assimilée à une partie fermée de R3,
et modélisée par une combinaison de primitives
géométriques à l'aide des opérations ensemblistes binaires union et
intersection. On se limite à une seule primitive graphique, le demi-espace,
et une seule méthode de rendu, le lancer(Rayon r), qui retourne
le Trajet du rayon, c'est-à-dire la liste des demi-espaces
correspondant aux surfaces de la scène
qui sont traversées par le rayon, dans l'ordre des l croissants.
(70,30)="uc" - '(70,0)="dc" '(0,0) '(0,30) "uc"
- '(5,55)="lc" (5, 30) -- '(5, 30);(5,25) "dc"
(0,30.05); (65,30.05) **dr^|
(5,55) ; (70,30) ** ?>/-4mm/ **dr_|
(12,6) = "ro" ; (50,52) = "re"
**?(0.20) **@- *x
; "re"**?(0.55) **@. *x
; "re"**?!"lc";"uc" **@--
; "re" **@-
"ro" *· *[l]org
|->@*[|(2)] "re"** ?>/-10mm/; ?>/-3mm/ _dir
|
(15,55)="uc" =**dr^| '(60.05,30) (60.05,0)
- '"uc";(15,25) '(60,0) '(60,30) "uc"
(15,8)="dz";(37, 25)="dx" ** ?>/-8mm/="dv" **@.
"dz" *· *[l](0,0,0)
x->@*[|(2)]"dx";"dv"_norm
|-|"dz";"dx" <-10pt>_dist |
Un demi-espace est défini par une normale norm et une
«distance» à l'origine dist
(qui peut être négative): c'est
l'ensemble des points p tels que norm.p £
dist.
Question 2.
Écrivez les définitions des classes Scene, Union,
Inter, DemiEspace, et Trajet correspondant
à cette architecture, en utilisant l'héritage, et en incluant les
constructeurs, mais en omettant le corps de lancer(Rayon
r), qui fera l'objet d'une question ultérieure.
Corrigé.
abstract class Scene {
abstract Trajet lancer (Rayon);
}
class Union extends Scene {
Scene sg, sd;
Union (Scene s1, Scene s2) { sg = s1; sd = s2; }
Trajet lancer (Rayon r) { ... }
}
class Inter extends Scene {
Scene sg, sd;
Inter (Scene s1, Scene s2) { sg = s1; sd = s2; }
Trajet lancer (Rayon r) { ... }
}
class DemiEspace extends Scene {
Vec3 norm; float dist.
DemiEsp (Vec3 n, float d) { norm = n; dist = d; }
Trajet lancer (Rayon r) { ... }
}
class Trajet {
DemiEspace face; Trajet suivant;
Trajet (DemiEspace f, Trajet t) { face = f; suivant = t; }
}
Question 3.
Écrivez une méthode pave(Vec3 p, Vec3 q) qui retourne la
Scene constituée du parallépipède rectangle dont les côtés
sont parallèles aux axes et dont p et q sont deux
sommets diagonalement opposés. (Si q est dans l'octant
supérieur de p, cette Scene sera l'ensemble des
points de coordonnées (x,y,z) telles que p.x£ x £
q.x et p.y£ y £ q.y et
p.z£ z £ q.z.)
Corrigé.
static Scene pave (Vec3 p, Vec3 q) {
float dx = p.x - q.x, dy = p.y - q.y, dz = p.z - q.z;
Scene fpx = new DemiEspace (new Vec3 (-dx, 0, 0), -dx * p.x);
Scene fpy = new DemiEspace (new Vec3 (0, -dy, 0), -dy * p.y);
Scene fpz = new DemiEspace (new Vec3 (0, 0, -dz), -dz * p.z);
Scene fqx = new DemiEspace (new Vec3 (dx, 0, 0), dx * q.x);
Scene fqy = new DemiEspace (new Vec3 (0, dy, 0), dy * q.y);
Scene fqz = new DemiEspace (new Vec3 (0, 0, dz), dz * q.z);
Scene cxy = new Inter (new Inter (fpx, fqx), new Inter (fpy, fqy));
return new Inter (cxy, new Inter (fpz, fqz));
}
Question 4.
Ajoutez une méthode sans arguments compl() à Scene
pour calculer la scène complémentaire (plus précisément, le
complémentaire de l'intérieur de la scène, c'est-à-dire que le
complémentaire d'un demi-espace est un demi-espace), et définissez-la
dans toutes les sous-classes de
Scene (vous pouvez ignorer les cas dégénérés de demi-espaces
tangents). Utilisez compl() pour définir une méthode
diff(Scene s1, Scene s2) qui calcule la différence entre deux
scènes.
Corrigé.
// dans Scene
abstract Scene compl();
// dans Union
Scene compl () { return new Inter (sg.compl(), sd.compl()); }
// dans Inter
Scene compl () { return new Union (sg.compl(), sd.compl()); }
// dans DemiEsp
Scene compl () {
return new DemiEsp (new Vec3 (-norm.x, -norm.y, -norm.z), -dist);
}
Un rayon peut soit entrer, soit sortir, soit être disjoint de, soit
être inclus dans ou tangent à un demi-espace. On ajoute donc les
constantes suivantes à la classe Rayon.
final static int ENTRE = 0, SORT = 1, DISJOINT = 2, INCLUS = 3;
Question 5.
Ajoutez à la classe Rayon une méthode incidence(DemiEsp f)
qui retourne l'un des quatre codes ci-dessus, ainsi qu'une méthode
parametre(DemiEsp f) qui calcule le l > 0 pour lequel
f.pos(l) est sur la surface du
demi-espace, dans le cas où incidence(f) retourne ENTRE ou
SORT.
Corrigé.
int incidence (DemiEsp f) {
float a = f.dist - f.norm.prod(org), b = f.norm.prod(dir);
if (a * b > 0) return a < 0 ? ENTRE : SORT;
return a - b < 0 ? DISJOINT : INCLUS;
}
float parametre (DemiEsp f) {
return (f.dist - f.norm.prod(org)) / f.norm.prod(dir);
}
Afin de simplifier le lancer de rayon et de permettre de déterminer
une unique couleur d'affichage, on s'impose de ne produire que des
trajets alternés, où les surfaces apparaissent par l
strictement croissant, et les entrées alternent avec les sorties;
en particulier on supprimera les intersections d'épaisseur nulle.
En outre, un trajet plein qui ne comporte pas d'intersections
parce qu'il est entièrement inclus dans la scène sera représenté non
pas par la liste vide, mais par la constante
final static Trajet PLEIN = new Trajet (new DemiEsp (Vec3.NUL, 1), null);
Question 6.
Ajoutez à la classe Rayon une méthode alterne(Trajet t)
qui teste que le trajet est bien alterné pour le rayon.
Corrigé.
boolean alterne (Trajet t) {
if (t == null || t == Trajet.PLEIN) return true;
int i = incidence (t.face); float a = 0;
while (t != null) {
if (incidence (t.face) != i) return false;
float at = parametre (t.face);
if (at <= a) return false;
i = (i == SORT ? ENTRE : SORT); a = at; t = t.suivant;
}
return true;
}
Question 7.
Definissez les méthodes lancer(Rayon r) des sous-classes de
Scene. On vous recommande d'utiliser une ou plusieurs
méthodes auxiliaires récursives dans la classe Rayon pour
fusionner les trajets des opérations Inter et Union
(il est possible de factoriser ces deux cas). On ne précise pas de
règle de priorité lorsqu'un rayon coupe plusieurs faces en un
même point.
Corrigé.
// dans DemiEspace
Trajet lancer (Rayon r) {
int i = r.incidence(this);
if (i == Rayon.DISJOINT) return null;
if (i == Rayon.INCLUS) return Trajet.PLEIN;
return new Trajet (this, null);
}
// dans Inter
Trajet lancer (Rayon r) {
return r.fusion (sg.lancer(r), sd.lancer(r), true);
}
// dans Union
Trajet lancer (Rayon r) {
return r.fusion (sg.lancer(r), sd.lancer(r), false);
}
// dans Rayon, méthodes auxiliaire et auxiliaire récursive
Trajet fusion (Trajet t1, Trajet t2, boolean inter) {
if (t1 == null || t2 == Trajet.PLEIN) return inter ? t1 : t2;
if (t2 == null || t1 == Trajet.PLEIN) return inter ? t2 : t1;
int i1 = incidence (t1), i2 = incidence (t2);
boolean m = (i1 == SORT) ^ inter;
return fusion (t1, t2, m, i1 != i2, parametre (t1.face));
}
// * m == le Trajet t1 commence par un segment masquant la fusion
// (c'est-a-dire plein pour une union, vide pour une intersection).
// * d == les Trajets t1 et t2 commencent par des type de segment différents.
// * a1 == parametre (t1.face).
Trajet fusion (Trajet t1, Trajet t2, boolean m, boolean d, float a1) {
if (t2 == null) return (m ^ d) ? t2 : t1;
float a2 = parametre (t2.face);
if (m ? a1 < a2 : a1 <= a2) { // echanger t1 et t2
Trajet t = t1; t1 = t2; t2 = t; m ^= d; a1 = a2;
} // ici on a parametre(t2) <= a1, strictement si m est faux et d est vrai
Trajet t = fusion (t1, t2.suivant, m, !d, a1);
return m ? t : new Trajet (t2.face, t);
}
Partie II : Recherche de cliques dans un graphe non orienté
Dans cette partie on se place dans un graphe non orienté G = (V, E).
Une clique de G est une partie X de V qui induit un
sous-graphe complet de G et qui est maximale pour cette propriété,
c'est-à-dire que
-
X Í V
- Si x, y Î X, avec x ¹ y, alors (x,y)Î E
- Si x Î V \ X, alors il existe un y Î X tel que
(x,y) ÏE.
La notion de clique a plusieurs applications, notamment en analyse de
données (data-mining), car il permet d'identifier des groupes homogènes
(p. ex., une communauté de pages Web).
Le problème ci-dessous porte sur l'énumération de toutes les cliques de G.
Pour les questions de programmation, on utilisera la représentation
par liste d'adjacence du cours (classes Liste et Graphe).
Question 8.
Listez toutes les cliques du graphe suivant :
-20pt@C-5pt
*++[o][F-]1 -[dr]*++[o][F-]2 -[dl] -[ddrr] -[dd] -[rr]*++[o][F-]5 -[ddll] -[dr] -[dd]*++[o][F-]3 -[dr]
-@`c,c-(0,17),p-(0,17),p[rrrr]*++[o][F-]7 -[dl]*++[o][F-]4 -[rr]*++[o][F-]6
Corrigé.
On a les cinq cliques suivantes.
-20pt@C-5pt
*++[o][F-]1 -[dr]*++[o][F-]2 -[dl] -[dd]*++[o][F-]2 -[dd] -[ddrr] -[rr]*++[o][F-]5 -[ddll] -[dd]*++[o][F-]5 -[dr] -[dd]*++[o][F-]3 -@`c,c-(0,17),p-(0,17),p[rrrr]*++[o][F-]7*++[o][F-]3*++[o][F-]3 -[dr]*++[o][F-]7 -[dl]*++[o][F-]4*++[o][F-]4 -[rr]*++[o][F-]6*++[o][F-]6
Considérons l'ensemble X de toutes les séquences s =
x0··· xk-1 de sommets distincts de V qui induisent un
sous-graphe complet de G (i.e., xi ¹ xj et (xi,xj)Î E
pour tout i¹ j, 0 £ i < k, 0 £ j < k). X est un
arbre (il est clos par préfixe), et ses feuilles correspondent aux
cliques de G.
Question 9.
Dessiner l'arbre X pour le graphe G suivant:
-5pt@C-5pt
*++[o][F-]1 -[d] -[r]*++[o][F-]3 -[dl] -[r]*++[o][F-]5 -[d]*++[o][F-]2 -[r]*++[o][F-]4*++[o][F-]6
Combien de fois apparaissent dans X chacune des cliques de G ?
Corrigé.
=0ptáñ
-[dlllllllllll]|-*+[o] 1
-[dllllll]|-*+[o] 2
-[d]|-*+[o] 3
-[drrrr]|-*+[o] 4
-[drrrrrrr]|-*+[o] 5
-[drrrrrrrrrr]|-*+[o] 6á 1ñ -[dl]|-*+[o] 2 -[dr]|-*+[o] 3á 2ñ -[dll]|-*+[o] 1 -[d]|-*+[o] 3 -[drr]|-*+[o] 4á 3ñ -[dll]|-*+[o] 1 -[d]|-*+[o] 2 -[drr]|-*+[o] 5á 4ñ -[d]|-*+[o] 2á 5ñ -[dl]|-*+[o] 3 -[dr]|-*+[o] 6á 6ñ -[d]|-*+[o] 5á 12ñ -[d]|-*+[o] 3á 13ñ -[d]|-*+[o] 2á 21ñ -[d]|-*+[o] 3á 23ñ -[d]|-*+[o] 1á 24ñá 31ñ -[d]|-*+[o] 2á 32ñ -[d]|-*+[o] 1á 35ñá 42ñá 53ñá 56ñá 65ñá 123ñá 132ñá 213ñá 231ñá 312ñá 321ñ
La clique {1,2,3} apparaît 6 fois, les cliques {2,4},
{3,5} et {5,6} apparaissent 2 fois chacunes.
Question 10.
Ajoutez à la classe Graphe une méthode cliques() qui
imprime toutes les cliques de G en effectuant un parcours préfixe
naïf de l'arbre X. Utilisez une
Pile s pour stocker la séquence s correspondant au
noeud courant, que vous pourrez imprimer à l'aide de l'instruction
System.out.println(s). (On vous suggère d'utiliser
une méthode récursive auxiliaire, et de stocker dans un tableau
ns, pour chaque sommet x le nombre de sommets de s
qui sont voisins de x.)
Corrigé.
void cliques () {
int n = succ.length; Pile s = new Pile(n); int[] ns = new int[n];
for (int x = 0; x < n; x++) cliques (x, s, 0, ns);
}
void cliques (int x, Pile s, int h, int[] ns) {
for (Liste a = succ[x]; a != null; a = a.suivant) ++ns[a.val];
boolean feuille = true; s.empiler(x);
for (Liste a = succ[x]; a != null; a = a.suivant)
if (ns[a.val] > h) { feuille = false; cliques (a.val, s, h + 1; ns); }
for (Liste a = succ[x]; a != null; a = a.suivant) --ns[a.val];
if (feuille) System.out.println(s);
s.depiler();
}
This document was translated from LATEX by
HEVEA.