. Ecole Nationale Polytechnique Département Génie Industriel Année Universitaire 2011/2012 Cours Algorithmique et Programmation Les structures de données Les Listes Les structures de données Une structure est une combinaison d'objets simples en un objet plus complexe. Exemples de structures de données 1. Structures finies Variables, Enregistrements (struct en C, Record en Pascal ) 2. Structures indexées : Tableaux, Tableaux associatifs. Accès aux éléments par un index (Exemple : un numéro d'ordre). 3. Structures séquentielles : Listes, Arbres, Graphes. Chaque élément indique la position de l élément suivant. Une structure de données implémente concrètement un Type Abstrait. Liste : une certaine généralisation du tableau.. Plan du Chapitre Les structures de données linéaires 1. Les structures de données Listes 2. Les structures de données Piles 3. Les structures de données Files Plan du cours Les structures de données Listes 1. Définition récursive : 2. Définition du TDA Liste 3. Implémentation du TDA Élément et Position 4. Mise en œuvre des listes en tableau a. Interface et implémentation du TDA b. Liste avec tableau à taille variable 5. Mise en œuvre des listes par cellules chaînées a. Interface et implémentation du TDA b. Listes chaînées particulières Un même TDA peut donner lieu à plusieurs structures de données, avec des performances différentes. Les structures de données linéaires S2D : Caractérisées par leur comportement Caractéristique : une fonction Successeur Structures indexées : le successeur est l élément suivant. Structures séquentielles (récursives) : lien explicite vers le successeur (son adresse par exemple). ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 1 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 2
Les listes Toute suite d informations est une liste. Exemples : liste d étudiants, de livres. Liste de produits, de clients Une liste est une suite ordonnée d éléments de même type. Structures de données récursives : Structure de taille n = objet + structure de taille (n-1) Structure de taille 1 = objet Structure de taille 0 = vide L ordre chaque élément possède une position dans la liste. Exemples : Un tableau, c est : tableau de taille 1 = une case tableau de taille n = une case + tableau de taille n-1 o Liste =une certaine généralisation du tableau o Par opposition à un tableau, le nombre d éléments est variable. Operations : On peut ajouter, supprimer ou modifier un élément, en début ou en fin de liste. On peut vider une liste ou savoir si elle contient un ou plusieurs éléments. Définition récursive d une liste : Une liste est soit une liste vide, soit un élément suivi d'une liste. Ce qui peut s écrire Liste ::= Élément, Liste Opérations usuelles sur les listes Créer une liste vide et tester si une liste est vide. Ajouter un élément en tête ou en fin de liste. Ajouter un élément en en une position donnée. La queue est la liste de tous les éléments suivants. Exemple L= L= 1, 8, 71, 27, 57, T= 1, 8, 71, 27, 57 Supprimer un élément en tête ou en fin de liste. Supprimer un élément donné. Rechercher un élément. Afficher, dupliquer une liste Concaténer, fusionner des listes. ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 3 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 4
Définition du TDA Liste Encapsulation des données : Accès à la représentation interne via des fonctions l'utilisateur ne voit que les services (l interface) pas la représentation interne. TDA Liste (L) Utilise Element(E), Position (P), Booleen (B) Primitives (Opérations de base) Opérations de création /destruction Créer() :() L Créer une nouvelle liste vide Liste() :() L Détruire L () Supprime la liste ainsi que tous ses éléments. EstVide() : L B Test si une liste est vide Opérations de gestion modifier : E x L L modifie un élément à une position donnée de la liste. ajouter() : E x L L insère un élément à la tête de la liste insérer : E x L xp L insère un élément à une position donnée de la liste supprimer : L xp L supprime un élément à une position donnée de la liste. Opérations de parcours tete : L E retourne le premier élément de la liste suivant : L xp L retourne la liste qui suit un élément à une position donnée de la liste. dernier: L E retourne le dernier élément de la liste queue : L L Préconditions : Soit «l» une liste tête(l) NON(estVide(l)) queue(l) NON(estVide(l)) retourne la liste qui suit le premier élément de la liste. Les opérations «tête» et «queue» ne peuvent être appelées que si la liste est non vide Axiomes Soient «e» un élément et «l» une liste 1. EstVide( Créer() ) = VRAI 2. EstVide( Ajouter(e, l) ) = FAUX 3. Tête( Ajouter(e, l) ) = e 4. Queue( Ajouter(e, l) ) = l 5. Ajouter( Tête(l), Queue(l) ) = l Opérations classiques sur les listes : Opérations primitives on peut exprimer les opérations évoluées, sans manipuler ni pointeurs, ni tableaux : Opérations évoluées taille : L N appartient : E x L B dupliquer : L L inverser : L L Axiomes Soient «e» un élément et «l» une liste 1. taille( créer() ) = 0 2. taille(l) = taille( queue(l) ) + 1 3. inverser (créer() ) = créer() 4. inverser (l) = ajouter(queue( tête(l), inverser(queue(l))) Les fonctions de manipulation d'une liste se prêtent souvent à une implémentation récursive. L'algorithme général : deux cas : o cas de base pour une liste vide [] sans appel récursif o cas de propagation pour [E L] avec appel récursif sur L. Exemple : Longueur d une liste Si la liste est vide taille =0 Si non taille = taille (queue) +1 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 5 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 6
Mise en œuvre du TDA Liste Longueur d une liste : Version itérative : fonction taille (liste l ) :entier Var entier longueur = 0 ; début tant que NON(estVide(l)) faire longueur longueur + 1 l queue(l) fait retourne(longueur) fin Complexité : O(n) Longueur d une liste : Version recursive : fonction taille (liste l ) :entier var longueur : entier début si estvide(l) alors longueur 0 sinon longueur taille( queue(l) ) + 1 retourne (longueur) ; fin Complexité : O(n) Operation : inverser une liste fonction inverser(liste l ) :liste l2: liste début l2 = créer() ; tantque NON(estVide(l)) faire l2 ajouter( tête(l), l2 ) ; l queue(l) ; fait retourne (l2) fin Complexité : O( ) Algo : appartient tester si un élément x appartient à une liste l Entrée : une liste l et une donnée x Sortie : un booléen r égal à vrai ssi x appartient à L fonction appartient (liste l, element x ) :booleen var l2: liste r booleen debut r= faux; tantque NON(estVide(l)) faire si (tete(l)= x) alors r= vrai; fsi; l= queue(l); Fait retourne (r) fin Implémentation du TDA Liste Hypothèses (Liste générique) Tous les éléments de la liste appartiennent à un TDA ELEMENT. Les éléments de la liste peuvent êtres repérées à l aide d un TDA_ POSITION. On dit alors que Le TDA LISTE est le Conteneur TDA ELEMENT est le Contenant ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 7 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 8
Définition du TDA ELEMENT Le TDA ELEMENT est d'un type quelconque (entier, caractère, ) muni des opérations de base (primitives ) suivantes : Construire et initialiser un élément ( allocation mémoire) Détruire un élément (libérer la mémoire allouée). Dupliquer un élément. Modifier (saisir) un élément Afficher un élément Tester si l élément est vide. Affecter un élément à un autre. Comparer deux éléments Définition du TDA POSITION Le TDA POSITION peut être le rang de l'élément (indice d un tableau) ou un pointeur sur la cellule contenn élément. Le TDA POSITION doit nous permettre de : Repérer un élément dans la liste (fonction Position()) Tester si deux positions sont égales (fonction Identique()) Affecter dans une position une autre position (fonction Affecter()). Lors de l'implémentation du TDA, une POSITION sera un entier ou un pointeur. Définition d une cellule Une liste est constituée de cellules contenant : Un champ d'informations de type ELEMENT. un champ successeur indiquant la position de la cellule suivante. Analogie entre les maillons d une chaine et les cellules d une liste L implémentation des primitives du TDA Liste revient à définir les fonctions élémentaires associées à une liste, qui permettent : de créer une cellule ; d'insérer une cellule dans la liste ; de supprimer une cellule de la liste ; Un TDA est indépendant de son implémentation: Plusieurs implémentations différentes: 1. Listes contiguës (en utilisant un tableau) 2. Listes chaînées Implémentation du TDA Liste en tableau Plusieurs représentations sont possibles 1. Mise en œuvre par une structure (Entier+Tableau). 2. Mise en œuvre par-curseurs-faux-pointeurs (Tableau 2D, Structure dont le 2e champ est un indice). ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 9 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 10
TDA Liste : Implémentation par une structure Une liste est un pointeur sur une structure à deux champs un tableau surdimensionné à taille fixe (NMax), qui contient les éléments de la liste. un entier indiquant l'indice du dernier élément de la liste (NE=Taille logique de la liste). typedef int TPOSITION; #define POSITION_INVALIDE -1 //typedef enum FAUX, VRAI bool ; #define int bool typedef struct int NB2Elements //n; TCELLULE* Tab; TList_Tab; Interface Une cellule contient un élément (où un pointeur vers un élément) // Accès direct aux éléments typedef TELEMENT TCELLULE Accès indirect Chaque cellule de la liste contient l adresse d un élément (une structure ou même un tableau) typedef TELEMENT * TCELLULE Interface du TDA LISTE_TABLEAU Opérations Prototypes de foncions Interface du TDA LISTE_TABLEAU TListe_Tab Creer (int NMax); void detruire(liste_tab liste); bool EstVide(LISTE_TAB liste); bool EstPleinne(TListe_Tab liste); TELEMENT element(tliste_tab liste, int pos); int affecter(tliste_tab liste, int pos, ELEMENT e); int taille(tliste_tab liste); int ajouter_debut(tliste_tab liste, ELEMENT e); int ajouter_fin(tliste_tab liste, ELEMENT e); TListe_Tab inserer(tliste_tab L, TDA_ELEMENT e,tda_position pos ); TListe_Tab supprimer (TListe_Tab L, TDA_POSITION pos) TListe _Tab rechercher(tliste_tab liste, ELEMENT e); ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 11 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 12
Réalisation du TDA LISTE_TABLEAU Réalisation Implémentation des opérations struct int NB2Elements; TCELLULE * Tab; TListe_Tab; TListe_Tab Creer (); TListe_Tab L L.Tab=(TCELLULE *) malloc(nmax*sizeof(tda_cellule)); if(l.tab==null) printf(" \nproblème de mémoire") ;exit(0) ; L-> NB2Elements = 0; return(l); int taille(tliste_tab L) return L.NB2Elements bool EstVide(TListe_Tab L) return (L.NB2Elements ==0) bool EstPleinne(TListe_Tab * L) return ( L.NB2Elements ==NMax) ELEMENT element(liste_tab liste, int pos); return L.Tab[pos]; LISTE_TAB affecter(liste_tab liste, int pos, ELEMENT e) Tab[pos]=e; return(l) TListe_Tab inserer() (TListe_Tab L, TELEMENT e, TPOSITION pos) If ((pos> L.NB2Elements+1) (pos<1)) return(l); else L.NB2Elements++; for(int i= L.NB2Elements-1;i>=pos; i--) L.Tab[i] = L.Tab[i-1]; L.Tab[pos-1]=e; return(l); TListe_Tab supprimer (TListe_Tab L, TDA_POSITION pos) If (pos<1 pos>l.nb2elements) return(l); else for(int i=pos-1;i<l.nb2elements-1; i++) L.Tab[i]=L.Tab[i+1]; L.NB2Elements--; return(l); TListe_Tab ajouter_debut(tliste_tab L, TELEMENT e) inserer (L, e, 1) ; TListe_Tab ajouter_fin(tliste_tab L, TELEMENT e) inserer (L, e, L.NB2Elements+1) ; void detruire(tliste_tab liste) liste.nb2elements=0 // si accès indirecte il faut détruire aussi les éléments // avant de détruire le tableau de pointeurs free(liste.tab); ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 13 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 14
Listes avec tableau: Récapitulation Complexité Insertion et suppression sont en O(n). Précédent et accès direct sont en O(1). Avantages Parcours facile à implémenter et efficace Simplicité. Limitation 1. Taille fixe : aucune flexibilité. 2. Besoin de sur-dimensionner les tableaux : 3. Coût élevé des opérations d insertion et de suppression Coût proportionnel à la taille de la liste. Inapproprié pour de très grandes listes. Liste avec tableau à taille variable Liste(taille fixe ) liste(taille variable ) : Possibilité d allouer dynamiquement un tableau. Réallocation pour suivre la croissance de la liste. aucun changement de spécification sur les opérations du TDA. Technique largement utilisées en C++, Java, C#. Levée des problèmes De surconsommation de mémoire. De manque de flexibilité. Maintien des problèmes d efficacité des opérations d insertion et de suppression Alternative : Les listes chaînées Mise en œuvre des listes par cellules chaînées Cellules chaînées Une cellule est composée de deux champs : <element>, contenant un élément de la liste Cellules doublement chaînées La structure d une cellule contient, en plus de l élément (la donnée), deux pointeurs: Un pointeur sur la cellule suivante, Un pointeur sur la cellule précédente. typedef struct TELEMENT element; TCELLULE * suivant ; TCELLULE * precedent; TCELLULE; parcours de la liste dans les deux sens. On parle de liste doublement chaînée Liste chaînée //Un liste est un pointeur sur une cellule typedef struct TCELLULE * PCELLULE ; //Pointeur typedef int TPOSITION; #define POSITION_INVALIDE -1 typedef TCELLULE * TLISTE ; <Suivant>, contenant un pointeur sur la cellule suivante typedef struct TELEMENT element; TCELLULE * suivant ; TCELLULE; ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 15 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 16
Interface du TDA Liste chainée Opérations Prototypes de foncions TLISTE Creer (); void detruire(tliste liste); int taille(tliste liste); bool EstVide(TLISTE liste); TELEMENT tete(tliste L) ; TLISTE queue(tliste L) TELEMENT element(tliste liste, TPOSITION pos); int affecter(tliste liste, TPOSITION pos, TELEMENT e); TLISTE ajouter_debut(liste liste, ELEMENT e); TLISTE ajouter_fin(tliste liste, TELEMENT e); TLISTE inserer(tliste L, TELEMENT e, TPOSITION pos ); TLISTE supprimer (TLISTE L, TPOSITION pos) Réalisation du TDA Liste Chainée Implémentation des primiyives TCELLULE * Creer_Cellule (); PCELLULE * pcell pcell=(pcellule) malloc(1*sizeof(tcellule)); if (pcell==null) printf(" \nproblème de mémoire") ;exit(-1) ; pcell->suivant=null; return(pcell); bool EstVide(TLISTE L) return (L==NULL) TELEMENT tete(liste L) If!(EstVide(L)) return (L->element LISTE queue(tliste L) If (!(EstVide(L))) return (L->suivant int taille(tliste_ L) int longueur = 0 ; PCELLULE p=l ; while (!(EstVide(p))) longueur = longueur + 1 ; p = queue(p) ; return(longueur) ; Ajout et Insertion d'un élément dans la liste Algorithme Déclarer l'élément à insérer Allouer de la mémoire pour le nouvel élément Remplir le contenu du champ de données Mettre à jour les pointeurs en faisant attention aux cas particuliers (Premier et dernier élément, liste avec un seul élément etc) TLISTE Creer (); TLISTE L =NULL ; // TLISTE L = Creer_Cellule() ; return(l); TListe ajouter_debut(tliste L, TELEMENT e) PCELLULE Nouvelle_Cellule=Creer_Cellule (); Nouvelle_Cellule ->element=e; Nouvelle_Cellule ->suivant=l; l= Nouvelle_Cellule ; ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 17 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 18
TListe ajouter_fin(tliste L, TELEMENT e) PCELLULE, dernier, p=l; while (!(EstVide(p))) dernier=p; p=p->suivant; Nouvelle_Cellule ->suivant=null; dernier->suivant= Nouvelle_Cellule; return l; /* insérer un élément après une position donnée */ TLISTE inserer (TLISTE l, TELEMENT e, TPOSITION pos) // précondition :0 < pos < longueur(l)+1 if (pos<1 pos>taille(l)+1) printf("erreur : rang non valide!\n"); exit(-1); PCELLULE Nouvelle_Cellule=Creer_Cellule (); Nouvelle_Cellule ->element=e; Nouvelle_Cellule ->suivant=null; if (pos==1) Nouvelle_Cellule ->suivant=l; l= Nouvelle_Cellule ; else int k; PCELLULE p=l; for (k=1; k<pos-1; k++) p=p->suivant; Nouvelle_Cellule ->suivant= p->suivant; p->suivant= Nouvelle_Cellule; return l; Supprimer un élément après une position donnée Algorithme Atteindre l 'élément à supprimer qui se trouve à une position donnée Utiliser un pointeur temporaire pour sauvegarder l'adresse de l élément à supprimer. Faire pointer le pointeur suivant de l'élément précédent vers l'adresse du pointeur suivant de l'élément à supprimer. Libérer la mémoire occupée par l'élément supprimé TLISTE suppimer (TListe * liste, TPOSITION pos) if (pos<1 pos>taille(l)) printf("erreur : rang non valide!\n"); exit(-1); TCELLULE *=liste; TCELLULE *p1 for (int i = 1; i < pos; ++i) p= p->suivant; // position pos p1 = p->suivant; // position pos+1 p->suivant = p->suivant->suivant; // position pos+2 //éventuellement free (p1-> element); free (p1) return liste; ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 19 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 20
void detruire (TListe liste) TLISTE p2delete; while ((liste!= NULL) p2delete = liste; liste = liste->suivant; //free (p2delete->element); free (p2delete); Manipulation de listes chaînées Recherche par valeur TLISTE Rechercher( TLISTE liste, TELEMENT e) TLISTE p = liste; While ((p!= NULL) If ( p->element ==e)) return p else p = p->suivant; return p; Recherche par position TLISTE Renvoyer( TLISTE liste, TPOSITION pos) Listes chaînées particulières Pour avoir le contrôle de la liste il est préférable de sauvegarder certains éléments : le premier élément (début), le dernier élément (fin), l élément courant, le nombre d'éléments. Un liste est [un pointeur sur] une structure composée de plusieurs champs. typedef struct TCELLULE * debut; TCELLULE * fin;.. TCELLULE *Courant; int NB2Elements; TListe; Complexité : Plusieurs autres variantes des listes chaînées Liste doublement chaînée : On introduit un double chaînage pour autoriser un parcours dans les deux sens. Liste avec sentinelle : On peut insérer une tête de liste fictive (une sentinelle, toujours présente), pour simplifier quelques opérations et éviter plusieurs validations. ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 21 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 22
Liste avec sentinelle La liste vide doit comporter au moins un élément, la sentinelle L adresse de la liste n est pas modifiée par les insertions/ suppressions en tête de liste. Liste circulaire ou anneau : Le pointeur final pointe sur le premier élément de la liste Donner un successeur au dernier élément de la liste. Le premier et le dernier élément de la liste sont comme tous les autres. Liste circulaire avec sentinelle. Le premier élément de la liste, sentinelle, est toujours présent, le dernier élément pointe vers la sentinelle. Fin de liste : on ne teste plus si le pointeur est nul, mais s il est égal à la sentinelle. On peut utiliser la place pour mettre une information temporaire (cas de la recherche séquentielle). Comparatif des deux représentations Tableaux/listes chainées. Tableaux Accès direct et rapide aux éléments. Coût élevé des opérations d insertion et de suppression d un élément (déplacement d éléments).. Inapproprié pour de très grandes listes Gestion de la mémoire Tableaux Taille maximale fixée Gestion statique de la mémoire Tout l espace mémoire est alloué à l avance. Espace mémoire optimisé pour la sauvegarde des éléments de la Listes chainées Accès séquentiel, souvent dans une seule direction. Insertion et suppression rapides et efficaces. insertion par déviation suppression par court-circuit bien adaptée pour la manipulation des séquences de longueurs différentes Listes chainées Pas de limitation de taille gestion dynamique de la mémoire. Surencombrement de l espace mémoire pour la sauvegarde des pointeurs liste. Complexité des opérations Opération Tableau Liste Accès à un élément O(1) O(n) Insertion/Suppression en tête O(n) O(1) Insertion/suppression au milieu O(n) O(n) Parcours O(n) O(n) ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 23 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 24
Conclusion Choix de l implémentation? Tout dépend des opérations envisagées. ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 25 ENP /2011/2012/G.I /1 Année / Chap4 : S2D, Les Listes 26