Exercices sur les algorithmes et les structures de données - 2° partie, Exercices de Algorithmique et programmation d'applications
Christophe
Christophe28 February 2014

Exercices sur les algorithmes et les structures de données - 2° partie, Exercices de Algorithmique et programmation d'applications

PDF (136.7 KB)
32 pages
269Numéro de visites
Description
Exercices de mathématique - examen sur les algorithmes et les structures de données - 2° partie. Les principaux thèmes abordés sont les suivants: Structures de donnees, Les listes.
20points
Points de téléchargement necessaire pour télécharger
ce document
Télécharger le document
Aperçu3 pages / 32
Ceci c'est un aperçu avant impression
Chercher dans l'extrait du document
Ceci c'est un aperçu avant impression
Chercher dans l'extrait du document
Aperçu avant impression terminé
Chercher dans l'extrait du document
Ceci c'est un aperçu avant impression
Chercher dans l'extrait du document
Ceci c'est un aperçu avant impression
Chercher dans l'extrait du document
Aperçu avant impression terminé
Chercher dans l'extrait du document
cours.dvi

4. Si les fonctions ont même ordre de grandeur, il faut pour les comparer,

évaluer finement les constantes et souvent évaluer des termes d’ordre

inférieur dans les comportements asymptotiques. (Difficile).

2.3.3 Impact pratique de la notion d’ordre de grandeur

de la complexité des algorithmes

Il est toujours d’actualité de rechercher des algorithmes efficaces

même si les progrès technologiques accroissent les performances

du matériel.

On dispose pour résoudre un problème donné de 7 algorithmes dont

les complexités dans le cas le pire ont pour ordre de grandeur 1 (fonction

constante qui ne dépend pas de la taille des données), log2n, n, nlog2n, n 2,

n3, 2n.

On suppose que l’ordinateur peut faire 106 opérations par seconde.

– Tableau A : donne une estimation du temps d’exécution pour chaque

algorithme et pour différentes tailles.

– Tableau B : estimation de la taille maximale du problème que l’on peut

résoudre en un temps donné.

– Tableau C : augmentation de la complexité en fonction de l’augmenta-

tion de la taille des données.

Il apparâıt clairement que certains algorithmes sont peu utilisables ou

même inutilisables pour résoudre des problèmes sur un ordinateur. Ceux qui

sont utilisables sont ceux qui s’exécutent en temps

1. Constant.

38

2. Logarithmique : recherche dichotomique, nombre de comparaisons dans

le pire des cas.

3. Linéaire : recherche séquentielle d’un élément dans une liste non triée,

nombre de comparaisons dans le pire des cas.

4. nlog(n) : tri par fusions successives, nombre de comparaisons dans le

pire des cas.

5. Polynômial ie θ(nk), k > 0

– k = 2 nombre de déplacements dans le pire des cas pour les 2 tris

précédents.

– k = 3 nombre d’opérations pour multiplier 2 matrices.

Au delà de k = 3 (et encore), et pour les algorithmes exponentiels ie en

θ(2n), on ne parle plus d’algorithmes utilisables (jeu d’échecs) : on envisage

alors des méthodes approchées appelées heuristiques qui sont des méthodes

produisant la plupart du temps un résultat acceptable non nécessairement

optimal, dans un temps raisonnable.

Durée : 1H30

39

Chapitre 3

Structures de données

3.1 Notion de type abstrait

Un algorithme utilise des structures de données pour représenter et

manipuler les données du problème qu’il solutionne. Jusqu’à présent, nous

avons utilisé des données numériques ainsi que des tableaux. Dans ce cha-

pitre, nous allons voir comment spécifier de nouveaux types qui pourront être

utilisés dans un algorithme.

Un algorithme doit être aussi indépendant que possible d’une implémentation

particulière. Les données doivent être considérées de manière abstraite, détachées

au maximum de leur représentation interne. Toutefois, il ne faut évidemment

pas perdre de vue qu’elles sont destinées à être implémentées et donc s’assurer

de cette possibilité.

Un type abstrait est une spécification de données décrivant l’ensemble

des opérations associées à ces données et l’ensemble des propriétés de ces

