• Aucun résultat trouvé

OPERATIONS COURANTES SUR LES LISTES 79

Structures de donnees elementaires

3.5. OPERATIONS COURANTES SUR LES LISTES 79

e1 e2 e3

a

e01 e02 e03 e04 b

e1 e2 e3

Append(a;b)

Figure 3.7 : Concatenation de deux listes par Append

e1 e2 e3

a

e01 e02 e03 e04 b

Figure 3.8 : Concatenation de deux listes parNconc

Append copie son premier argument, il partage la n de liste de son resultat avec son deuxieme argument.

function Append (a: Liste; b: Liste): Liste;

begin

if a = nil then Append := b else

Append := Cons (a^.contenu, Append (a^.suivant, b));

end;

procedure Nconc (var a: Liste; b: Liste);

var c: Liste;

begin

if a = nil then a := b else

begin c := a;

while c^.suivant <> nil do c := c^.suivant;

c^.suivant := b;

end;

end;

La procedure de calcul de l'image miroir d'une liste a consiste a construire une liste dans laquelle les elements de a sont rencontres dans l'ordre inverse de ceux de a. La realisation de cette procedure est un exercice classique de la programmation sur

les listes. On en donne ici deux solutions l'une iterative, l'autre recursive, quadratique mais classique. A nouveau, Nreverse modie son argument, alors que Reverse ne le modie pas et copie une nouvelle liste pour son resultat.

procedure Nreverse (var a: Liste);

var b, c: Liste;

begin b := nil;

while a <> nil do begin

c := a^.suivant;

a^.suivant := b;

b := a;

a := c;

end;

a := b;

end;

function Reverse (a: Liste): Liste;

begin

if a = nil then Reverse := nil else

Reverse := Append (Reverse (a^.suivant), Cons (a^.valeur, nil));

end;

Un autre exercice formateur consiste a gerer des listes dans lesquelles les elements sont ranges en ordre croissant. La procedure d'ajout devient alors plus complexe puis-qu'on doit retrouver la position de la cellule ou il faut ajouter apres avoir parcouru une partie de la liste.

Nous ne traiterons cet exercice que dans le cas des listes circulaires gardees, voir page 68. Dans une telle liste, la valeur du champ contenu de la premiere cellule n'a aucune importance. On peut y mettre le nombre d'elements de la liste si l'on veut. Le champ suivantde la derniere cellule contient lui l'adresse de la premiere.

procedure Insert (v: integer; var a: liste);

var b: liste;

begin b := a;

while (b^.suivant <> a) and (v > b^.suivant^.contenu) do b := b^.suivant;

e1 e2 e3

b

e4 e5 e6 e7

c

a

Figure 3.9 : Transformation d'une liste au cours deNreverse

3.6. PROGRAMMES EN C 81

e1 e2 e3 e4

a

Figure 3.10 : Liste circulaire gardee

b^.suivant := Cons (v, b^.suivant);

a^.contenu := a^.contenu + 1;

end;

3.6 Programmes en C

typedef int Element;

struct Cellule { Element contenu;

struct Cellule *suivant;

};

typedef struct Cellule Cellule;

typedef struct Cellule *Liste;

Remarque: Nous voulons suivre les signatures des procedures Pascal. On aurait pu ne pas declarer les types Element etListe, et utiliser des declarations tres courantes en C comme :

int FaireLvide(Liste *ap) {

*ap = NULL;

}

int Lvide (Liste a) /* voir plus bas */

/* Liste vide, voir page 64 */

{

return a == NULL;

}

Remarque: NULLest deni dans<stdio.h>.

void Lajouter(Element x, Liste *ap) /* Ajouter, voir page 65 */

{

Liste b;

b = (Liste) malloc(sizeof(Cellule));

b -> contenu = x;

b -> suivant = *ap;

*ap = b;

}

int Lrecherche(Element x, Liste a) /* Recherche, voir page 65 */

{

while (a != NULL) { if (a -> cont == x)

return 1;

a = a -> suivant;

}

return 0;

}

int Llongueur(Liste a) /* Longueur d'une liste, voir page 66 */

{

if (a == NULL) return 0;

else

return 1 + Llongueur (a -> suivant);

}

int Llongueur(Liste a) /* Longueur d'une liste, voir page 66 */

{

int r = 0;

while (a != NULL) { ++r;

a = a -> suivant;

}

return r;

}

void Lsupprimer(Element x, Liste *ap) /* Supprimer, voir page 66 */

