Beruflich Dokumente
Kultur Dokumente
FIFO
Implantation statique du TDA File
Représentation statique du TDA File en ADA
Représentation linéaire
Représentation circulaire
Représentation statique du TDA File en C
Représentation statique du TDA File en JAVA
Représentation statique du TDA File en C++
Implantation dynamique du TDA File
Représentation dynamique du TDA File en ADA et en C
Représentation dynamique du TDA File en Java
Utilisation du TDA File
TDA Listes (List)
Listes linéaires
Listes Triées
Implantation semi-dynamique (Curseurs)
Implantation semi-dynamique en ADA
Implantation dynamique
Implantation dynamique en C
Sentinelle et double chaînage
Implantation récursive du TDA Liste
Graphes (Graphs)
Graphes non orientés
Graphes orientés simples
Définitions
Spécification du TDA GrapheOrienté simple
Utilisation des Graphes
Représentation d'un graphe simple orienté
Matrice booléenne associée (ou matrice d'adjacence)
Matrice d'incidence aux arcs
Première solution : Liste de successeurs
Deuxième solution : Liste de sommets et de successeurs
Troisième solution : Structures homogènes
Parcours de Graphes
Parcours en profondeur
Parcours en largeur
Détection d'un circuit
Tri topologique
Arbres
Terminologie
Spécification du TDA ArbreBin (Type Arbre Binaire (2-aire))
Utilisation des Arbres
Parcours d'Arbre
Parcours en profondeur d'abord
Représentation des Arbres
Arbre Binaire : Représentation chaînée
Implantation dynamique d'ArbreBin en ADA
Implémentation dynamique d'ArbreBin en C
Arbre n-aire : Représentation chaînée
Arbre Binaire : Représentation semi-dynamique
ArbreBinaire en largeur : représentation dans un tableau
Arbre Binaire de recherche
Implantation dynamique d'un arbre de recherche en ADA
Utilisation d'un Arbre Binaire de Recherche pour trier un tableau
Arbre équilibré
Isomorphisme liste-arbre
Modularité : Principes
Principes à respecter :
Le problème doit être décomposé en modules indépendants : division du travail et simplification du problème au niveau
de chaque module.
Les modules produits doivent pouvoir être facilement combinés les uns avec les autres pour construire de nouvelles
applications éventuellement dans des environnements très variés.
Les modules développés dans la cadre d'une application doivent être compréhensibles séparément (indépendamment les
uns des autres) (commentaires explicites, spécification bien pensée, lisibilité du code, sous-programmes bien ciblés et
courts, etc.).
L'application doit être conçue de telle façon qu'un changement mineur de spécification aura pour résultat des
changements très limités et bien contrôlés au niveau des modules.
· Principe N°1 : ne pas utiliser de constantes "en dur" dans le code.
Chaque module qui effectue un traitement susceptible de produire une erreur doit le plus possible en assurer le
traitement.
· L'opération qui effectue une saisie clavier doit assurer que l'entrée tapée par l'utilisateur est correcte avant de la
transmettre à l'appelant.
Chaque module doit correspondre avec aussi peu de modules que possible :
Deux modules qui communiquent s'échangent aussi peu d'informations que possible, juste le nécessaire.
Si deux modules communiquent, ceci doit être évident au coup d'œil (paramètres explicites, commentaires,
documentation…).
Encapsulation : masquage de l'information. Tout accès à une donnée d'un module doit être protégé. Une modification
d'une donnée doit être effectuée uniquement par le module dans lequel est défini cette donnée (pas de modification d'une
variable d'un module dans du code extérieur au module.
un module est construit à partir d'un paquetage (package) comprenant les deux parties suivantes :
● la spécification du package qui contient les données et signatures des
opérations (sous-programmes) visibles depuis l'extérieur => INTERFACE,
En ADA
● · le corps du package (body) qui contient le code des opérations
(sous-programmes) déclarées dans la partie spécification du package et le code
des opérations (sous-programmes) non visibles depuis l'extérieur => CORPS.
un module est implanté par une classe qui décrit les attributs (données) et les méthodes
En JAVA
(sous-programmes) accessibles ou non accessibles depuis l'extérieur.
Point2D_P.creerPoint(p);
Point2D_P.changerValeur(p, 10.0, 20.0);
Point2D_P.changerValeur(p, 11.0, 22.0);
float_io.put(Point2D_P.abscisse(p)); -- affiche 11.0
end essai1;
En C
Point2D.h
/* Définition des données */
/* le module exporte le type Point2D */
/* ATTENTION, déclaré ainsi le type n'est pas protégé */
typedef struct {
float coord[2];
} Point2D;
/* Spécification des opérations (sous-programmes) */
/* Le Point est créé en (0,0) */
extern void creerPoint(Point2D *); /* Point en IN OUT */
/* Le Point est déplacé de (x,y) */
extern void changerValeur ( Point2D *,/* Point2D en IN OUT */
float, /* coord en x */
float); /* coord en y */
Point2D.c
#include "Point2D.h"
/* Corps des sous-programmes */
/* Le Point est créé en (0,0) */
void creerPoint(Point2D *p) { /* Point2D en IN OUT */
(*p).coord[0] = 0.0f;
(*p).coord[1] = 0.0f;
}
/* Le Point est déplacé de (x,y) */
void changerValeur ( Point2D * p, /* Point2D en IN OUT */
float x, /* coord en x */
float y) { /* coord en y */
(*p).coord[0] = (*p).coord[0] + x;
(*p).coord[1] = (*p).coord[0] + y;
}
En JAVA :
// la classe met en oeuvre le type Point2D
class Point2D {
// attributs = données qui définissent un Point2D
protected double coord[]; // l'accès de la donnée est protégé
// opérations, méthodes
// Le Point est créé en (0,0)
public Point2D() { // méthode de création d'un nouveau Point2D
coord = new double[2];
coord[0] = 0.0;
coord[1] = 0.0;
}
// Le Point est déplacé de (x,y)
public void changerValeur ( float x, // coord en x
float y) {// coord en y
coord[0] = coord[0] + x;
coord[1] = coord[0] + y;
}
}
Définitions
Un TDA est un ensemble de données et d'opérations sur ces données.
Un TDA est une vue logique (abstraite) d'un ensemble de données, associée à la spécification des
opérations nécessaires à la création, à l'accès et à la modification de ces données.
Un TDA est un ensemble de données, organisé pour que les spécifications des données et des opérations sur
ces données soient séparées de la représentation interne des données et de la mise en œuvre des opérations.
Un TDA est caractérisé par :
Son identité (nom, adresse en mémoire).
Le type des données qu'il manipule (ce qui définit son état ou caractéristiques).
Les opérations sur les données (sous-programmes définis dans le TDA).
Les propriétés de ces opérations (axiomes permettant de définir le TDA).
Exemple : Spécification et Body d'un package ADA qui peuvent être dans des fichiers séparés.
● Les TDA permettent de cacher à l'utilisateur la façon dont les données sont mises en œuvre et
donc d'empêcher l'utilisateur des TDA de modifier lui-même les données. Il faut l'obliger à
utiliser des opérations du TDA pour modifier les données.
Exemple : En ADA, le type déclaré dans la partie Spécification doit être déclaré PRIVATE
(voire LIMITED PRIVATE) pour empêcher toute modification depuis l'extérieur du package.
● La spécification des types abstraits est indépendante du langage, de l'implémentation.
Par contre la mise en œuvre doit être réalisée avec un langage permettant l'abstraction et
l'encapsulation des données.
● Les TDA permettent la prise en compte de types complexes (bâtis à partir des types de base).
● En finale, les TDA sont les briques d'une conception modulaire rigoureuse.
Exemple : Pile
Intéressons nous à la façon de spécifier un TDA en prenant l'exemple de la Pile qui est basé sur l'observation de piles de la
vie réelle : Pile de livres, d'assiettes …
Les Piles sont très utilisées en informatique, non seulement dans des application utilisateurs mais également lors de
l'exécution de programmes où elles sont utilisées pour stocker les contextes d'exécution des sous-programmes.
· On accède au 1° élément : le sommet.
· On enlève le 1° élément : on dépile.
· On ajoute un élément sur la pile : on empile.
On décrira des opérations manipulant une pile avec les termes : sommet , empiler , dépiler…
● Avantages : c'est une formulation universelle qui est indépendante du langage cible (C, ADA, JAVA, etc.).
● Contrainte : il faudra construire avec le langage cible une structure de données représentant ce type abstrait de
données, à l'aide de tableaux, structures, pointeurs, etc. et les fonctions ou procédures implantant les opérations sur le
type.
● Le type est connu à l'extérieur de la Pile. Dans la signature des opérations se trouve la Pile passée en paramètre.
· empiler (p : in out Pile, e : Element);
· sommet (p : Pile) return Element;
· …
● Le client de la Pile applique les opérations à une Pile particulière qu'il a déclarée.
· en ADA
with Pile_P, integer_io;
procedure … is
p1, p2 : Pile_P.Pile -- on considère que ce sont des Piles d'entier
begin
Pile_P.empiler(p1, 10);
Pile_P.empiler(p2, 20);
integer_io.put(Pile_P.sommet(p1)); -- affiche 10
integer_io.put(Pile_P.sommet(p2)); -- affiche 20
…
Machine abstraite :
Le TDA peut être conçu comme une MachineAbstraite (machine à états abstraits)
● Dans ce cas, le type n'est pas connu de l'extérieur de la Pile (pas exporté). Le client de la Pile ne voit que les opérations
:
· empiler (e : Element);
· sommet return Element;
· …
● Si le client veut utiliser une Pile il faut créer une ou plusieurs références à la machine abstraite Pile :
· en ADA:
● La classe EST un type. Elle protège ses données en ne permettant leur accès qu'au travers des opérations exportées.
· en JAVA :
class Pile {
// Attributs
…
// méthodes (opérations)
public void empiler (int i) { …}
public Element sommet ( ) {…}
…
}
// classe cliente de la Pile
import Pile;
class Bidon {
// attributs
protected Pile p1, p2; // ici p1 et p2 ne sont que des références nulles
// opérations
// constructeur de la classe Bidon :
// alloue de la place pour les attributs de la classe Bidon
public Bidon ( ) {
Spécification
La propriété fondamentale d'une Pile est qu'on ne peut accéder qu'à l'élément en sommet de Pile. Tous les autres éléments,
placés en dessous du sommet, ne peuvent être accédés qu'après avoir tout d'abord enlevé les éléments placés au dessus d'eux
un par un.
● Comment définir ou spécifier le concept abstrait de Pile ?
Il est nécessaire de spécifier tout ce qu'il faut connaître pour utiliser correctement et aisément une Pile, sans pour autant
avoir à connaître l'implantation sous-jacente.
Représentation et implantation
Il y a plusieurs manières de représenter les piles.
Statique : Tableaux => nombre max d'éléments fixés a priori.Gestion simple mais taille statique.
Chaînages mis en œuvre à l'aide de pointeurs => nombre d'éléments uniquement limité par la place mémoire
Dynamique :
disponible.Gestion plus complexe mais taille dynamique.
Types de base
Définition :
Le typage est le fait d'associer à une variable (un objet), un ensemble de valeurs possibles
ainsi que d'un ensemble d'opérations admissibles sur ces valeurs.
Type = {ensemble de valeurs, opérations possibles}
Le langage de programmation doit vérifier (autant que possible) que les valeurs que l'on
place dans une variable, ainsi que le résultat d'une opération sont compatibles avec le(s)
type(s) correspondant à l'affectation et à l'opération utilisée.
Les entiers
Ils représentent des valeurs numériques signées ne comportant pas de partie fractionnaire.
Le nombre de valeurs représentables sur un ordinateur étant fini, les entiers sont
généralement limités à un intervalle de valeurs possibles.
En ADA integer'last qui est donc le plus grand nombre entier représentable. Les valeurs de
integer'first et integer'last dépendent de l'implantation : sur un micro-ordinateur, leur valeur
sera plus petite que sur de plus gros systèmes.
En C, les valeurs représentables dépendent de la taille du mot machine.
Les booléens
Les objets de type booléen ne peuvent prendre que deux valeurs possibles : true et false. Les
expressions comportant des valeurs booléens sont appelées des expressions logiques. Les
opérations possibles sont : and, or, not et la comparaison ( =, /= ).
Il est à noter que les opérateurs de comparaison donnent un résultat booléen, même si les
opérandes sont d'un autre type.
Les caractères
Le jeu de caractères d'une machine est l'énumération, dans un certain ordre, des lettres et
signes affichables sur un écran ou imprimables sur papier.
Les opérations possibles sont principalement les comparaisons : =, <>, >, <, <=,
>=..
Les intervalles
Les intervalles ne forment pas, à proprement parler, un type de base. Il s'agit plutôt de types,
générés à partir de types de base ou définis par l'utilisateur, restreignant l'ensemble des
valeurs possibles du type parent.
Exemple :
subtype JourOuvrable is Semaine range lundi .. vendredi;
subtype natural is integer range 0..integer'last;
subtype positive is integer range 1..integer'last;
Toutes les opérations applicables au type de base sont applicables au type qui en est dérivé.
L'intérêt de l'utilisation des intervalles réside dans l'amélioration de la lisibilité des
programmes (on voit plus clairement quelles sont les valeurs possibles) et l'augmentation de
la sécurité de programmation (des valeurs illégales peuvent être automatiquement détectées
à la compilation et à l'exécution).
Ensemble de valeurs
Vrai, faux
Opérations :
not : booléen à booléen // fournit non b
en entrée : b : booléen
en sortie : b' : booléen
pré conditions : aucun
post conditions : b' == vrai si b == faux, b' == faux sinon
ou : booléen x booléen à booléen // ou de deux booléens
en entrée : a, b : booléen
en sortie : b': booléen , a et b non modifiés
pré conditions : aucun
post conditions : b' == vrai si a == vrai ou si b == vrai, b' == faux sinon
et : booléen x booléen à booléen // et de deux booléens
en entrée : a, b : booléen
en sortie : b': booléen , a et b non modifiés
pré conditions : aucun
Ensemble de valeurs
integer'first .. integerl'last à intervalle dans Z
Opérations :
+ - * / mod % power -unaire
!= == < > <= >=
:= pred succ
(on ne détaillera que les suivantes)
succ : integer à integer // fournit l'entier suivant
en entrée : i : integer
en sortie : i' : integer (i non modifié)
pré conditions : i < integer'last
post conditions : i' = i+1
+ : integer x integer à integer // addition de deux entiers
en entrée : i, j : integer
en sortie : i' : integer (i et j non modifiés)
pré conditions : i + j <= integer'last et i + j >= integer'first
post conditions : i' = i+j
- : integer à integer // moins unaire
en entrée : i: integer
en sortie : i' : integer
pré conditions : aucun
post conditions : i' = -i
== : integer x integer à booléen // égalité d'entiers
en entrée : i, j : integer
en sortie : b : booléen (i et j non modifiés)
pré conditions : aucune
post conditions : b = vrai si i ==j, faux sinon
< : Natural x Natural à booléen // comparaison d'entiers Natural
en entrée : i, j : Natural
en sortie : b : booléen (i et j non modifiés)
pré conditions : aucune
post conditions : b = vrai si i < j, faux sinon
Axiomes : m, n, p : integer
(1) 0 + n = n // élément neutre à gauche
(1') n + 0 = n // élément neutre à droite
(2) succ(m) + n = succ(m+n) // définition de +
(3) (m + n) + p = m + (n + p) // associativité de +
(4) m + n = n + m // commutativité de +
(5) 0 == 0 est vrai // définition de ==
(6) succ(m) == 0 est faux // ""
(7) 0 == succ(n) est faux
(8) succ(m) == succ(n) <=> n == m
(9) 0 < 0 est faux // définition de <
(10) succ(m) < 0 est faux
(11) 0 < succ(n) est vrai
(12) succ(m) < succ(n) <=> m < n
(13) m < n et n < p <=> m < p
(14) -0 == 0 // définition du moins unaire
(15) -(-n) == n // moins unaire involutive
(16) succ(n) + (-succ(m)) == n + (-m) // n+1 + (-(m+1))
(17) -n + (-m) = -(n + m)
Axiome concernant les opérations arithmétiques :
m = ((m / n) * n) + (m mod n) // 3 = ((3 / 2) * 2) + (3 mod 2) = (2) + (1) = 3
// 2 = ((2 / 3) * 3) + (2 mod 3) = (0) + (2) = 2
Les réels
● réels:
° virgule flottante
° virgule fixe
De tous les types de base, ce sont les nombres réels qui posent le plus de problèmes de
représentation. L'ensemble des nombres réels étant, par définition, infini et indénombrable,
il est impossible de le représenter avec une totale exactitude dans un ordinateur. La
représentation qui en est faite ne peut donc constituer qu'une approximation. Il existe
d'ailleurs plusieurs techniques de représentation, chacune ayant ses avantages et ses
inconvénients.
Conversion de type
Le langage ADA impose, des contraintes lors de la conversion de types. Il fait la distinction
entre types dérivés et sous-types. Un sous-type, de même qu'un type dérivé, consiste en une
restriction apportée à un type de base. Un sous-type reste compatible avec son type de base
alors qu'un type dérivé est incompatible avec son type de base.
Exemples :
subtype OneDigit is integer range 0..9;
subtype JourOuvrable is Semaine range
Lundi..Vendredi;
type Entier is new integer;
type MilieuDeSemaine is new Semaine range
Mardi..Jeudi;
Dans les exemples qui précèdent :
OneDigit est compatible avec integer,
JourOuvrable est compatible avec Semaine,
mais Entier n'est pas compatible avec integer : c'est un nouveau type !
et MilieuDeSemaine n'est pas compatible avec Semaine : c'est également un nouveau
type...
Il est toutefois possible de faire une conversion explicite d'un type dérivé vers son type de
base (ou vice-versa) en préfixant l'objet du type à convertir par le type du résultat. Par
exemple, si Jour est une variable de type Semaine, MilieuDeSemaine(Jour) est de type
MilieuDeSemaine (il y aura erreur si la valeur associée à la variable Jour n'est pas comprise
dans l'ensemble des valeurs possibles pour le type MilieuDeSemaine ).
L'intérêt de cette distinction entre types dérivés et sous-types est une fois encore au niveau
de la sécurité de programmation. Par exemple, si une variable est sensée représenter une
superficie et une autre variable sensée représenter une longueur, cela n'aurait pas beaucoup
de sens de vouloir les additionner, même si elles sont toutes les deux des valeurs numériques
entières.
Types composés
Les types structurés (composés) sont implantés grâce à des :
· structures statiques
ou
· structures dynamiques
Les tableaux
Les tableaux sont constitués d'un regroupement de données de même type. Les données
individuelles sont repérées par un sélecteur que l'on nomme indice du tableau. L'indice d'un
tableau doit être de type énuméré. Cela pourra donc être un type énuméré défini par
l'utilisateur, un intervalle d'entiers ou de booléens.
Des variables de type tableau ne changent que de valeurs, jamais de structure
ou d'ensemble de valeurs de base. Conséquence : l'espace qu'elles occupent en
mémoire reste constant.
Exemple
procedure essai is
subtype Index is integer range 0..9;
type Semaine is (Dimanche, Lundi, Mardi, Mercredi,
Jeudi, Vendredi, Samedi);
type T1 is array (Index) of integer;
type T2 is array (0..9) of integer;
type T3 is array (boolean) of character;
type T4 is array (false..true) of character;
type T5 is array (Semaine) of integer;
heuresDeTravail : T5;
begin
...
heuresDeTravail[Lundi] := 8;
...
end essai;
● La spécification de la structure de tableau peut se faire de la manière suivante :
leur déclaration spécifie un intervalle d'indices et un type de base. Ceci détermine
indirectement la taille du tableau.
le rôle du tableau est de conserver pour chaque valeur d'indice une valeur de type de
base associée.
une seule valeur du type de base peut être associée à chaque valeur d'indice.
● Les primitives de manipulations sont :
associer une valeur du type de base à une certaine valeur d'indice,
fournir la valeur de type de base associée à une certaine valeur d'indice.
en fonction de la déclaration, diagnostiquer la validité de l'indice fourni.
● On peut envisager de pouvoir associer, lors de la déclaration d'un tableau, une valeur
initiale à quelques (ou toutes les) valeurs d'indices. C'est le cas en Ada, par exemple.
● Deux valeurs se suivent dans le tableau si les valeurs des indices auxquels elles sont
associées se suivent dans la séquence des indices. Cela ne signifie pas pour autant que
ces valeurs seront effectivement stockées dans des zones contiguës de la mémoire
(même si c'est presque toujours le cas en réalité, pour des raisons d'efficacité).
contenir.
· La taille nécessaire pour un enregistrement est ainsi fixée.
· Chaque champ doit contenir une valeur correspondante à son type.
● Les opérations possibles sur les enregistrements dans leur globalité sont :
· l'affectation
· la comparaison ( =, <> ).
· Les opérations possibles sur les différents champs d'un enregistrement sont celles
associées au type du champ.
En C, les chaînes de caractères sont stockées dans des tableaux de caractères et doivent
obligatoirement être terminées par un caractère spécial qui indique la fin de la chaîne dans le
tableau : '\0'. Ainsi, une chaîne de n caractères doit être contenue dans un tableau d'au
moins n+1 caractères.
La spécification des chaînes de caractères s'apparente à celle des tableaux, avec des
opérations supplémentaires, différentes selon les langages.
Les ensembles
Les ensembles sont des structures que l'on ne retrouve quasiment qu'en Pascal. Ada ne les
Opérations :
créer : -> Ensemble vide // possibilité d'opérateur créerSingleton
entrée : aucun
sortie : e : Ensemble
pré conditions :
post conditions : e == {} ensemble vide, card(e) == 0
ajout : Elément x Ensemble -> Ensemble
entrée : i : Elément, e : Ensemble
sortie : e' : Ensemble
pré conditions : !present(i, e)
post conditions : e' == e union {i}
suppression : Elément x Ensemble -> Ensemble
entrée : i : Elément, e : Ensemble
sortie : e' : Ensemble
pré conditions : present(i, e)
post conditions : e' == e - {i}
présent : Elément x Ensemble -> Booléen
card (boolean) = 2
card (integer) = 2*(MaxInt+1) { pour le langage Pascal }
Résumé
On peut résumer les principales caractéristiques des structures cartésiennes simples dans le
tableau suivant :
Structure : Tableau Enregistrement Ensemble
r : record
S1 : T1;
Déclaration a : array(I) of To S2 : T2; set of To
Sn : Tn;
end;
Sélecteur : a(i) (i∈I) r.s (S∈S1,...Sn ) aucun
par le sélecteur avec Test d'appartenance avec
Accès aux par le sélecteur
le nom déclaré d'un l'opérateur de relation
composantes: et le calcul de l'indice
élément présent
Type des Toutes de même type Peuvent être de type Toutes de même type
composantes: To différent scalaire To
n
∏ card (Ti)
Cardinalité : Card(To)Card(I) 2Card(TO)
i=1
prod. cartésien
Ces structures simples peuvent être combinées pour former des structures plus complexes .
e1 = creerEnsemble ();
e2 = creerSingleton(1);
printf("e2 = ");
print(e2);
i = 1;
while (ajout(i, &e1) == TRUE)
i++;
printf("e1 = ");
print(e1);
if (suppression(3, &e1)== TRUE) {
printf("e1 = ");
print(e1);
}
printf("e2 = ");
print(e2);
ajout(2, &e2);
printf("e2 + 2 = ");
print(e2);
e3 = clone(e1);
printf("e3 = clone(e1) = ");
print(e3);
if (equals(e1, e3))
printf("e3 egale e1\n");
else printf("pb equals\n");
e3 = creerSingleton(3);
ajout(4, &e3);
if (unionEns(e2, e3, &e4)== TRUE) {
printf("e4 = union(e2, e3) = ");
print(e4);
}
if (inclus(e4, e3)) printf("e3 inclus dans
e4\n");
if (!inclus(e4, e1)) printf("e1 pas inclus dans
e4\n");
e3 = interEns(e1, e4);
printf("e3 = inter(e1, e4) = ");
print(e3);
printf("elementAt(e3, 1) = %d\n", elementAt(e3,
1));
e2 = diffEns(e4, e3);
printf("e2 = diff(e4, e3) = ");
print(e2);
}
// il y a un Element de moins
(*ens).n = (*ens).n - 1;
return TRUE;
}
}
// Element non trouve
return FALSE;
}
// methodes independantes de l'implantation
//--------------------------
int present(Element e, Ensemble ens) {
int i;
// on teste tous les Elements de l'Ensemble
for (i=0; i < cardinal(ens); i++)
// des qu'on trouve on arrete la recherche
if (elementAt(ens, i) == e)
return TRUE;
// tout a ete examine sans trouver
return FALSE;
}
int unionEns(Ensemble ens1, Ensemble ens2, Ensemble *
e) {
int i;
int result;
e = creerEnsemble();
for (i=0; i < cardinal(ens1); i++)
if (present(elementAt(ens1, i), ens2))
ajout(elementAt(ens1, i), &e);
return e;
}
Ensemble diffEns(Ensemble ens1, Ensemble ens2) {
Ensemble e;
int i;
e = clone(ens1);
for (i=0; i < cardinal(ens2); i++)
if (present(elementAt(ens2, i), e))
suppression(elementAt(ens2, i), &e);
return e;
}
int inclus(Ensemble ens1, Ensemble ens2) {
int i;
e = creerEnsemble();
for (i=0; i < cardinal(ens); i++)
ajout(elementAt(ens, i), &e);
return e;
}
void print (Ensemble ens) {
int i;
printf("Ensemble : ");
for (i = 0; i < cardinal(ens); i++)
printf("%d, ", elementAt(ens, i));
printf(" \nFIN Ensemble\n");
}