opérations. Cette spécification ne précise pas la représentation interne. La

40

conception de l’algorithme se fait en utilisant les opérations des types abs-

traits. De ce fait, les algorithmes sont indépendants de la représentation in-

terne des données. Nous pouvons distinguer deux sortes de types abstraits :

– Les types abstraits de base qui correspondent aux types disponibles

dans la plupart des langages de programmation. Ces types abstraits

sont utilisés pour construire d’autres types abstraits.

– Les types abstraits construits au moyens de types abstraits de base ou

bien d’autres types abstraits construits prédéfinis.

Concernant les types abstraits de base, il est possible de les définir de la

même façon que les types abstraits construits, c’est-à-dire, sans préciser leur

représentation interne.

Exemple. On peut définir le type réel par les opérations et leurs propriétés,

sans connâıtre leur représentation interne (mantisse, exposant).

3.2 Définition d’un type abstrait

La définition d’un type de donnée consiste à en donner une spécification.

Un type abstrait est la donnée

– de sa signature décrivant la syntaxe du type avec le nom des opérations

valides ainsi que le type de leurs arguments,

– des propriétés des opérations du type qui seront données sous forme

d’un ensemble d’axiomes (formules logiques). On introduit alors une

sémantique (une signification) aux objets de la signature.

41

3.2.1 Signature

La signature d’un type abstrait est la donnée

– d’un ensemble de noms pour un certain nombre d’ensembles de valeurs;

ces noms sont appelés des sortes (cette notion correspond à la notion

de types pour des langages de programmation).

– d’un ensemble de noms d’opérations et de leur signature. La signa-

ture d’une opération est la précision de son domaine de définition ainsi

que l’ensemble auquel appartient le résultat.

EXEMPLE 1. Signature du type abstrait booléen

Sorte booléen

Opérations

vrai : → booléen faux : → booléen ¬ - : booléen → booléen - ∧ -: booléen × booléen → booléen - ∨ -: booléen × booléen → booléen

Les fonctions vrai et faux sont sans arguments. Ce sont des constantes.

On donne en même temps que le nom de la fonction la place des arguments

quand la notation n’est pas fonctionnelle.

42

EXEMPLE 2. On considère le type abstrait séquence d’éléments réels

numérotés par des entiers.

Il serait peu pratique de donner un détail la signature complète des sortes

entier et réel.

On s’autorise alors la réutilisation de types abstraits prédéfinis

(types abstraits de base), ou définis auparavant.

Sorte séquence-réelle

Utilise entier, réel

Opérations ∣

séquence-réelle : entier → séquence-réelle ième : séquence-réelle× entier → réel changer-ième : séquence-réelle× entier × réel→ séquence-réelle taille : séquence-réelle→ entier

La signature est l’union des signatures. Cet exemple introduit la notion

de hiérarchie entre les types. On introduit une classification parmi les sortes

et les opérations d’un type abstrait

– sorte définie : sorte de nom nouveau (exemple séquence).

– sorte prédéfinie : sorte de nom utilisé (exemple entier et réel).

– opération interne : opération donnant un résultat d’une sorte définie

(exemple changer-ième).

– observateur : opération ayant au moins un argument de sorte définie et

donnant un résultat de sorte prédéfinie (exemple ième, taille).

43

3.2.2 Propriétés d’un type abstrait

On donne une sémantique aux sortes et aux opérations de la signature

au moyen d’axiomes agissant sur les variables. On obtient une définition

algébrique ou axiomatique du type abstrait.

Soient s, i, r des variables de sortes séquence-réelle, entier et réel. On

considère les axiomes suivants :

– 1 ≤ i ≤ taille(s)⇒ ième(changer-ième(s, i, r), i) = r

– 1 ≤ i ≤ taille(s) et 1 ≤ j ≤ taille(s) et i 6= j ⇒ ième(changer-ième(s, i, r), j) = ième(s, j)

– taille(changer-ième(s, i, r)) = taille(s)

– taille(séquence-réelle(l)) = l

Les deux premiers axiomes expriment que la fonction changer-ième ne

change que le ième élément de la séquence.

Remarques

– Consistance: les axiomes ne sont pas contradictoires.