{

Liste b,c;

b = *ap;

if (b != NULL)

if (b -> contenu == x){

c = b;

b = b -> suivant;

free(c);

} else

Lsupprimer (x, &b -> suivant);

*ap = b;

}

La fonction LsupprimerIterqui est non recursive s'ecrit bien plus simplement en C:

void LsupprimerIter (Element x, Liste *ap) {

Liste a, b, c;

3.6. PROGRAMMES EN C 83

a = *ap;

if (a != NULL)

if (a -> contenu == x){

c = a;

a = a -> suivant;

free(c);

} else { b = a ;

while (b != NULL && b -> suivant -> contenu != x) b = b -> suivant;

if (b != NULL) { c = b -> suivant;

b -> suivant = b -> suivant -> suivant;

free(c);

} }

*ap = a;

}

Liste ListePremier (int n) /* Liste des nombres premiers, voir page 69 */

{

Liste a, b;

int i, j, k;

FaireLvide (a);

for (i = n; i >= 2; --i) { Lajouter (i, &a);

}

k = a -> contenu;

for (b = a; k * k <= n ; b = b -> suivant){

k = b -> contenu;

for (j = k; j <= n/k; ++j) Lsupprimer (j * k, &a);

} return(a);

}

Declarations et operations sur les piles voir page 71

struct Pile {

int hauteur ; Element contenu[MaxP];

};

typedef struct Pile Pile;

int FairePvide(Pile *p) {

p -> hauteur = 0;

}

int Pvide(Pile *p)

{

return p -> hauteur == 0;

}

void Pajouter(Element x, Pile *p) {

p -> contenu[p -> hauteur] = x;

++ p -> hauteur;

}

Element Pvaleur(Pile *p) {

int i;

i = p -> hauteur -1;

return p -> contenu [i];

}

void Psupprimer(Pile *p) {

-- p -> hauteur;

}

Evaluation des expressions prexees voir page 72:

enum Nature {Symbole, Nombre};

struct Element {

enum Nature nature;

int valeur;

char valsymb;

};

typedef struct Element Element;

typedef Expression Element[MaxP];

int Calculer (char a, int x, int y) {

switch (a) {

case '+': return x + y;

case '*': return x * y;

} }

void Inserer (Element x, Pile *p) {

Element y, z;

if (Pvide (p) || x.nature == Symbole) Pajouter(x, p);

else {

y = Pvaleur(p);

if (y.nature == Symbole) Pajouter(y, p);

else {

3.6. PROGRAMMES EN C 85

Psupprimer(p);

z = Pvaleur(p);

Psupprimer(p);

x.valeur = Calculer(z.valsymb, x.valeur, y.valeur);

Inserer(x,p);

} } }

int Evaluer (Expression u, int l) {

int i;

Pile p;

FairePvide (&p);

for (i = 1; i <= l ; ++i)

if (u[i].nature == Symbole ||

u[i].valsymb == '+' ||

u[i].valsymb == '*') Inserer(u[i] ,&p);

return (Pvaleur (&p)).valeur;

}

#define MaxF 100 typedef int Element;

typedef struct Fil { /* les les representees par */

int debut; /* un vecteur voir page 75 */

int fin;

Element contenu[MaxF];

} Fil;

int Successeur(int i) {

return i % MaxF;

}

int Fvide(Fil *f) {

return f -> debut == f -> fin;

}

void FaireFil(Fil *f) {

f -> debut = 0;

f -> fin = 0;

}

int Fvaleur (Fil *f) {

int i = Successeur(f -> debut);

return f -> contenu[i];

}

void Fajouter (Element x, Fil *f) {

f -> fin = Successeur(f -> fin);

f -> contenu[f -> fin] = x;

}

void Fsupprimer (Fil *f) {

f -> debut = Successeur(f -> debut);

}

typedef int Element; /* les les representees par une liste voir page 76 */

typedef struct Cellule {

Element contenu;

struct Cellule *suivant;

} Cellule, *Liste;

typedef struct Fil{

Liste debut;

Liste fin;

} Fil;

void FaireFvide (Fil *f) {

f -> debut = (Liste) malloc (sizeof(Cellule));

f -> fin = f -> debut;

}

Liste Successeur (Liste a) {

return a -> suivant;

}

int Fvide (Fil f) {

return f.debut == f.fin;

}

Element Fvaleur (Fil f) {

Liste b = Successeur(f.debut);

return b -> contenu;

}

