Vous êtes sur la page 1sur 11

Université Paris Diderot JAVA

MASS L2 Année 2007-2008

TD n◦9 - Correction
Listes

Une liste est une structure permettant de stocker des éléments, un peu à la manière d’un
tableau. Une liste est composée d’éléments, un élément étant constituée d’une valeur (valeur que
l’on souhaite stocker) et d’une référence vers un autre élément. Une liste est donc une chaine
d’élément (d’où le terme liste chainée).
Dans toute la suite du TD, nous allons travailler avec les 2 classes suivantes :

class Liste{
public Element debut;
public Liste()
{
debut=null;
}
...
}

class Element{
public int valeur;
public Element suivant;
...
}

Par exemple, la liste 3-1-2 sera representée de la façon suivante :

Liste

debut

3 suivant 1 suivant 2 null

Exercice 1 Listes simplement chainées


1. Dans la classe Liste, écrire une méthode void affiche() permettant d’afficher les valeurs
de tous les éléments de la liste.
Correction :
Repérez bien le schéma utilisé pour parcourir la liste.
void affiche()
{
if (debut==null) return;
//la liste est vide...

1
Element e=debut;

System.out.println(e.valeur);

while(e != null)
{
e=e.suivant;
System.out.println(e.valeur);
}

}
les instructions
Element e=debut;
while(e != null)
{
e=e.suivant;
}
sont le noyau central de toutes les méthodes itératives permettant de parcourir une liste.


2. Écrire une méthode boolean estVide().


Correction :
Il s’agit simplement de tester si la liste contient des éléments ou pas :
boolean estVide()
{
return (debut==null)
}


3. Dans la classe Element, écrire une méthode récursive permettant d’afficher l’élément cou-
rant et tous ses successeurs.
Correction :
pour afficher l’élément courant et tous ses successeurs, il faut :
(a) afficher l’élément courant.
(b) afficher l’élément suivant et tous ses successeurs (s’il y a un élément suivant.)
on a donc la méthode récursive suivante, dans la classe Element.
void afficheRec()
{
System.out.println(e.valeur);
if (suivant != null)
suivant.afficheRec();
}
Muni de cette méthode, nous pouvons écrire d’une autre façon la méthode d’affichage de la classe
Liste :
void affiche()
{
if (debut==null) return;
debut.afficheRec();
}

2
attention, il faut tout de même bien vérifier que debut n’est pas null, sinon, on aurait une erreur
à l’exécution en appelant debut.afficheRec();.
Question subsidiaire, que se passerait-il si on remplaçait afficheRec() par la méthode suivante :
void afficheRec2()
{
if (suivant != null)
suivant.afficheRec();
System.out.println(e.valeur);
}


4. Dans la classe Liste, écrire une méthode int longueur() renvoyant la longeur de la liste.
Correction :
Nous pouvons faire ça itérativement :
int longueur()
{
if (debut==null) return 0;
//la liste est vide...

Element e=debut;

int c=1;

while(e != null)
{
e=e.suivant;
c++;
}

return c;

}
ou itérativement, en remarquant que la longueur d’une liste, c’est 1 plus la longueur de la liste
partant de l’élément suivant.
//dans la classe Element
int longueurRec()
{
if (suivant==null) return 1;
return 1+suivant.longueurRec();
}

...

//dans la classe Liste


int longueur2()
{
if (begin==null) return 0;
return debut.longueurRec();
}


Dans toutes les questions suivantes, nous travaillerons maintenant dans la classe Liste.

3
5. Écrire une méthode void supprDebut() supprimant le premier élément de la liste.
Correction :
Repartons de l’exemple donné au début :

Liste

debut

3 suivant 1 suivant 2 null

Supprimer le premier élément revient juste à modifier le chainage des éléments de la liste, pour
aboutir au schéma suivant :

Liste

debut

3 suivant 1 suivant 2 null

L’ancien premier élément n’est alors plus accessible, et java va le supprimer tout seul :

Liste

debut

1 suivant 2 null

et on a bien la liste de départ dont on a supprimé le premier élément.


D’un point de vue code, on doit donc modifier la flêche correspondant à debut dans l’objet de type
Liste, et la faire pointer vers le second élément (c’est-à-dire debut.suivant ). En rajoutant un test
au cas où la liste est vide, celà donne :
void supprDebut()
{
if (debut != null)
debut=debut.suivant;
}

L’opération de suppression au début d’une liste s’effectue en temps constant, alors qu’elle serait
linéaire dans le cas d’un tableau (il faut créer un tableau plus petit, recopier le tableau courant
dedans sans le premier élément. . .).


4
6. Écrire une méthode void ajoutDebut(int i) ajoutant un élément au début de la liste
de valeur i.
Correction :
Ici, si l’on rajoute 0 par exemple, on veut aboutir au schéma suivant :

Liste
0 suivant
debut

3 suivant 1 suivant 2 null

Il y a donc 2 choses à faire :


(a) créer un nouvel Element, contenant la bonne valeur.
Element e=new Element();
e.valeur=i;

Liste
0 suivant
debut

3 suivant 1 suivant 2 null

(b) refaire les chainages correctement. Sur schéma il y a deux flêches à mettre en place : il faut
que debut référence le nouveau premier élément e, et que e.suivant référence l’ancien premier
élément. Allons-y :
debut=e;

Liste
0 suivant
debut

3 suivant 1 suivant 2 null