– Complétude: les axiomes sont suffisants pour décrire l’ensemble des

propriétés du type abstrait. En fait, seule la complétude suffisante

est utilisée pour les types abstraits :

On doit pouvoir déduire une valeur pour tous les observa-

teurs, sur tout objet d’une sorte définie appartenant au do-

maine de définition de chaque observateur.

44

3.3 Types abstraits de base

Dans cette section, nous allons rappeler ou bien définir au moyen du for-

malisme des types abstraits, les types de base que l’on trouve communément

dans les langages de programmation. Ces définitions fourniront une plate-

forme utile pour l’implémentation.

3.3.1 Types primitifs

Les types primitifs correspondent à des quantités numériques codées sur

des cases mémoire de taille fixe. On y trouve les entiers, réels, caractères, etc.

Les opérations admises par les nombres sont les opérations arithmétiques

habituelles (+,*,/,-) ainsi que des fonctions mathématiques (log, sin, cos,

etc.). Nous ne proposerons pas de définition abstraite pour ces types, bien

que cela soit tout à fait possible, mais nous les utiliserons pour spécifier des

types abstraits non primitifs.

3.3.2 Type tableau

Un tableau est une séquence finie d’éléments de même type permettant

un accés direct à chaque élément en fonction de son indice. Cet accés permet

la lecture de la valeur de l’élément ou bien sa modification.

Nous pouvons utiliser une définition analogue à celle du type abstrait

séquence-réelle pour définir un tableau. Toutefois, pour être totalement conforme

au type tableau généralement fourni par les langages de programmation,

la définition doit être générique, c’est-à-dire permettre de varier les types

d’éléments. Nous introduisons donc un nouveau type séquence dit générique,

45

admettant le type de ses éléments en paramètre.

Sorte séquence

Paramètre élément

Utilise entier

Opérations ∣

seq : entier × element→ séquence ième : séquence× entier → élément changer-ième : séquence× entier × élément→ séquence taille : séquence→ entier

Si l’on conserve les mêmes axiomes que ceux du type séquence-réelle, le

type abstrait séquence correspond à peu près au type tableau du langage C,

à un décalage près de la numérotation des indices (on commence à 1 au lieu

de 0 en C).

Pour simplifier la notation, puisque le type tableau est largement utilisé,

on pourra écrire :

– t −→ tableau[réel](n, e) pour construire un nouveau tableau t de n éléments de type réel initialisés à e.

– t[i] pour ième(t,i)

– t[i]← e pour changer-ième(t,i,e)

– t.taille pour taille(t)

On remarquera la notation tableau[réel] permettant de spécifier le type cor-

respondant au paramètre élément au moment de la construction d’un ta-

46

bleau.

3.3.3 Type structure

Le type structure permet d’assembler des valeurs de types différents. On

dit qu’une structure est composée de champs qui ont chacun un nom et un

type. La valeur de chaque champ peut être lue ou modifiée. Soient E1, E2, . . .,

En n types abstraits, le type structure peut se définir de la façon suivante.

Sorte structure

Paramètres E1, E2, . . ., En

Opérations ∣

structure : Idn × E1 × E2...× En → structure e1 : structure→ E1 e2 : structure→ E2 . . .

en : structure→ En changer-e1 : E1 × structure→ structure changer-e2 : E2 × structure→ structure . . .

changer-en : En × structure→ structure

La sorte structure admet en paramètres les types de ses champs. La

fonction de construction admet les noms des champs en paramètres (Id est

l’ensemble des identificateurs possibles) et les valeurs d’initialisation. Les

opérations sont limitées aux accés en lecture ou en écriture aux champs.

47

De même que pour les tableaux, on pourra utiliser une notation plus

simple que la notation fonctionnelle et plus proche de celle des langages de

programmation.

– s = structure[E1, E2, . . . , En](id1 = v1, id2 = v2, . . . , idn = vn) pour

construire une structure s composée des champs de noms id1, id2, . . .,

idn admettant respectivement pour types E1, E2, . . ., En et intialisés

respectivement à v1, v2, ..., vn.

– s.idi pour ei(s), 1 ≤ i ≤ n

