J-0
00m
00j
00h
00min
00s

Version interactive avec LaTeX compilé

X ENS Option Informatique MP 2012

Arbres combinatoires

Notez ce sujet en cliquant sur l'étoile
0.0(0 votes)
Logo x-ens
2025_08_29_0281e09cbbb008d47c38g

COMPOSITION D'INFORMATIQUE - A - (XULC)

(Durée : 4 heures)
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.

Arbres combinatoires

On étudie dans ce problème des outils pour la combinatoire, qui peuvent être utilisés en particulier pour répondre à des questions telles que : Combien existe-t-il de façons de paver un échiquier par 32 dominos de taille ?
La partie I introduit la structure d'arbre combinatoire, qui permet de représenter un ensemble d'ensembles d'entiers. La partie II étudie quelques fonctions élémentaires sur cette structure. La partie III propose ensuite un principe de mémorisation, pour définir des fonctions plus complexes sur les arbres combinatoires. La partie IV utilise les résultats précédents pour répondre au problème de dénombrement ci-dessus. Enfin, les deux dernières parties expliquent comment construire et manipuler efficacement des arbres combinatoires, à l'aide de tables de hachage.
Les parties peuvent être traitées indépendamment. Néanmoins, chaque partie utilise des notations et des fonctions introduites dans les parties précédentes. Les tableaux sont indexés à partir de 0 et la notation t est utilisée dans les questions pour désigner l'élément d'indice du tableau t , indépendamment du langage de programmation choisi.
La complexité, ou le coût, d'un programme (fonction ou procédure) est le nombre d'opérations élémentaires (addition, soustraction, multiplication, division, affectation, etc...) nécessaires à l'exécution de . Lorsque cette complexité dépend d'un paramètre , on dira que a une complexité en , s'il existe tel que la complexité de est au plus , pour tout . 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.
Dans l'ensemble de ce problème, on se fixe une constante entière n , avec . On note l'ensemble .

Partie I. Arbres combinatoires

Dans cette partie, on étudie les arbres combinatoires, une structure de données pour représenter un élément de , c'est-à-dire un ensemble de parties de . Un arbre combinatoire est un arbre binaire dont les nœuds sont étiquetés par des éléments de et les feuilles par ou . Voici un exemple d'arbre combinatoire:
Un nœud étiqueté par , de sous-arbre gauche et de sous-arbre droit sera noté . L'arbre ci-dessus peut donc également s'écrire sous la forme
Dans ce sujet, on impose la double propriété suivante sur tout (sous-)arbre combinatoire de la forme : d'une part
éé
(ordre)
et d'autre part
(suppression)
Ainsi les deux arbres

ne correspondent pas à des arbres combinatoires, car celui de gauche ne vérifie pas la condition (ordre) et celui de droite ne vérifie pas la condition (suppression).
À tout arbre combinatoire on associe un ensemble de parties de , noté , défini par
L'interprétation d'un arbre de la forme est donc la suivante : est le plus petit élément appartenant à au moins un ensemble de est le sous-ensemble de des ensembles qui ne contiennent pas , et est le sous-ensemble de des ensembles qui contiennent auxquels on a enlevé . Ainsi, l'arbre

est interprété comme l'ensemble .
Question 1 Donner l'ensemble défini par l'arbre combinatoire de l'exemple (1).
Question 2 Donner les trois arbres combinatoires correspondant aux trois ensembles , et .
Question 3 Soit un arbre combinatoire différent de . Montrer que contient au moins une feuille .
Question 4 Combien existe-t-il d'arbres combinatoires distincts (en fonction de n ) ? On justifiera soigneusement la réponse.

Partie II. Fonctions élémentaires sur les arbres combinatoires

On se donne le type ac suivant pour représenter les arbres combinatoires.
    (* Caml *)
type ac = Zero | Un
| Comb of int * ac * ac;;
    { Pascal }
type ac = `noeud;
noeud = record element : integer;
gauche : ac; droit : ac; end;
Le constructeur Zero représente et le constructeur Un représente . En Pascal, on suppose que deux constantes Zero et Un de type ac ont été définies. On se donne une fonction cons pour construire un nœud de la forme .
(* Caml *) cons: int -> ac -> ac -> ac
{ Pascal } function cons(i: integer; a1: ac; a2: ac) : ac;
Cette fonction suppose que les propriétés (ordre) et (suppression) sont vérifiées. On suppose que cette fonction a un coût .
Dans les questions suivantes, une partie de est représentée par la liste de ses éléments, triée par ordre croissant. On note ensemble le type correspondant, c'est-à-dire
    (* Caml *)
type ensemble == int list;;
        { Pascal }
type ensemble =
    record tete: integer; queue: `ensemble; end;
