Banière du site

[koala01.free.fr]->Tutoriaux->Principes de Programmation ->La Pile

Image d'imprimante   image d'enveloppe

19.1 Le principe

Quand on souhaite gérer des données, il est parfois intéressant (voire utile ou nécessaire) de pouvoir disposer de ces données dans l'ordre inverse de celle dans laquelle elles ont été introduites, sans que les données ne soient triées d'une manière ou d'une autre (mis à part l'ordre d'introduction)

Le principe que nous allons utiliser pour y arriver est ce que les gens habitués à la gestion de stock nomment un système L.I.F.O., qui n'est que l'acronyme de «Last In First Out», ou pour ceux qui préfèrent le français: Dernier Entré, Premier Sorti.

C'est un principe relativement simple à mettre en oeuvre, car il "suffit", dans la vie courante, de mettre les objets les uns sur les autres (de les empiler), un peu à la manière de caisses.

Le seul objet auquel on ait un accès sans risquer de tout faire tomber, c'est fatalement celui qui se trouve tout en haut de la pile…

On peut alors retirer l'objet qui est tout au dessus de la pile, puis le suivant (qui sera par conséquent devenu le nouvel objet qui est au dessus de la pile) et ainsi de suite jusqu'à ce qu'il n'y ait plus d'objets à supprimer (le dernier à partir étant celui qui repose sur le sol ou sur l'étagère, et c'est effectivement le premier qui a été mis en place).

