forked from cathleruste/Logique2-Syllabus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlog2-chapitre-listeChainee.tex
1236 lines (1044 loc) · 43.3 KB
/
log2-chapitre-listeChainee.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
%=========================
\chapter{La liste chainée}
%=========================
\begin{center}
\href{http://static.commentcamarche.net/www.commentcamarche.net/faq/images/0-Anm7iJKj-dl-ex2-s-.png}
{\includegraphics[width=6.094cm,height=4.094cm]{image/listeChainee.png}}
\end{center}
\marginicon{objectif}
Tout comme un tableau, une liste chainée est une collection d'éléments.
Mais, contrairement aux tableaux, les éléments ne sont pas contigus
en mémoire mais «~chainés~». Nous voyons ses avantages et inconvénients
par rapport aux tableaux, comment les implémenter en orienté-objet
et en non orienté-objet et les opérations de base sur les listes.
%=====================
\section{Définitions}
%=====================
Voici quelques définitions de la liste chainée (\textit{linked list}
en anglais) issues de différents livres sur le
sujet. Elles nous indiquent à la fois à quoi cela ressemble
et l'utilité d'une telle structure.
\begin{itemize}
\item
\textit{Une liste chainée désigne en informatique une structure de données
représentant une collection ordonnée et de taille arbitraire d'éléments
de même type. L'accès aux éléments d'une liste se fait de manière
séquentielle~: chaque élément permet l'accès au suivant,
contrairement au cas du tableau dans lequel l'accès se fait de
manière absolue, par adressage direct de chaque cellule
du dit tableau.}
(Wikipédia, août 2013)
\item
\textit{Une liste est un conteneur séquentiel capable d'insérer
et de supprimer des éléments localement de façon constante,
c'est-à-dire indépendamment de la taille du conteneur.}
(Structures de données en Java -- Hubbard -- ed. Schaum's)
\item
\textit{Les listes sont des conteneurs destinés aux insertions
s'effectuant en temps constant quelle que soit la position
dans le conteneur.}
(La bibliothèque standard STL du C++ -- Fontaine -- InterEditions)
\item
\textit{Une liste chainée est une structure de données dans
laquelle les objets sont arrangés linéairement. Toutefois,
contrairement au tableau, pour lequel l'ordre linéaire est
déterminé par les indices, l'ordre d'une liste chainée est
déterminé par un pointeur dans chaque objet.}
(Introduction à l'algorithmique -- Cormen, Leiserson, Rivest -- Dunod)
\end{itemize}
%===================================
\section{Liste versus liste chainée}
%===================================
Dans la terminologie moderne, le terme \textit{liste} est
plutôt utilisé pour dénoter toute collection séquentielle
d'éléments (il existe un premier élément, un deuxième, ...).
Autrement dit, chaque élément a une position précise dans
la collection. En ce sens, un tableau ou un fichier sont
également des \textit{listes} !
On peut opposer la liste à la structure d'\textit{ensemble}
où les éléments ne sont pas positionnés (un élément
\textit{est} ou \textit{n'est pas} dans un ensemble mais
sans que sa position puisse être donnée).
Le concept de liste ne doit pas être confondu avec la notion
de \textit{tri}. On peut parler de la \textit{liste}
contenant les éléments 3, 8 et 2 dans cet ordre
(au sens de \textit{séquence} et pas d'\textit{ordre de tri}).
%=======================================
\section{Représentation et terminologie}
%=======================================
Pour écrire explicitement une liste chainée et son contenu
sous forme compacte, on indique la liste des valeurs qu'elle
contient entre parenthèses. Exemple~: la liste (3, 6, 5, 2).
Schématiquement, on la représente plutôt ainsi~:
\begin{center}
\includegraphics[width=11.324cm,height=2.355cm]{image/a2012Logique2eme-img002.png}
\end{center}
De ce schéma, il apparait que chaque \textbf{élément}
d'une liste chainée est composé de 2 parties~: la valeur
proprement dite et un \textit{accès} à l'élément suivant.
De plus, on accède au premier élément de la liste via une
zone spéciale, la \textbf{tête} de la liste chainée.
Le dernier élément n'ayant pas de suivant, on indique \textit{rien}
dans la zone réservée à l'accès au suivant. Dans la
littérature et selon les langages, on trouvera, à la place de \textit{rien},
les notations \textit{null}, \textit{nil}, ... ou encore Ø.
Le schéma ci-dessus peut encore se compactifier davantage,
en une forme qui ne fait pas apparaitre la tête de liste ni
les accès aux éléments suivants~:
\begin{center}
\includegraphics[width=7.779cm,height=1.429cm]{image/a2012Logique2eme-img003.png}
\end{center}
Au contraire du tableau, donc, il n'y a pas d'accès direct à un élément
(en donnant son indice) mais il est nécessaire
de partir du premier élément et de suivre la «~\textit{chaine~»}.
Comment représenter ce \textit{lien}, cet
\textit{accès} en mémoire ? Cela dépend du type de langage~:
\begin{itemize}
\item
en programmation procédurale (non orienté-objet),
on utilise la notion de pointeur (cf. le cours de C/C++) tout en
montrant qu'il est même possible de s'en sortir avec un langage
qui ne dispose pas de la notion de pointeur (comme par exemple Cobol) ;
\item
en orienté-objet (OO), un élément est représenté par un objet
dont un attribut est une référence vers l'objet
représentant l'élément suivant.
\end{itemize}
%===========================================================
\section{Rappel~: opérations sur les tableaux et complexité}
%===========================================================
Afin de mieux comprendre ce qu'apporte la liste chainée
par rapport au tableau, revoyons ce que «~coûte~» les opérations
courantes sur les tableaux. Le tableau ci-dessous donne la complexité
(par l'algorithme le plus économique) des
opérations élémentaires pour un tableau trié et non trié.
\begin{center}
\tablefirsthead{}
\tablehead{}
\tabletail{}
\tablelasttail{}
\begin{supertabular}{|m{5.511cm}|m{3.6139998cm}|m{3.631cm}|}
\hline
\bfseries Opération &
\bfseries Tableau non trié &
\bfseries Tableau trié\\
\hline
Ajout d'un élément &
\centering{O(1)} &
\centering\arraybslash{O($n$)}\\
\hline
{Suppression d'un élément} &
\centering{O($n$)} &
\centering\arraybslash{O($n$)}\\
\hline
{Recherche d'un élément} &
\centering{O($n$)} &
\centering\arraybslash{O($log_{2}n$)}\\
\hline
{Parcours} &
\centering{O($n$)} &
\centering\arraybslash{O($n$)}\\\hline
\end{supertabular}
\end{center}
Pour rappel, la notation O($n$) indique que l'opération
prend un temps qui est proportionnel à $n$, la
taille du tableau. La plupart des opérations sont de complexité
O($n$), par exemple, lors d'une suppression, il
faut décaler des éléments pour boucher le trou qui est apparu.
Il en est de même pour un ajout dans un tableau trié. Si
le tableau n'est pas trié, il est évident qu'on peut rajouter
l'élément en fin de tableau, ce qui est immédiat
(complexité O(1)). Dans le cas d'un tableau trié, c'est
l'algorithme de recherche dichotomique qui permet de trouver
rapidement un élément avec une complexité O($log_{2}n$).
%==================================================
\section{Ajout d'un élément dans une liste chainée}
%==================================================
Dans une liste chainée (qu'elle soit ordonnée ou non),
il n'est pas nécessaire de décaler des éléments pour un ajouter
un. Reprenons la liste chainée de l'exemple ci-dessus
et ajoutons l'élément 8 entre le 6 et le 5~:
\ Avant~: \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Après~:
\includegraphics[width=15.558cm,height=3.334cm]{image/a2012Logique2eme-img004.png}
Il aura suffi de modifier des «~flèches~» (c'est-à-dire des «~liens~»
ou des «~accès~»). L'opération prend donc le même
temps quelle que soit la taille de la liste chainée
(pour autant qu'on soit déjà positionné à l'endroit de l'ajout).
La complexité de cette opération est donc O(1). Il n'y a ici pas de décalage d'éléments comme pour un tableau, pour la
simple raison que les éléments d'une liste chainée n'ont aucune raison d'être à des endroits contigus dans la mémoire
de l'ordinateur.
Il y a un ajout qui fait figure de cas particulier, c'est l'ajout en tête de liste chainée, car il modifie
obligatoirement l'accès au premier élément. Par exemple, si nous ajoutons 8 en première position, cela donne~:
\ Avant~: \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Après~:
\includegraphics[width=15.452cm,height=3.122cm]{image/a2012Logique2eme-img005.png}
%========================================================
\section{Suppression d'un élément dans une liste chainée}
%========================================================
La suppression d'un élément se fait tout aussi simplement.
L'opération est également de complexité O(1). Par exemple,
supprimons l'élément de valeur 6~:
\ Avant~: \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Après~:
\includegraphics[width=15.61cm,height=1.773cm]{image/a2012Logique2eme-img006.png}
Ici aussi, le cas de la suppression du premier élément est particulier~:
\ Avant~: \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Après~:
\includegraphics[width=15.425cm,height=1.72cm]{image/a2012Logique2eme-img007.png}
Remarquez que l'élément, quoique supprimé de la liste chainée,
est encore présent sur les schémas. Bien que l'élément ne
fasse plus logiquement partie de l'ensemble des éléments
de la liste chainée, il est encore présent physiquement dans
la mémoire. La suppression définitive de l'élément est gérée
soit par le programmeur lui-même, soit c'est le système
qui nettoie de la mémoire les éléments auxquels plus aucun accès ne mène.
C'est le rôle du \textit{garbage collector},
solution que nous avons adoptée en logique OO,
mais qui n'est pas vraie pour tous les langages OO (cf. C++).
%===============================
\section{Recherche d'un élément}
%===============================
La recherche d'un élément dans une liste chainée non triée
est similaire à celle d'un tableau, il faut obligatoirement
tout parcourir pour savoir si l'élément est présent ou non.
Au contraire d'un tableau, le fait de savoir que la liste chainée
est triée (sur les valeurs des éléments) ne permet pas
de développer un algorithme rapide de type \textit{recherche dichotomique}.
Tout au plus on peut, comme avec un tableau, s'arrêter lorsqu'on
dépasse la position où l'élément recherché aurait dû se trouver.
%=====================================
\section{Liste chainée versus tableau}
%=====================================
Rajoutons dans le tableau ce que l'on a vu
de la complexité des listes chainées~:
\begin{center}
\tablefirsthead{}
\tablehead{}
\tabletail{}
\tablelasttail{}
\begin{supertabular}{|m{5.145cm}|m{2.023cm}|m{1.7329999cm}|m{2.836cm}|m{2.675cm}|}
\hline
{\bfseries Opération} &
{\bfseries Tableau non trié} &
{\bfseries Tableau trié} &
{\bfseries Liste chainée non triée} &
{\bfseries Liste chainée triée}\\
\hline
{Ajout d'un élément} &
\centering{O(1)} &
\centering{O($n$)} &
\centering{O(1)} &
\centering\arraybslash{O(1)}\\
\hline
{Suppression d'un élément} &
\centering{O($n$)} &
\centering{O($n$)} &
\centering{O(1)} &
\centering\arraybslash{O(1)}\\
\hline
{Recherche d'un élément} &
\centering{O($n$)} &
\centering{O($log_{2}n$)} &
\centering{O($n$)} &
\centering\arraybslash{ O($n$)}\\
\hline
{Parcours} &
\centering{O($n$)} &
\centering{O($n$)} &
\centering{O($n$)} &
\centering\arraybslash{O($n$)}\\
\hline
\end{supertabular}
\end{center}
On voit que la liste chainée montre tout son intérêt pour les ajouts
et les suppressions mais se montre moins efficace
pour les recherches dans le cas où l'information est triée.
Le choix d'un tableau ou d'une liste chainée pour représenter
une collection dans un problème précis dépend dès lors du
rapport entre le nombre d'ajouts/suppressions et de recherches
que l'on compte effectuer.
Remarquons aussi que dans le cas où les ajouts/suppressions se font
en \textit{fin} de tableau (le cas d'une \textit{pile} par exemple),
la complexité rejoint celle d'une liste chainée.
Nous allons étudier l'implémentation de la liste chainée,
d'abord procédurale (ou \textit{«~C-like~»}) car c'est celle
que vous utiliserez au labo C, ensuite nous verrons l'implémentation OO.
%===================================
\section{Implémentation procédurale}
%===================================
\subsection{Notations}
%=====================
Pour les besoins de cette implémentation, nous devons introduire
une nouvelle notation~: l'\textbf{accès} à une
variable, ou encore \textbf{référence} ou \textbf{pointeur}.
La déclaration de cet accès ce fera comme suit~:
\cadre{
\begin{pseudo}
\Decl p~: accès à T
\end{pseudo}
}
ce qui signifie que \textstyleCodeInsr{p} permet d'accéder à un élément
de type \textstyleCodeInsr{T}.
Dans le cadre de ce cours, comme les éléments d'une liste
chainée peuvent être vus comme composés (structurés) d'un champ valeur
et d'un champ accès, nous n'aurons que des accès
à des structures mais les langages non objets permettent généralement
des accès vers des éléments de type quelconque.
Dans le contexte des références, il faut pouvoir créer dynamiquement
l'élément référencé. Cela s'indique par la
primitive \textstyleCodeInsr{allouer}, à utiliser comme suit~:
\cadre{
\begin{pseudo}
\Decl p \Gets \Alloc(T)
\end{pseudo}
}
où \textstyleCodeInsr{T} est le type de l'élément référencé.
\textbf{Exemple}~:
\begin{pseudo}
\Decl p \Gets \Alloc(Personne)
\end{pseudo}
Cette instruction provoque l'allocation d'un espace mémoire pour
une variable de type \textstyleCodeInsr{Personne} et l'accès à
cet espace est placé dans \textstyleCodeInsr{p}.
En C, p pourrait être un pointeur recevant
l'adresse de l'espace alloué lors de cette demande d'allocation.
Pour accéder à la variable d'accès \textstyleCodeInsr{p},
on utilisera la flèche vers la droite (comme en C), et donc,
\cadre{
\begin{pseudo}
\Stmt p\Gives truc
\end{pseudo}
}
désignera le champ \textit{truc} de la variable accessible par \textstyleCodeInsr{p}.
Pour récupérer l'espace mémoire inutilement occupé par une variable
qui n'est plus utilisée, on a l'opération inverse
qui se notera par la nouvelle primitive~:
\cadre{
\begin{pseudo}
\Free p
\end{pseudo}
}
qui supprime définitivement la variable d'accès \textstyleCodeInsr{p},
ce qui a pour effet de rendre cet espace mémoire à nouveau
allouable.
Nous avons vu qu'un élément de liste est composé d'un champ \textit{valeur}
(qui contient la valeur proprement dite de
l'élément) et d'un champ permettant d'accéder à l'élément suivant,
que nous nommerons simplement \textit{suivant}. Le
type \textstyleCodeInsr{T} de la valeur de l'élément est noté entre crochets~:
\cadre{
\begin{pseudo}
\Struct{ÉlémentListe<T>}
\Decl valeur~: T
\Decl suivant~: accès à ÉlémentListe<T>
\EndStruct
\end{pseudo}
}
\textbf{Exemple}~: pour un élément de liste chainée d'entiers, on définirait la structure~:
\cadre{
\begin{pseudo}
\Struct{ÉlémentListe<entier>}
\Decl valeur~: entier
\Decl suivant~: accès à ÉlémentListe<entier>
\EndStruct
\end{pseudo}
}
En non orienté-objet, une liste chainée se reconnait simplement
par le fait que des éléments semblables (de même type)
sont chainés, ce qui signifie que chacun d'eux contient un accès
à son suivant. Pour manipuler un tel ensemble de
données, la seule condition est de pouvoir accéder au premier
élément de cet ensemble par une variable de type \textstyleCodeInsr{accès},
que l'on peut appeler \textit{premier}, \textit{têteListe} ou encore
\textit{TL} (et qui n'a nul besoin d'être
structurée). La liste chainée n'est donc rien d'autre qu'un accès,
celui au premier élément. Si cette liste est vide,
cet accès a pour valeur \textit{rien}~:
\cadre{
\begin{pseudo}
\Decl têteListe~: accès à ÉlémentListe<T>
\RComment accès au premier élément, rien si vide
\end{pseudo}
}
\textbf{Exemple}~: pour déclarer une liste chainée d'entiers, on aurait~:
\cadre{
\begin{pseudo}
\Decl têteListe~: accès à ÉlémentListe<entier>
\end{pseudo}
}
\subsection{Exemple~1~: taille d'une liste chainée}
%==================================================
Voici un exemple d'algorithme calculant le nombre
d'éléments d'une liste chainée d'accès TL.
\cadre{
\begin{pseudo}
\Module{tailleListeChainée}{TL~: accès à ÉlémentListe<T>}{entier}
\Decl courant~: accès à ÉlémentListe<T>
\Decl cpt~: entier
\Let cpt \Gets 0
\Let courant \Gets TL
\While{courant ${\neq}$ rien}
\Let cpt \Gets cpt + 1
\Let courant \Gets courant\Gives suivant
\EndWhile
\Return cpt
\EndModule
\end{pseudo}
}
\subsection{Exemple~2~: mise d'un fichier en liste chainée}
%==========================================================
Dans ce 2\textsuperscript{ème} exemple, on montre comment mettre
le contenu d'un fichier de variables structurées
Identité (nom, prénom~: chaines, dateNais~: Date) dans une liste chainée.
\cadre{
\begin{pseudo}
\Module{miseEnListe}{Personnes~: fichier de Identité}{accès à ÉlémentListe<Identité>}
\Decl courant, précédent, TL~: accès à ÉlémentListe<Identité>
\Decl enr~: Identité
\Let TL \Gets rien
\RComment la liste est vide au départ
\Stmt \K{ouvrir} Personne (\K{IN})
\Read Personnes ; enr
\If{NON EOF(Personnes)}
\RComment traitement spécial pour le 1\textsuperscript{er} enregistrement
\Decl TL \Gets \Alloc(ÉlémentListe<Identité>)
\Decl TL\Gives valeur \Gets enr
\Decl TL\Gives suivant \Gets rien
\Decl précédent \Gets TL
\Read Personnes ; enr
\EndIf
\While{NON EOF(Personnes)}
\Decl courant \Gets \Alloc(ÉlémentListe<Identité>)
\Decl courant\Gives valeur \Gets enr
\Decl courant\Gives suivant \Gets rien
\Decl précédent\Gives suivant \Gets courant
\Decl précédent \Gets courant
\Read Personnes ; enr
\EndWhile
\Stmt \K{fermer} Personnes
\Return TL
\EndModule
\end{pseudo}
}
%=====================================
\section{Représentation sans pointeur}
%=====================================
La plupart des langages non orienté-objet possèdent la notion
de pointeur mais il y a des exceptions (Cobol par
exemple). Comment implémenter une liste chainée dans un tel langage?
En simulant par exemple la liste chainée via un
tableau qui contient 2 champs (la valeur de l'élément
et l'indice de l'élément suivant).
\textbf{Exemple}~: la liste chainée (3, 6, 5, 2)
pourrait être donnée par le tableau suivant~:
\begin{center}
\tablefirsthead{}
\tablehead{}
\tabletail{}
\tablelasttail{}
\begin{supertabular}{|m{0.40900004cm}|m{0.403cm}|m{0.403cm}|m{0.403cm}|m{0.38200003cm}|}
\multicolumn{1}{m{0.40900004cm}}{{\itshape 1}} &
\multicolumn{1}{m{0.403cm}}{{\itshape 2}} &
\multicolumn{1}{m{0.403cm}}{{\itshape 3}} &
\multicolumn{1}{m{0.403cm}}{{\itshape 4}} &
\multicolumn{1}{m{0.38200003cm}}{{\itshape 5}}\\\hline
{ 2} &
{ 6} &
{ 3} &
{ 5} &
{ ?}\\
\hline
{ 0} &
{ 4} &
{ 2} &
{ 1} &
{ ?}\\
\hline
\end{supertabular}
\end{center}
À condition de savoir aussi où commencer, c'est-à-dire connaitre
l'indice du premier élément (le 3\textsuperscript{ème}
dans notre exemple). L'indice 0 indique la fin de la liste chainée.
Pour ajouter un élément, il suffit de l'ajouter en
fin de tableau et d'adapter les indices sur la deuxième ligne.
La suppression est aussi aisée mais crée un «~trou~»
qu'il faut pouvoir gérer (par exemple via une liste des places libres).
%=====================================
\section{Implémentation orienté-objet}
%=====================================
\subsection{implémentation}
%==========================
Pour l'implémentation OO, nous allons définir deux classes,
une pour un élément de liste chainée, et une autre pour la
liste chainée elle-même.
\cadre{
\begin{pseudo}
\Class{ÉlémentListe<T>}
\Private
\Decl valeur~: T
\Decl suivant~: ÉlémentListe<T>
\Public
\ConstrSign{ÉlémentListe<T>}{val~: T, elt~: ÉlémentListe<T>}
\ConstrSign{ÉlémentListe<T>}{val~: T}
\RComment suivant à rien
\MethodSign{getvaleur}{}{T}
\MethodSign{setValeur}{val~: T}{}
\MethodSign{getSuivant}{}{ÉlémentListe<T>}
\RComment ne renvoie rien si pas de suivant
\MethodSign{setSuivant}{elt~: ÉlémentListe<T>}{}
\EndClass
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Class{ListeChainée<T>}
\Private
\Decl premier~: ÉlémentListe<T>
\Public
\ConstrSign{ListeChainée<T>}{}
\LComment crée une liste vide
\MethodSign{getPremier}{}{ÉlémentListe<T>}
\MethodSign{estVide}{}{booléen}
\MethodSign{insérerTête}{val~: T}{}
\LComment insère en début de liste un nouvel élément de valeur val
\MethodSign{insérerAprès}{elt~: ÉlémentListe<T>, val~: T)}{}
\LComment insère un nouvel élément de valeur val après elt
\MethodSign{supprimerTête}{}{}
\LComment supprime le premier élément d'une liste chainée
\MethodSign{supprimerAprès}{elt~: ÉlémentListe<T>)}{}
\LComment supprime l'élément qui suit elt
\EndClass
\end{pseudo}
}
\subsection{Remarques~:}
%========================
\begin{itemize}
\item
Pour la classe ListeChainée, on pourrait envisager l'ajout d'autres
attributs pour accélérer certaines opérations. Par
exemple, on pourrait définir comme attribut la taille de la liste
chainée, ce qui serait redondant puisqu'on peut la
calculer en parcourant les éléments. On parle d'attribut
«~calculable~» pour indiquer un attribut qui contient une
information qui pourrait être calculée à partir des autres attributs.
La pratique montre qu'il ne faut pas abuser de ce type d'attribut.
\item
À votre avis, pourquoi n'y a t-il pas de méthode
\textsf{supprimer(elt~: ÉlémentListe)} qui supprime
de la liste chainée l'élément donné en paramètre ?
\end{itemize}
Nous détaillons à présent le contenu des constructeurs
et méthodes définies ci-dessus.
Pour la classe ÉlémentListe<T>~:
\cadre{
\begin{pseudo}
\Constr{ÉlémentListe<T>}{val~: T, elt~: ÉlémentListe<T>}
\Decl valeur \Gets val
\Let suivant \Gets elt
\EndConstr
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Constr{ÉlémentListe<T>}{val~: T}
\Let valeur \Gets val
\Let suivant \Gets rien
\EndConstr
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Method{getvaleur}{}{T}
\Return valeur
\EndMethod
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Method{setValeur}{val~: T}{}
\Let valeur \Gets val
\EndMethod
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Method{getSuivant}{}{ÉlémentListe<T>}
\Return suivant
\EndMethod
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Method{setSuivant}{elt~: ÉlémentListe<T>}{}
\Let suivant \Gets elt
\EndMethod
\end{pseudo}
}
Passons à présent à la classe ListeChainée~:
\cadre{
\begin{pseudo}
\Constr{ListeChainée<T>}{}
\Let premier \Gets rien
\EndConstr
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Method{getPremier}{}{ÉlémentListe<T>}
\Return premier
\EndMethod
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Method{estVide}{}{booléen }
\Return premier = rien
\EndMethod
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Method{insérerTête}{val~: T}{}
\Decl elt~: ÉlémentListe<T>
\Let elt \Gets \K{nouveau} ÉlémentListe<T>(val, premier)
\Let premier \Gets elt
\EndMethod
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Method{insérerAprès}{elt~: ÉlémentListe<T>, val~: T}{}
\Decl eltAInsérer~: ÉlémentListe<T>
\If{elt = rien}
\K{erreur} «~insérer après rien est impossible~»
\EndIf
\Let eltAInsérer \Gets \K{nouveau} ÉlémentListe<T>(val, elt.getSuivant())
\Stmt elt.setSuivant(eltAInsérer)
\EndMethod
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Method{supprimerTête}{}{}
\LComment Les lignes en italique sont nécessaires si la libération de
\LComment l'espace inutilisé est à charge du programme (comme en C++)
\textit{\Decl sauve~: ÉlémentListe<T>}
\If{premier = rien}
\Stmt \K{erreur} «~la liste est vide~»
\EndIf
\Let \textit{sauve} \Gets \textit{premier}
\Let premier \Gets premier.getSuivant()
\textit{\Free sauve}
\EndMethod
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Method{supprimerAprès}{elt~: ÉlémentListe<T>}{}
\LComment Les lignes en italique sont nécessaires si la libération de
\LComment l'espace inutilisé est à charge du programme (comme en C++)
\textit{\Decl sauve~: ÉlémentListe<T>}
\If{elt = rien}
\Stmt \K{erreur} «~élément inexistant~»
\Else
\If{elt.getSuivant( ) = rien}
\Stmt \K{erreur} «~pas de suivant~»
\EndIf
\EndIf
\Let \textit{sauve} \Gets \textit{elt.getSuivant()}
\Stmt elt.setSuivant(elt.getSuivant().getSuivant())
\textit{\Free sauve}
\EndMethod
\end{pseudo}
}
%=============================================
\section{Exemple~: taille d'une liste chainée}
%=============================================
Pour exemple, réécrivons en OO le module calculant la taille d'une liste chainée.
\cadre{
\begin{pseudo}
\Module{tailleListeChainée}{maListe~: ListeChainée<T>}{entier}
\Decl courant~: ÉlémentListe<T>
\Decl cpt~: entier
\Let cpt \Gets 0
\Let courant \Gets maListe.getPremier( )
\While{courant ${\neq}$ rien}
\Let cpt \Gets cpt + 1
\Let courant \Gets courant.getSuivant( )
\EndWhile
\Return cpt
\EndModule
\end{pseudo}
}
%====================
\section{Commentaire}
%====================
Dans l'implémentation procédurale, nous aurions pu plagier complètement
la version OO où 2 classes ont été définies (ListeChainée et ÉlémentListe),
il aurait fallu 2 structures correspondantes et les modules correspondants
aux constructeurs et méthodes. Ceci a été fait pour EléméntListe mais pas
pour ListeChainée. À votre avis, pourquoi ?
Il existe quelques variantes à la liste chainée, notamment la
\textit{liste bidirectionnelle} et la \textit{liste circulaire}.
%===============================
\section{Liste bidirectionnelle}
%===============================
Dans une \textbf{liste bidirectionnelle}, chaque élément possède,
en plus d'une référence à l'élément suivant,
une référence à l'élément \textit{précédent}. Un des avantages est
de faciliter grandement la suppression car il n'est
plus nécessaire de connaître l'élément précédent pour pouvoir le faire
puisqu'on peut le retrouver à partir de
l'élément à supprimer.
Comme les liens entre les éléments permettent de se déplacer
dans les deux sens, il est logique qu'à la tête de liste
corresponde une fin de liste (ou queue de liste). Schématiquement~:
\begin{center}
\includegraphics[width=13.203cm,height=3.175cm]{image/a2012Logique2eme-img008.png}
\end{center}
On peut facilement construire les classes ÉlémentListeBD et ListeBD
en adaptant légèrement les classes pour la liste
chainée simple. La classe ListeBD aura comme attributs les deux accès
aux extrémités de la liste, nommés
\textit{premier} et \textit{dernier}.
\cadre{
\begin{pseudo}
\Class{ÉlémentListeBD<T>}
\Private
\Decl valeur~: T
\Decl suivant~: ÉlémentListeBD<T>
\Decl précédent~: ÉlémentListeBD<T>
\Public
\ConstrSign{ÉlémentListeBD<T>}{val~: T, suiv, prec~: ÉlémentListeBD<T>}
\ConstrSign{ÉlémentListeBD<T>}{val~: T}
\RComment précédent et suivant à rien
\MethodSign{getvaleur}{}{T}
\MethodSign{setValeur}{val~: T}{}
\MethodSign{getSuivant}{}{ÉlémentListeBD<T>}
\RComment ne renvoie rien si pas de suivant
\MethodSign{setSuivant}{elt~: ÉlémentListeBD<T>}{}
\MethodSign{getPrécédent}{}{ÉlémentListeBD<T>}
\RComment ne renvoie rien si pas de précédent
\MethodSign{setPrécédent}{elt~: ÉlémentListeBD<T>}{}
\EndClass
\end{pseudo}
}
\cadre{
\begin{pseudo}
\Class{ListeBD<T>}
\Private
\Decl premier~: ÉlémentListeBD<T>
\Decl dernier~: ÉlémentListeBD<T>
\Public
\ConstrSign{ListeBD<T>}{}
\MethodSign{getPremier}{}{ÉlémentListeBD<T>}
\MethodSign{getDernier}{}{ÉlémentListeBD<T>}
\MethodSign{estVide}{}{booléen}
\MethodSign{insérerTête}{val~: T}{}
\MethodSign{insérerFin}{val~: T}{}
\MethodSign{insérerAprès}{elt~: ÉlémentListeBD<T>, val~: T)}{}
\MethodSign{insérerAvant}{elt~: ÉlémentListeBD<T>, val~: T)}{}
\MethodSign{supprimerTête}{}{}
\MethodSign{supprimerFin}{}{}
\MethodSign{supprimer}{elt~: ÉlémentListeBD<T>)}{}
\EndClass
\end{pseudo}
}
%=========================
\section{Liste circulaire}
%=========================
Dans une \textbf{liste circulaire}, le dernier élément ne référence
pas «~rien~» mais indique le premier élément.
Schématiquement~:
\begin{center}
\includegraphics[width=11.351cm,height=2.54cm]{image/a2012Logique2eme-img009.png}
\end{center}
Il n'est pas nécessaire de créer une classe nouvelle pour les
éléments de la liste circulaire, puisqu'ils sont
identiques à ceux d'une liste chainée simple. On peut faire fonctionner
ce type de liste avec les outils développés
pour la liste chainée, il faut simplement veillez ici à la cohérence
de la liste circulaire, c'est-à-dire qu'à tout
moment le dernier élément doit être lié au premier. Noter que premier
a ici un sens différent du point de vue logique,
dans une liste circulaire il n'y a pas réellement de \textit{premier}
élément, mais un accès à un élément quelconque de
la liste, peu importe sa position.
%==================
\section{Exercices}
%==================
{\bfseries
Remarques préliminaires}
\begin{itemize}
\item
\textit{Dans les exercices qui suivent, vous serez souvent amenés à
écrire des algorithmes similaires dont le code ne diffère
qu'en un seul endroit. Dans cette situation, il faut toujours se
demander s'il n'est pas possible de factoriser le
code, c'est-à-dire de le rendre modulaire de façon à limiter
l'écriture de code semblable.}
\item
\textit{Dans les exercices sur les listes, on s'efforcera toujours
de trouver la méthode la plus efficace (éviter de parcourir
inutilement la liste) et aussi la plus économique (éviter si possible
les nouvelles demandes d'allocation d'éléments si
ce n'est pas nécessaire).}
\item
\textit{Sauf si c'est précisé, les exercices peuvent se faire
indifféremment en OO ou non, ce qui laisse la possibilité de
s'entraîner dans les deux notations.}
\end{itemize}
\begin{Exercice}{Opérations de base}
Écrire, dans la syntaxe de l'implémentation procédurale objet,
les modules qui correspondent aux méthodes de la liste
chainée dans la représentation OO, c'est-à-dire~:
\begin{enumerate}
\item {
un module qui vérifie si une liste chainée est vide}
\item {
un module qui insère en début de liste un nouvel élément de valeur \textbf{val}}
\item {
un module qui insère un nouvel élément de valeur \textbf{val} après celui d'accès p}
\item {
un module qui supprime le premier élément d'une liste chainée}
\item {
un module qui supprime l'élément qui suit celui d'accès \textbf{p}}
\end{enumerate}
\end{Exercice}
\begin{Exercice}{Insertions, recherches et suppressions}
Soit une liste chainée contenant des valeurs d'un type T quelconque. Écrire~:
\begin{enumerate}
\item {
un module qui ajoute dans la liste un nouvel élément de valeur
\textbf{val} (à l'endroit qui permet le code le plus simple)}
\item {
un module qui recherche dans la liste la valeur \textbf{val}
et retourne l'accès à l'élément qui le contient (ou rien si
la valeur ne s'y trouve pas). Si la valeur s'y trouve plusieurs
fois, le module en retourne la première occurrence.}
\item {
un module qui retourne un booléen indiquant si la liste
contient la valeur \textbf{val}}
\item {
un module qui supprime de la liste le premier élément contenant
une valeur \textbf{val}. Le module retournera un booléen
indiquant si la suppression a été réalisée~: vrai si \textbf{val}
a été supprimé, et faux sinon (parce que la valeur ne
s'y trouvait pas).}
\item {
un module qui supprime de la liste toutes les occurrences de la valeur
\textbf{val} et retourne le nombre de suppressions réalisées}
\end{enumerate}
{\itshape
N.B.~: cet exercice peut être réalisé de trois façons~(1) en procédural
(2) en orienté objet (3) sous forme de méthodes de la classe ListeChainée}
\end{Exercice}
\begin{Exercice}{Le grand nettoyage}
Soit une liste chainée dont les valeurs (non ordonnées) sont des
entiers compris entre 1 et 10 inclus. On demande
d'écrire l'algorithme qui supprime de cette liste toutes les
occurrences de la valeur la plus fréquente. En cas
d'\textit{ex-æquo}, c'est la plus grande des valeurs qui est éliminée.
\textit{Exemple}~: la liste (8, 7, 6, 7, 7, 1, 7, 1, 5, 7) devient (8, 6, 1, 1, 5).
\end{Exercice}
\begin{Exercice}{Liste chainée ordonnée}
Soit une liste chainée dont les éléments sont triés (les