Chapitre 3 Structures de Données Linéaires : Les Listes PREMIERE PARTIE 1 Listes ◼ ◼ ◼ ◼ ◼ Une liste est une structure linéaire particulièrement souple : elle peut grandir ou rétrécir à volonté, et ses éléments, peuvent être insérés ou supprimés à n’importe quel endroit. D’un point de vue mathématique, une liste est une suite, vide ou non, d’éléments d’un type donné listes homogènes : ◼ tous les éléments sont du même type listes hétérogènes : ◼ les éléments sont de types différents dans la suite nous nous intéressons aux listes homogènes 2 Principes des TDA ◼ ◼ ◼ se placer à un niveau d'abstraction élevé ◼ éviter les erreurs de conception programmer avec des opérations de haut niveau ◼ qui ne dépendent pas de la représentation interne ◼ qui permettent de changer de représentation ◼ qui permettent à l'utilisateur de se concentrer sur les problèmes de conception en ignorant les détails de réalisation encapsuler les données ◼ n'accéder à la représentation interne que via des fonctions ◼ l'utilisateur ne voit que les services (l’interface) pas la représentation interne 3 Les types de données abstraits (TDA) ◼ ◼ définition : ◼ un ensemble de données organisé pour que les spécifications des objets et des opérations sur ces objets soient séparées de la représentation interne des objets et de la mise en œuvre des opérations nés de préoccupation de génie logiciel ◼ abstraction ◼ encapsulation ◼ vérification de types 4 Incarnation d'un TDA ◼ ◼ définition : une incarnation (une réalisation, une mise en œuvre, une « implémentation ») d'un TDA est ◼ la déclaration de la structure de données particulière retenue pour représenter le TDA ◼ L’interface (fichier .h) ◼ et la définition (i.e le code) des opérations primitives dans un langage particulier ◼ La réalisation (fichier .c) on parle aussi de type concret de données 5 Les éléments d’une liste: ◼ Les éléments de la liste sont déclarés de type ELEMENT permettant ainsi de définir une liste générique. ◼ On distingue alors les éléments à stockage ◼ direct ◼ indirect 6 Stockage direct ◼ ◼ ◼ ◼ Définition ◼ Un conteneur est en stockage direct quand il contient les objets Exemples ◼ liste d’entiers l = (1 2 3 4 ) Utilisation ◼ « Petits objets » en mémoire et facile à copier ◼ Nombres, caractères, éventuellement des structures avec des champs simples Allocation/Libération des objets contenus ◼ Un conteneur en stockage direct est propriétaire des objets qu’il contient cad ses objets sont alloués et libérés de façon automatique en même temps que le conteneur 7 Stockage indirect ◼ ◼ ◼ ◼ Définition ◼ Un conteneur est en stockage indirect quand il contient des pointeurs sur des objets (cad l’adresse des objets stockés) Exemples ◼ liste de chaînes de caractères, listes de pointeurs sur des structures, listes de listes Utilisation ◼ Gros objets, objets avec des champs dynamiques, objets partagés par plusieurs conteneurs Allocation/libération des objets contenus ◼ Un conteneur en stockage indirect n’est pas propriétaire des objets qu’il contient i.e. il n’est pas responsable de l’allocation/libération des objets pointés ◼ En C, c’est la responsabilité du programmeur de libérer la mémoire allouée dynamiquement ◼ Une difficulté du C 8 TDA ELEMENT ◼ tous les éléments appartiennent à un TDA ELEMENT qui possède un élément vide et sur lequel on peut : ◼ ◼ ◼ ◼ ◼ ◼ ◼ ◼ saisir un élément afficher un élément affecter un élément dans un autre élément copier les informations d’un élément dans un autre élément (le dupliquer) comparer deux éléments allouer dynamiquement la mémoire ou initialiser un élément libérer la mémoire allouée pour un élément On va créer un header (ELTPRIM.H) 9 TDA ELEMENT /*************************************************** * Fichier : ELTPRIM.H * Contenu : Déclaration des primitives du TDA ELEMENT. ***************************************************/ #ifndef _ELTPRIM_H #define _ELTPRIM_H #include "ELTSDD.H" /* Lecture d'un élément*/ void elementLire(ELEMENT *); /* Affichage d'un élément/ void elementAfficher(ELEMENT); /* Affectation du 2eme argument dans le 1er qui est donc modifié et passé par adresse */ void elementAffecter(ELEMENT*, ELEMENT); /* Copie du contenu du deuxième argument dans le premier, les deux arguments ont des adresses différentes (duplication)*/ void elementCopier(ELEMENT *, ELEMENT) ; /* Comparaison des arguments retourne un entier 0, < 0 ou > 0 la "différence" (e1-e2) */ int elementComparer(ELEMENT, ELEMENT); /* Création d'un élément*/ ELEMENT elementCreer(void) ; /* Libération de mémoire */ void elementDetruire (ELEMENT); #endif 10 Remarque ◼ ◼ Les deux primitives elementAffecter et elementCopier sont utiles dans le cas du stockage indirect (la première permet de pointer sur le même élément alors que la seconde permet de changer les valeurs) Voir exemple (suite) Dans le cas du stockage direct ces deux primitives sont identiques 11 Exemple 1: stockage direct (entiers) (1) La définition est faite dans le ELTINT.h /*************************************************** * Fichier : ELTINT.H * Contenu : Déclaration de la structure de données adoptée * pour une réalisation du TDA ELEMENT. ***************************************************/ #ifndef _ELTINT_H #define _ELTINT_H typedef int ELEMENT; #endif 12 Exemple 1: stockage direct (entiers) (2) /******************************************************** * Fichier : ELTINT.C * Contenu : Définition des primitives pour une réalisation par des entiers du TDA ELEMENT. ********************************************************/ #include <stdio.h> ELEMENT est #include "ELTPRIM.H" définit dans #define ELEMENT_VIDE 32767 ELTINT.H qui est reconnu ELEMENT elementCreer (void) { automatiquement dans ELTINT.C return ELEMENT_VIDE ;} void elementDetruire (ELEMENT elt) { /* ne fait rien en stockage direct*/ Dans le stockage direct les opérations de création et de libération sont simplifiées il n’y a ni allocation ni libération de la mémoire 13 Exemple 1: stockage direct (entiers) (3) void elementAffecter(ELEMENT * e1, ELEMENT e2) { *e1 = e2 ;} void elementCopier(ELEMENT * e1, ELEMENT e2) { *e1 = e2 ;} Dans le stockage direct elementAffecter et elementCopier sont identiques 14 Exemple 1: stockage direct (entiers) (4) void elementLire(ELEMENT * elt) { printf(« \nun entier svp :") ; scanf("%d",elt); } void elementAfficher(ELEMENT elt) { printf(" %d ",elt); } int elementComparer(ELEMENT e1, ELEMENT e2) { return (e1-e2); } 15 Exemple 2: stockage indirect (client) (1) Le client est une structure : ➢ Nom ➢ Adresse ➢ cin /***************************************************** * Fichier : ELTCLT.H * Contenu : Déclaration de la structure de données adoptée * pour une réalisation du TDA ELEMENT par client. *****************************************************/ #ifndef _ELTCLT_H #define _ELTCLT_H typedef struct { char nom[20]; char adresse[30]; int cin; } elem, *ELEMENT; #endif ELEMENT ici est un pointeur sur une structure 16 Exemple 2: stockage indirect (client) (2) /************************************************* * Fichier : ELTCLT.C * Contenu : Définition des primitives pour une réalisation par des clients du TDA ELEMENT. ************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "ELTPRIM.H" Ici on utilise malloc et free car on est dans le stockage indirect (pointeurs) #define ELEMENT_VIDE NULL ELEMENT elementCreer (void) { ELEMENT L; L = (ELEMENT) malloc(sizeof(elem)); return L; } void elementDetruire (ELEMENT elt) { free (elt); } Sizeof(elem) est different de sizeof(ELEMENT)) 17 Exemple 2: stockage indirect (client) (3) void elementLire(ELEMENT* elt) { char x; printf(" \nDonnez un nom svp :") ; fgets((*elt)->nom, 18, stdin); printf(" \nDonnez une adresse svp :") ; fgets((*elt)->adresse, 18,stdin); printf(" \nDonnez le numéro CIN :") ; scanf("%d",&((*elt)->cin)); x=getchar(); } void elementAfficher(ELEMENT elt) { printf(«\nnom = %s , adresse = %s , CIN= %d ",elt>nom, elt->adresse, elt->cin); } 18 Exemple 2: stockage indirect (client) (4) void elementAffecter(ELEMENT* e1, ELEMENT e2) { *e1 = e2 ; } e1 @ cl1 @ cl1 ali adr1 10 e2 ◼ ◼ hamed adr2 20 e1: contient une adresse d’un client ◼ *e1 est égal à @cl1 ◼ (*e1)->nom est égal à ali ◼ (*e1)->adresse est égal à adr1 ◼ (*e1)->cin est égal à 10 e2: est une adresse d’un client ◼ e2->nom est égal à hamed ◼ e2->adresse est égal à adr2 ◼ e2->cin est égal à 20 elementAffecter(&e1, e2) e1 @ cl1 e2 ali adr1 10 e2 hamed adr2 20 ◼ e1: contient une adresse d’un client ◼ *e1 est égal à e2 ◼ (*e1)->nom est égal à hamed ◼ (*e1)->adresse est égal à adr2 ◼ (*e1)->cin est égal à 20 19 Exemple 2: stockage indirect (client) (5) int elementCopier(ELEMENT *e1, ELEMENT e2) { strcpy((*e1)->nom, e2->nom); strcpy((*e1)->adresse, e2->adresse); (*e1)->cin = e2->cin; } e1 @ cl1 @ cl1 ali adr1 10 e1 @ cl1 @ cl1 hamed adr2 20 e2 e2 hamed adr2 20 hamed adr2 20 elementCopier(&e1, e2) Remarque : (**e1).cin est equivalent à (*e1)->cin (*e2).cin est equivalent à e2->cin 20 Exemple 2: stockage indirect (client) (6) int elementComparer(ELEMENT e1, ELEMENT e2) { return ((e1->cin) – (e2->cin)) ; } 21 Remarque ◼ ◼ Afin d’établir le lien entre ELTPRIM.H et les implémentations on utilise un fichier intermédiaire ELTSDD.H qui va définir le fichier d’implémentation ELTSDD.H sera modifié quand on voudra changer la représentation adoptée (entier, clients etc.) /****************************************************** * Fichier : ELTSDD.H * Contenu : Inclusion du fichier où est déclarée la structure de données adoptée pour réaliser le TDA ELEMENT. ******************************************************/ #ifndef _ELTSDD_H #define _ELTSDD_H #include "ELTCLT.H" ou ELTINT.H #endif 22 /* ELTSDD.h sera modifié quand on voudra changer la représentation adoptée (entier string etc.)*/ Schéma général ELTSDD.h ou include ELTPRIM.h entiers ELTINT.h ELTINT.c … …. …. 23 L’implantation des listes en C ◼ TDA indépendant de son implémentation: donc on peut proposer plusieurs implémentations différentes: ◼ Listes contiguës (en utilisant un tableau) ◼ Listes chaînées 24 Primitives du TDA LISTE /***************************************************************** * Fichier : LSTPRIM.H * Contenu : Déclaration des primitives du TDA LISTE ******************************************************************/ #ifndef _LSTPRIM_H #define _LSTPRIM_H #include "ELTPRIM.H" #include "LSTSDD.H" LISTE listeCreer(void); void listeDetruire(LISTE); Toutes les primitives des éléments peuvent êtres utilisées int estVide(LISTE); int estSaturee(LISTE); int listeTaille(LISTE); ELEMENT recuperer(LISTE, int); int inserer(LISTE, ELEMENT,int); int supprimer(LISTE, int); void listeAfficher(LISTE); LISTE listeCopier(LISTE); int listeComparer(LISTE, LISTE); #endif 25 Implémentation contiguë ◼ ◼ Les éléments de la liste sont mémorisés dans les cellules contiguës d’un tableau. Dans ce cas, une liste est caractérisée par: ◼ ◼ une taille physique une taille logique (lg) 26 Implémentation contiguë (en C) ◼ une liste contiguë est un pointeur sur une structure à deux champs ◼ ◼ un tableau qui contient les éléments de la liste (éléments) : la première case est vide afin d’éviter le décalage entre les positions et les indices un entier indiquant l'indice du dernier élément de la liste (lg) =taille logique L éléments 0 1 2 3 100 lg % a b c ... @ 3 27 Exemple: liste en stockage direct ◼ ◼ Liste d’entiers Chaque case de la liste contient un entier L éléments 0 1 2 3 4 % 123 34 5 67 ... ... 100 lg @ 4 28 Exemple: liste en stockage indirect ◼ ◼ L Liste de clients Chaque case de la liste contient l’adresse d’une structure client (pointe sur un client) éléments 0 1 2 3 4 % lg @ cl2 hamed adr2 20 @ cl3 salah adr3 30 @ cl4 karim adr4 40 @ cl1 @ cl2 @ cl3 @ cl4 ... ... 100 @ cl1 ali adr1 10 @ 4 29 Déclaration de la structure /*************************************************** * Fichier : LSTTAB.H * Contenu : Déclaration de la strucure de données pour réaliser * le TDA LISTE PAR TABLEAU ***************************************************/ #ifndef _LSTTAB_H #define _LSTTAB_H #include "ELTPRIM.H" #define LongMax 100 /* longueur maximale d'une liste */ typedef struct { ELEMENT elements[LongMax]; int lg; } laStruct,*LISTE; /* tableau automatique */ /* taille logique de la liste */ #endif 30 Remarques ◼ ◼ ◼ La mise en œuvre par tableau facilite les parcours de listes indexées et l’insertion d’éléments en fin de liste. En revanche, l’insertion d’un élément au milieu d’une liste nécessite de déplacer tous les éléments suivants dans le tableau pour créer la place nécessaire au nouveau venu. De même, la suppression d’un élément quelconque du tableau demande que l’on déplace tous ses successeurs pour "boucher le trou" produit, sauf lorsqu’il s’agit du dernier élément de la liste. 31 Implémentation des primitives /****************************************************** * Fichier : LISTAB.C * Contenu : Définition des primitives pour une réalisation * par tableau du TDA LISTE. ******************************************************/ #include <stdlib.h> #include <stdio.h> #include "LSTPRIM.H" LISTE listeCreer(void) { LISTE L; L = (LISTE) malloc(sizeof(laStruct)); if(!L) { printf(" \nProblème de mémoire") ; exit(0) ; } L->lg = 0; return(L); } void listeDetruire(LISTE L){ int i; for(i = 1;i <= L->lg; i++) elementDetruire(L->elements[i]); free(L); } 32 Insertion int inserer (LISTE L, ELEMENT e, int pos) { int i; int succee=1; if (estSaturee(L)){ printf ("\nListe saturée"); succee=0;} else { if ((pos < 1) || (pos > L->lg + 1)) { printf ("\nPosition invalide"); succee=0; } else { for(i = L->lg; i>= pos; i--) elementAffecter(&L->elements[i+1], L->elements[i]); elementAffecter(&L->elements[pos], e); (L->lg)++; } } return(succee); } 33 Exemple Insertion (stockage direct) Insérer (77) à la position 2 L éléments 0 1 2 3 4 % 123 34 5 67 ... ... 100 lg @ 4 34 Exemple Insertion (stockage direct) Insérer (77) à la position 2 elementAffecter(&L->elements[5], L->elements[4]); L éléments 0 1 lg % 123 2 3 4 5 34 100 @ 5 67 67 ... 4 35 Exemple Insertion (stockage direct) Insérer (77) à la position 2 elementAffecter(&L->elements[4], L->elements[3]); L éléments 0 1 lg % 123 2 3 4 5 34 100 @ 5 5 67 ... 4 36 Exemple Insertion (stockage direct) Insérer (77) à la position 2 elementAffecter(&L->elements[3], L->elements[2]); L éléments 0 1 lg % 123 2 3 4 5 34 100 @ 34 5 67 ... 5 37 Exemple Insertion (stockage direct) Insérer (77) à la position 2 elementAffecter(&L->elements[2], 77); (L->lg)++; L éléments 0 1 lg % 123 2 3 4 5 77 100 @ 34 5 67 ... 5 38 Exemple Insertion (stockage indirect) Insérer (saber, adr5, 50) qui est à l’adresse @ cl5 à la position 2 L éléments 0 1 2 3 4 100 lg % @ cl1 ali adr1 10 @ cl2 hamed adr2 20 @ cl3 salah adr3 30 @ cl4 karim adr4 40 @ cl5 saber adr5 50 @ cl1 @ cl2 @ cl3 @ cl4 ... ... @ 4 39 Exemple Insertion (stockage indirect) Insérer (saber, adr5, 50) à la position 2 elementAffecter(&L->elements[5], L->elements[4]); la 5eme case du tableau reçoit @ cl4 (pointe sur le client à l’adresse @ cl4) L éléments 0 1 2 3 4 5 % @ cl1 ali adr1 10 @ cl2 hamed adr2 20 @ cl3 salah adr3 30 @ cl4 karim adr4 40 @ cl5 saber adr5 50 @ cl1 @ cl2 @ cl3 @ cl4 @ cl4 ... 100 lg @ 4 40 Exemple Insertion (stockage indirect) Insérer (saber, adr5, 50) à la position 2 elementAffecter(&L->elements[4], L->elements[3]); la 4eme case du tableau reçoit @ cl3 (pointe sur le client à l’adresse @ cl3) L éléments 0 1 2 3 4 5 % @ cl1 ali adr1 10 @ cl2 hamed adr2 20 @ cl3 salah adr3 30 @ cl4 karim adr4 40 @ cl5 saber adr5 50 @ cl1 @ cl2 @ cl3 @ cl3 @ cl4 ... 100 lg @ 4 41 Exemple Insertion (stockage indirect) Insérer (saber, adr5, 50) à la position 2 elementAffecter(&L->elements[3], L->elements[2]); la 3eme case du tableau reçoit @ cl2 (pointe sur le client à l’adresse @ cl2) L éléments 0 1 2 3 4 5 % @ cl1 ali adr1 10 @ cl2 hamed adr2 20 @ cl3 salah adr3 30 @ cl4 karim adr4 40 @ cl5 saber adr5 50 @ cl1 @ cl2 @ cl2 @ cl3 @ cl4 ... 100 lg @ 4 42 Exemple Insertion (stockage indirect) Insérer (saber, adr5, 50) à la position 2 elementAffecter(&L->elements[2], @ cl5 ); la 3eme case du tableau reçoit @ cl5 (pointe sur le client à l’adresse @ cl5) L (L->lg)++; éléments 0 1 2 3 4 5 % @ cl1 ali adr1 10 @ cl2 hamed adr2 20 @ cl3 salah adr3 30 @ cl4 karim adr4 40 @ cl5 saber adr5 50 @ cl1 @ cl5 @ cl2 @ cl3 @ cl4 ... 100 lg @ 5 43 Suppression int supprimer (LISTE L, int pos ) { int i; int succee=1; if (estVide(L)) { printf ("\nListe vide"); succee=0;} else { if ((pos < 1) || (pos > L->lg)) { printf (“\nPosition invalide"); succee=0;} else { elementDetruire(L->elements[pos]); for(i=pos;i<=L->lg;i++) elementAffecter(&L->elements[i], L->elements[i+1]); (L->lg)--; } } return(succee); } 44 Récupérer ELEMENT recuperer(LISTE L, int pos) { ELEMENT elt= elementCreer(); if (estVide(L)) printf (« \nListe vide"); else { if ((pos < 1) || (pos > L->lg)) printf (« \nPosition invalide"); else elt=(L->elements[pos]); } return(elt); } ◼ ◼ ◼ s'il y a une erreur on affiche un message et on retourne un élément vide dans le cas de stockage indirect récupérer retourne un pointeur sur l'élément et non une copie de cet élément cad si on modifie les champ pointés par le résultat on modifie directement la liste Exemple: ◼ elt=recuperer(L,2) ◼ elt->cin=0 ◼ Le cin du client à la position 2 devient 0 45 Copier (1) LISTE listeCopier(LISTE L){ LISTE LR = listeCreer(); int i; ELEMENT elt; for(i = 1;i <= L->lg; i++) { elt=elementCreer(); elementCopier(&elt, recuperer(L,i)); inserer(LR, elt, i); } return LR; } 46 Copier (2) Si on utilise elementAffecter au lieu de elementCopier alors pour les listes qui contiennent des éléments en stockage indirect on aura: L éléments 0 % 1 @ cl1 2 @ cl2 3 @ cl3 4 @ cl4 ... ... 100 @ lg 4 @ cl1 ali adr1 10 @ cl2 hamed adr2 20 @ cl3 salah adr3 30 @ cl4 karim adr4 40 LR éléments % 0 @ cl1 1 @ cl2 2 @ cl3 3 @ cl4 4 ... ... @ 100 4 lg 47 Comparaison int listeComparer (LISTE L1,LISTE L2 ) { int test= 1; int i=1; if (listeTaille(L1) != listeTaille(L2)) test= 0; while ((i<=listeTaille(L1)) && (test)) { if (elementComparer(recuperer(L1,i),recuperer(L2,i))!=0) test=0; i++; } return test; } Retourne 1 si les listes sont égales et 0 sinon 48 listeAfficher, estVide, estSaturee, listeTaille void listeAfficher(LISTE L) { int i; for(i = 1; i <= L->lg; i++) elementAfficher(L->elements[i]); } int estVide(LISTE L) { return (L->lg == 0); } int estSaturee(LISTE L) { return (L->lg == LongMax); } int listeTaille(LISTE L) { return (L->lg); } 49 Remarque ◼ ◼ Afin d’établir le lien entre LSTPRIM.H et les implémentations on utilise un fichier intermédiaire LSTSDD.H qui va définir le fichier d’implémentation LSTSDD.H sera modifié quand on voudra changer la représentation adoptée (chaînée, contiguë etc.) /************************************************ * Fichier : LSTSDD.H * Contenu : Inclusion du fichier où est déclarée la structure * de données adoptée pour réaliser le TDA LISTE. ************************************************/ #ifndef _LSTSDD_H #define _LSTSDD_H #include "LSTTAB.H" #endif Ou LSTPTR.H 50 Schéma général /* LSTSDD.h sera modifié quand on voudra changer la représentation adoptée (chainée- contigue etc.)*/ LSTSDD.h ou include LSTPRIM.h chainée contiguë LSTTAB.h (structure) LSTPTR.h (structure) ELTPRIM.h LSTTAB.c LSTPTR.c …. 51 L’utilisation: exemple /******************************************** * Fichier : main.c * Contenu : Test du TDA LISTE ********************************************/ #include <stdio.h> #include "ELTPRIM.H" #include "LSTPRIM.H" int main() { int i; ELEMENT elt; LISTE L = listeCreer(); printf ("\nEntrez une liste de 4 entiers svp : \n") ; for (i = 0; i < 4; i++) { ElementLire(&elt) ; inserer(elt, i+1, L); } listeAfficher(L); listeDetruire(L); return 0 ; } 52 Création du projet sous Visual C++ 53 Création du projet sous Visual C++ ◼ ◼ Dans cet exemple on utilise une implémentation contiguë des listes d’entiers A chaque fois qu’on veut changer d’implémentation ou de type d’éléments il suffit de changer les fichiers du projet (ou créer un autre projet) 54 Remarques ◼ ◼ ◼ C++, JAVA, ADA sont conçus pour supporter la réalisation de TDA en C comme vous avez constaté, utiliser les TDA c'est assez acrobatique et même contre nature pourquoi le langage C ? ◼ ◼ ◼ le plus répandu impensable encore de se dire informaticien en sans maîtriser le C mettre en œuvre les TDA en C vous permettra de maîtriser le C mais aussi de mieux comprendre les problèmes résolus par C++ (ou java) et d'accepter de payer le prix de la complexité technique des langages orientés objets pour obtenir une plus grande sécurité de programmation. 55