L'utilisation des calculatrices n'est pas autorisée pour cette épreuve.
Le langage de programmation choisi par le candidat doit être spécifié en tête de la copie.
Sandwich au jambon
Le problème dit du sandwich au jambon ou bien encore appelé théorème de Stone-Tukey s'énonce de la manière suivante : un ensemble de n points en dimension d peut toujours être séparé en deux parties de cardinal au plus ⌊n/2⌋ par un hyperplan de dimension d − 1 (certains points peuvent être dans l'hyperplan), où ⌊n/2⌋ désigne la partie entière de n/2. De manière concrète, un ensemble de points dans l'espace peut être séparé en deux parties quasi-égales par un plan. De même un ensemble de points dans le plan peut être séparé en deux par une droite et même en 4 à l'aide de deux droites. Ce sujet porte sur la résolution algorithmique de ce problème et de problèmes connexes selon différentes méthodes.
Dans tout le problème, les tableaux sont indicés à partir de 1 . L'accès à la i-ème case d'un tableau tab est noté tab[i]. Quel que soit le langage utilisé, on suppose que les tableaux peuvent être passés comme arguments des fonctions. En outre, il existe une primitive allouer (m, c) pour créer un tableau de taille m dont chaque case contient c à l'origine, ainsi qu'une primitive taille (t) qui renvoie la taille d'un tableau t. Enfin, on disposera d'une fonction floor( x ) qui renvoie la partie entière ⌊x⌋ pour tout réel x ≥ 0.
La complexité, ou le temps d'exécution, d'un programme P (fonction ou procédure) est le nombre d'opérations élémentaires (addition, soustraction, multiplication, division, affectation, etc...) nécessaires à l'exécution de P. Lorsque cette complexité dépend d'un paramètre n, on dira que P a une complexité en O(f(n)), s'il existe K > 0 tel que la complexité de P est au plus Kf(n), pour tout n. Lorsqu'il est demandé de garantir une certaine complexité, le candidat devra justifier cette dernière si elle ne se déduit pas directement de la lecture du code.
Partie I. Grand, petit et médian
Dans cette partie, nous supposerons donné un tableau tab contenant n nombres réels. Les indices du tableau vont de 1 à n. Nous dénoterons par tab[a..b] le tableau pris entre les indices a et b c'est-à-dire les cellules tab[a], tab[a + 1], …, tab[b − 1], tab[b]. Nous supposerons dans l'énoncé que a ≤ b.
Nous utiliserons le tableau de taille 11 suivant pour nos exemples :
3
2
5
8
1
34
21
6
9
14
8
Question 1 Écrire une fonction calculeIndiceMaximum( tab, a, b ) qui renvoie l'indice d'une case où se trouve le plus grand réel de tab[a..b]. Sur le tableau précédent avec a = 1 et b = 11, la fonction renverra 6 car la case 6 contient la valeur 34 .
Question 2 Écrire une fonction nombrePlusPetit( tab, a, b, val ) qui renvoie le nombre d'éléments dans le tableau tab [a..b] dont la valeur est plus petite ou égale à val. Sur le tableau exemple, pour une valeur de val égale à 5 , et a = 1, b = 11, la fonction devra renvoyer la valeur 4 car seuls les nombres 3, 2, 5, 1 sont inférieurs ou égaux à 5 .
Nous allons maintenant calculer un médian d'un tableau. Rappelons qu'une valeur médiane m d'un ensemble E de nombres est un élément de E tel que les deux ensembles E_(< m) (les nombres de E strictement plus petits que m ) et E_(> m) (les nombres de E strictement plus grands que m ) vérifient |E_(< m)| ≤ ⌊n/2⌋ et |E_(> m)| ≤ ⌊n/2⌋. Notez que le problème du médian est une reformulation de problème dit du sandwich au jambon pris en dimension 1 . Une méthode naïve consiste donc à parcourir les éléments de l'ensemble et à calculer pour chacun d'eux les valeurs de |E_(< m)| et |E_(> m)| mais il est possible de faire mieux comme nous allons le voir dans la partie suivante.
Partie II. Un tri pour accélérer
Une méthode plus efficace serait de trier le tableau par ordre croissant tout en prenant la cellule du milieu dans le tableau trié. Cette méthode certes rapide requiert O(nlnn) opérations. Il existe une méthode optimale en temps linéaire O(n) pour trouver le médian d'un ensemble de n éléments. Cette partie a pour but d'en proposer une implémentation.
Une fonction annexe nécessaire pour cet algorithme consiste à savoir séparer en deux un ensemble de valeurs. Soit un tableau tab et un réel appelé pivot p = tab[i], il s'agit de réordonner les éléments du tableau en mettant en premier les éléments strictement plus petits que le pivot tab_(< p), puis les éléments égaux au pivot p, et en dernier les éléments strictement plus grands tab_(> p). Sur le tableau exemple, en prenant comme valeur de pivot 8 on obtiendra le tableau résultat suivant :
3, 2, 5, 1, 6, 8, 8, 21, 34, 9, 14
Notez que dans le résultat les nombres plus petits que le pivot 3, 2, 5, 1, 6 peuvent être dans n'importe quel ordre les uns par rapport aux autres.
Question 3 Écrire une fonction partition(tab, a, b, indicePivot) qui prend en paramètre un tableau d'entiers tab[a..b] ainsi qu'un entier a ≤ indicePivot ≤ b. Soit p = tab [ indicePivot ]. La fonction devra réordonner les éléments de tab[a..b] comme expliqué précédemment en prenant comme pivot le nombre p. La fonction retournera le nouvel indice de la case ou se trouve la valeur p.
Dans cette question, on suppose que les modifications effectuées par la fonction sur le tableau tab sont persistantes, même après l'appel de la fonction.
Remarquons que le ⌊n/2⌋-ème élément dans l'ordre croissant d'un tableau de taille n est un élément médian du tableau considéré. Nous allons donc non pas programmer une méthode pour trouver le médian mais plus généralement pour trouver le k-ème élément d'un ensemble. Nous allons utiliser l'algorithme suivant :
On cherche le k-ème élément du tableau tab[a..b].
Si k = 1 et a = b alors renvoyer tab[a]
Sinon, soit p = tab[a]. Partitionner le tableau tab[a..b] en utilisant le pivot p en mettant en premier les éléments plus petits que p. Soit i l'indice de p dans le tableau résultant.
Si i − a + 1 > k chercher le k-ème élément dans tab[a..i − 1] et renvoyer cet élément.
Si i − a + 1 = k renvoyer le pivot.
Si i − a + 1 < k chercher le ( k − i + a − 1 )-ème élément dans tab[i + 1..b] et renvoyer cet élément.
Question 4 Écrire une fonction elementK (tab, a, b, k) qui réalise l'algorithme de sélection du k-ème élément dans le tableau tab[a..b] décrit précédemment et renvoie cet élément.
Question 5 Supposons que dans l'algorithme précédent nous voulions rechercher le premier élément mais qu'à chaque étape le pivot choisi est le plus grand élément, quel est un ordre de grandeur du nombre d'opérations réalisées par votre fonction?
L'algorithme précédent ne semble donc pas améliorer le calcul du médian. Le problème vient du fait que le pivot choisi peut être mauvais c'est-à-dire qu'à chaque étape un seul élément du tableau a été éliminé. En fait, si l'on peut choisir un pivot p dans tab[a..b] tel qu'il y ait au moins (b − a)/5 éléments plus petits et (b − a)/5 plus grands alors on peut montrer que l'algorithme précédent fonctionne optimalement en temps O(n).
Pour choisir un tel élément dans tab[a..b], on réalise l'algorithme choixPivot suivant où chaque étape sera illustrée en utilisant le tableau donné en introduction en prenant a = 2 et b = 9.
On découpe le tableau en paquets de 5 éléments plus éventuellement un paquet plus petit.
On calcule l'élément médian de chaque paquet.
S'il n'y a qu'un paquet on renvoie son médian. Sinon on place ces éléments médians au début du tableau.
5
9
2
8
1
34
21
6
On réalise choixPivot sur les médians précédents. Dans notre exemple on recommence donc les étapes précédentes en prenant a = 2 et b = 3.
Question 6 Écrire la fonction choixPivot (tab, a, b) qui réalise l'algorithme précédent et renvoie la valeur du pivot.
Partie III. De la 1D vers la 2D, des nombres aux points.
Pour un réel x ≥ 0, on note dans cette partie ⌈x⌉ la partie entière supérieure de x, c'est-à-dire, le plus petit entier qui est plus grand ou égal à x : ⌈x⌉ − 1 < x ≤ ⌈x⌉. On supposera disposer d'une fonction ceil( x ) qui renvoie la partie entière supérieure ⌈x⌉ pour tout réel x ≥ 0.
Dans la partie précédente, nous avons étudié le problème du médian en dimension 1 . On supposera donc que vous disposez maintenant d'une fonction indiceMedian(tab, a, b ) qui calcule un élément médian du tableau tab[ a..b ] et renvoie l'indice de cet élément. Vous supposerez de plus que cette fonction ne modifie pas l'ordre des éléments du tableau tab.
Dans cette partie, nous généralisons l'algorithme de manière à trouver deux droites dans le plan séparant un ensemble de n points en 4 parties de cardinal plus petit que ⌈n/4⌉, Soit E un ensemble de n points tel qu'il n'existe pas trois points alignés. Nous allons chercher deux lignes L_=et L_/séparant cet ensemble de points en 4 parties comme le montre la troisième figure ci-dessous. En effet, dans cette figure chaque partie est composée d'exactement 2 points, les points situés sur les lignes n'étant pas pris en compte. Nous supposerons donnés dans cette partie deux tableaux tabX et tabY de taille n contenant les coordonnées des n points. Ainsi le point i a comme abscisse tabX[i] et comme ordonnée tabY[i].
La première étape est de séparer les points en deux ensembles de même cardinal. Il suffit de remarquer que l'on peut toujours effectuer cette séparation par une ligne horizontale notée L_= passant par un point d'ordonnée médiane comme le montre le second schéma ci-dessus.
Question 7 Écrire une fonction coupeY( tabX, tabY ) qui renvoie l'ordonnée d'une ligne horizontale séparant les points en deux parties de cardinal au plus ⌊n/2⌋.
La seconde ligne L_/est plus difficile à trouver. Nous allons en réalité trouver le point d'intersection des deux lignes L_=et L_/.
Soit P un point sur la droite horizontale L = précédente de coordonnées (x, y). On veut vérifier si ce point peut être le point d'intersection des deux lignes L_=et L_/. Nous allons trouver dans un premier temps l'angle entre L_=et L_/en utilisant le fait que L_/doit séparer en deux parties de cardinal proche les points au dessus de L_=. Ensuite nous allons vérifier si la droite L_/ ainsi devinée sépare les points en dessous de L = en deux parties presque égales.
Concrètement on considère les demi-droites partant de P = (x, y) et joignant les k points de E au dessus strictement de L = comme indiqué sur le schéma ci-dessus. On calcule ensuite les angles θ entre L_=et ces demi-droites. Remarquez alors que toute demi-droite d'angle médian partage en deux parties de cardinal ≤ ⌊k/2⌋ les points au dessus de L_=.
Nous supposerons donnée une fonction angle( x, y, x2, y2 ) qui calcule et renvoie l'angle formé par une demi-droite horizontale partant de (x, y) et allant vers la droite et le segment (x, y) − (x2, y2). La valeur retournée sera comprise dans l'intervalle [0, 2π[.
Question 8 Écrire une fonction demiDroiteMedianeSup (tabX, tabY, x, y) qui calcule et renvoie un angle médian entre L_=et les segments reliant P = (x, y) avec les points de E dont l'ordonnée est supérieure à y.
Pour un point P donné, nous avons déterminé l'angle que doit prendre L_/pour couper les points au dessus de L = en 2 parties de taille au plus moitié. Il faut maintenant vérifier que cette droite L_/coupe aussi les points en dessous de L_=en 2 parties quasi-égales. Il suffit de vérifier que l'angle de L_/partitionne les angles formés entre P et les ℓ points en dessous de L_=en deux parties de cardinal ≤ ⌈ℓ/2⌉.
Question 9 Écrire une fonction verifieAngleSecondeDroite (tabX, tabY, x, y, theta) qui calcule les ℓ angles formés entre L_=et les points strictement au dessous de L_=. Votre fonction devra renvoyer : → 0 si theta est une médiane des ℓ angles. → − 1 si le nombre d'angles strictement plus petits que theta est > ⌈ℓ/2⌉ → 1 si le nombre d'angles strictement plus grands que theta est > ⌈ℓ/2⌉
Si x min est l'abscisse minimale des points de E et xmax l'abscisse maximale, alors il est évident que l'intersection entre L_=et L_/ait une abscisse entre x min et x max . Nous allons donc rechercher cette abscisse en utilisant l'algorithme suivant :
Trouver L = d'ordonnée y.
Soit α = xmin et β = xmax.
On calcule P le point au milieu de (α, y) et (β, y).
On calcule l'angle possible de la droite L_/par la fonction demiDroiteMedianeSup.
Soit d la valeur donnée par la fonction verifieAngleSecondeDroite avec l'angle trouvé précédemment. Si d = 0 alors on a trouvé L_/. Si d = − 1 on recommence à partir du point 3 en prenant l'abscisse de P à la place de β. Si d = 1 on recommence mais en prenant l'abscisse de P à la place de α.
Question 10 Écrire la fonction secondeMediane (tabX, tabY, y) qui à partir d'un ensemble E de points donnés par leurs coordonnées et de l'ordonnée de la droite L = calcule et renvoie un tableau contenant dans la première case l'abscisse x du point d'intersection de L_=et de L_/ainsi que l'angle de L_/dans la seconde case.
Pas de description pour le moment
Commentaires• X ENS Informatique Commune MP PC 2012
Connectez-vous pour participer aux discussions
Partagez vos avis, posez des questions et échangez avec la communauté