Vous êtes sur la page 1sur 6

MI2E - 1 E A NNÉE

A LGORITHMIQUE G ÉNÉRALE
C. Murat, V. Mousseau

T.D. S TRUCTURES DE D ONNÉES S ÉQUENTIELLES

Considérons les notations suivantes :


– L : une liste d’objets de type élémentype
– x : un objet de type élémentype
– p : une position (indice, ou pointeur, ou autre...)
– FIN(L) : est la position suivant la position contenant le dernier élément de L

Soient les primitives définies sur les listes :


1. INSERER(x,p,L) : Insère x à la position p dans la liste L avec déplacement de l’élément précé-
dennnent situé en p et de tous les suivants d’une position vers la fin de la liste. Si la liste L n’a
pas de position p alors l’opération est impossible.
2. LOCALISER(x,L) : Retourne la position de x dans la liste L. Si x apparaît plusieurs fois, c’est la
position de la première occurrence qui est retournée. Si x n’apparaît pas dans L alors la fonction
retourne la position FIN(L).
3. ACCEDER(p,L) : Retourne l’élément qui est à la position p dans la liste L (mais sans l’en
retirer). L’opération est impossible si p=FIN(L) ou si L n’a pas de position p.
4. SUPPRIMER(p,L) : Permet de supprimer l’élément qui est à la position p dans L. Cette fonction
entraîne le déplacement des éléments précédemment placés après p d’une position vers le début
de la liste. L’opération est impossible si p=FIN(L) ou si L n’a pas de position p.
5. SUIVANT(p,L) et PRECEDENT(p,L) : Retournent respectivement la position suivant ou précé-
dant la position p dans la liste L. Si p est la dernière position de cette liste alors SUIVANT(p,L)
= FIN(L) et si p=FIN(L) alors SUIVANT(p,L) n’est pas défini. De même, PRECEDENT(p,L)
est impossible à déterminer si p est la position du 1er élément. Enfin, ces 2 opérations n’ont pas
de sens s’il n’y a pas de position p dans L.
6. RAZ(L) : Transforme la liste L en liste vide et retourne la position FIN(L).
7. PREMIER(L) : Retourne la première position dans la liste L. Si L est vide, la position retournée
est FIN(L).
8. LISTER(L) : Affiche les éléments de L dans l’ordre de leur apparition.

Exercice 1 : Considérons la mise en oeuvre des liste par tableau. Après avoir défini vos conventions,
mettre en oeuvre les primitives suivantes : INSERER(x,p,L), LOCALISER(x,L), ACCEDER(p,L),
SUPPRIMER(p,L), SUIVANT(p,L), PRECEDENT(p,L), RAZ(L), PREMIER(L) et LISTER(L).
Exercice 2 : En utilisant une mise en oeuvre des listes sous forme de tableaux (à préciser) et les primi-
tives définies à l’exercice 1, écrire l’algorithme qui effectue les opérations ci-dessous, puis exécutez-le
“à la main”.
1. créer une liste vide,
2. afficher le premier élément de cette liste,
3. demander à l’utilisateur de donner un entier i et lire sa valeur,
4. mettre en première position l’entier i,
5. mettre en première position l’entier 3 ∗ i,
6. mettre en première position l’entier 5 ∗ i,
7. mettre en première position l’entier i + 3,
8. mettre en première position l’entier 2 ∗ i + 7,
9. mettre en première position l’entier 6 ∗ i,
10. mettre en première position l’entier i,
11. mettre en première position l’entier i ∗ 10,
12. mettre en première position l’entier i ∗ 100,
13. mettre en première position l’entier 100 ∗ i,
14. retourner le 3e élément de la liste,
15. déterminer la position de l’entier i,
16. compter le nombre d’éléments de la liste et l’afficher,
17. faire la somme des n éléments de la liste et l’afficher,
18. enlever de la liste l’élément qui est à la 5e position,
19. afficher tous les éléments de la liste ainsi que leur position respective,
20. retourner les deux éléments qui se trouvent juste avant et juste après l’élément i + 3,
21. mettre en 4e position l’élément i,
22. afficher tous les éléments de la liste,
23. calculer le nombre d’éléments et la somme des éléments de la liste, afficher le résultat,
24. vider la liste.

Exercice 3 : Pointeur, adressage, indirection


Un pointeur est une variable dont le contenu est l’adresse d’un emplacement mémoire. Pour décla-
rer une variable p de type pointeur, il faut spécifier le type de la variable sur laquelle pointe p. On
écrira p :↑TypeElem pour spécifier que p peut contenir l’adresse d’une zone mémoire réservée à une
variable de type TypeElem. Schématiquement : p
• TypeElem

