• 430 likes • 529 Views
Leçon 6 : Structures de données dynamiques. IUP 2 Génie Informatique Méthode et Outils pour la Programmation Françoise Greffier. Structures de données dynamiques. Notion de conteneur Généricité Implantation dynamique d ’une pile surcharge de l ’opérateur =
E N D
Leçon 6 :Structures de données dynamiques IUP 2 Génie Informatique Méthode et Outils pour la Programmation Françoise Greffier
Structures de données dynamiques • Notion de conteneur • Généricité • Implantation dynamique d ’une pile • surcharge de l ’opérateur = • surcharge du constructeur par copie • Implantation : chaînage arrière
Des structures de données classiques Exemples : liste, pile, file attente, arbres, graphes ... Exemples :Une liste d’étudiants, de notes, de courses Il existe des algorithmes classiques associées à ces structures de données : parcours, insertion, suppression ... • La bibliothèque standard (Standard Template Library : STL) fournit au programmeur des classes prédéfinies qui offrent : • des structures de données classiques • associées à leurs opérations classiques
Conteneur Un conteneur est une classe très générale modélisant une collection d’éléments de n’importe quel type. • Il existe deux grandes catégories de conteneurs : • les séquences • les conteneurs associatifs
Les conteneurs modélisants des structures de données classiques (liste, pile, file d ’attente, graphe …) sont implantées en bibliothèque. Des composants réutilisables http://www.sgi.com/tech/stl/ Les opérations de base y sont incluses : - ajouter un élément (insert) - supprimer un élément (erase) - sélecteur vide? (empty) etc ...
Une séquence est un conteneur dans lequel les éléments sont organisés linéairement (il y a un premier, un suivant … , un dernier). Les séquences On distingue trois séquences : vector (tableau) list (liste) et deque (suite circulaire) Les classes vector, list et deque se distinguent par les opérations d ’accès et par un compromis entre les coûts des accès, insertions et suppressions.
Les séquences : un exemple Conteneur : vector vector<int> v(3); // Declare a vector of 3 elements. v[0] = 7; v[1] = v[0] + 3; v[2] = v[0] + v[1]; // v[0] == 7, v[1] == 10, v[2] == 17 reverse(v.begin(), v.end()); // v[0] == 17, v[1] == 10, v[2] == 7
A partir des séquences de base, on dérive trois types abstraits : pile, file d ’attente et file d ’attente prioritaire. Les séquences : types abstraits Les classes (conteneur) : - pile : stack - file d ’attente : queue - file d ’attente prioritaire : priority_queue
Un conteneur associatif offre la possibilité de rechercher rapidement les éléments mémorisés par leur clé. Les conteneurs associatifs Contrairement aux séquences, les conteneurs associatifs peuvent identifier leurs éléments par la valeur d ’une clé. Cette clé a parfois pour valeur une partie de la valeur de l ’élément. Ces conteneurs sont particulièrement adaptés dans des applications où l ’on doit rechercher des éléments connaissant leur clé.
Les conteneurs associatifs Ces conteneurs sont « paramétrés » avec le type de la clé correspondant. . On doit avoir une relation d ’ordre total sur les clés Opérations sur les clés : recherche : find (complexité logarithmique) Comptage selon la clé : count
Opérations utilisant les clés : recherche : find (complexité logarithmique) Comptage selon la clé : count Les conteneurs associatifs • On trouve quatre conteneurs associatifs • set : clé = valeur de l ’élément • multiset : set avec occurrences multiples • map : élément = (clé,v) • multimap : map avec occurrences multiples sur clé
Conteneur pile Un conteneur est un objet contenant d ’autres objets • Nous étudions dans ce chapitre le conteneur : pile • Spécification • Implantation
Une pile A une pile sont associées 4 opérations qui la caractérisent Empiler un composant Consulter(sommet) Sélecteur vide(état) Dépiler Valeur placée au sommet Vrai ou faux FINALEMENT : Une pile permet de traiterdes composants dans l ’ordreinverse de leur apparition
La classe pile class pile{ public : pile(void); // constructeur ~pile(void); // destructeur // IC non vide X& sommet (void); // Sélecteur => valeur placée au sommet de IC bool vide(void); // Sélecteur => (IC vide?) void empiler(const X&); // x est ajouté au sommet de IC // IC non vide void depiler(void); // composant placé au sommet est supprimé private : … };
Réutilisabilité On cherche à écrire des objets réutilisables. On aimerait concevoir une classe qui modélise une pile de portée générale : une pile n’importe quels composants. pile<char> Pcar; pile <personne> Ppersonne; ==> Concevoir une classe générique
Facteurs de qualité d ’un logiciel • Une classe décrit une famille d ’objets : • Cohérente • Lisible • Extensible • Réutilisable Réutilisabilité Possibilité d ’obtenir toutes les occurrences d ’une classe s ’appliquant à différents types, en ne décrivant qu’une seule fois la classe.
Paramètre génériqueFormel template en C++ Le mot clé template permet l ’écriture de fonctions génériques ou de classes génériques. template <class X>class pile{ public : pile(void); // constructeur void empiler(constX&); // x est ajouté au sommet de IC private :int taille; X* t;//si implantation dans un tableau de X}; //t est un tableau dynamique
Paramètre génériqueeffectif Utilisation d ’une classe générique void main (void) { pile <char> Pcar; char* ch; ch=new char[20]; cout << "\n entrez une chaîne :"; cin >> ch;int l=length(ch); for (int i=0;i<l;i+=1) Pcar.empiler(ch[i]);//============================== cout << "\n Chaîne inversée :"; while (!Pcar.vide()) { cout << Pcar.sommet(); Pcar.depiler(); } delete [ ] ch; }
Les paramètres Le paramètre générique effectif d ’une classe peut être un identificateur de type prédéfini ou un identificateur de classe. Une classe générique peut avoir plusieurs paramètres. Syntaxe : template <class A, class B>
Les paramètres Les paramètres peuvent avoir des valeurs par défaut template <class X, int max=100>class pile{ public : ... // IC non vide X sommet (void); // Sélecteur => valeur placée au sommet de IC void empiler(constX&); // x est ajouté au sommet de IC private : int taille; X T[max]; // si implantation dans un tableau}; // statique
Utilisation d ’une classe générique void main (void) { pile <char,20> Pcar; //pile de 20 caractères maximum pile <personne> PPersonne; //pile de 100 personnes maximum ... }
Le compilateur instancie une fonction int min (const int&, const int&) Exemple de fonction générique template <classX> X min (constX& A,constX& B){ if (A<B) return(A); return (B); } L ’opérateur < doit être surchargé dans les classes qui utiliseront le modèle min voidmain (void){ int i,j,m; cin >> i,j; int m=min <int>(i,j); ... }
Implantation de la classe pile • Implantation dans un tableau statique private : int taille; X T[max]; // si implantation dans un tableau}; // statique pbs : Evaluation de max Limite de la mémoire statique ==> gestion dynamique
Implantation de la classe pile • Implantation dans un tableau dynamique private : int taille; X* t; // si implantation dansint maximum; }; // un tableau dynamique Tableau t réservé dans le tas Constructeur avec un paramètre : taille maximum ==> bonne implantation si on peut évaluer la taille maximum à l’utilisation de la pile
Utilisation de la pile void main (void) { pile <char> Pcar(25); char* ch; ch=new char(26); cout << "\n entrez une chaîne :"; ... }
Implantation de la pile # include <iostream.h> template<classX> class pile { public: pile (int); //constructeur : init taille maxi ... ~pile (void); // destructeur private : X* t; // si implantation dansint indexSommet; // un tableau dynamique int maximum; };
Implantation de la pile • Constructeur template <class X> pile<X> :: pile (int max) { t = new X [max]; indexSommet = -1; maximum =max; } • Destructeur template <class X> pile<X> :: ~pile (void) { if (t!=NULL) delete [ ] t; }
Implantation de la pile • dépiler template <class X> voidpile<X> :: depiler(void){ if (!vide())indexSommet -= 1;} • Sélecteur : vide template <class X> bool pile<X> :: vide (void){ return (indexSommet == -1); }
Implantation de la pile template <class X> voidpile<X> :: empiler(const X& e){ if (indexSommet+1 < maximum) { indexSommet += 1;t[indexSommet] = e;} } • empiler Remarque : on suppose que l ’opérateurd ’affectation existe ou est surchargé dans La classe qui correspond au paramètre X. => intérêt de la surcharge des opérateurs
A V E C Surcharge de l’opérateur d’affectation L ’opérateur d ’affectation est une méthode implicite dans toute classe. Par défaut, il réalise une copie des valeurs qui correspondent à la section private de la classe. p1.t pile<char> p1; ... pile<char> p2; p2=p1; Problème : Toute modification du tableau des caractères empilés dans la pile p1 se répercute dans la pile p2 (et inversement) p2.t
p1.t A V E C A V E C p2.t Surcharge de l’opérateur d’affectation Pour réaliser une vraie copie de pile par affectation, il faut surcharger l ’opérateur implicite. pile<char> p1; ... pile<char> p2; p2=p1;
Surcharge de l’opérateur d’affectation template<classX> class pile { public: pile<X>& operator = (const pile<X>&); // opérateur d ’affectation p1 = P2 <=> p1.operator = (p2); Le comportement habituel de l’opérateur = autorise à enchaîner les affectations : p1=p2=p3; => la fonction retourne une référence et l ’argument est une référence.
Implantation template <classX> pile<X>& pile<X> ::operator = (const pile<X>& p) { if (t!=NULL) delete [ ] t; //on nettoie this t=newX [p.maximum]; maximum=p.maximum; for (int i=0; i<maximum;i+=1) t[i]=p.t[i]; indexSommet=p.indexSommet; return (*this);//retourner : une référence sur l ’objet courant }
Surcharge du constructeur par copie Le constructeur par copie est une méthode implicite dans toute classe. Cette méthode est appelée automatiquement dans les opérations suivantes : • Création et initialisation d ’une nouvelle instance X I2=I1; X I2(I1); • passage d ’un argument par valeur • retour d ’une fonction return (I); // une copie de I est retournée
Surcharge du constructeur par copie Prototype : class C { public : C (const C&); // constructeur par copie ...}; Par défaut, cette méthode implicite réalise une copie des valeurs qui correspondent à la section private de la classe. Si l ’implantation d ’un objet est dynamique le constructeur par copie, réalisera par défaut une copie de pointeur. ==> Surcharger cet opérateur afin qu’il crée un vrai clône de l ’instance qui le déclenche.
Implantation template <classX> pile<X> :: pile (const pile<X>& p) { t=new X [p.maximum]; maximum=p.maximum; for (int i=0; i<maximum;i+=1) t[i]=p.t[i]; indexSommet=p.indexSommet; }
C P.PtrSommet E E P.PtrSommet V V A A NULL NULL Implantation de la classe pile • Implantation par chaînage de cellules P.depiler( )
8 5 9 1 Implantation de la classe pile Les conteneurs doivent-ils stocker nécessairement des composants ou des pointeurs sur les composants ? • Quand le conteneur stocke les composants, et que ceux-ci sont homogènes, alors les données peuvent être recopiées dans la structure. Cette gestion est simplifiée. Exemple : une pile d ’entiers
8 1/5 9+3i 3,1416 Implantation de la classe pile Les conteneurs doivent-ils stocker nécessairement des composants ou des pointeurs sur les composants ? Quand le conteneur stocke des pointeurs sur les composants : • On peut stocker des données non homogènes. • On évite les copies multiples des données (intéressant si la taille du composant est importante) . • La gestion des composants (création, destruction est à la charge du programmeur). Exemple : une pile de nombres Un nombre peut être un entier, un réel, un complexe, un rationnel.
Classe cellule #include <iostream.h> template <class X> class cellule {public: cellule(X*, cellule< X>*);//constructeur ~cellule (void); //destructeur X composant (void); //sélecteur => valeur du composant }; friendclass pile <X>; private : X* ptr; cellule<X> * ptrprec;
Classe pile template <class X> class pile {public: pile (void); // IC vide est créée pile (const pile<X>&); // Constructeur par copie ~pile (void); // IC est détruite pile<X>& operator=(const pile<X>& P); // IC = P void empiler (const X& a); // a est empilé dans IC //IC non vide void depiler (void); // l'élément placé au sommet de IC est supprimé //IC non vide X& sommet (void); // sélecteur : valeur placée au sommet bool vide(void); //sélecteur : ( IC est vide?) }; private : cellule<X>* ptrSommet; // pointeur sur la cellule sommet
destructeur Implantation de la classe pile constructeur template <class X> pile<X>::~pile(void) { if (ptrSommet != NULL) detruire( );} template <class X> pile<X>::pile (void) { ptrSommet = NULL; } template <class X> void pile<X>::detruire (void) { cellule<X>* p; while(ptrSommet != NULL){ p=ptrSommet->ptrprec; delete (ptrSommet); ptrSommet=p; } }
//IC void detruire (void); //détruire toutes les cellules de la pile IC template <class X> cellule<X>:: ~cellule (void){ if (ptr!=NULL) delete ptr; } Implantation de la classe pile Pour que l ’espace occupé par les composants pointés dans chaque cellule soit libéré lorsque la fonction détruire est appliquée, il faut que le destructeur de cellule ait cet effet : Chaque fois que l ’instruction deleteest appliquée sur un pointeur de cellule, alors le destructeur de la classe cellule est activé. Libère l ’espace occupé par le composant pointé par ptr