Question 5 Écrire une fonction un_elt qui prend en argument un arbre combinatoire , supposé différent de , et qui renvoie un ensemble arbitraire. On garantira une complexité au plus égale à la hauteur de .
(* Caml *) un_elt: ac -> ensemble
{ Pascal } procedure un_elt(a: ac; var v: ensemble);
Question 6 Écrire une fonction singleton qui prend en argument un ensemble et qui renvoie l'arbre combinatoire représentant le singleton . On garantira une complexité .
(* Caml *) singleton: ensemble -> ac
{ Pascal } function singleton(s: ensemble) : ac;
Question 7 Écrire une fonction appartient qui prend en arguments un ensemble et un arbre combinatoire et qui teste si appartient à . On garantira une complexité .
(* Caml *) appartient: ensemble -> ac -> bool
{ Pascal } function appartient(s: ensemble; a: ac) : boolean;
Question 8 Écrire une fonction cardinal qui prend en argument un arbre combinatoire et qui renvoie , le cardinal de .
(* Caml *) cardinal: ac -> int
{ Pascal } function cardinal(a: ac) : integer;

Partie III. Principe de mémorisation

Taille d'un arbre combinatoire. On définit l'ensemble des sous-arbres d'un arbre combinatoire , noté , par
La taille d'un arbre combinatoire , notée , est définie comme le cardinal de , c'est-à-dire comme le nombre de ses sous-arbres distincts.
Question 9 Quelle est la taille de l'arbre combinatoire de l'exemple (1) ?
Principe de mémorisation. Pour écrire efficacement une fonction sur les arbres combinatoires, on va mémoriser tous les résultats obtenus par cette fonction, de manière à ne pas refaire deux fois le même calcul. Pour cela, on suppose donnée une structure de table d'association indexée par des arbres combinatoires. Plus précisément, on suppose donné un type table1 représentant une table associant à des arbres combinatoires des valeurs d'un type quelconque et les quatre fonctions suivantes :
  • cree1() renvoie une nouvelle table, initialement vide;
  • ajoute1 ajoute l'association de la valeur à l'arbre dans la table ;
  • present1( ) renvoie un booléen indiquant si l'arbre est associé à une valeur dans la table ;
  • trouve1 renvoie la valeur associée à l'arbre dans la table , en supposant qu'elle existe.
    On suppose que les trois fonctions ajoute1, present1 et trouve1 ont toutes un coût constant . On suppose de même l'existence d'un type table2 représentant des tables d'association indexées par des couples d'arbres combinatoires et quatre fonctions similaires cree2, ajoute2, present2 et trouve2, également de coût constant. (Les parties V et VI expliqueront comment de telles tables peuvent être construites.)
Question 10 Réécrire la fonction cardinal de la question 8 à l'aide du principe de mémorisation pour garantir une complexité . Justifier soigneusement la complexité du code proposé.
(* Caml *) cardinal: ac -> int
{ Pascal } function cardinal(a: ac) : integer;
Question 11 Écrire une fonction inter qui prend en arguments deux arbres combinatoires et et qui renvoie l'arbre combinatoire représentant leur intersection, c'est-à-dire l'arbre tel que .
(* Caml *) inter: ac -> ac -> ac
{ Pascal } function inter(a1: ac; a2: ac) : ac;
Question 12 Montrer que, pour tous arbres combinatoires et , on a

Partie IV. Application au dénombrement

On en vient maintenant au problème de dénombrement évoqué dans l'introduction. Soit un entier pair supérieur ou égal à 2 . On cherche à déterminer le nombre de façons de paver un échiquier de dimensions avec dominos de taille . Voici un exemple de tel pavage pour :
Pour cela, on va construire un arbre combinatoire tel que le cardinal de est exactement le nombre de pavages possibles.
Question 13 Combien existe-t-il de façons différentes de placer un domino sur l'échiquier?
Dans ce qui suit, on suppose que est égal à la réponse à la question précédente, et que chaque élément représente un placement possible de domino. Chaque case de l'échiquier est représentée par un entier tel que , les cases étant numérotées de gauche à droite, puis de haut en bas. On se donne une matrice de booléens m de taille . Le booléen vaut true si et seulement si la ligne correspond à un placement de domino qui occupe la case . (On suppose avoir rempli ainsi la matrice m , qui est une variable globale.)
Un élément de représente un ensemble de lignes de la matrice m. Il correspond à un pavage si et seulement si chaque case de l'échiquier est occupée par exactement un domino, i.e. si et seulement si pour toute colonne il existe une unique ligne telle que true. On parle alors de couverture exacte de la matrice m.
Question 14 Écrire une fonction colonne qui prend en argument un entier , avec , et qui renvoie un arbre combinatoire tel que, pour tout ,
On garantira une complexité .
(* Caml *) colonne: int -> ac
{ Pascal } function colonne(j: integer) : ac;
Question 15 En déduire une fonction pavage qui renvoie un arbre combinatoire tel que le cardinal de est égal au nombre de façons de paver l'échiquier, et majorer le coût de pavage en fonction de n .
(* Caml *) pavage: unit -> ac
{ Pascal } function pavage : ac;

Partie V. Tables de hachage