Pour déclarer un type lien pointeur sur une variable de type TypeElem on écrira lien=↑TypeElem
et on pourra alors définir une variable p de type lien en écrivant p :lien. On accède à la variable
(de type TypeElem) sur laquelle pointe le pointeur p (de type lien) par l’écriture p↑.

Déclarer une variable pointeur p de type lien permet de réserver l’espace mémoire pour stocker
la variable pointeur p, mais ne reserve pas l’espace mémoire pour stocker la variable p↑ (de type
TypeElem) sur laquelle pointe p. La procédure Allouer(p) alloue un espace mémoire (crée une
variable de type TypeElem) et place l’adresse de l’emplacement alloué dans la variable p. Cette
emplacement mémoire sera donc accessible par p↑. La procédure Desallouer(p) libère l’empla-
cement mémoire sur lequel p pointait ; cet emplacement redevient disponible et p↑ n’est plus défini.

Il exite une valeur particulière pour un pointeur : null. Lorsqu’un pointeur prend la valeur null, il
ne “pointe sur rien”. Shématiquement : p p
null ou bien

Soient les déclarations de type pt-entier=↑entier et de variables p,q,r :pt-entier et


m,n :entier.

2
a) Les instructions suivantes sont-elles correctes ?
– Allouer(m)
– Allouer(p↑)
– Allouer(q)
– p↑ ← n
– n ← p
– m ← q↑
– p↑ ← q↑
– Desallouer(q↑)
– Desallouer(p)

b) Schématiser l’état de la mémoire successivement après l’exécution de chacune des instructions


suivantes :
– Allouer(p)
– Allouer(q)
– q↑ ← 12
– n ← 25
– p↑ ← n
– r ← q
– q ← p
– p ← r

Considérons les déclarations de listes chaînées suivantes :


lien=↑cellule
cellule=Enregistrement
ele :TypeElem
suiv :lien
FinEnregistrement
L,p,q :lien

Supposons que la liste chaînée représentée ci-dessous soit présente en mémoire.


L
• a • b • c • d

c) Schématiser l’état de la mémoire successivement après l’exécution des instructions suivantes :


Allouer(p)
p↑.ele ← z
q ← L
p↑.suiv ← null
Tant que q↑.suiv 6= null faire
q ← q↑.suiv
Fin Tant que
q↑.suiv ← p
q ← L↑.suiv
p ← L
L ← p↑.suiv
p↑.suiv ← L↑.suiv
L↑.suiv ← p

3
Exercice 4 : Reprendre l’exercice 1 (mise en oeuvre des primitives de listes), mais en supposant que
les listes L=(a1 ,a2 ,...,ai...,an) sont représentées par des listes simplement chaînées (cf.
déclaration de l’exercice 3). On posera les conventions suivantes :
– Ajout d’une cellule Entête : Pour éviter que la liste vide (sans aucun élément) ne soit réduite
“à rien”, et ne pas distinguer les tests “aux limites”, on choisit d’ajouter une cellule ne contenant
aucune information pertinente et qui pointe sur la cellule contenant le premier élément.
– Choix de la position : Si la position i est représentée par un pointeur sur la cellule contenant
ai , alors FIN(L) est l’adresse contenu dans le champ suiv de la cellule contenant an (i.e.,
null). Pour pouvoir distinguer FIN(L) d’une mauvaise position (null), on choisit de définir
la position i comme un pointeur sur la cellule qui pointe sur la cellule contenant ai . D’où,
position 1 = pointeur sur l’Entête et FIN(L) = pointeur sur la dernière cellule
L Entête FIN(L)
• ? • a1 • a2 • ... an
position 1

Exercice 5 : On suppose que la mise en oeuvre des listes se fait par des listes chaînées. Dans ce cas,
reprendre l’exercice 2 jusqu’à l’instruction 22) et finir l’algorithme avec les étapes suivantes :
23) retourner le 1er élément de la liste,
24) donner le dernier élément de la liste,
25) vider la liste.

Exercice 6 : Ecrire un algorithme pour échanger les cellules contenant les éléments aux positions p
et SUIVANT(p) dans une liste simplement chaînée.