– s.idi = e pour changer-ei(e, s), 1 ≤ i ≤ n

Exercice : écrire les axiomes du type structure.

Durée : 1H30

3.3.4 Exemple : matrice rectangulaire

Signature

Sorte matrice

Paramètre élément

Utilise entier

Opérations

48

matrice : entier × entier × element→ matrice nblignes : matrice→ entier nbcolonnes : matrice→ entier changer : matrice× entier × entier × élément→ matrice ijième : matrice× entier × entier → élément

Axiomes

– nblignes(matrice(n, c, v)) = n.

– nbcolonnes(matrice(n, c, v)) = c.

– nblignes(changer(m, i, j, e)) = nblignes(m).

– nbcolonnes(changer(m, i, j, e)) = nbcolonnes(m).

– ijème(changer(m, i, j, e), i, j) = e, si 1 ≤ i ≤ nblignes(m) et 1 ≤ j ≤ nbcolonnes(m).

– ijème(changer(m, i, j, e), k, l) = ijème(m, k, l), si 1 ≤ i ≤ nblignes(m), 1 ≤ j ≤ nbcolonnes(m), 1 ≤ k ≤ nblignes(m) et 1 ≤ l ≤ nbcolonnes(m), et k 6= i l 6= j.

– ijème(matrice(n, c, v)) = v.

Implémentation au moyen de tableau

MATRICE[e](n,c,v)

retourner tableau[tableau](n, tableau[e](c,v))

49

NBLIGNES(m)

retourner m.taille

NBCOLONNES(m)

retourner m[1].taille

CHANGER(m,i,j,e)

m[i][j] = e

IJIEME(m,i,j)

retourner m[i][j]

3.4 Structures de données dynamiques

Un algorithme est souvent amené à traiter des ensembles de données. Or,

il est parfois impossible de prédire le nombre d’éléments survenant au mo-

ment de l’éxecution. Les structures de données dynamiques permettent de

représenter des ensembles ayant un nombre quelconque de données, inconnu

au moment de l’écriture de l’algorithme. Le terme dynamique fait référence

à l’éxecution, alors que le terme statique fait référence à l’écriture de l’algo-

rithme ou du programme.

Nous allons voir dans cette section deux sortes de structures dynamiques :

– Les structures contigues : tableaux infinis

50

– Les structures châınées : châınes simples ou doubles de noeuds.

Nous nous bornons dans cette section à l’étude des structures linéaires.

Avant d’introduire les types abstraits de ces structures, il est nécessaire

de clarifier la notion d’adresse et la façon dont nous allons l’utiliser.

3.4.1 Clarifications sur la notion d’adresse et sur l’af-

fectation

Références (présentation formelle)

Un algorithme admet un ensemble d’identificateurs auxquels sont associés

des types et des valeurs. Pour associer une valeur à un identificateur, le

langage C utilise la notion d’adresse en mémoire et propose un type de base

pointeur. D’autres langages (lisp, java) utilisent les références. C’est le modèle

sur lequel nous allons nous baser.

Soit Id l’ensemble des identificateurs d’un algorithme A, soit S l’ensemble

des types de ces identificateurs. Soit M l’ensemble des cases mémoire stockant

les valeurs associées aux identificateurs à un instant donné t0. On peut définir

M comme suit

M = {(i, d), i ∈ N, ∃T ∈ S|d ∈ T}

où chaque couple (i, d) représente une case mémoire numérotée i et contenant

la valeur d. Considérons une fonction de valuation à un l’instant t0, associant

à chaque identificateur une case mémoire :

v : Id→M

Nous allons préciser l’affectation au moyen de cette définition. Soient

51

id1 et id2 deux identificateurs de même type. Soit v la valuation courante,

telle que v(id1) = (i1, d1) et v(id2) = (i2, d2) et soit M l’ensemble des cases

mémoire. Considérons l’affectation suivante

id1 ← id2

Dans le cas où le type de id1 et id2 est primitif (entier, etc.), le nouvel

ensemble de cases mémoire M ′ est défini comme suit :

M ′ = {c ∈ M, c 6= (i1, d1)} ∪ {(i1, d2)}

