Banière du site

[koala01.free.fr]->Tutoriaux->Principes de Programmation ->La liste chainée

Image d'imprimante   image d'enveloppe

21.1 Introduction

Je vous parlais, dans les pages précédentes, de la file, en vous disant que le fait de prévoir un tri des données qu'elle contient dénoterait une totale méconnaissance du principe même du terme "file".

A vrai dire, l'impossiblité d'effectuer un tri dans une file est purement sémantique, du fait que la file fait appel au principe FIFO (First In First Out), et que l'idée même d'effectuer un tri est alors en contradiction avec ce principe.

Cependant, de même qu'il n'est pas rare, quand vous faites la file à la caisse d'un magasin, et que vous n'avez qu'une bouteille de lait, de rencontrer une gentille personne qui vous propose de passer devant elle, il est parfois intéressant de pouvoir trier les différents éléments de la file.

De cette manière, je prenais l'exemple d'une application qui aurait la gestion de contact , mais, il faut avouer que le fait de pouvoir, par exemple, les trier par ordre alphabétique, sur base de leur nom, pourrait présenter de sérieux avantages.

La «liste simplement chaînée», souvent éludée au simple terme de «liste», ou encore simplement «liste chainée» est le concept qui permet de sortir de cette "impossibilité de tri", du moins, du point de vue purement sémantique du terme "file".

fleche haut

21.2 La structure

La structure d'une liste simplement chaînée sera donc strictement identique à celle d'une file, à savoir qu'elle devra contenir les données à garder en mémoire, et disposer d'un pointeur permettant de récupérer l'élément qui suit dans la liste.

Seul le fait de disposer de la capacité d'effectuer un tri modifiera quelques peu les choses, mais du point de vue de la conception des différents algorithmes, et non de celui de la structure.

Imaginons donc 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 souci 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 sera introduite juste après (nous le nommerons logiquement "Suivant").

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
Suivant Contact Pointeur qui contiendra l'adresse à laquelle trouver le contact introduit apres 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 *Suivant |

}

nota: J'aurais très bien pu d&eactue;cider d'une autre autre structure, mais j'ai décidé, dans un soucis de cohérence avec les chapitres précédents, et pour vous permettre de faire plus facilement le rapprochement entre les différents concepts, de garder une structure séparée pour les informations elles-même.

Les remarques concernant les structures énoncées dans les chapitres précédents sont donc exactement les mêmes.

fleche haut

21.3 Pousser l'analyse

Vous penserez peut être que le simple fait de prévoir la possibilité d'un tri ne posera pas *énormément* de problèmes…

Hé bien, je tiens à vous détromper tout de suite, car ce simple fait rend les besoins d'analyse bien plus importants qu'il n'y parraît de premier abord.

D'abord, le fait même de penser à trier des données inclut qu'il faille se poser la question du critère de tri, facile à déterminer s'il n'y a qu'une information dans les données, mais devenant de plus en plus complexe au fur et à mesure qu'on ajoute des informations.

En effet, si l'on crée une liste de nombres, on n'aura qu'à se poser la question de savoir si on souhaîte les trier de manière ascendante (du plus petit au plus grand), descendante (du plus grand au plus petit), voire, avoir la possibilité de faire les deux…

Si l'on ne travaille qu'avec deux informations (le nom et la ville, par exemple), il faudra se poser la question de savoir si l'on veut trier en fonction du nom, de manière ascendante ou descendante, en fonction de la ville, également de manière ascendante ou descendante, mais il est possible aussi de trier sur une "composition" des deux, d'abord la ville, puis le nom, ou d'abord le nom, puis la ville, chaque possibilité étant susceptible de donner un résultat différent, et, bien sûr, sans oublier qu'il est a priori possible de le faire de manière ascendante ou descendante.

Avec seulement deux informations distinctes, il est donc possible de trouver 8 critères de tri différents possibles.

Soyez conscient que, de manière générale, on peut estimer que le nombre de critères de tri potentiel est de l'ordre de l'élévation au cube du nombre d'informations disponibles, même si, de fait, certains critères de tri peuvent ne pas être retenus, le tout dépendant de l'application que vous souhaitez créer.

De la même manière, si l'on accepte qu'un élément de la liste soit rajouté "n'importe où dans la liste", il devient nécessaire d'envisager que l'on doive être en mesure de supprimer n'importe quel élément de la liste, et non seulement le premier ou le dernier.