Exercice 7 :
a) Proposez une structure de données de listes qui permette, étant données deux listes L1 et L2 ,
de retourner la liste L3 qui sera la concaténation de L1 puis L2 , en une complexité en O(1).
b) Ecrivez la fonction concaténation(L1 ,L2 ) qui retourne L3 en O(1).
c) On suppose que chacune des listes L1 et L2 est mise en oeuvre dans un tableau de taille
TAILLEMAX
c1 ) Donner la fonction qui prend en entrée les listes L1 et L2 et retourne la liste L3 qui constitue
l’union des 2 précédentes (en gardant éventuellement des doublons). Quelle est la complexité
de cette fonction ?
c2 ) Ecrire un algorithme qui prend en entrée une liste L1 et renvoit la liste L2 correspondant
à la liste L1 dans laquelle les doublons ont été supprimés (seule la première occurrence de
chaque élément étant conservée dans la liste L2 ). En déduire l’algorithme qui concatene deux
listes en ne conservant pas de doublons.
c3 ) Reprendre la question précédente mais en construisant progressivement la liste L3 (sans
utiliser de concatenation). La complexité a-t-elle évolué ?

4
Exercice 8 : Soient les primitives définies sur des files :
– RAZ(F ) : Vide le contenu de la file F
– VIDE (F ) : Retourne vrai si F est vide et faux sinon
– TETE(F ) : Retourne (sans l’enlever) l’élément en tête de la file F
– DEFILER(F ) : Supprime l’élément en tête de la file F
– ENFILER(x,F ) : Insère l’élément x dans la file F (en dernière position).
L’implantation sous forme de tableau qui a été définie pour les listes, n’est pas judicieuse pour les
files (l’opération DEFILER a une complexité en O(n)). Pour éliminer cet inconvénient, on introduit la
notion de tableau circulaire représenté sur le schéma suivant :

1 2 3 4 i j Longmax
a1 a2 an
file

sens du tableau circulaire

Les éléments de la file se trouvent disposés le long de ce tableau à partir d’une position initiale variable
dans le temps, sur un ensemble de positions “consécutives” (par exemple une file de 4 éléments peut
très bien occuper les deux dernières et les deux premières cases du tableau, ces 4 positions étant jugées
comme consécutives dans le tableau circulaire) ; la dernière position se trouve donc “en avant” de la
première, relativement au sens de la flèche sur le graphique précédent.

Dans ce cas, quand une insertion est effectuée, le pointeur F.queue est déplacée d’une position vers
l’avant et l’élément est inséré à cette position. Une suppression provoque alors simplement le déplace-
ment du pointeur F.tete d’une position en avant. Ainsi, la file se déplace le long du tableau. Dans un
tel modèle, il est possible d’écrire les procédures Enfiler et Défiler en un nombre d’étapes constant.

a) Considérons la file représentée dans le tableau suivant (dans lequel longmax=10) :


1 2 3 4 5 6 7 8 9 10
a b c d e f
On effectue les opérations suivantes sur la file ci-dessus, que devient la file dans le tableau
circulaire (représenter le tableau et les éléments de la file) :
1. appliquer deux fois DEFILER(F )
2. appliquer ENFILER (x,F )
3. appliquer ENFILER (y,F )
4. appliquer ENFILER (x,F )

b) Si F.queue est à un indice plus petit que F.tete, où se situe la file dans le tableau circulaire ?
(représenter la sur un schéma).

c) Si ni F.tete, ni F.queue n’est associé à une “case factice”, comment représentez-vous la file
vide ? C’est-à-dire, si F.tete pointe sur la case contenant le 1er élément de la file et F.queue
pointe sur la case contenant le dernier élément de la file, comment représentez-vous la file vide ?
Se distingue-t-elle de celle contenant un seul élément ?

Pour ces raisons, nous choisirons pour toute la suite, arbitrairement, que F.queue pointera sur
la case suivant celle qui contient le dernier élément de la liste, dans le sens du tableau circulaire.
Combien valent F.tete et F.queue sur la file de la question 1), avant la réalisation des opérations
demandées ?

5
d) Si la file contient longmax éléments, où se situe l’indice F.queue par rapport à F.tete ? Le
représenter sur un schéma du tableau circulaire

e) Considérons une file ne contenant qu’un seul élément. Où pointent F.tete et F.queue ? Si l’on
supprime cet élément, où pointeront-ils ? Ce cas de figure est-il similaire à celui obtenu lors de
la question d) ? Quelle est alors la difficulté rencontrée ? Proposez un moyen pour la soulever.

f) Soit les déclarations de structure de file que nous utiliserons par la suite :
file=Enregistrement
ele :Tab[LongMax] de TypeElem
tête,queue :entier
FinEnregistrement
F :file
Commencez par écrire une fonction ajouter(i) qui étant donné l’indice i du tableau circulaire,
retourne l’indice suivant respectivement au sens du tableau circulaire.

g) A l’aide, si nécessaire, de la fonction précédente, écrire les cinq primitives RAZ(F ), VIDE(F ),
TETE(F ), ENFILER(x,F ) et DEFILER (F) en utilisant la représentation de file définie précé-
demment.

h) Donner la complexité de chacune des primitives précédentes.