et la nouvelle valuation

v′(id) =

v(id), ∀id ∈ Id, id 6= id1 (i1, d2), id = id1

Dans le cas où le type de id1 et id2 n’est pas primitif, l’ensemble des cases

mémoire est inchangé et la valuation v est remplacée par v ′ telle que

v′(id) =

v(id), ∀id ∈ Id, id 6= id1 v(id2), id = id1

Les deux identificateurs id1 et id2 référencent alors la même valeur.

FAIRE DES DESSINS

Références (présentation intuitive)

Variable (Rappel). Une variable permet de ranger une valeur en mémoire.

C’est une zone mémoire modifiable, identifiée par un nom (identificateur) et

caractérisée par un type.

Variable de type primitif. Elle contient une quantité numérique (codée

en base deux) dont l’interprétation dépend de son type.

52

Variable de type non primitif. Une variable dont le type n’est pas pri-

mitif ne contient pas directement la valeur qui lui est associée, mais l’adresse

de cette valeur. On dit qu’elle est une référence.

Référence

objet

Affectation.

Type primitif: lors de l’affectation d’une variable v1 par une variable v2,

de types primitifs, la valeur contenue dans la variable v1 est modifiée et

reçoit la valeur contenue dans la variable v2. Lors de l’affectation d’une

variable par une valeur, son contenu reçoit aussi la valeur affectée.

Type non primitif: lors de l’affectation d’une variable v1 par une variable

v2, de types non primitifs, l’adresse contenue dans la variable v1 est

modifiée et reçoit l’adresse contenue dans la variable v2. Les deux va-

riables v1 et v2 référencent alors la même valeur. Lors de l’affectation

d’une variable par une valeur, son contenu reçoit l’adresse de la valeur.

Remarques.

– En ce qui concerne le passage de variables en paramètres ou bien en

retour de fonction, le mécanisme est le même.

– On notera NULL la valeur de référence ne référençant pas de valeur.

53

3.4.2 Tableaux infinis

Il est parfois impossible de prédire une borne supérieure pour le nombre

d’éléments d’un tableau. (En particulier quand ce nombre dépend d’une

intéraction extérieure). Nous proposons un type abstrait correspondant à

un tableau qui s’étend en fonction des besoins de l’algorithme qui l’utilise.

Il est à noter que la complexité des opérations de ce type n’est plus

en O(1), comme pour les types abstraits présentés précédemment.

Signature

Sorte tableau-infini

Paramètre élément

Utilise entier

Opérations ∣

tableau-infini : element → tableau-infini changer-ième : tableau-infini× entier × élément→ tableau-infini ième : tableau-infini× élement→ entier

Axiomes

– Pour 1 ≤ i et 1 ≤ j

ième(changer-ième(t, i, e), j) =

e, si i = j

ième(t, j) si i 6= j

– ième(tableau-infini(v)) = v

54

Remarque. Nous ne nous intéressons ici qu’aux dépassements provoqués

par des indices trop grands.

Implémentation avec des tableaux fixes

La construction initiale d’un tableau infini s’implémente par la construc-

tion d’un tableau t de taille fixe UNITE. Ensuite, à chaque fois qu’un accès

en lecture ou bien en écriture se produit à un indice i dépassant la taille du

tableau, un nouveau tableau t′ de taille suffisante est construit. Après avoir

recopié t dans t′, on peut accéder au ième élément.

Remarque. Un tableau de taille fixe peut changer d’adresse après un allon-

gement (donc après un accés par les fonctions changerième et ième). Notre

tableau infini pourrait changer d’adresse après la fonction changerième, puis-

qu’elle renvoie le tableau en résultat. Mais la fonction ième, quant à elle, ren-

voie un résultat entier. Il est donc nécessaire de fournir une implémentation

qui ne change pas l’adresse du tableau infini. Pour cela, on peut utiliser une

structure ayant pour champ un tableau fixe.

Allongement. L’algorithme d’extension du tableau que nous proposons ici

est le suivant.

t.taille + k × UNITE avec t.taille × (k − 1)UNITE < i ≤ t.taille + k × UNITE

Cet algorithme est simple et robuste, mais il est inefficace. Si UNITÉ