Dans cette partie, on explique comment réaliser les structures de données table1 et table2, qui ont notamment permis d'obtenir des fonctions inter et cardinal efficaces. L'idée consiste à utiliser des tables de hachage.
On abstrait le problème en considérant qu'on cherche à construire une structure de table d'association pour des clés d'un type clé et des valeurs d'un type valeur, ces deux types étant supposés déjà définis. On se donne un entier et on suppose l'existence d'une fonction hache de coût constant, des clés vers les entiers, telle que pour toute clé
L'idée consiste alors à utiliser un tableau de taille et à stocker dans la case les entrées correspondant à des clés pour lesquelles hache . Chaque case du tableau est appelée un seau. Comme plusieurs clés peuvent avoir la même valeur par la fonction hache, un seau est une liste d'entrées, c'est-à-dire une liste de couples (clé, valeur). On adopte donc le type suivant :
    (* Caml *)
type table == (clé * valeur) list
vect;;
        { Pascal }
type table = array[0..H-1] of `seau;
type seau = record k: clé; v: valeur;
    suivant: `seau; end;
On suppose par ailleurs qu'on peut comparer deux clés à l'aide d'une fonction egal à valeurs dans les booléens, également de coût constant, telle que pour toutes clés et
Question 16 Écrire une fonction ajoute qui prend en argument une table de hachage , une clé et une valeur , et ajoute l'entrée ( ) à la table . On ne cherchera pas à tester si l'entrée existe déjà dans et on garantira une complexité .
(* Caml *) ajoute: table -> clé -> valeur -> unit
{ Pascal } procedure ajoute(t: table; k: clé; v: valeur);
Question 17 Écrire une fonction present qui prend en argument une table de hachage et une clé , et qui teste si la table contient une entrée pour la clé .
(* Caml *) present: table -> clé -> bool
{ Pascal } function present(t: table; k: clé) : boolean;
Question 18 Écrire une fonction trouve qui prend en argument une table de hachage et une clé , et qui renvoie la valeur associée à la clé dans , en supposant qu'elle existe.
(* Caml *) trouve: table -> clé -> valeur
{ Pascal } function trouve(t: table; k: clé) : valeur;
Question 19 Sous quelles hypothèses sur la valeur de et la fonction hache peut-on espérer que le coût des fonctions ajoute, present et trouve soit effectivement ?

Partie VI. Construction des arbres combinatoires

Il reste enfin à expliquer comme réaliser une fonction de hachage, une fonction d'égalité et une fonction cons sur les arbres combinatoires, qui soient toutes les trois de complexité .
L'idée consiste à associer un entier unique à chaque arbre combinatoire , noté unique , et à garantir la propriété suivante pour tous arbres combinatoires et :
Pour cela, on pose unique(Zero) et unique(Un) . Pour un arbre de la forme , on choisira pour unique une valeur arbitraire supérieure ou égale à 2 , stockée dans le nœud de l'arbre. On modifie donc ainsi la définition du type ac :
    (* Caml *)
type unique == int;;
type ac = Zero | Un
    | Comb of unique * int * ac *
ac;;
    { Pascal }
type ac = ^noeud;
noeud = record unique: integer;
element: integer; gauche: ac; droit: ac;
end;
On propose alors la fonction hache suivante sur les arbres combinatoires :
(Le choix de cette fonction, et du coefficient 19 en particulier, relève de considérations pratiques uniquement.) De même, on propose la fonction egal suivante sur les arbres combinatoires :
Question 20 Montrer que les fonctions hache et egal ci-dessus vérifient bien la propriété (2).
Question 21 Proposer un code pour la fonction cons qui garantisse la propriété (3), en supposant que les arbres combinatoires sont exclusivement construits à partir de Zero, Un et de la fonction cons. On garantira un coût en utilisant une table globale de type table1 contenant les arbres combinatoires déjà construits. (On suppose que le type table1 et ses opérations ont été adaptés au nouveau type ac.)
(* Caml *) cons: int -> ac -> ac -> ac
{ Pascal } function cons(i: integer; a1: ac; a2: ac) : ac;
Pour résoudre le problème de pavage de la partie IV, on construit au total 22518 arbres combinatoires. Si on prend et la fonction de hachage proposée ci-dessus, la longueur des seaux dans la table utilisée par la fonction cons n'excède jamais 7. Plus précisément, les arbres se répartissent dans cette table de la manière suivante :
longueur du seau 0 1 2 3 4 5 6 7
nombres de seaux
de cette longueur
6450 7340 4080 1617 400 96 11 3
Question 22 Quel est, dans cet état, le nombre moyen d'appels à la fonction egal réalisés par un nouvel appel à la fonction cons
  1. dans le cas où l'arbre doit être construit pour la première fois;
  2. dans le cas où l'arbre apparaissait déjà dans la table?
On donnera les valeurs avec deux décimales, en les justifiant soigneusement.
Note : La solution au problème du pavage est obtenue en quelques secondes avec la technique proposée ici; on trouve 12988816. L'intérêt de cette technique est qu'elle s'applique facilement à d'autres problèmes de combinatoire. Par ailleurs, le problème de la couverture exacte peut être attaqué par d'autres techniques, telle que les << liens dansants >> de Knuth.
X ENS Option Informatique MP 2012 - Version Web LaTeX | WikiPrépa | WikiPrépa