void Fajouter (Element x, Fil *f) {

Liste a = (Liste) malloc (sizeof(Cellule));

a -> contenu = x;

a -> suivant = NULL;

f -> fin -> suivant = a;

f -> fin = a;

}

3.6. PROGRAMMES EN C 87

void Fsupprimer (Fil *f) {

Liste a = f -> debut;

f -> debut = Successeur (f -> debut);

free(a);

}

Liste Tail (Liste a) /* Tail et Cons voir page 78 */

{

if (a == NULL) {

fprintf(stderr, "Tail d'une liste vide.\n");

exit (1);

} else

return a -> suivant;

}

Liste Cons (int v, Liste a) {

Liste b = (Liste) malloc (sizeof(Cellule));

b -> contenu = v;

b -> suivant = a;

return b;

}

Liste Append (Liste a, Liste b) /* Append et Nconc voir page 79 */

{

if (a == NULL) return b;

else

return Cons (a -> contenu, Append(a -> suivant, b));

}

void Nconc (Liste *ap, Liste b) {

Liste c;

if (*ap == NULL)

*ap = b;

else { c = *ap;

while (c -> suivant != NULL) c = c -> suivant;

c -> suivant = b;

} }

void Nreverse (Liste *ap) /* Nreverse et Reverse, voir page 80 */

{

Liste a, b, c;

a = *ap;

b = NULL;

while (a != NULL) { c = a -> suivant;

a -> suivant = b;

b = a;

a = c;

}

*ap = b;

}

Liste Reverse (Liste a) {

if (a == NULL) return a;

else

return Append (Reverse (a -> suivant), Cons (a -> contenu, NULL));

}

void Insert (Element v, Liste *ap) /* Insert, voir page 80 */

{

Liste a, b;

a = *ap;

b = a -> suivant;

while (b -> suivant != a && v > b -> suivant -> contenu) b = b -> suivant;

b -> suivant = Cons (v, b -> suivant);

++ a -> contenu;

*ap = a;

}

Arbres

Nous avons deja vu la notion de fonction recursive dans le chapitre 2. Considerons a present son equivalent dans les structures de donnees: la notion d'arbre. Un arbre est soit un arbre atomique (une feuille), soit un nud et une suite de sous-arbres.

Graphiquement, un arbre est represente comme suit n1

n2

n3

n4

n5 n6

n7

n8

n9 n10 n11

Figure 4.1 : Un exemple d'arbre

Le nudn1 est laracinede l'arbre, n5,n6,n7,n9,n10,n11sont lesfeuilles,n1,n2,n3, n4, n8 les nuds internes. Plus generalement, l'ensemble des nuds est constitue des nuds internes et des feuilles. Contrairement a la botanique, on dessine les arbres avec la racine en haut et les feuilles vers le bas en informatique. Il y a bien des denitions plus mathematiques des arbres, que nous eviterons ici. Si une branche relie un nudni

a un nudnj plus bas, on dira queniest unanc^etredenj. Une propriete fondamentale d'un arbre est qu'un nud n'a qu'un seul pere. Enn, un nud peut contenir une ou plusieurs valeurs, et on parlera alors d'arbres etiqueteset de la valeur (ou des valeurs) d'un nud. Lesarbres binairessont des arbres tels que les nuds ont au plus 2 ls. La hauteur, on dit aussi la profondeurd'un nud est la longueur du chemin qui le joint a la racine, ainsi la racine est elle m^eme de hauteur 0, ses ls de hauteur 1 et les autres

89

+

5

2 3

+

10 10

9 9

Figure 4.2 : Representation d'une expression arithmetique par un arbre nuds de hauteur superieure a 1.

Un exemple d'arbre tres utilise en informatique est la representation des expressions arithmetiques et plus generalement des termes dans la programmation symbolique.

Nous traiterons ce cas dans le chapitre sur l'analyse syntaxique, et nous nous restrein-drons pour l'instant au cas des arbres de recherche ou des arbres de tri. Toutefois, pour montrer l'aspect fondamental de la structure d'arbre, on peut tout de suite voir que les expressions arithmetiques calculees dans la section 3.3 se representent simplement par des arbres comme dans la gure 4.2 pour(* (+ 5 (* 2 3)) (+ (* 10 10) (* 9 9))). Cette representation contient l'essence de la structure d'expression arithmetique et fait donc abstraction de toute notation prexee ou postxee.

Documents relatifs