est la taille d’un élément du tableau, lors de l’ajout du i-ème élément, il

faut recopier i − 1 éléments en plus de l’affectation du nouvel élément. Par

55

conséquent, l’ordre de grandeur de la complexité dans le pire des cas de

l’affectation d’un élement n est en θ(n), puisqu’elle consiste à affecter n

éléments dans le tableau. L’ordre de grandeur de l’initialisation d’un tableau

en partant du premier élément jusqu’au dernier est en θ(n2). Si UNITÉ est

égal à la taille de k mots mémoire, cette complexité est simplement divisée

par k. On verra en TD des algorithmes plus efficaces.

TABLEAU-INFINI[e]()

retourner structure[tableau[e](UNITE)](tab)

CHANGERIEME(t,i,e)

taille ←t.tableau.taille % Si l’indice depasse la taille

si taille < i

% Construction d’un tableau suffisamment grand

% taille * (k-1) UNITE < i ≤ taille + k*UNITE alors t’ ←tableau[e](taille + k * UNITE)

% Copie du tableau initial dans le nouveau tableau

pour j ←1 a taille faire t’[j] ←t.tableau[j] % Affectation du nouveau tableau

t.tableau ←t’ % Affectation du ieme element

t.tableau[i] ←e

56

retourner t

IEME(t,i)

taille ←t.tableau.taille si taille < i

alors t’ ←tableau[e](taille + k * UNITE) pour j ←1 a taille faire t’[j] ←t.tableau[j] t.tableau ←t’

retourner t.tableau[i]

Remarques.

– On pourra discuter de la possibilité d’initialiser un tableau infini au

moment de sa construction.

– En pratique, il faudrait que le rallongement se produise rarement afin

que la complexité en moyenne soit proche de O(1).

– Le type abstrait tableau infini ne doit être utilisé que dans le cas où

les éléments doivent être contigus. Il faut remarquer que l’accés direct

n’est plus en 0(1) dans tous les cas. Il est parfois préférable, en ce qui

concerne la complexité d’utiliser des structures châınées (voir section

suivante) qui ne nécessitent pas la recopie.

Durée : 1H30??

57

3.4.3 Structures châınées

Une structure châınée est un type structure ayant au moins un champ de

type structure. Dans le cas où une structure S a des champs de type S, on a

une définition récursive. On dit qu’une structure est simplement châınée si elle

comporte un champ de type S (habituellement nommé suivant) et qu’elle est