Comme il n'y a pas de loi fixe sur le sujet, et que tout dépend réellement de l'application que vous vous apprêtez à mettre en oeuvre, il est primordial que vous vous posiez dés le départ la question de savoir quels critères de tri seront nécessaires pour l'application à créer.

De plus, si vous retenez plusieurs critères de tri, il est possible d'envisager l'ajout d'un élément dans la liste de différentes manières:

Enfin, s'il est quelque part embêtant d'avoir besoin d'un critère de tri que l'on n'a pas envisagé lors de la conception, il est presque tout aussi frustrant d'avoir prévu des critères de tri que l'on n'utilise jamais…

C'est la raison pour laquelle je vous invite, quand vous décidez d'utiliser des listes (ou toute autre structure contenant plusieurs informations et destinée à être triée) à analyser en profondeur les besoins de votre application en ce qui concerne les critères à retenir pour effectuer un tri.

Dans le cadre de cette page, je vais me limiter à une analyse très restrictive, ne prenant en compte qu'un seul critère de tri, et qui veille à trier les informations directement lors de l'insertion d'un nouvel élément à la liste.

La raison de cette manière de faire est que le tri d'informations mérite à lui seul un chapitre complet, pour ne pas dire plus, dont je parlerai plus loin, et qu'il faut bien commencer par apprendre à marcher avant de vouloir courir.

Une application utilisant une liste chaînée

Pour ne pas faire tout de suite trop compliqué (après tout, un exemple n'a pas vraiment besoin d'être complexe pour présenter un concept ), nous allons "simplement" essayer de créer une application qui soit en mesure de recevoir un nombre inconnu de contacts, en nous basant sur la structure dont je viens de parler, et de les trier uniquement par ordre croissant en fonction de leur nom de famille.

Peut être vous souvenez-vous que je vous parlais, il y a quelques pages de la récursivité.

Je tentais de vous convaincre, et je n'ai nullement abandonné cette idée, que la récursivité pouvait présenter une alternative cohérente à certains problèmes.

Il se fait que la gestion dynamique d'information (par file, par liste ou par arbres, dont on parlera plus loin) présente justement le domaine idéal pour y faire appel.

Bien que toutes les informations aient déjà été fournies, l'analyse des buts et des besoins de l'application, ainsi que sa conception étant, rappelons le, toujours bien plus importante que la création du code, je ne vais pas déroger à ma these, et vais donc vous présenter l'analyse complete de cette application.

fleche haut

21.4 Analyse de l'application

fleche haut

21.5 La routine principale

Le nassichneiderman de la routine principale

Le nassichneiderman de la routine principale

les variables de la routine principale
Variable Type Description
liste contact (pointeur) noeud qui mémorisera la tête de la liste
info coordonnee structure temporaire récupérant les informations du contact en cours d'encodage
encore caractere caractere récupérant la valeur du choix de l'utilisateur d'encoder un nouveau contact ou non

fleche haut

21.6 Et les explications

La première ligne de l'algorithme initialise liste à 0 (NULL), de manière à être sûr qu'il ne pointe pas sur une valeur qui serait en mesure de poser problème.

Ensuite, nous récupérons dans info, qui a, rappelons le le type "coordonnee", les informations concernant le contact que l'on encode, en faisant appel à une fonction qui ne s'occupe que de cela.

L'appel d'ajout va permettre de gérer toute la gestion de la liste, et nous renvoyer l'adresse mémoire vers la tete de liste.  Adresse mémoire que nous assignerons logiquement à notre pointeur liste.

Une fois l'insertion du contact gérée, il est temps de demander à l'utilisateur s'il souhaite en introduire un nouveau.

Le fait de faire passer sa réponse en majuscule nous permet de simplifier le test, qui a pour seul but de lui demander d'être attentif.

La première fin de boucle jusque a pour but d'empecher qu'on sorte de la question tant que l'utilisateur est inattentif.

La seconde fin de boucle attend que l'utilisateur décide de ne plus introduire de contacts pour sortir de la période d'acquisition.

Nous appelons alors une procédure qui affichera toutes les informations des différents contact.

Enfin, il ne faut pas oublier de vider la liste des contacts avant de quitter l'application.  Par sécurité, nous remettons d'ailleurs notre variable liste à 0(NULL).

