Algorithmique
Structures de données
Florent Hivert
Mél : Florent.Hivert@lri.fr
Page personnelle : http://www.lri.fr/˜hivert
2 de 87
Types de données
Retenir
Avoir choisi les bons types de données permet d’avoir un
programme
plus lisible car auto documenté
plus facile à maintenir
souvent plus rapide, en tout cas plus facile à optimiser
Structures
séquentielles : les
tableaux
Structures séquentielles : les tableaux 5 de 87
Un tableau en mémoire
Définition
Dans le tableau, tous les éléments ont la même taille mémoire.
Nombre d’élément : n, taille d’un élément t :
Un tableau en mémoire
Définition
Dans le tableau, tous les éléments ont la même taille mémoire.
Nombre d’élément : n, taille d’un élément t :
Tableau en Java
Retenir
Pour simuler un tableau de taille variable, on peut
réserver une certaine quantité de mémoire appelée capacite,
et ranger les valeurs au début du tableau.
Opérations de base
Hypothèses :
tableau de taille capacite alloué
éléments 0 ≤ i < taille ≤ capacite initialisés
Comportements possibles :
Erreur (arrêt du programme, exception)
Ré-allocation du tableau avec recopie, coût : Θ(taille)
Structures séquentielles : les tableaux 12 de 87
Comportements possibles :
Erreur (arrêt du programme, exception)
Ré-allocation du tableau avec recopie, coût : Θ(taille)
Structures séquentielles : les tableaux 13 de 87
Ré-allocation
En C : realloc
Ré-allocation : coût
Problème
Quel est le coût des réallocations ?
Ré-allocation : coût
Problème
Quel est le coût des réallocations ?
1 − q n+1
u0 + u1 + · · · + un = u0 si q 6= 1
1−q
1 − q n+1
1 + q + q2 + · · · + qn =
1−q
Structures séquentielles : les tableaux 16 de 87
n
X n(n + 1)
Bilan : i= écritures.
2
i=1
Structures séquentielles : les tableaux 16 de 87
n
X n(n + 1)
Bilan : i= écritures.
2
i=1
Structures séquentielles : les tableaux 17 de 87
k k
X X k(k + 1) n2
Bilan : bi = b i =b ≈ écritures.
2 2b
i=1 i=1
Structures séquentielles : les tableaux 17 de 87
k k
X X k(k + 1) n2
Bilan : bi = b i =b ≈ écritures.
2 2b
i=1 i=1
Structures séquentielles : les tableaux 18 de 87
Retenir
On suppose que l’on réalloue une case supplémentaire à chaque
débordement. Coût (nombre de copies d’éléments) :
n
X n(n + 1)
i= ∈ Θ(n2 )
2
i=1
Retenir
On suppose que l’on réalloue une case supplémentaire à chaque
débordement. Coût (nombre de copies d’éléments) :
n
X n(n + 1)
i= ∈ Θ(n2 )
2
i=1
Question
Que ce passe-t-il si l’on double la taille à chaque fois ?
Structures séquentielles : les tableaux 20 de 87
k
X 1 − 2k+1
Bilan : 2k = = 2k+1 − 1 ≈ 2n écritures.
1−2
i=0
Structures séquentielles : les tableaux 20 de 87
k
X 1 − 2k+1
Bilan : 2k = = 2k+1 − 1 ≈ 2n écritures.
1−2
i=0
Structures séquentielles : les tableaux 21 de 87
Quelques valeurs :
K 1.01 1.1 1.2 1.5 2 3 4 5 10
K
K −1 101 11 6 3 2 1.5 1.33 1.25 1.11
Interprétation :
Si l’on augmente la taille de 10% à chaque étape, chaque nombre
sera recopié en moyenne 11 fois.
Si l’on double la taille à chaque étape, chaque nombre sera en
moyenne recopié deux fois.
Structures séquentielles : les tableaux 22 de 87
Quelques valeurs :
K 1.01 1.1 1.2 1.5 2 3 4 5 10
K
K −1 101 11 6 3 2 1.5 1.33 1.25 1.11
Interprétation :
Si l’on augmente la taille de 10% à chaque étape, chaque nombre
sera recopié en moyenne 11 fois.
Si l’on double la taille à chaque étape, chaque nombre sera en
moyenne recopié deux fois.
Structures séquentielles : les tableaux 22 de 87
Quelques valeurs :
K 1.01 1.1 1.2 1.5 2 3 4 5 10
K
K −1 101 11 6 3 2 1.5 1.33 1.25 1.11
Interprétation :
Si l’on augmente la taille de 10% à chaque étape, chaque nombre
sera recopié en moyenne 11 fois.
Si l’on double la taille à chaque étape, chaque nombre sera en
moyenne recopié deux fois.
Structures séquentielles : les tableaux 23 de 87
Bilan
Compromis Espace/Temps
Retenir
C’est une situation très classique : dans de nombreux problèmes, il
est possible d’aller plus vite en utilisant plus de mémoire.
Compromis Espace/Temps
Retenir
C’est une situation très classique : dans de nombreux problèmes, il
est possible d’aller plus vite en utilisant plus de mémoire.
Compromis Espace/Temps
Retenir
C’est une situation très classique : dans de nombreux problèmes, il
est possible d’aller plus vite en utilisant plus de mémoire.
En pratique . . .Python
En pratique . . .Java
Conclusion
Retenir
Les bibliothèques standards savent gérer la réallocation d’un tableau
qui grossit sans perte de complexité. Mais il y a un certain surcoût.
Si l’on sait par avance quelle est la taille finale, on peut demander
de réserver de la mémoire dès le début :
en C++ : vector.reserve(size_type n)
en Java : ArrayList.ensureCapacity(int minCapacity)
en Python : liste avec des cases non initialisées [None]*n
Variables Dynamiques et pointeurs 31 de 87
Variables Dynamiques
et pointeurs
Variables Dynamiques et pointeurs 32 de 87
Retenir
Une variable dynamique est anonyme :
(adresse, type, valeur) .
On y accède grâce à un pointeur.
adresse type
les quatre propriétés d’une variable usuelle nom valeur
x ↑T
schéma pour le pointeur p qui repère la VD p a
a T
d’adresse a, de type T et de valeur v
v
x ↑T
le lien dynamique entre le pointeur p et la VD p •
T
qu’il repère est illustré par une flèche
v
Variables Dynamiques et pointeurs 33 de 87
adresse type
les quatre propriétés d’une variable usuelle nom valeur
x ↑T
schéma pour le pointeur p qui repère la VD p a
a T
d’adresse a, de type T et de valeur v
v
x ↑T
le lien dynamique entre le pointeur p et la VD p •
T
qu’il repère est illustré par une flèche
v
Variables Dynamiques et pointeurs 33 de 87
adresse type
les quatre propriétés d’une variable usuelle nom valeur
x ↑T
schéma pour le pointeur p qui repère la VD p a
a T
d’adresse a, de type T et de valeur v
v
x ↑T
le lien dynamique entre le pointeur p et la VD p •
T
qu’il repère est illustré par une flèche
v
Variables Dynamiques et pointeurs 34 de 87
Retenir
Un pointeur p peut valoir NULL : il ne repère aucune VD.
↑T
schéma pour le pointeur p de type ↑ T qui ne p NULL
repère aucune VD
↑T
l’accès à aucune VD pour le pointeur p de p
type ↑ T est illustré par une croix
NULL est une valeur commune à tous les pointeurs, à ceux du type
↑ T comme à ceux des autres types.
Variables Dynamiques et pointeurs 34 de 87
Retenir
Un pointeur p peut valoir NULL : il ne repère aucune VD.
↑T
schéma pour le pointeur p de type ↑ T qui ne p NULL
repère aucune VD
↑T
l’accès à aucune VD pour le pointeur p de p
type ↑ T est illustré par une croix
NULL est une valeur commune à tous les pointeurs, à ceux du type
↑ T comme à ceux des autres types.
Variables Dynamiques et pointeurs 34 de 87
Retenir
Un pointeur p peut valoir NULL : il ne repère aucune VD.
↑T
schéma pour le pointeur p de type ↑ T qui ne p NULL
repère aucune VD
↑T
l’accès à aucune VD pour le pointeur p de p
type ↑ T est illustré par une croix
NULL est une valeur commune à tous les pointeurs, à ceux du type
↑ T comme à ceux des autres types.
Variables Dynamiques et pointeurs 34 de 87
Retenir
Un pointeur p peut valoir NULL : il ne repère aucune VD.
↑T
schéma pour le pointeur p de type ↑ T qui ne p NULL
repère aucune VD
↑T
l’accès à aucune VD pour le pointeur p de p
type ↑ T est illustré par une croix
NULL est une valeur commune à tous les pointeurs, à ceux du type
↑ T comme à ceux des autres types.
Variables Dynamiques et pointeurs 34 de 87
Retenir
Un pointeur p peut valoir NULL : il ne repère aucune VD.
↑T
schéma pour le pointeur p de type ↑ T qui ne p NULL
repère aucune VD
↑T
l’accès à aucune VD pour le pointeur p de p
type ↑ T est illustré par une croix
NULL est une valeur commune à tous les pointeurs, à ceux du type
↑ T comme à ceux des autres types.
Variables Dynamiques et pointeurs 35 de 87
↑T ↑T
p ? allouer(p) p •
======⇒ T
?
Variables Dynamiques et pointeurs 36 de 87
↑T ↑T
p ? allouer(p) p •
======⇒ T
?
Variables Dynamiques et pointeurs 37 de 87
Utilisations
Retenir
Si p repère une VD, cette variable est notée ∗p.
↑T
la VD de type T et de valeur v repérée par p p •
T
est notée ∗p
∗p v
Utilisations
Retenir
Si p repère une VD, cette variable est notée ∗p.
↑T
la VD de type T et de valeur v repérée par p p •
T
est notée ∗p
∗p v
Utilisations
Retenir
Si p repère une VD, cette variable est notée ∗p.
↑T
la VD de type T et de valeur v repérée par p p •
T
est notée ∗p
∗p v
↑T ↑T
p • désallouer(p) p ?
T ========⇒
v
Variables Dynamiques et pointeurs 38 de 87
↑T ↑T
p • désallouer(p) p ?
T ========⇒
v
Variables Dynamiques et pointeurs 38 de 87
↑T ↑T
p • désallouer(p) p ?
T ========⇒
v
Variables Dynamiques et pointeurs 38 de 87
↑T ↑T
p • désallouer(p) p ?
T ========⇒
v
Variables Dynamiques et pointeurs 39 de 87
Variable dynamique en C
Retenir
Déclaration d’un pointeur p de type ↑ T : T *p;
Allocation d’une VD pointée par p :
p = (T *) malloc(sizeof(T)) ;
note : en cas d’échec de l’allocation malloc retourne NULL.
Accès à la VD et à sa valeur :
*p = ...; ... = (*p) + 1;
Désallocation de la VD pointée par p :
free(p);
Variables Dynamiques et pointeurs 42 de 87
Les pointeurs en C
Retenir
Tout type de pointeur
supporte l’opération d’affectation =
peux être utilisé comme type de retour d’une fonction
Note : ajout d’un pointeur à un entier (en cas de variable
dynamique multiple ; tableau)
Pour deux pointeurs de même type :
test d’égalité ==, de différence !=
Note : comparaison <,<=,>,>= (en cas de variable dynamique
multiple ; tableau)
Variables Dynamiques et pointeurs 43 de 87
Le programme :
char *p, *q
p = (char *) malloc(sizeof(char));
*p = ’A’;
q = (char *) malloc(sizeof(char));
*q = ’B’;
*p = *q;
printf("%i, %i\n", p == q, *p == *q);
affiche :
0, 1
Variables Dynamiques et pointeurs 43 de 87
Le programme :
char *p, *q
p = (char *) malloc(sizeof(char));
*p = ’A’;
q = (char *) malloc(sizeof(char));
*q = ’B’;
*p = *q;
printf("%i, %i\n", p == q, *p == *q);
affiche :
0, 1
Variables Dynamiques et pointeurs 43 de 87
Le programme :
char *p, *q
p = (char *) malloc(sizeof(char));
*p = ’A’;
q = (char *) malloc(sizeof(char));
*q = ’B’;
*p = *q;
printf("%i, %i\n", p == q, *p == *q);
affiche :
0, 1
Variables Dynamiques et pointeurs 44 de 87
Retenir
Sauf pour les types de base,
variables = référence = pointeurs cachés sur var. dyn.
allocation avec la commande new
désallocation automatique si plus aucune référence
pas accès à l’adresse, déréférencement automatique
objet Null ou None = pointeur invalide
pas d’appel de méthode sur Null
Variables Dynamiques et pointeurs 45 de 87
Passage de paramètres
Retenir
Tous les paramêtres (type de base, objets, tableaux, . . .), sont
passés par valeur.
Sauf pour types de base (int, float), les valeurs sont des
références.
Passage de paramètres
Retenir
Tous les paramêtres (type de base, objets, tableaux, . . .), sont
passés par valeur.
Sauf pour types de base (int, float), les valeurs sont des
références.
Exemple en Java
public class RefDemo {
changeContenu(arr, 10);
System.out.println(arr[0]); // Affiche 10..
}
Variables structurées, enregistrements 47 de 87
Variables structurées,
enregistrements
Variables structurées, enregistrements 48 de 87
type structuré
Syntaxe
Déclaration de type structuré :
nom_de_type = structure:
nom_du_champ_1 : type_du_champ_1;
nom_du_champ_2 : type_du_champ_2;
...
v: nom_de_type;
Variables structurées, enregistrements 50 de 87
struct nom_de_struct {
nom_du_champ_1 : type_du_champ_1;
nom_du_champ_2 : type_du_champ_2;
...
};
struct nom_de_struct v;
struct s_date {
char nom_jour[9]; // lundi, mardi, ..., dimanche
int num_jour; // 1, 2, ..., 31
int mois; // 1, 2, ..., 12
int annee;
}
Retenir
Toute opération valide sur une variable de type type_du_champ_i
est valide sur v.nom_du_champ_i.
Retenir
Accès à un champ en temps constant : O(1).
Variables structurées, enregistrements 54 de 87
Langages Objets
Retenir
Les objets sont des structures avec,
une syntaxe particulière pour l’appel de fonction
des mécanismes de contrôle de type et de réutilisation de code
amélioré (Héritage)
En conséquence,
Accès à un attribut en temps constant : O(1).
Appel de méthode en temps constant (hors coût des copies si
passage de paramètre par valeur).
Variables structurées, enregistrements 55 de 87
Retenir
En JAVA, comme toutes les valeurs sont des références, le coût
d’un passage de paramêtre est le coût de la copie d’une référence
(ie : pointeur = adresse) : O(1).
Notion d’invariant de structure/classe 56 de 87
Définition
Ensemble des règles qui définissent si un objet d’une classe est
dans un état valide
mise en place par les constructeurs publiques
supposé valide en entrée de chaque méthode publique
peut être cassé en cours de méthode
doit être restauré en sortie de chaque méthode publique
Contrexemple
Contrexemple
Contrexemple
Attributs :
size : entier
capacite : entier
tab : tableau d’éléments elem
0 ≤ size ≤ capacite
tab alloué de taille capacite
pour 0 ≤ i < size, l’élement tab[i] est initialisés
pour size ≤ i < capacite, alors tab[i] = NULL
Notion d’invariant de structure/classe 60 de 87
C++ bitset :
Java : LinkedList.java
Java : HashMap.java
/**
* Recursive invariant check
*/
static <K,V> boolean checkInvariants(TreeNode<K,V> t) {
TreeNode<K,V> tp = t.parent, tl = t.left, tr = t.right,
tb = t.prev, tn = (TreeNode<K,V>)t.next;
if (tb != null && tb.next != t) return false;
if (tn != null && tn.prev != t) return false;
if (tp != null && t != tp.left && t != tp.right) return false;
if (tl != null && (tl.parent != t || tl.hash > t.hash)) return false;
if (tr != null && (tr.parent != t || tr.hash < t.hash)) return false;
if (t.red && tl != null && tl.red && tr != null && tr.red) return false;
if (tl != null && !checkInvariants(tl)) return false;
if (tr != null && !checkInvariants(tr)) return false;
return true;
}
Notion d’invariant de structure/classe 61 de 87
C++ DeQueue :
* Class invariants:
* - For any nonsingular iterator i:
* - i.node points to a member of the %map array. [...]
* - i.first == *(i.node) (This points to the node (first Tp element).)
* - i.last == i.first + node_size
* - i.cur is a pointer in the range [i.first, i.last). [...]
* - Start and Finish are always nonsingular iterators. [...]
* - For every node other than start.node and finish.node, every
* element in the node is an initialized object. If start.node ==
* finish.node, then [start.cur, finish.cur) are initialized
* objects, and the elements outside that range are uninitialized
* storage. Otherwise, [start.cur, start.last) and [finish.first,
* finish.cur) are initialized objects, and [start.first, start.cur)
* and [finish.cur, finish.last) are uninitialized storage.
* - [%map, %map + map_size) is a valid, non-empty range.
* - [start.node, finish.node] is a valid range contained within
* [%map, %map + map_size).
* - A pointer in the range [%map, %map + map_size) points to an allocated
* node if and only if the pointer is in the range
* [start.node, finish.node].
Structures linéaires : les listes chaînées 62 de 87
Structures linéaires :
les listes chaînées
Structures linéaires : les listes chaînées 63 de 87
Idée
Chaînages dynamiques
Chaînages dynamiques
Chaînages dynamiques
Chaînages dynamiques
Retenir
Une liste chaînée est obtenue à partir du type cellule définie par
cellule = structure:
val: element
next : ^cellule
liste = ^cellule
En C :
struct s_cell
{
element val;
struct s_cell * next;
};
typedef struct s_cell cell; // Cellule
typedef struct s_cell *list; // Liste Chaînée
Structures linéaires : les listes chaînées 65 de 87
Retenir
Une liste chaînée est obtenue à partir du type cellule définie par
cellule = structure:
val: element
next : ^cellule
liste = ^cellule
En C :
struct s_cell
{
element val;
struct s_cell * next;
};
typedef struct s_cell cell; // Cellule
typedef struct s_cell *list; // Liste Chaînée
Structures linéaires : les listes chaînées 66 de 87
Pointeur et structure
Syntaxe
Accès aux données d’une liste chaînée :
VD pointée par lst : *lst
champ val de cell : cell.val
champ val de la VD pointée par lst :
Les notions sur les suites finies passent aux listes dynamiques
qu’elles implantent : longueur, concaténation, position...
Calcul itératif :
int longueur(list lst) {
int k = 0;
list q = lst;
while (q != NULL) {
q = q->next; k++;
}
return k;
}
Calcul récursif :
int longueur(list lst) {
if (lst == NULL) return 0;
else return 1 + longueur(lst->next);
}
Structures linéaires : les listes chaînées 69 de 87
Les notions sur les suites finies passent aux listes dynamiques
qu’elles implantent : longueur, concaténation, position...
Calcul itératif :
int longueur(list lst) {
int k = 0;
list q = lst;
while (q != NULL) {
q = q->next; k++;
}
return k;
}
Calcul récursif :
int longueur(list lst) {
if (lst == NULL) return 0;
else return 1 + longueur(lst->next);
}
Structures linéaires : les listes chaînées 69 de 87
Les notions sur les suites finies passent aux listes dynamiques
qu’elles implantent : longueur, concaténation, position...
Calcul itératif :
int longueur(list lst) {
int k = 0;
list q = lst;
while (q != NULL) {
q = q->next; k++;
}
return k;
}
Calcul récursif :
int longueur(list lst) {
if (lst == NULL) return 0;
else return 1 + longueur(lst->next);
}
Structures linéaires : les listes chaînées 70 de 87
lst • a1 • a2 • an
à la fin
res • x •
lst • a1 • a2 • an
à la fin
res • x •
lst • a1 • a2 • an
à la fin
res • x •
plst • lst • a1 • a2 • an
à la fin
tmp • x •
plst • lst • a1 • a2 • an
à la fin
tmp • x •
plst • lst • a1 • a2 • an
à la fin
tmp • x •
plst • lst • a1 • a2 • an
à la fin
tmp • x •
lst • a1 • a2 • an
à la fin
res • a1 • a2 • an • x
lst • a1 • a2 • an
à la fin
res • a1 • a2 • an • x
plst • lst • a1 • a2 • an • x
à la fin cur •
tmp •
plst • lst • a1 • a2 • an • x
à la fin cur •
tmp •
le pointeur p repère la
lst • a1 • a2 • an
LDSC avec une fausse
tête qui implante la suite
? •
ha1 , a2 , . . . , an i
Intérêts :
en mode avec mutation ;
évite d’avoir à distinguer les cas de l’élément de tête ou de la
liste vide dans les opérations d’insertion et de suppression.
Structures linéaires : les listes chaînées 74 de 87
le pointeur p repère la
lst • a1 • a2 • an
LDSC avec une fausse
tête qui implante la suite
? •
ha1 , a2 , . . . , an i
Intérêts :
en mode avec mutation ;
évite d’avoir à distinguer les cas de l’élément de tête ou de la
liste vide dans les opérations d’insertion et de suppression.
Structures linéaires : les listes chaînées 75 de 87
pointeurs repérant
respectivement la tête et la
•• a1 • a2 • an
queue de la LDSC qui implante
la suite ha1 , a2 , . . . , an i
Intérêts :
en mode avec mutation ;
ajout en queue sans parcours de la LDSC ;
concaténation sans parcours des LDSC.
pointeurs repérant
respectivement la tête et la
•• a1 • a2 • an
queue de la LDSC qui implante
la suite ha1 , a2 , . . . , an i
Intérêts :
en mode avec mutation ;
ajout en queue sans parcours de la LDSC ;
concaténation sans parcours des LDSC.
pointeurs repérant
respectivement la tête et la
•• a1 • a2 • an
queue de la LDSC qui implante
la suite ha1 , a2 , . . . , an i
Intérêts :
en mode avec mutation ;
ajout en queue sans parcours de la LDSC ;
concaténation sans parcours des LDSC.
a1 •
• an •
Intérêts :
en mode avec mutation ;
ajout en queue et suppression en tête sans parcours de la
LDSC ;
concaténation sans parcours des LDSC.
Structures linéaires : les listes chaînées 76 de 87
a1 •
• an •
Intérêts :
en mode avec mutation ;
ajout en queue et suppression en tête sans parcours de la
LDSC ;
concaténation sans parcours des LDSC.
Structures linéaires : les listes chaînées 77 de 87
pointeurs repérant
respectivement la
tête et la queue de a1 •
•• • a2 • • an
la LDDC qui
implante la suite
ha1 , a2 , . . . , an i
Intérêts :
en mode avec mutation ;
marches avant et arrière ;
ajout en queue et suppression en tête sans parcours de la
LDDC ;
concaténation sans parcours des LDDC.
Structures linéaires : les listes chaînées 77 de 87
pointeurs repérant
respectivement la
tête et la queue de a1 •
•• • a2 • • an
la LDDC qui
implante la suite
ha1 , a2 , . . . , an i
Intérêts :
en mode avec mutation ;
marches avant et arrière ;
ajout en queue et suppression en tête sans parcours de la
LDDC ;
concaténation sans parcours des LDDC.
Structures linéaires : les listes chaînées 78 de 87
struct s_cell
{
element val;
struct s_cell * next;
struct s_cell * prev;
};
struct s_lddc
{
struct s_cell * first;
struct s_cell * last;
};
Structures linéaires : les listes chaînées 79 de 87
Remarque
Une Linked list contient toujours une RefCell qui est créé
à la construction et n’est jamais modifiée. Elle joue le rôle de
la fausse tête ;
Les RefCell que l’on trouve dans les cellules d’une liste
modélise les positions dans la liste ;
Remarque
Une Linked list contient toujours une RefCell qui est créé
à la construction et n’est jamais modifiée. Elle joue le rôle de
la fausse tête ;
Les RefCell que l’on trouve dans les cellules d’une liste
modélise les positions dans la liste ;
Remarque
Une Linked list contient toujours une RefCell qui est créé
à la construction et n’est jamais modifiée. Elle joue le rôle de
la fausse tête ;
Les RefCell que l’on trouve dans les cellules d’une liste
modélise les positions dans la liste ;
Applications
Applications
Liste des • a • • b • • c •
candidats ;
sélection de b choix •
choix->next->prev = choix->prev;
choix->prev->next = choix->next;
Suppression de • a • • b • • c •
b de la liste
choix •
Liste des • a • • b • • c •
candidats ;
sélection de b choix •
choix->next->prev = choix->prev;
choix->prev->next = choix->next;
Suppression de • a • • b • • c •
b de la liste
choix •
Tables d’associations
Tables d’associations 86 de 87
Tables d’associations
Définition (table)
Une table est une fonction d’un ensemble d’entrées dans un
ensemble de valeurs.
C’est un objet informatique : il s’agit de ranger une information
(valeur) associée à chaque index (entrée), de retrouver l’information
à partir de l’index, de la modifier ou de supprimer couple
index-information.
Tables d’associations 87 de 87