La formalisation du concept d'ensemble et l'édiction des règles qui permettent de les construire a été l'un des plus grand défis des mathématiques à la charnière entre le xix-ème siècle et le xx-ème siècle. Cette question a été centrale pour la résolution de la crise des fondements des mathématiques et a abouti à la construction axiomatique que l'on connaît aujourd'hui. Elle sera abordée dans le chapitre sur la calculabilité du cours de master Complexité algorithmique. Nous nous contenterons ici d'en donner une définition informelle qui suffira pour les besoins de ce cours.
Un ensemble est une collection d'objets distincts. Ces objects sont appelés les éléments de l'ensemble . Si est un élément de on écrit (parfois ) et dans le cas contraire plutôt que .
Il existe un unique ensemble qui ne contient aucun élément, c'est l'ensemble vide. On le note ou encore . On appelle singleton tout ensemble réduit à un unique élément et paire tout ensemble réduit à deux éléments. On appelle sous-ensemble ou partie d'un ensemble tout ensemble dont tous les éléments sont aussi des éléments de soit . On écrit alors où est le symbole dit d'inclusion et on dit que est inclus dans . Deux ensembles et sont égaux si et seulement si et . L'ensemble de toutes les parties d'un ensemble est appelé ensemble des parties de et on le note . Il contient en particulier la partie vide et . Pour tout ensemble l'inclusion définit une relation d'ordre partiel sur . Si et on écrit et on dit que est strictement inclus dans .
On décrit un ensemble à l'aide d'accolades ouvrante et fermante qui servent de délimiteurs. Il existe deux manières de décrire un ensemble :
En extension. Dans ce cas on écrit explicitement la liste de tous les éléments qui constituent l'ensemble comme qui décrit l'ensemble constitué des trois entiers et . ;
En compréhension. Cette fois les éléments de l'ensemble sont décrits à l'aide d'un prédicat qui les caractérisent, autrement dit un prédicat que seuls ces objets satisfont. On écrit alors cet ensemble .
Attention, un ensemble est caractérisé par les objets qu'il contient et qui doivent être distincts, l'ensemble n'est constitué que du seul entier naturel il s'agit donc d'un singleton et non d'une paire, l'écriture est simplement redondante et inutile.
Les abus de langage et de notations sont nombreux, par exemple est admis pour décrire l'ensemble des entiers naturels compris entre et un entier naturel que l'on devrait décrire en compréhension à l'aide de l'expression . On note généralement de manière condensée pour décrire la partie d'un ensemble constituée par ses éléments qui satisfont le prédicat (l'existence d'un tel ensemble est un axiome de la théorie des ensembles appelé axiome de sélection).
L'ensemble des entiers naturels inférieurs à 4 s'écrit en extension et en compréhension.
L'ensemble des entiers naturels pairs s'écrit en compréhension :
L'intervalle semi-ouvert à droite de est défini par une écriture en compréhension :
Décrivez l'ensemble des entiers premiers en compréhension. Décrivez l'ensemble des fonctions réelles paires, impaires en compréhension.
Soit un ensemble quelconque. Discutez de la valeur de vérité de chacune des assertions suivantes :
Soit un ensemble. Montrez que et sont respectivement le plus petit et le plus grand élément de pour la relation d'inclusion .
Soit . Écrivez l'ensemble des parties de en extension. Montrez que si est un ensemble fini de cardinal alors .
Soit . Quels sont les éléments minimaux et quels sont les éléments maximaux de l'ensemble pour l'inclusion ? (voir paragraphe ci-dessous pour la définition de la différence ensembliste ).
Opérations ensemblistes
Soient et deux ensembles. On définit la différence ensembliste (on dit privé de ) des deux ensembles et par l'ensemble des éléments de qui n'appartiennent pas à :
Dans le cas particulier où est une partie de la différence symétrique est appelée complémentaire de dans et notée . En général le contexte est non ambigü et l'ensemble contenant est omis dans l'écriture, on écrit donc plus simplement ou . On définit la réunion des deux ensembles et par l'ensemble des éléments qui appartiennent à ou à :
On définit l'intersection des deux ensembles et par l'ensemble des éléments qui appartiennent à et à :
Deux ensembles dont l'intersection est vide sont dits disjoints. On définit la différence symétrique des deux ensembles et par l'ensemble des éléments qui appartiennent à ou à mais pas aux deux, i.e.
Vous pouvez visualiser le résultat de ces opérations ensemblistes en les survolant ci-dessous. La zone en surbrillance fournit le résultat. Ce type de représentation s'appelle un diagramme de Venn :
Soient et trois ensembles. Montrez que la réunion et l'intersection sont des lois associatives et commutatives :
Montrez que l'intersection est distributive sur la réunion et réciproquement :
Soient deux ensembles. Montrer que si et seulement si .
Structures de données
Le lecteur trouvera dans le chapitre Modèle et structures de données liste les définitions des tableaux et des listes chaînées qui vont être utilisés ici. Pour l'aspect statique des opérations, au sens ou les ensembles impliqués ne sont pas modifiés, nous devons bien sûr pouvoir accéder à chaque élément d'un ensemble et les opérations courantes sont :
Appartenance : déterminer si un objet appartient ou non à un ensemble ;
Cardinalité : calculer le cardinal d'un ensemble ;
Réunion : calculer la réunion de deux ensembles.
Intersection : calculer l'intersection de deux ensembles ;
Différence : calculer la différence de deux ensembles.
Différence sym. : calculer la différence symétrique de deux ensembles.
Pour l'aspect dynamique, nous devons pouvoir réaliser :
Ajout : ajouter un élément à un ensemble ;
Suppression : supprimer un élément d'un ensemble ;
Tableaux
Ce sont en général les structures de données associées au modèle de données liste qui sont utilisées pour coder les ensembles. Autrement dit, un ensemble est codé par une liste de ses éléments, il bénéficie donc des mécanismes d'indexation quand la structure associée le permet, comme un tableau par exemple. Dans ce cas les éléments de l'ensemble sont par conséquent ordonnés à travers leurs positions dans la structure (cf. exercice ci-dessous). Dans toute la suite, et désignent deux ensembles de cardinal et respectivement.
Soit un ensemble fini. Par définition il existe un entier et une bijection ⟦⟧. Démontrez que la relation définie sur par est une relation d'ordre total.
Tableaux
Nous allons aborder succinctement les algorithmes réalisant les 8 opérations précédentes dans le cas où la structure pour coder un ensemble est un tableau. Sans autre hypothèse, tester l'appartenance d'un objet à nécessite de parcourir la structure associée et le coût est de dans le meilleur des cas ( est en première position) et dans le pire des cas ( n'appartient pas au tableau). En revanche, si l'ensemble peut-être muni d'une relation d'ordre total et que les éléments de sont triés dans le tableau, la recherche peut se faire en par dichotomie si
Calculer le cardinal de est une opération qui est généralement réalisée en cette information étant souvent rangée dans la structure de donnée et mise à jour en cas d'ajout ou de suppression d'un élément. Dans le cas contraire, il faut évidemment opérations.
La réunion de deux ensembles et consiste à créer un tableau résultat et à y ranger successivement les objets de puis ceux de le coût est donc .
Si les objets ne sont pas triés, le calcul de l'intersection se fait en créant un tableau résultat et en y rangeant chaque élément de uniquement s'il appartient à . Le coût est donc . Si les tableaux sont triés, on teste alors l'appartenance d'un élément du plus petit des deux ensembles (disons ) dans par dichotomie, le coût est alors réduit en .
La différence s'obtient de manière similaire au calcul de l'intersection, en s'assurant cette fois que l'élément courant n'appartient pas à . Les complexités sont donc identiques.
La différence symétrique est obtenue en appliquant successivement la réunion et l'intersection. (Peut-on faire un peu mieux ?)
Compte tenu de la structure statique de tableau, l'ajout d'un nouvel élément dans n'est pas réalisable directement. Cela nécessite de créer un nouveau tableau de taille et d'y recopier le tableau initial avant de rajouter . Ceci suppose que n'appartenait pas à avant l'opération. Le coût est donc en ). La suppression d'un élément se passe de manière similaire en allouant un nouveau tableau de taille puis en testant lors de la copie si l'élément courant à ranger est différent de . Le nouveau tableau peut bien sûr remplacer l'ancien si l'ajout ou la suppression d'un objet nécessite ou non de le conserver.
Écrivez les fonctions C qui réalisent ces 8 opérations sur des ensembles représentés à l'aide de tableaux.
Listes chaînées
Nous reprenons la même démarche que pour les tableaux mais cette fois avec la structure dynamique de liste chaînée. Tester l'appartenance d'un objet nécessite de parcourir la liste, on retrouve donc la même complexité. En revanche l'absence d'accès direct ne permet pas de faire une recherche dichotomique pour baisser le coût.
La réunion est réalisée de la même manière que pour une structure statique, le coût est donc identique en . En revanche, si l'on n'a pas la nécessité de conserver les deux listes, il suffit de chaîner la dernière cellule de la liste chaînée codant vers la première cellule de la liste chaînée codant et dans ce cas le coût est .
L'intersection est réalisée de la même manière que pour les tableaux sans pouvoir bénéficier du tri des éléments, le coût reste donc en .
Pour ajouter un élément qui n'est pas déjà dans la liste, il suffit de créer une nouvelle cellule en tête de liste ou en bout de liste, le coût est donc en . La suppression d'un objet présent dans le liste demande de le cherche en parcourant la liste, la suppression en elle même n'a qu'un coût de puisqu'il suffit de décrocher la cellule et de la libérer de la mémoire, le coût est donc .
Nous verrons au chapitre Fonctions et tables de hachage une autre structure permettant, entre autres, de coder les ensembles et comment elle permet d'obtenir des opérations parfois moins coûteuses.
Écrivez les fonctions C qui réalisent ces 8 opérations sur des ensembles représentés à l'aide de listes chaînées.
Codage par vecteur caractéristique
Algèbre de Boole
En logique, les trois opérateurs de négation, de disjonction et de conjonction sont notés respectivement et mais le mathématicien britannique George Boole a noté des similitudes entre le calcul algébrique sur les nombres et le calcul en logique propositionnelle, il a donc algébrisé la logique. En interprétant comme la valeur de vérité faux et 1 comme la valeur de vérité vrai, on peut munir l'ensemble d'une structure algébrique particulière appelée algèbre de Boole à l'aide de ces trois opérateurs. Pour cela, on leur réserve une notation plus adaptée au calcul algébrique :
0
1
1
0
non
0
0
0
0
1
1
1
0
1
1
1
1
ou
0
0
0
0
1
0
1
0
0
1
1
1
et
Tables de vérité des opérateurs logiques non, ou, et.
Ces trois opérateurs fonctionnent de manière analogue à ce que leur notation algébrique suggère en héritant essentiellement des mêmes propriétés que s'il s'agissait du calcul dans . La loi multiplicative est ainsi souvent notée et on omet parfois de l'écrire dans les expressions algébriques quand il n'y aucune ambiguité, au lieu de ou .
La négation est donc involutive i.e. le tiers exclus s'exprime et la non-contradiction. On dispose d'autre part des propriétés suivantes :
Le principe de dualité permet de déduire les mêmes propriétés pour l'opérateur . Il suffit d'échanger le rôle des opérateurs et et celui des valeurs et . Par exemple est la propriété duale de . Notons que n'admet pas de symétrique pour l'addition mais qu'il est son propre symétrique pour la multiplication. On retrouve également les lois de De Morgan (du mathématicien britannique Augustus De Morgan) qui sont deux lois duales :
On définit trois nouveaux opérateurs, la disjonction exclusive, l'implication et l'équivalence à partir des trois premiers à l'aide des équivalences logiques suivantes :
Leurs tables de vérité sont :
0
0
0
0
1
1
1
0
1
1
1
0
ou exclusif
0
0
1
0
1
1
1
0
0
1
1
1
implication
0
0
1
0
1
0
1
0
0
1
1
1
équivalence
Tables de vérité des opérateurs logiques ou exclusif, implication et équivalence.
Démontrez la propriété de redondance (et sa duale) i.e.
Montrez la propriété de simplification, i.e. ,
Dressez les tables de vérité des deux opérateurs logiques (non ou) et (non et) définis respectivement par
Démontrez que l'opérateur non ou est un opérateur universel, c'est-à-dire que l'on peut définir les trois opérateurs non, ou et et par combinaison de cet opérateur. Indication : on a . Montrez que l'opérateur non et est lui aussi universel.
Il faut remarquer les similitudes entre les lois de l'algèbre de Boole et les opérations ensemblistes :
négation complémentaire
ou réunion
et intersection
ou exclusif différence symétrique
Vecteurs caractéristiques
Nous allons étudier à présent une autre structure de données très utilisée en pratique car extrêmement performante en terme de temps et d'espace. Soit un ensemble. On rappelle que la fonction indicatrice d'une partie de est définie par
𝟙
Supposons à présent que soit un ensemble fini est notons son cardinal. On peut donc numéroter ses éléments et écrire (cf. exercice). On définit une application par
𝟙𝟙𝟙
Soit un ensemble et une partie de . Le vecteur est appelé vecteur caractéristique de la partie de .
Prenons l'exemple d'un jeu de cartes. Comment représenter une main, c'est-à-dire un sous-ensemble de cartes ? Il faut tout d'abord se fixer une énumération de l'ensemble des cartes, par exemple dans l'ordre des couleurs et des valeurs suivantes :
Notons que pour tester l'appartenance l'opération renvoie un entier non-nul si et 0 sinon. Pour les langages de programmation, les opérateurs bit à bit agissant sur des registres vus comme des entiers, il est donc impossible de conserver les notations usuelles de l'algèbre de Boole puisque l'opérateur par exemple est généralement dédié à l'addition sur les entiers et non pas au ou logique. Le langage utilise les symboles ~A, A | B, A & B, A ^ B, A << s et A >> s respectivement à la table ci-dessus.
Écrivez un algorithme Poids qui calcule le poids binaire d'un entier naturel en supposant que le nombre de ses chiffres est plus petit que la taille d'un registre machine et en utilisant les opérateurs bit à bit. En supposant que les registres puissent avoir une taille arbitrairement grande, calculez la complexité de votre algorithme en fonction du nombre de chiffres de .
Soit un entier naturel. Calculez l'entier et comparez son écriture binaire avec celle de . Que remarquez vous ? En déduire un nouvel algorithme de calcul du poids binaire. Calculez sa complexité.
Le corps à deux éléments
En guise de complément, nous présentons une autre structure algébrique fondamentale sur l'ensemble et qui intervient principalement dans le domaine de l'information et de la communication.
On rappelle qu'une loi de composition interne*Une loi de composition externe est une application est une application de . On utilise la notation infixe plutôt que la notation préfixe généralement employée pour les fonctions. Une loi de composition interne peut disposer de propriétés remarquables :
Associativité : .
Élément neutre : ( est l'élément neutre).
Élément symétrique : ( est le symétrique de ).
Commutativité : .
Si est muni de deux lois de composition, et on peut disposer de la propriété de distributivité de sur :
Si la loi est commutative, la distributivité à gauche entraîne la distributivité à droite et réciproquement. Dans ce cas, on ne précise plus à gauche ou à droite.
Un ensemble muni d'une loi associative, disposant d'un élément neutre et d'un élément symétrique pour tout élément de constitue ce que l'on appelle un groupe. Ce groupe est un groupe commutatif si la loi est commutative. Par exemple est un groupe commutatif, tout comme . De plus la distributivité de sur font du triplet ce que l'on nomme un corps (commutatif ici car la loi multiplicative est commutative).
Comme pour l'ensemble des nombres réels on peut munir la paire d'une structure de corps commutatif avec deux lois de composition internes, l'addition notée et la multiplication notée ou dont les graphes sont fournis par les tables ci-dessous. On note ce corps .
0
1
0
0
1
1
1
0
0
1
0
0
0
1
0
1
Tables d'addition et de multiplication de .
Pour l'addition, les deux éléments et sont leurs propres symétriques (on dit aussi opposés pour une loi additive), i.e. et et pour la multiplication est son propre symétrique (on dit aussi inverse pour une loi multiplicative), i.e. .
Soit un entier naturel. Le produit cartésien de copies de l'ensemble est muni d'une structure d'espace vectoriel à l'aide d'une loi de composition interne et une loi de loi de composition externe définies par
Aucune ambiguïté n'étant possible et afin de ne pas allourdir les notations, on utilise généralement les mêmes symboles et pour désigner ces opérations sur les deux ensembles, et . Un élément de est appelé vecteur binaire. Le corps et le -espace vectoriel jouent un rôle absolument fondamental en informatique.
La table de multiplication sur coïncide bien avec la table de vérité du et logique, et les notations sont identiques, la table d'addition correspond en revanche à la table de vérité du ou exclusif. Il faut donc prendre garde au contexte pour les notations.
Travaux pratiques
Écrivez la fonction Cushort poids(ullong mot) qui calcule le poids binaire d'un mot (cf. exercice).
Écrivez les fonctions C qui réalisent les opérations fondamentales sur les ensembles représentés à l'aide de vecteurs caractéristiques et des opérateurs bit à bit (décalage, opérations logiques et masques) et calculez la complexité de vos fonctions.
En exploitant la structure de vecteur caractéristique, écrivez un programme en C qui simule le jeu de la pêche avec un jeu de 32 cartes.
On supposera qu'il y a 4 joueurs. Le gagnant est celui qui se sera débarrassé avant les autres des cartes de sa main. Initialement on distribue au hasard 5 cartes à chacun et les 12 cartes restantes sont placées en pile face cachée au milieu de la table. Celle pile constitue le lac.
À tour de rôle chaque joueur se débarrasse de toutes les paires qu'il peut constituer avec les cartes dans sa main en les déposant en pile dans son seau. S'il n'a pas ou plus de paire, il demande à un joueur de son choix (choisi au hasard par le programme) une carte qui lui permettrait de constituer une paire (choisie au hasard parmi les cartes qui restent dans sa main). Deux situations se présentent :
L'autre joueur peut lui fournir la carte, la paire est alors placée dans le seau. S'il n'a plus de carte, la partie est gagnée, sinon il recommence.
L'autre joueur ne peut pas lui fournir la carte, le joueur en pêche une dans le lac et la rajoute dans sa main. C'est au prochain joueur de jouer.
Estimez de manière expérimentale la durée moyenne d'une partie, l'unité de mesure étant le nombre d'actions réalisées, soit une paire déposée dans un seau ou une carte pêchée dans le lac. En numérotant les joueurs de 1 à 4 dans l'ordre de jeu, le joueur numéro 1 étant celui qui commence, calculez expérimentalement la probabilité que le joueur numéro gagne la partie.