et là, c’est le drame : nous n’avons plus de moyen d’accéder à l’ancien premier élément (celui
qui contient 3). On voudrait écrire e.suivant=quelquechose, mais il n’y a plus moyen que
quelquechose référence le bon Element.

5
Il y a deux solutions pour remédier à ça :
– On peut garder une référence vers l’ancien premier élément, pout pouvoir y accéder au
moment venu :
Element a=debut;
debut=e;

Liste
0 suivant
debut

a 3 suivant 1 suivant 2 null

et maintenant, on peut écrire :


e.suivant=a;

Liste
0 suivant
debut

a 3 suivant 1 suivant 2 null

La méthode est alors la suivante :


void ajoutDebut(int i)
{
Element e=new Element();
e.valeur=i;

Element a=debut;
debut=e;
e.suivant=a;
}

6
– On peut aussi bouger les flêches dans l’autre ordre :
e.suivant=debut;

Liste
0 suivant
debut

3 suivant 1 suivant 2 null

et maintenant
debut=e;

Liste
0 suivant
debut

3 suivant 1 suivant 2 null

void ajoutDebut(int i)
{
Element e=new Element();
e.valeur=i;

e.suivant=debut;
debut=e;
}

Dans tous les cas, l’opération d’insertion au début d’une liste s’effectue en temps constant, alors
qu’elle serait linéaire dans le cas d’un tableau.


7
7. Écrire une méthode int val(int i) qui renvoie la valeur du ième élément de la liste.
Quelle est sa complexité ? Quels sont les avantages et inconvénients de l’utilisation d’une
liste par rapport à un tableau ?
Correction :
L’accés direct au ième élément n’est pas possible : il faut passer par tous les éléments précédents.
On peut faire ça itérativement :
int val(int i)
{
if (i > longueur) return -1;
//il faudrait renvoyer une erreur...

Element e=debut;

//on avance i-1 fois


for (int k=0; k<i-1; k++)
{
e=e.suivant;
}
// e est maintenant le ième élément
// on n’a plus qu’à retourner sa valeur.

return e.valeur;
}
ou récursivement. Retourner le ième élément à partir de l’élément courant, c’est retourner le i-1ème
à partir de l’élément suivant. Avec la bonne condition d’arrêt en plus, celà donne :
//dans la classe Element
int valRec(int i)
{
if (i==1) return valeur;
return suivant.valRec(i-1);
}

...

//dans la classe Liste


int val2(int i)
{
if (i > longueur) return -1;
//il faudrait renvoyer une erreur...

return debut.valRec(i);
}
L’accés à un élément quelconque est donc plus couteux que dans le cas d’un tableau.
Il y donc des cas où on aura intérêt à utiliser listes plutôt que tableaux (beaucoup d’ajouts
d’éléments, peu d’accès aléatoires), et des cas où ce sera l’inverse.


8
Exercice 2 Listes doublement chainées
Insérer et supprimer à la fin d’une liste simplement chainée est une opération couteuse et
peu naturelle. Nous allons donc de nouvelles classes permettant de décrire des listes doublement
chainées :

class ListeD{
public ElementD debut;
public ElementD fin;
...
}

class ElementD{
public int valeur;
public ElementD suivant;
public ElementD precedent;
...
}

1. Écrire une méthode void supprFin() supprimant le dernier élément de la liste.


Correction : Je ne vais pas détailler autant la correction pour cette partie. Tracer les schémas
correspondant à ces algorithmes, c’est un très bon exercice (il y a maintenant deux flêches partant
de chaque Element ). Il y a un double chainage, donc il faut être beaucoup plus attentif à ce qu’on
fait.
void supprFin()
{
//liste vide
if (fin == null) return;

//liste réduite à un seul élément


if (fin == debut)
{
debut=null;
fin=null;
return;
}

fin.precedant.suivant=null;
//fin.precedant est l’avant-dernier élément
fin=fin.precedant;
}


9
2. Écrire une méthode void ajoutFin(int i) ajoutant un élément à la fin de la liste de
valeur i.
Correction :
void ajoutFin(int i)
{
ElementD e=new ElementD();
e.valeur=i;

//liste vide
if (fin == null)
{
debut=e;
fin=e;
e.suivant=null;
e.precedant=null;
return;
}

e.precedant=fin;
fin.precedant.suivant=e;
fin=e;
}


10
3. Écrire une méthode void inserer(int i, int v) qui insère un élément de valeur v en
ième position.
Correction :
Un peu plus pénible à écrire très proprement, il y a plein de cas à vérifier. . . Je vais supposer que
nous avons écrit des méthodes pour calculer la longueur et insérer au début.
void inserer(int i, int v)
{
// cas particuliers
if (i<=1) {ajoutDebut(v); return;}
if (i>longueur()) {ajoutFin(v); return;}

//cas général : on commence par récupérer les (i-1)ème


// et ième éléments, càd ceux auxquels il va falloir toucher
// aux fl^eches.

Element e=debut;

//on avance i-2 fois


for (int k=0; k<i-1; k++)
{
e=e.suivant;
}

Element tmp1=e;
Element tmp2=e.suivant;

// on veut maintenant insérer entre les éléments


// tmp1 et tmp2.

//on créé le nouvel élément


ElementD nouveau=new ElementD();
nouveau.valeur=i;

// on refait les chainages


nouveau.suivant=tmp2;
nouveau.precedant=tmp1;
tmp2.precedant=nouveau;
tmp1.suivant=nouveau;

}


11

Vous aimerez peut-être aussi