Mais on peut aussi décider d'arrêter de retirer des objets, et d'en mettre de nouveaux (c'est ce qui se passe avec vos caleçons, si du moins vous ne choisissez pas le caleçon que vous mettez, quand votre femme ou votre mère vient ranger les caleçons qui ont été lavés).

Evidemment, il se peut très bien, en fonction de la fréquence d'arrivage de nouveaux objets, que les quelques premiers qui ont été rangés ne soient jamais retirés…

fleche haut

19.2 La structure

Le gros problème en informatique, c'est que, comme nous allons demander d'allouer la mémoire en vue de contenir les informations qui entrent de manière dynamique, on ne sera jamais en mesure de déterminer nous même l'adresse mémoire à laquelle se trouvent ces données…

La solution sera alors de créer une structure qui, non seulement, contiendra les données qui nous sont nécessaires (et certaines de ces données peuvent d'ailleurs être des structures qui contiennent elles-mêmes des données, selon un principe de poupées russes), mais aussi un pointeur vers l'adresse mémoire qui contient la donnée qui a été introduite juste avant.

Chaque élément qui prendra place dans une pile (comme dans toute structure «dynamique») prendra le nom, pour respecter le langage informatique, de «noeud» (node en Anglais).

fleche haut

19.3 Un exemple pour la compréhension

Imaginons quelques instants que nous souhaitions disposer de la possibilité d'introduire les coordonnées de nos connaissances, contacts et amis.

Pour faire simple, nous considérerons que les coordonnées en question sont le nom, le prénom, et l'adresse.

Toujours dans un soucis de simplicité, nous estimons dans cet exemple que le nom et ne prénom ne dépasseront pas 20 caractères et que l'adresse ne dépassera pas 50 caractères.

Il est très facile de créer une structure (nommons la logiquement Coordonnee) qui nous permette d'introduire ces trois informations pour un contact, sous la forme de:

Coordonnee
Nom type Commentaire
Nom 20 caractères Pour garder le nom du contact
Prenom 20 caractères Pour garder le prénom du contact
Adresse 50 caractères Pour garder l'adresse du contact

 Et dont l e code pour la créer ressemblera à s'y méprendre à:

structure Coordonnee
{
	caracrere Nom[20] |
	caractere Prenom[20] |
	caractere Adresse[50] |
}

Il devient ensuite facile de créer une autre structure,(que nous nommerons logiquement Contact) dont un des éléments sera de type "Coordonnee" (type dont on dispose, vu qu'on vient de le créer) nommé "Data", et dont un autre élément sera un pointeur vers la structure Contact qui a été précédemment introduite (nous le nommerons logiquement "Precedent").

La forme prise par cette deuxième structure sera donc du genre de

Contact
Nom Type Commentaire
Data Coordonnee permettra d'avoir le nom, le prénom et l'adresse de notre contact
Precedent Contact Pointeur qui contiendra l'adresse à laquelle trouver le contact introduit avant celui sur lequel on travaille

Le code pour la création de cette structure ressemblera à quelque chose du genre de:

Structure Contact 
{
	Coordonnee Data|
	Contact *Precedent |

}

nota: Au risque de me répéter encore une fois, il n'y a pas de mauvaise solution, mais seulement des solutions plus efficaces que d'autres.

Si j'ai décidé d'utiliser une structure à part pour les informations du contact, ce n'est qu'en suivant une fois de plus l'inspiration du moment, et nous aurions très bien pu décider de ne créer qu'une structure qui aurait contenu les "champs" Nom, Prenom, Adresse et le pointeur Precedent…

L'avantage que l'on peut trouver à travailler comme je l'ai fait sera, par exemple, de disposer d'une structure qui soit en mesure de récupérer ou d'envoyer les informations dans un fichier externe.

Par contre, si le but n'est pas d'utiliser un fichier externe, une structure unique ne présente aucun inconvénient majeur (d'aucuns pourraient estimer qu'il sera plus difficile de s'y retrouver, mais bon)

fleche haut

19.4 Le nécessaire de travail

Pour pouvoir travailler sereinement, il faudra que l'on dispose principalement d'un pointeur (ici de type Contact) qui prenne l'adresse du dernier noeud qui a été rajouté.

Pour la manipulation de notre pile (ajout/suppression d'éléments à la pile), nous aurons également besoin d'un pointeur temporaire de travail (toujours de type Contact).

fleche haut

19.5 Petit Rappel

Oui, je le sais très bien, je radote…  Mais j'estime nécessaire de rappeler que pour vous éviter des problèmes, il est toujours préférable d'initialiser un pointeur sur l'adresse mémoire 0(NULL) tant qu'on n'a pas déterminé avec certitude une adresse allouée sur laquel il doive pointer…

fleche haut

19.6 L'«emiplage»

Vous serez sans doute d'accord avec moi si je vous dis que le fait de prévoir la possibilité de gérer des données sans introduire de donnée ne sert pas vraiment à grand chose…

Vous serez d'ailleurs tout aussi d'accord avec moi si je vous dis que vous aurez du mal à utiliser des données sans les avoir récupérées (d'une manière ou d'une autre) au préalable

Vous serez donc également d'accord avec moi que la première chose qu'il vous faudra sans doute faire, c'est de remplire une première fois votre pile… (oui, je sais, je viens d'enfoncer trois portes ouvertes)

Ben dans ce cas, qu'attendons nous pour le faire (si ce n'est de savoir la manière de s'y prendre)?

Le principe pour remplir notre pile sera tout simple, et utilisera un pointeur de travail temporaire.

Le shéma ci-dessous vous permettra sans doute de mieux comprendre…

shéma de principe de l'empilage

shéma de principe de l'empilage

Ce shéma ne présente que l'ajout de trois noeuds, mais le principe reste bien évidemment identique qu'il s'agisse d'enempiler deux, dix ou plusieurs centaines.

fleche haut

19.7 Le «dépilage»

Tout comme il est impossible de se passer de l'empilage, il est tout à fait impossible de se passer du dépilage (du fait de libérer la mémoire de la totalité de la pile) avant de quitter l'application, pour éviter la fuite de mémoire.

Le principe utilisé pour vider notre pile sera tout aussi simple que celui qui a été utilisé pour la remplir.

Il utilisera également un pointeur de travail temporaire:

Nous donnons à notre pointeur de travail la valeur du pointeur Precedent du premier élément disponible de la pile (ce qui correspond bien à lui donner comme valeur l'adresse du deuxième noeud).

Nous libérons la mémoire du premier noeud

Nous finissons en signalant que le noeud auquel on a acces est maintenant celui pointé par notre pointeur de travail

Il n'y a plus qu'à recommencer tant qu'il y a des noeuds à supprimer.

Pour ceux qui comprennent plus facilement s'il visualisent, le shéma ci-dessous devrait rendre la compréhension plus facile

shéma de principe du dépilage

shéma de principe du dépilage

Evidemment, le principe restera identique qu'il s'agisse de libérer la mémoire de quatre éléments comme sur le shéma ou d'en libérer dix, cent ou dix mille…

fleche haut

19.8 L'accès aux données

Créer une pile de données sans essayer d'accéder à ces données n'aurait strictement aucun intérêt…

Le principe de l'accès aux données sera sensiblement identique à celui du dépilage, à ceci près qu'il sera alors hors de question de supprimer les données qui n'auraient pas été sélectionnées, pas plus qu'il ne serait question de modifier la valeur du pointeur de référence.

Tout le travail utilisera alors un pointeur de travail temporaire, qui parcourrera la pile à la recherche de l'élément qui nous intéresse:

Le pointeur de travail temporaire prendra alors la valeur de notre pointeur de référence et servira à vérifier si les données sont celles qui nous intéressent

Si les données ne nous intéresserent pas, le pointeur de travail prendra alors la valeur de l'adresse pointée par Precedent, de manière à accéder à ce nouvel élément

Nous retrounons dans la boucle jusqu'à ce que l'on trouve les valeurs qui nous intéressent ou que la valeur de notre pointeur de travail soit 0(NULL), ce qui représente la fin de la pile.

Voici le dernier petit shéma de principe (du moins, en ce qui concerne la pile) qui peut vous permettre de comprendre plus facilement.

shéma de principe de recherche de données dans la pile

shéma de principe de recherche de données dans la pile

fleche haut

19.9 Pour être complet

Il serait tout à fait possible d'envisager une application qui empilerait dix élements (traiterait ces données), puis en supprimerait trois (retraitement éventuel), en rajouterais cinq nouveaux (nouveau traitement), pour en retirer quatre (encore retraitement), avant d'en rajouter six, etc avant de finir en libérant la mémoire de toute la pile…

Les données auxquelles l'application aurait accès ressembleraient alors à ceci:

Données accessibles par l'application

Données accessibles par l'application

Données accessibles par l'application

fleche haut

19.10 Les nassichneiderman

Maintenant que ces différents principes sont acquis, il est peut être temps de s'attaquer aux nassichneiderman qui en permettent la mise en oeuvre…

Encore une fois, les nassichneiderman que je vous présente n'ont absolument pas la prétention d'être les meilleurs, mais simplement celle de fonctionner, et suivent en grande partie l'inspiration du moment.

Nassichneiderman de principe pour la gestion des piles

Nassichneiderman de principe pour la gestion des piles

J'ai, dans ce cas, estimé préférable de travailler carrément avec trois routines qui reçoivent comme argument un pointeur vers notre pointeur de référence (qui peut etre nul si la pile est vide) et qui renverront un pointeur vers la structure sélectionnée

fleche haut

19.11 La routine Empile(actuel)

Cette routine servira, vous vous en rendez surment compte, à empiler les éléments de la pile.

Le terme Tant que données représente ici un test qui provoquera l'entrée dans la boucle tant qu'il y aura des données fournies à placer dans l'élément à créer (le test en lui même étant à adapter en fonction de l'application), ce qui pourrait se traduire par la réussite de lecture dans un fichier.

Il serait tout à fait envisageable d'utiliser une boucle de type "jusque" si l'introduction des informations devait se faire par exemple, par introduction clavier (avec proposition du choix de continuer)

La variable ok quant à elle permet de s'assurer que l'allocation mémoire précédente (s'il y en a eu une) s'est bien déroulée. Il est en effet inutile de continuer à vouloir allouer de la mémoire pour donner des valeurs dès le moment où une allocation a échoué…

fleche haut

19.12 La routine Depile(actuel)

Cette routine aura pour but de vider intégralement la pile, mais il suffirait d'adapter le test de la boucle "jusque", en ajoutant une condition pour qu'elle ne retire de la file qu'un nombre donné d'éléments, ou que les éléments qui se ont été introduits avant celui dont un champs a une valeur donnée.

nota:Il est primordial, si vous adaptez le test de laisser le test de base vérifiant la valeur de actuel, car il s'agit de s'assurer que la boucle sera quittée si on vient à retirer le dernier élément présent dans la pile.

fleche haut

19.13 La routine Recherche(actuel)

Cette routine est une simple boucle qui recherche l'élément de la pile qui correspond à nos souhaits (représenté par pas trouvé).

Evidemment, la boucle doit d'office être quittée une fois que tous les éléments de la pile ont étés testés…

Important Cette routine ne doit en aucun cas servir à la redéfinition du pointeur de référence (sous peine de perdre le "bout" de la pile, ce qui provoquerait des problèmes majeurs)

Les seules routines qui doivent permettre la modification du pointeur de référence (celui qu'on retrouvera dans la routine principale) sont celles qui gèrent l'allocation et/ou la libération de mémoire pour les différents éléments de la pile

fleche haut

19.14 Le tri des données

Il est bien sûr envisageable de créer un algorithme qui permettrait le tri des données, mais le principe même de la pile (le dernier ajouté est le premier à partir) rend la manoeuvre inutile.

Essayer de l'implémenter dénoterait la totale incompréhesion du but recherché en utilisant la pile.

Eneffet, le but premier de la pile est de pouvoir répérer les données dans l'ordre inverse de celui dans lequel elles ont éées introduites…

fleche haut

19.15 La remarque finale

Rien n'empêcherait d'essayer directement d'accéder au Nième élément de la pile avec un code qui ressemblerait à

 trouve=Dernier->precedent->precedent->precedent//accède au troisième élément de la pile

mais à la condition expresse que l'on se soit assuré qu'il y ai effectivement un nombre suffisant d'éléments dans la pile (sous peine de provoquer un plantage de l'application)

image d'imprimante   image de mail   fleche haut

Evaluation donnée par les visiteurs
Cette page a été évaluée 1 fois et a obtenu une moyenne de compréhensible, sans plus
Mon appréciation sur la compréhensibilitéde cette page est:
  • incompréhensible
  • mal expliquée
  • compréhensible, sans plus
  • bien expliquée
  • très bien expliquée

fleche haut

[koala01.free.fr]->Tutoriaux->Principes de Programmation ->La Pile

Copyright (©) 2005 (Philippe Dunski)

Ce cours est libre, vous pouvez le redistribuer et/ou le modifier selon les termes de la Licence Publique Générale GNU publiée par la Free Software Foundation (version 2 ou bien toute autre version ultérieure choisie par vous).

Ce cours est distribué car potentiellement utile, mais SANS AUCUNE GARANTIE, ni explicite ni implicite, y compris les garanties de commercialisation ou d'adaptation dans un but spécifique. Reportez-vous à la Licence Publique Générale GNU pour plus de détails.

Cependant, l'auteur apprécierait grandement que vous lui fassiez part de toute modification apportée à son contnu

Vous pouvez le contacter par mail à l'adresse koala01@free.fr

Vous pouvez trouver une adaptation française de la licence GNU/GPL à l'URL http://www.linux-france.org/article/these/gpl.html