fleche haut

21.7 La fonction Intro()

La fonction intro() se "contentera" d'appeler une autre fonction de sécurisation de l'introduction (nommée acquis) en lui fournissant le nombre de caractères qu'elle doit accepter.

En voici le nassichneiderman:

Nassichneiderman de la fonction d'introduction des informations du contact

Nassichneiderman de la fonction d'introduction des informations du contact

Liste des variable de la fonction Intro()
Nom Type Description
Info Coordonnee Structure utilisée pour récupérer l'ensemble des coordonnées d'un contact

Comme vous le constatez, cette routine toute simple appelle en réalité trois fois une routine nommée ici Acquis, en lui passant un paramètre, qui n'est autre que la taille maximale que l'on a déterminé pour chacun des éléments de la structure.

fleche haut

21.8 La fonction Acquis()

Cette routine aura pour principal objectif de sécuriser l'acquisition des informations introduites par l'utilisateur, tout en permettant éventuellement l'effacement d'un (ou plusieurs) caractères.

En voici le nassichneiderman

nassichneiderman de la routine d'acquisition

nassichneiderman de la routine d'acquisition

Liste des variables utilisées pour acquis
Nom Type Description
Combien Entier (passée en argument) Variable permettant de garder le nombre maximal de caractères à accepter
x Entier Variable permettant de garder la coordonnée "X"(abscisse) de départ du curseur
y Entier Variable permettant de garder la coordonnée "Y"(ordonnée) de départ du curseur
Taille Entier Variable permettant de garder la taille actuelle de la chaine introduite
car Caractère variable permettant de garder le caractère introduit par l'utilisateur
chaine Caractère
(tableau de 51 éléments)
Chaine de caractère gardant les caractères introduits par l'utilisateur (et d'une taille de 51 éléments, de manière à être sûr de disposer d'un tableau suffisant pour l'introduction de toutes les données que l'on peut envisager)

fleche haut

21.9 Et ses explications

Comme la chaine la plus grande que l'on peut envisager pour les informations, de par la structure que l'on a créée, est de 50 éléments, il nous est indispensable de disposer au minimum d'un tableau de…50 éléments.

Le fait de disposer d'un tableau de 51 éléments nous garanti donc d'être en tout temps en mesure de disposer d'un tableau de taille suffisante.

La plupart des langages de programmation permettent de récupérer les coordonnées (x et y) du curseur à l'écran, en se basant sur le coin supérieur gauche de l'écran. La première chose que nous devons faire, c'est initialiser x et y à leurs valeurs respectives, de manière à pouvoir placer correctement les caractères que nous déciderons d'afficher.

Nous initialisons en outre la taille à 0, du fait… qu'il n'y a, à la base, aucun caractère dans la chaine que l'on veut utiliser .

Nous entrons alors dans une boucle de type "jusque", qui sera effectuée jusqu'à ce que le caractère introduit par l'utilisateur soi le caractère 13 (code ASCII de la touche <Enter>).

Nous récupérons la touche enfoncée par l'utilisateur sur son clavier dans la variable car.

Le premier test a pour but de nous assurer que n'ous n'avons pas attiend la taille maximale de la chaine attendue (ce qui pourrait poser problème lors du renvois de la chaine)

Si la taille maximale est atteinte, on arrive directement en fin de boucle.

Par contre, si la taille maximale n'est pas atteinte, nous pouvons commencer nos vérifications.

Le deuxième test a pour but de vérifier si nous avons affaire à un caractère "imprimable" ou non (pour rappel, la valeur du premier caractère ASCII imprimable est 32 et correspond à un caractère "espace", le dernier caractère affichable, bien que difficilement accessible ayant une valeur ASCII de 255)

Si nous avons un caractère affichable, nous l'ajoutons à la bonne place dans la chaîne de caractères temporaire, nous l'affichons à l'écran (après nous être placés au bon endroit à l'écran), et nous incrémentons la valleur de la taille en attente de la lettre suivante.

Si ce n'est pas le cas, nous vérifions d'abord s'il ne s'agirait pas du caractère correspondant à la touche <BackSpace> (retour arrière), qui a un code ASCII de valeur 8.

Si la touche <BackSpace> a été enfoncée, c'est que… l'utilisateur s'est rendu compte qu'il a fait une erreur…