doublement châınée si elle comporte deux champs de type S (habituellement

nommés suivant et précédant.

FAIRE UN DESSIN

Une telle structure est utilisée comme “brique de base” pour châıner des

éléments entre eux. Elle est communément appelée noeud.

Signature du noeud simple

Un noeud simple est une structure particulière.

Sorte noeud

Paramètre élément

Utilise noeud et booléen

Opérations ∣

noeud : élément× noeud→ noeud contenu : noeud→ élément changer-contenu : noeud× élément→ noeud suivant : noeud→ noeud changer-suivant : noeud× noeud→ noeud suivant? : noeud→ booléen

58

Axiomes du noeud

– suivant(noeud(e,s)) = s

– contenu(noeud(e,s)) = e

– suivant(changer-suivant(n,s)) = s

– contenu(changer-contenu(n,e)) = e

– suivant(changer-contenu(n,e)) = suivant(n)

– contenu(changer-suivant(n,s)) = contenu(n)

– suivant?(n) = faux si suivant(n) = NULL

– suivant?(n) = vrai si suivant(n) 6= NULL

Implémentation du noeud

Le noeud s’implémente de façon triviale au moyen d’une structure.

NOEUD[E](e,n)

s ←structure[E,noeud](contenu=e, suivant=n) s.contenu ←e s.suivant ←n retourner s

CONTENU(n)

retourner n.contenu

CHANGER-CONTENU(n,e)

n.contenu ←e

59

retourner n

SUIVANT(n)

retourner n.suivant

CHANGER-SUIVANT(n1,n2)

n1.suivant ←n2 retourner n1

SUIVANT?(n)

retourner n.suivant = NULL

3.5 La pile

La pile est une structure de donnée très simple et très utile en informa-

tique. Elle permet de stocker des éléments par ordre d’arrivée et de les faire

sortir dans l’ordre inverse : le dernier arrivé est le premier sorti. (Penser à

une pile de crêpes). Beaucoup d’auteurs utilisent le sigle anglosaxon LIFO

(signifiant Last In First Out) pour y qualifier cette structure.

3.5.1 Signature du type abstrait pile

Les opérations de base sur la pile sont :

– Tester si une pile est vide.

60

– Accéder à la valeur au sommet de la pile.

– Empiler un élément : ajouter un élément au sommet de la pile.

– Dépiler un élément : enlever l’élément qui se trouve au sommet de la

pile.

La signature du type abstrait pile est donc la suivante.

Sorte pile

Paramètre élément

Utilise booléen

Opérations ∣

pile :→ pile empiler : pile× élément→ pile dépiler : pile→ pile sommet : pile→ élément vide? : pile→ booléen

Les opérations empiler, dépiler et pile sont des opérations internes, alors

que les opérations sommet et vide? sont des observateurs de pile.

3.5.2 Axiomes du type abstrait pile

– Les opérations sommet et dépiler ne sont définies que pour des piles

non vides.

– sommet(empiler(p,e)) = e

– dépiler(empiler(p,e)) = p

61

– vide?(pile()) = vrai

– vide?(empiler(p,e)) = faux

3.5.3 Implémentation d’une pile avec une structure et

un tableau

Nous allons représenter une pile par un couple composé d’un tableau

stockant les éléments de la pile et un entier stockant l’indice dans le tableau du

sommet de la pile. Un tel couple peut donc être représenté par une structure

à deux champs

– pile : tableau de MAX éléments. Discuter du problème de définition de

MAX et du problème de son dépassement. Un tableau infini serait ici

préférable.

– sommet : un entier pointant un élément dans le tableau.

Selon cette représentation, pile vide est repérée par le fait que son sommet

est nul.

PILE[e]()

p =structure[tableau[e](MAX),entier](pile,sommet)

p.sommet = 0

retourner p

EMPILER(p,e)

% La specification formelle du type n’est pas respectee ici

si p = MAX

62

alors erreur

sinon p.sommet = p.sommet + 1

p.pile[p.sommet] = e

retourner p

DEPILER(p)

si vide?(p)

alors erreur

sinon p.sommet = p.sommet - 1

retourner p

SOMMET(p)

si vide?(p)

alors erreur

sinon retourner p.pile[p.sommet]

VIDE?(p)

retourner p.sommet=0

3.5.4 Exemple : évaluation d’une expression postfixée

On représente un expression postfixée par un tableau d’entiers e dont les

éléments sont soit des entiers soit des opérateurs binaires. On suppose que

63

l’on dispose d’une fonction permettant de différencier les deux cas.

EVAL(e)

p ←pile[entier](e.taille) pour i ←1 a e.taille faire si e[i] est un entier

alors p ←empiler(p,e[i]) sinon a1 ←sommet(p)

p ←depiler(p) a2 ←sommet(p) p ←depiler(p) r ←e[i] applique a a1 et a2 empiler r

retourner sommet(p)

Exemple. 6 5 4 + 2 * -

Durée : 1H30

3.6 La file

Dans le cas d’une file, on fait des ajouts à une extrémité et les accés et les

suppressions par l’autre extrémité. On a une structure FIFO “First In First

Out” pour “premier entré premier sorti”.

64

3.6.1 Signature du type abstrait file

Sorte file

Paramètre élément

Utilise booléen

Opérations ∣

file :→ file ajouter : file× élément→ file retirer : file→ file premier : file→ élément vide? : file→ booléen

Les opérations ajouter, retirer et file sont des opérations internes, alors

que les opérations premier et vide? sont des observateurs de file.

3.6.2 Axiomes du type abstrait file

– Les opérations premier et retirer ne sont définies que pour des files non

vides.

– vide?(file()) = vrai

– vide?(ajouter(f,e)) = faux

– vide?(f) = vrai ⇒ premier(ajouter(f,e)) = e

– vide?(f) = faux ⇒ premier(ajouter(f,e)) = premier(f)

– vide?(f) = vrai ⇒ vide?(retirer(ajouter(f,e))) = vrai

– vide?(f) = faux ⇒ retirer(ajouter(f,e)) = ajouter(retirer(f),e)

65

3.6.3 Implémentation d’une file par un tableau circu-

laire

3.7 Les listes

La liste n’est pas un type abstrait aussi simple et bien défini que la pile ou

la file. Il est impossible de proposer une définition précise. Le terme de liste est

souvent associé à un type dans lequel les éléments sont rangés successivement

et dans lequel il est possible d’insérer et de supprimer n’importe où. Mais une

telle propriété ne suffit pas à définir un type car plusieurs signatures peuvent

y correspondre.

Une référence standard, car elle est reconnue de fait, est la structure de

listes du langage lisp. Elle correspond à la structure des données princi-

pale du langage ainsi qu’à la structure des programmes. Pourtant, ces listes

n’ont pas une signature offrant la possibilité d’insérer et de supprimer n’im-

porte quel élément. Bien au contraire, elle fournissent une signature minimale

et optimale en terme de complexité permettant d’implémenter toute sorte

d’opérations sur les ensembles dont la taille est inconnue.

Certains auteurs utilisent le terme de listes châınées. Ils font alors référence

à une implémentation de liste au moyen de structures châınées et non à un

type abstrait.

Dans cette section, nous présentons d’abord la référence lisp (qui corres-

pond en fait exactement à la pile). Puis, nous présentons une signature assez

classique, que l’on trouve souvent dans la littérature, fournissant des fonc-

tions de suppression et d’insertion n’importe où dans la liste. Nous mettons

ensuite en évidence les défauts de cette signature et proposons une meilleure

66

signature (en terme de complexité) utilisant un curseur.

3.7.1 La liste récursive

Définition

Une liste récursive l est

– soit la liste vide

– soit l = {e, l′} où e est le premier élément de l, aussi appelé tête ou car(l) et l′ est le reste de la liste, obtenu quand on enlève e. Alors, l′

est aussi une liste récursive.

3.7.2 Signature

Appelons listelisp la sorte liste récursive. Nous utilisons ici les noms des

accesseurs lisp.

Sorte listelisp

Paramètre élément

Utilise booléen

Opérations ∣

listelisp :→ listelisp car : listelisp→ élément cdr : listelisp→ listelisp cons : listelisp × élément→ listelisp listelisp − vide? : listelisp→ booléen

67

Remarque. Le type abstrait liste récursive est le même, à un renommage

des noms de fonctions près, que le type pile.

3.7.3 Axiomes

Voir la pile.

3.7.4 Implémentation

Une liste récursive peut aussi s’implémenter au moyen d’une structure

châınée ou d’un tableau.

LISTELISP[E]() retourner NULL

CONS(l,e)

retourner noeud[E](e,l)

CAR(l)

si vide?(p)

alors erreur

sinon retourner l.contenu

CDR(l)

si vide?(p)

alors erreur

sinon retourner l.suivant

68

LISTELISP-VIDE(l)

retourner l=NULL

3.7.5 Listes avec insertion et suppression

Première signature

Nous proposons une signature assez répandue dans les manuels d’ASD.

La liste est une suite d’éléments ayant chacun un rang. On peut insérer

un élement en fournissant le rang qui sera le sien après insertion. On peut

supprimer un élement en indiquant son rang. De plus une fonction ieme

permet de connâıtre la valeur de l’élément d’un certain rang.

Sorte liste

Paramètre élément

Utilise booléen

Opérations ∣

liste :→ liste ieme : liste× entier → élément inserer : liste× entier × element→ liste supprimer : liste× entier → liste liste− vide? : liste→ booléen

69

commentaires (0)
Aucun commentaire n'a été pas fait
Écrire ton premier commentaire
Ceci c'est un aperçu avant impression
Chercher dans l'extrait du document
Docsity n'est pas optimisée pour le navigateur que vous utilisez. Passez à Google Chrome, Firefox, Internet Explorer ou Safari 9+! Téléchargez Google Chrome