C'est la raison pour laquelle nous décrémentons la variable taille, pour remplacer le caractère "précédent" par un caracètre de valeur 0, ce qui aura l'avantage supplémentaire de représenter la fin de la chaîne.

Nous effaçons alors le caractère à l'écran en le faisant remplacer par un espace.

Je vous l'accorde, le dernier test a de quoi surprendre&hellip;

Il faut savoir que les touches de fonctions (les touche <F1> à <F12> que l'on trouve au dessus de toutes les touches sur la plupart des claviers) ne renvoient pas un, mais deux caractères.

Le malheur veut que les fonctions d'acquisition non bufferisées des touches ne récupère qu'un seul caractère à la fois, et qu'il y a donc un caractère "en trop" dans le buffer du clavier, caractère inutile dans notre optique, mais qui risque de poser problème du fait qu'il risque d'être récupéré à la prochaine exécution de la boucle.

Par chance, le premier caractère renvoyé par les touches fonctions est un caractère nul (0), ce qui est facilement testable.

On décide donc de "vider" le buffer du claver (on pourrait en profiter pour vérifier la valeur, et provoquer un événement sur l'appuis de la touche <F1>, par exemple) de manière à rester aussi sécuritaire que possible.

Quand enfin, l'utilisateur appuie sur la touche <Enter>, nous quittons la boucle, nous "terminons" la chaine de caractères en rajoutant un caractère nul (0), et nous la renvoyons à la routine appelante.

Il est à noter que cet algorithme est particulièrement adapté pour toute application qui aurait besoin d'une acquisition sécurisée de valeurs par l'intermédiaire du clavier (de très légères modifications permettront de l'adapter à toutes les situations).

fleche haut

21.10 La routine d'ajout

Nassichneiderman de la routine d'ajout dans la liste

Nassichneiderman de la routine d'ajout dans la liste

Les variables de la fonction ajout
Nom Type Description
tete Contact (pointeur) Pointeur, passé en argument, qui contiendra le premier élément de la liste (la "Tête" de liste)
info Coordonnee Structure, passée en argument, contenant les informations à donner à l'élément de la liste
nouveau Contact (pointeur) Pointeur vers l'élément que l'on va rajouter à la liste
temp Contact (pointeur) Pointeur temporaire permettant, le cas échéant, de savoir après quel élément de la liste il s'agit d'insérer le nouvel élément.

Ce nassichneiderman peut paraître compliqué, mais il faut savoir qu'Il y a trois cas bien distincts à prendre en compte lorsque l'on ajoute un élément dans une liste triée

C'est ce que prend en compte le nassichneiderman que je vous présente.

Vous remarquerez sans doute la présence de deux tests imbriqués, ainsi que le fait que, encore une fois, je n'ai pas forcément suivi la "recommandation" d'essayer de créer les tests de manière à ce que la plus grosse partie des actions soit effectuée dans la partie "vrai" du test.

La raison en est bien simple: il était impossible d'utiliser un test à choix multiple, simplement parce qu'il ne sagissait pas de proposer plusieurs valeurs pour une même variable, mais bel et bien d'effectuer différents tests qui n'ont, en définitive, pas grand chose à voir entre eux.

D'un autre côté, il me semblait "plus facile", au moment où j'ai tracé ce nassichneiderman, de gérer réellement chaque cas séparément, en prenant effectivement le risque que la partie "Non" du test soit plus importante…

Mais voyons comment cela fonctionne.

Nous commençons par tester si tête vaut NULL, ce qui revient, sommes toutes, à vérifier s'il s'agit du premier élément que l'on introduit dans la liste.

Si c'est le cas, nous demandons l'allocation d'un nouveau pointeur, nous lui fournissons les informations concernant le contact, et nous le faisons renvoyer comme «tête de liste»,comme l'indique le schéma qui suit

Schéma de principe d'ajout du premier élément

Schéma de principe d'ajout du premier élément

Si ce n'est pas le cas, nous vérifions si, à tout hasard, l'élément que l'on veut rajouter à la liste ne serait pas celui qui doit venir en «tête de liste»…

Là encore, si c'est le cas, nous demandons l'allocation d'un nouveau pointeur de type contact et nous lui fournissons les informations le concernant.

Cependant, comme il s'agit de le rajouter en tête de liste, nous faisons pointer son pointeur vers l'élément suivant sur… l'élément qui était en tête de liste avant qu'on ajoute l'élément en cours de traîtement, selon le principe du schéma qui suit:

Procédure d'ajout d'un élément en tête de liste

Procédure d'ajout d'un élément en tête de liste

Et nous terminons en renvoyant notre nouvel élément en tant que «tête de liste ».

Si l'élément que l'on veut ajouter n'est pas le premier élément de la liste, c'est fatalement… qu'il doit aller ailleurs.

Nous commençons par demander à ce qu'un pointeur temporaire nous fournisse l'élément de la liste après lequel doit venir celui qui est en cours de traitement, par appel de la fonction "Cherche" (dont je parlerai tout de suite).

Une fois que nous savons à quelle place il faut insérer notre élément en cours, nous demandons l'allocation d'un nouveau pointeur, de type Contact et nous lui fournissons les informations le concernant.

L'ordre des deux instructions suivantes est primoriale, comme l'indique le schéma que voici:

Schéma de principe d'ajout dans la liste

Schéma de principe d'ajout dans la liste

En effet, si nous faisons directement pointer l'élément Suivant du de l'élément qui précède celui qu'on traîte vers celui qu'on traîte, on perdra le reste de la liste, et on sera dans les problèmes jusqu'au cou (perte d'éléments introduits, "fuite de mémoire", et tout ce qui s'en suit).

C'est la raison pour laquelle il est important de fournir d'abord l'élément qui suit celui qu'on traite (accessible par l'élément "suivant" de notre pointeur temporaire) à celui que l'on traite, puis, seulement, de dire que celui que l'on traite est l'élément qui suit notre pointeur temporaire.

On termine en renvoyant la «tête de liste» qui n'a pas changé dans le cas qui nous intéresse.

Par facilité, j'ai créer une fonction (récursive) qui permet de chercher l'élément existant de la liste après lequel doit venir l'élément que l'on doit rajouter.

Voyons à quoi elle ressemble.

fleche haut

21.11 la fonction de recherche

Le nassichneiderman de la fonction de recherche

Le nassichneiderman de la fonction de recherche

les variables de la fonction de recherche
Nom Type Description
tete Contact (pointeur) Pointeur vers l'élément de la liste que l'on souhaite vérifier
info Coordonnee informations que l'on souhaite insérer

fleche haut

21.12 Le cas de base

Comme il s'agit d'une fonction récursive, la première chose à faire est de tester le cas de base (celui qui provoquera la sortie de la fonction).

Ici, étant donné qu'il s'agit de savoir après quel élément de la liste nous allons introduire notre nouvel élément, nous sommes en présence de deux possiblités:

C'est la raison pour laquelle nous testons les deux solutions, avec un opérateur logique "OU" car il est impossible que les deux conditions soient validées en même temps (du fait que, si on est en fin de liste, l'élément suivant… n'existe pas).

Si donc, nous sommes arrivés au cas de base, nous faisons sortir de la fonction récursive en faisant renvoyer l'élément de la liste que l'on a trouvé.

Si nous ne sommes pas dans le cas de base nous demandons à ce que notre pointeur "tete" prenne la valeur renvoyée par notre fonction récursive, puis nous le faisons renvoyer à la fonction appelante.

fleche haut

21.13 La routine d'affichage

La routine d'affichage peut également être traîtée de manière récursive, et ce, bien qu'il s'agisse ici d'une procédure, et non d'une fonction.

En voici le nassichneiderman

Nassichneiderman de la procédure d'affichage

Nassichneiderman de la procédure d'affichage

Liste des variables de la procédure d'affichage
Nom Type Description
tete Conact (pointeur) Variable permettant d'accéder, au début, à la tête de la liste, et par la suite, à tous ses éléments

fleche haut

21.14 Chassez le naturel…

…et il revient au galop…

En effet, le nassichneiderman que je vous présente est en contradiction flagrante avec tout ce que j'ai pu écrire au sujet des routines récursives, et pourtant:

Le cas de base est bel et bien vérifié

Il n'y a réellement appel de la récursivité que s'il existe un élément suivant dans la liste.

Simplement, les différences étaient tellement minimes entre le cas de base et les autres cas, qu'il m'a paru, dans ce cas précis, intéressant de travailler de manière un peu moins conventionnelle…

En effet, que l'on ait affaire ou non au cas de base, le but de la procédure était de provoquer l'affichage des informations concernant l'élément de la liste, et de le faire tant qu'il y avait un élément suivant.

De la même manière, la seule instruction qui changeait en fonction du cas de base était… le rappel de la récursivité.

C'est la raison pour laquelle il m'a paru opportun de me baser sur les conseils généraux énoncés (l'exception de l'exception, en quelque sorte).

Nous voici donc avec un algorithme qui ne vérifie le cas de base qu'une fois qu'il a effectué des actions précises.  Si ce n'est a priori pas vraiment conseillé, la taille de l'algorithme permettait néanmoins de le faire sans courir un *trop gros* risque d'erreur.

fleche haut

21.15 Et la fonction VideListe()

Nassichneiderman de la routine VideListe

Nassichneiderman de la routine VideListe

Liste des variable de la fonction de vidange
Nom Type Description
tete Contact (pointeur) Variable permettant d'accéder, au début, à la tête de la liste, et par la suite, à tous ses éléments

fleche haut

21.16 Et les explications

La vidange de la mémoire de la liste est peut être la seule routine de cet exemple pour laquelle on puisse trouver un (très léger) avantage à l'utilisation de la récursivité, bien qu'on aurait très bien pu s'en passer malgré tout.

En effet, le gros problème au moment de libérer la mémoire de la liste, c'est que, une fois que l'on a libéré la mémoire allouée à un élément, on a également perdu la référence vers l'élément qui le suit…

Si l'on n'avait pas fait appel à la récursivité, il aurait fallu commencer par garder une trace de l'élément qui suit celui que l'on s'apprète à libérer dans une variable temporaire, avant de libérer l'élément en cours de suppression, puis de donner la référence sauvegardée de l'élément comme élément à retirer (sans oublier, de toutes facons, de retourner dans une boucle, ou d'effectuer un test)…

Cette sauvegarde est effectuée "de facto" par l'appel de la procédure récursive.

Par contre, la suppression de l'élément visé ne doit s'effectuer qu'une fois l'appel à la récursivité effectué, et doit AUSSI s'effectuer dans le cas de base (qui recherche le dernier élément de la liste).

C'est la raison pour laquelle l'instruction de libération de la mémoire est effectuée en dehors du test du cas de base, ce qui est parfaitement autorisé, vu qu'une procédure ne renvoie de toutes manières aucune valeur un clin d'oeil

Avant de passer à la suite

Vous aurez sûrement remarqué qu'il m'arrive régulièrement de concevoir des algorithmes qui contredisent parfois quelque peu les conseils que je peux vous avoir donnés précédemment.

Le malheur veut que, si l'informatique est une science stricte (dans le sens qu'il ne faut pas essayer de faire exécuter une instruction en modifiant ne serait-ce qu'une lettre à celle-ci), il n'y a pas pour autant de loi parfaitement immuable, ni même de solution dont on puisse être sûr qu'elle sera à tous les coups la meilleure.

Bien sûr, les principes énnoncés tout au long de ce tutorial sont parfaitement valables, et ont même la prétention de vous donner autant que faire se peut les *bonnes* habitudes, mais il faut en tout temps garder en mémoire le fait que ce qui fait un *bon* créateur d'application n'est pas seulement la connaissance des principes, mais bien sa capacité d'adaptation entre ce qu'il connait et la situation particulière à laquelle il est confronté.

Comme je l'ai souvent répété tout au long de ce tutorial, gardez toujours en tête le fait qu'il n'y a pas de mauvaise solution, que l'on trouve simplement des solutions plus adaptées que d'autres, et que, de toutes manières, les solutions mises en oeuvre seront le plus souvent dépendante de l'état d'esprit de la personne qui les imagine au moment précis où il les imagine.

fleche haut

21.17 Les listes, comme je les indique ici, c'est bien mais…

Mais leur principal inconvéniant est qu'on ne peut les parcourir que dans un sens, alors que l'on peut juger intéressant, utile voir nécessaire, dans certaines circonstances, d'être en mesure de parcourir les données dans les deux sens.

Il est possible d'y arriver, mais il faudra alors se tourner vers une structure (à peine) plus compliquée: la liste doublement chaînée, dont je parle à la page suivante.

image d'imprimante   image de mail   fleche haut

Evaluation donnée par les visiteurs
Cette page n'a pas encore été évaluée sur la compréhension
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 liste chainée

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