0% ont trouvé ce document utile (0 vote)
69 vues81 pages

Algorithmique Cours

Ce document traite de la notion d'algorithme et de programmation. Il présente les concepts fondamentaux de l'informatique comme la formalisation et la modélisation de l'information ainsi que l'architecture des systèmes de traitement automatique de l'information. Il décrit ensuite ce qu'est un algorithme et comment les algorithmes sont exprimés sous forme de programmes à l'aide de langages de programmation.

Transféré par

Maryem Aitogni
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
69 vues81 pages

Algorithmique Cours

Ce document traite de la notion d'algorithme et de programmation. Il présente les concepts fondamentaux de l'informatique comme la formalisation et la modélisation de l'information ainsi que l'architecture des systèmes de traitement automatique de l'information. Il décrit ensuite ce qu'est un algorithme et comment les algorithmes sont exprimés sous forme de programmes à l'aide de langages de programmation.

Transféré par

Maryem Aitogni
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Algorithmique

Introduction

. Qu’est­ce que l’informatique ?


I
Dans un premier temps et pour résumer, on pourra dire que l’informatique
consiste en un traitement automatique de l’information.
I
I. Les concepts fondamentaux :
1er concept :
Pour pouvoir être traitée automatiquement, l’information doit être formalisée,
modélisée sous forme d’un ensemble structuré de relations
logicomathématiques : « formalisation et modélisation de l’information ».
2ème concept :
Il a trait au « système » qui va effectuer automatiquement le traitement de
l’information. Ce concept est celui « d’architecture des systèmes de
traitement automatique de l’information ».

I
I. Les concepts opérationnels :
I
A partir de ces deux concepts fondamentaux, ont été élaborés d’autres concepts
dits opérationnels tels que :
➢ Algorithme
➢ Programme
➢ Structure de données
Le concept de « formalisation et modélisation de l’information » mène tout droit
au concept d’algorithme.
« L’architecture des systèmes de traitement automatique de l’information » nous
amène au concept de programme.
Nous avons une information à traiter. Celle­ci est donc une donnée. Afin de
permettre au système de traiter cette donnée, il est donc nécessaire de lui donner
une structure adaptée.

1
Algorithmique

Ordinateurs, algorithmes et programmes

. Introduction
I
La révolution industrielle fut caractérisée essentiellement par l’augmentation de la
puissance physique de l’homme. (Le simple fait d’appuyer sur un bouton
permettait à une machine d’imprimer une forme sur une feuille de métal).
On peut dire par analogie que la révolution informatique est « l’accroissement
des capacités mentales de l’homme ». (Le simple fait d’appuyer sur un bouton
permet à une machine d’effectuer des calculs compliqués, de prendre des
décisions complexes, de stocker en mémoire et de retrouver de grandes quantités
d’information.
Qu’est donc un ordinateur ?
Un premier élément de réponse consiste à dire qu’un ordinateur est une machine
qui peut effectuer des tâches intellectuelles de routine en exécutant très
rapidement des opérations simples.
Naturellement un ordinateur ne peut accomplir que les tâches spécifiées dans les
termes des opérations simples qu’il est capable de réaliser.
On doit indiquer à l’ordinateur quelles opérations il doit accomplir.
Ceci nous amène à décrire brièvement la structure d’un ordinateur.
FAIRE DESCRIPTION SUR UN PC
Nous devons donc décrire à l’ordinateur comment la tâche que nous voulons
qu’il exécute doit être menée à bien.

V. Algorithmes
I
La capacité de l’homme à résoudre des problèmes repose pour une part sur la
connaissance et l’utilisation de méthodes de calcul permettant l’obtention d’un
résultat cherché.
Par exemple :
Nous connaissons une méthode qui, étant donnés deux nombres entiers, nous
permet d’en calculer la somme. L’utilisation d’un tel procédé ne suppose ni que
nous en comprenions la justification ni que nous ayons une connaissance
particulière de la théorie des nombres.
La connaissance de telles méthodes est potentiellement très riche (la méthode
d’addition permet de faire la somme de n’importe quel couple d’entiers) mais

2
Algorithmique

l’activité qui résulte de leur utilisation est par contre très pauvre ; elle ne suppose
ni compréhension ni apport créatif. Elle est répétitive, vite fastidieuse. On peut
souvent pour ce type d’activité substituer la machine à l’homme et obtenir sans
fatigue un résultat plus fiable.
Pour qu’une telle substitution soit possible il faut que le travail à effectuer soit
clairement et précisément défini.
La formulation précise d’un travail à effectuer passe par sa décomposition à un
niveau plus élémentaire.
La liste des actes ou opérations élémentaires à exécuter dans un enchaînement
strictement défini s’appelle un algorithme.
La notion d’algorithme n’est pas spécifique à l’informatique. Le concept
d’algorithme est à rapprocher de ceux de recette, méthode, procédure... .
Un algorithme décrit une succession d’opérations qui, si elles sont fidèlement
exécutées, produiront le résultat ou processus désiré.
Prenons par exemple une recette de cuisine :
Faire chauffer ¾ l de lait ;
Faire fondre 200g de chocolat dans le lait ;
Faire fondre 50g de beurre dans une casserole ;
Ajouter 35g de farine au beurre fondu et y verser petit à petit le lait
chocolaté ;
Faire cuire pendant 10 minutes en tournant ;
Servir froid.
Cette recette est l’algorithme d’un certain travail et pour exécuter ce travail il
n’est pas nécessaire de savoir qu’il consiste à faire une béchamel au chocolat.
Notons qu’ainsi formulée cette recette est communicable et ne relève plus de la
seule compétence de son inventeur.
C’est plus particulièrement à des algorithmes liés à des questions mathématiques
que nous nous intéresserons mais l’importance de la communicabilité des
méthodes est tout aussi fondamentale.
V. Programmes et langages de programmation
La mise au point d’un algorithme est une activité qui se rapproche de l’activité de
recherche et de création mathématique : partant d’un état initial et un but à
atteindre il s’agit de déterminer et d’organiser le travail à effectuer pour atteindre
ce but.
Mais la découverte d’un algorithme n’est pas une finalité en elle­même. On doit
3
Algorithmique

pouvoir le communiquer et c’est une partie importante du travail d’étude que de


parvenir à faire la description d’un algorithme.
Cette description peut se faire dans le langage « ordinaire » (c’est le cas pour
une recette de cuisine) mais on est amené très vite à préciser ce langage et à
introduire un certain nombre de conventions d’écriture destinées à en faciliter une
interprétation claire et sans ambiguïté.
Nous avons vu qu’un « processeur » effectue un traitement selon un algorithme
approprié. Par exemple, le cuisinier doit suivre une recette, le pianiste une
partition et ainsi de suite. Dans chaque cas, l’algorithme doit être énoncé d’une
manière telle que le « processeur » puisse le comprendre et en exécuter les
instructions. On dit que le « processeur » doit être capable d’interpréter
l’algorithme, ce qui signifie qu’il doit être capable de :
a)comprendre ce que chaque étape signifie et
b)faire l’opération correspondante.
Lorsque le processeur est un ordinateur, l’algorithme doit être exprimé sous la
forme d’un programme. Un programme est écrit dans un langage de
programmation, et l’activité qui consiste à écrire un algorithme sous la forme
d’un programme est appelée la programmation.
Chaque étape de l’algorithme est exprimée par une instruction du programme.
Définition :
Un programme consiste donc en une séquence d’instructions, chacune d’entre
elles spécifiant les opérations que l’ordinateur doit faire.
La nature des instructions d’un programme dépend du langage de
programmation utilisé. Il existe de nombreux langages de programmation et
chaque langage comporte son propre répertoire d’instructions.
Les langages les plus élémentaires, appelés langages machine, sont conçus de
manière que chaque instruction puisse être directement interprétée par
l’ordinateur. Cependant, puisque les instructions sont si élémentaires (par ex,
additionner deux nombres), chacune d’elle exprime uniquement une infime partie
d’un algorithme. Il faudra donc un grand nombre d’instructions pour exprimer la
plupart des algorithmes, et de ce fait la programmation en langage machine est
fastidieuse.
Pour rendre la programmation plus facile, d’autres types de langages ont été
développés. Ces langages évolués sont plus pratiques que les langages machine
en ceci que chaque instruction peut exprimer une étape plus importante d’un
algorithme. Naturellement, chaque énoncé devra être réinterprété par l’ordinateur
et il faudra que les capacités de l’Unité Centrale soient augmentées en
conséquence.
4
Algorithmique

Donc les programmes évolués sont traduits en langage machine avant leur
exécution. La traduction consiste à transformer chaque instruction du
programme évolué en une séquence équivalente d’instructions en langage
machine. Cette traduction est exécutée par l’ordinateur. Le programme qui
exécute cette tâche est un traducteur de langage.

Etapes d’exécution d’un algorithme par un ordinateur.

5
Algorithmique

Conception des algorithmes

[Link] aux langages de programmation :


Un algorithme ne peut être exécuté que s’il est exprimé sous la forme que le
processeur concerné peut comprendre. Dans les exemples que nous avons vu les
algorithmes sont exprimés sous une forme adaptée à la compréhension humaine.
Et il serait intéressant que les ordinateurs puissent comprendre l’une de ces
formes d’expression (anglais, français ou arabe …). Malheureusement, les
ordinateurs ne sont pas actuellement en mesure de le faire, et ce pour les raisons
suivantes :
1) Les langues ont un vocabulaire considérable et des règles grammaticales
(
complexes. Pour qu’un ordinateur puisse analyser des phrases, des algorithmes
appropriés lui seraient nécessaires. Mais la procédure d’analyse est si complexe
et si mal comprise que de tels algorithmes n’ont pas encore été inventés, sauf
pour des sous ensembles restreints de la langue (ex : l’analyseur grammatical du
traitement de texte Word fait souvent des erreurs).
2) L’interprétation d’une phrase dépend non seulement d’une analyse grammaticale
(
mais encore du contexte dans lequel la phrase apparaît. De nombreux mots ont
plusieurs significations qui peuvent être interprétées uniquement par le contexte.
Ex : l’expression « une drôle de situation » est ambiguë tant que la
signification du mot « drôle » (curiosité ou humour) n’est pas révélée par
le contexte sémantique créé par la phrase.
L’utilisation métaphorique et autres jeux de langages rendent également
difficile l’interprétation d’une langue.
Ex : Cueillons les roses de la vie.
Puisque l’anglais ou le français ou l’arabe sont trop complexes pour être
compris par les ordinateurs, les algorithmes exécutables doivent être
écrits plus simplement.
Par analogie avec les langages naturels, chaque langage de programmation
a son propre vocabulaire et ses propres règles grammaticales, qui dictent
les conditions dans lesquelles le vocabulaire peut être utilisé.
Ex : une des étapes d’un algorithme de comptabilité s’exprime de la
manière suivante :
MULTIPLIER prix PAR quantité DONNE coût
et en Pascal :
Coût := prix * quantité

6
Algorithmique

Les deux formes d’expression signifient la même chose :


Une instruction est donnée pour multiplier un nombre appelé prix par un
nombre appelé quantité afin d’obtenir un nombre appelé coût.

[Link] et sémantique :
Comprendre l’écriture même de l’algorithme se décompose en deux étapes.
En premier lieu le processeur doit être capable de reconnaître et de
comprendre les symboles par lesquels est exprimé l’algorithme (mots français
ou anglais, abréviations, symboles mathématiques, notes dans une partition
musicale).
L’ensemble des règles grammaticales qui régissent la manière dont les
symboles dans un langage doivent être utilisés est appelé la syntaxe du
langage.
Un programme qui respecte la syntaxe du langage dans lequel il est écrit est
dit syntaxiquement correct. Tout écart de syntaxe dans le langage est appelé
une erreur de syntaxe.
Ex : cuisson les aromates
ou
a+=b
La deuxième étape de la compréhension de l’écriture d’un algorithme consiste
à donner une signification à chaque énoncé en termes d’opérations que le
processeur doit effectuer.
Ex : maille à l’envers
indique comment manipuler les aiguilles à tricoter et la laine
coût := prix * quantité
signifie que deux nombres prix et quantité doivent être multipliés pour
donner un troisième nombre appelé coût.
La signification de formes données d’expressions dans un langage est appelée la
sémantique du langage.
Les langages de programmation sont conçus de telle sorte que leur syntaxe et
leur sémantique soient relativement simples et qu’un programme puisse être
analysé du point de vue de la syntaxe sans référence sémantique.
Dans les langages naturels, la syntaxe et la sémantique sont très complexes et
souvent corrélatives. Il est possible d’écrire des phrases à la syntaxe correcte
mais pourtant dépourvues de signification.
Ex : (dû à Noam Chomsky)
7
Algorithmique

Les idées vertes incolores dorment furieusement


De même un énoncé dans un algorithme peut être syntaxiquement correct
mais dépourvu de signification
Afficher le nom du 13ème mois de l’année
Outre les erreurs de syntaxe et de sémantique, il y a une troisième catégorie
d’erreurs. Il s’agit de l’erreur logique. Un programme peut ne pas décrire d’une
manière adéquate le traitement désiré.
Ex : considérons l’algorithme suivant qui consiste à calculer la
circonférence d’un cercle :
Calculer la circonférence en multipliant le rayon par
Cet algorithme est syntaxiquement et sémantiquement correct, mais le
résultat produit sera faux (car on a oublié de multiplier le rayon par 2).
3.Mémoire, registre, variable, constante, affectation, les entrées, les
sorties :
« Mémoire : faculté de conserver l’information, mais aussi de la retrouver et d’en
permettre une nouvelle exploitation. »
Il est rare que le résultat cherché puisse s’obtenir par un calcul direct ; l’étape
finale du calcul ou du raisonnement fait le plus souvent intervenir un certain
nombre de résultats intermédiaires établis antérieurement et mémorisés à cette
fin ; d’où l’intérêt de disposer d’une machine permettant le stockage de valeurs
numériques.
Sans rentrer dans le détail de son organisation, disons que la mémoire d’une
machine est constituée par un ensemble de registres dans chacun desquels on
pourra ranger une valeur puis lire cette valeur.
Un registre se conçoit bien en l’imaginant comme une case dans laquelle est
inscrite une valeur et une seule à la fois. On peut écrire dans cette case une
nouvelle valeur provoquant par là l’effacement du contenu précédent ; on peut
lire le contenu de cette case.
Chaque registre est repéré par un numéro ou adresse, mais plutôt que d’utiliser
cette adresse il est plus commode de donner à chacun des registres utilisés un
nom symbolique.
Dans la description des algorithmes la référence aux registres se fait donc par
l’intermédiaire de ces noms symboliques que l’on appelle des variables. A
chaque variable rencontrée est associé un registre et le contenu de ce registre à
un instant donné est appelé la valeur de la variable à cet instant.
Définition :

8
Algorithmique

Une variable est une zone mémoire dans laquelle on peut mémoriser de façon
temporaire une valeur pour une exploitation ultérieure. Son contenu peut varier au
cours de l’exécution du programme.
Notion de constante :
Une constante est une zone mémoire désignée par une adresse. La valeur de la
constante ne change jamais.
L’affectation :
Une première instruction importante est l’affectation d’une valeur à une variable,
c’est­à­dire le rangement d’une valeur dans le registre correspondant. Cette
instruction est notée par : ← ou :=
Ex : A ← 5 signifie affecter la valeur numérique 5 à la variable A c’est­à­dire
ranger cette valeur dans le registre nommé A.
L’affectation ne doit pas être confondue avec l’identité mathématique, c’est une
instruction qui est exécutée à un instant déterminé et cette notion de chronologie
est fondamentale pour bien comprendre le sens d’instructions plus complexes.
L’utilisation des valeurs ainsi mémorisées se fait en « rappelant » le contenu des
registres c’est­à­dire la valeur des variables.
Ex : A ← 2
B ← 2*A+1
Après exécution de ces deux instructions la valeur de B est égale à 5.
La notation A est une référence implicite à la valeur de A.
Les entrées, sorties :
Si l’on fait des calculs c’est surtout pour en connaître les résultats.
Le problème de l’accès aux résultats se conçoit bien dès l’instant que l’on
suppose que le programme est exécuté par un système qui nous est extérieur,
soit quelqu’un d’autre, soit une machine.
Une machine peut être considérée comme un espace clos ; nous sommes à
l’extérieur. Nous pouvons lui donner un travail à effectuer (ou programme), le lui
faire exécuter, mais nous ne nous contenterons pas de savoir qu’elle a effectué
ce travail et que quelque part, dans un de ses registres, se trouve inscrit le résultat
que nous cherchons. Il faut que, ce résultat, elle nous le communique à nous qui
sommes à l’extérieur, et cela ne se fera pas spontanément, mais uniquement si
nous lui en avons donné l’instruction.
La communication de l’intérieur d’un système vers l’extérieur s’appelle la
« sortie ».

9
Algorithmique

Nous traduisons cette instruction de sortir un résultat par écrire X qui signifie
écrire (imprimer, afficher sur écran, …) la valeur de la variable X.
La communication de l’extérieur vers l’intérieur s’appelle « l’entrée ».
Nous la traduisons par l’instruction entrer X qui signifie attendre qu’une valeur
soit donnée puis affecter cette valeur à la variable X.

. Les expressions arithmétiques :


VI
Les expressions arithmétiques s’écrivent en combinant des valeurs numériques,
des variables, des opérateurs et des fonctions avec les règles d’usage de la
notation scientifique.
Par exemple :
4+A+3*B
Une telle écriture nous est familière mais il convient de préciser la signification
exacte que nous lui donnons.
Une expression arithmétique est une instruction qui signifie calculer la valeur de
l’expression obtenue en remplaçant chaque variable par sa valeur, et ce avec les
règles d’usage.
Exemple :
Supposons que la valeur de A est 5 et celle de B est 3,
4 + A + 3 * B signifie calculer 4 + 5 + 3 * 3, le résultat est 18.
L’évaluation d’une expression s’accompagne souvent d’une affectation sous la
forme générale
variable ← expression
qui signifie évaluer l’expression puis affecter le résultat à la variable.
Ex :
Dans une suite d’instructions :
A ← 3
B ← 2
C ← 3*A+2
D ← C+2*B
Après exécution de cette séquence les valeurs respectives des variables A, B, C,
D sont 3, 2, 11, 15.
Evaluer l’expression d’abord puis affecter le résultat à la variable.
Ex :
10
Algorithmique

A ← 1 Affecter 1 à A
A ← A+2 évaluer A + 2 (ça fait 3) et affecter le résultat à A.
L’instruction A ← A + 2 peut étonner, elle ne doit pas surprendre :
L’affectation ← n’est pas l’identité mathématique.
Les opérateurs autorisés pour l’écriture d’une expression arithmétique sont a
priori l’addition +, la soustraction ­, la division /, la multiplication * et le
parenthésage. Nous pourrons y adjoindre les fonctions qu’il est possible de
calculer, en donnant au mot possible un sens concret et précis : ou bien la
machine dont nous disposons « sait » calculer cette fonction, ou bien nous
avons construit un algorithme qui permet de calculer cette fonction.
VI
I. Enchaînement des instructions (la séquence):
L’algorithme de préparation d’une tasse de café est très évident puisqu’il
comprend des étapes simples devant être parcourues l’une après l’autre. Nous
disons qu’un tel algorithme est une séquence d’étapes.
1) les étapes sont traitées une à une
(
2) chaque étape n’est traitée qu’une fois, aucune n’est répétée, aucune n’est omise.
(
3) l’ordre dans lequel les étapes sont parcourues est le même que celui dans lequel
(
elles ont été écrites.
4) Terminer la dernière étape implique de finir l’algorithme.
(
Dans une suite d’instructions chacune est exécutée après l’autre (et non
simultanément) dans un ordre précis. Bien que cette question n’ait pas été
explicitement abordée dans les exemples précédents nous avons adopté un ordre
implicite qui est celui dans lequel ces instructions sont écrites.
Le fait que deux instructions ne sont pas exécutées simultanément doit être clair.
Considérons par exemple l’échange de deux valeurs A et B (échange des
contenus des registres A et B) ni la figure 1.a ni la figure 1.b ne décrivent un tel
échange.
A ← B B ← A
B ← A A ← B
Figure 1.a figure 1.b
En effet, si nous supposons initialement que les valeurs de A et B sont
respectivement 2 et 3;
A B A B
2 3 2 3

11
Algorithmique

A ← B 3 3 B ← A 2 2
B ← A 3 3 A ← B 2 2
Figure 2.a figure 2.b

L’échange des valeurs est cependant possible en ayant recours à une variable
intermédiaire C ainsi que l’illustre la figure 3.

A B C
2 3 ­
C ← A 2 3 2
A ← B 3 3 2
B ← C 3 2 2
Figure 3.
Pour mieux mettre en évidence l’ordre dans lequel doivent être exécutées les
instructions nous pouvons les numéroter (pas nécessairement avec les entiers
successifs) et les écrire dans l’ordre croissant de leur numérotation.
Ex :
10 A ← 1
20 B ← 2
30 C ← A+B
Il y a redondance de l’information puisque nous imposons que l’ordre de
numérotation soit également l’ordre d’écriture.
La règle étant énoncée nous pouvons nous donner le moyen de la transgresser en
introduisant l’instruction
Aller à « numéro »
qui a pour effet de rompre l’enchaînement implicite des instructions (rupture de
séquence) et de poursuivre l’exécution à partir de l’instruction portant le numéro
indiqué.
10 aller à 40
20 B ← 2
30 Aller à 60
40 A ← 1
50 Aller à 20
12
Algorithmique

60 C ← A+B
Vérifier que nous obtenons ainsi le même résultat que dans l’exemple précédent.
Il est bon de savoir quand arrêter l’exécution. Jusqu’à présent c’était
implicitement, que faute d’instruction supplémentaire, nous abandonnions le
travail. Nous pouvons le dire explicitement au moyen de l’instruction : stop.
Ex :
10 aller à 60
20 C ← A+B
30 stop
40 B ← 2
50 Aller à 20
60 A ← 1
70 aller à 40
L’introduction de nouvelles conventions n’a pas pour unique effet de
« compliquer » des choses apparemment simples mais surtout d’ouvrir la voie à
des possibilités nouvelles.

VI
I. Le test (ou la sélection):
I
Nous avons vu que lorsqu’un algorithme n’a qu’une structure séquentielle il n’est
pas possible d’en modifier le traitement selon les circonstances. Ainsi le robot
domestique ne peut faire face au problème que pose un pot à café vide. Ce qui
serait utile serait que le robot soit capable d’exécuter une étape du type :
Prendre un nouveau pot dans le placard
Si le pot en service est vide, et de sauter cette étape dans le cas contraire. Une
telle capacité est appelée sélection.
On peut donc affiner l’étape 2 de l’algorithme ainsi :
2.1 Prendre le pot à café sur l’étagère
2.2 Si le pot est vide
Alors prendre un nouveau pot dans le placard
2.3 Enlever le couvercle du pot

L’étape cruciale est l’étape 2.2 qui exprime à la fois l’étape devant être
sélectionnée (prendre un nouveau pot dans le placard) et la condition (pot vide)

13
Algorithmique

dont dépend la sélection.


Forme générale :
Si condition
Alors étape
où « condition » détermine la circonstance dans laquelle l’étape doit être
accomplie. Si la condition est vraie, alors l’étape doit être exécutée ; dans le cas
contraire elle ne doit pas l’être. Le processeur doit être en mesure d’interpréter
les conditions qui figurent dans un algorithme de la même manière qu’il est
capable d’interpréter les étapes.
Dans cet exemple, la sélection est utilisée pour déterminer si une étape donnée
doit être exécutée ou non.
Une extension de cette forme de sélection est une forme qui détermine laquelle
des deux étapes possibles doit être accomplie.
Ex :
Supposons que X et Y soient deux variables et que l’on veuille affecter à une
troisième variable Z la valeur absolue de X – Y.
Selon les valeurs respectives de X et Y nous devons affecter Y – X ou bien
X – Y.
L’algorithme correspondant pourrait être :
Si X <= Y
alors Z ← Y–X
sinon Z ← X­Y
Cela est un cas particulier de sélection en deux étapes.
La forme générale est :
Si condition
alors étape 1
sinon étape 2
où « condition » détermine laquelle des étapes doit être parcourue.
Il est évident que la première forme si … alors de sélection n’est qu’une
partie de la forme si … alors … sinon puisque :
si condition si condition
alors étape alors étape
sinon ne rien faire

14
Algorithmique

sont équivalentes.
Ex :
Un algorithme qui décrit comment se comporter avec les feux de signalisation
routière.
Si le signal est rouge ou orange
alors s’arrêter
sinon continuer
Un algorithme un peu meilleur admet la possibilité que les signaux ne soient pas
en état de marche.
Si pas de signal
alors respecter la priorité à droite
sinon si signal rouge ou orange
alors s’arrêter
sinon continuer

Cet algorithme contient deux occurrences de la sélection, la seconde étant incluse


dans la première et exécutée seulement si le signal marche.
L’instruction si … alors … sinon (test conditionnel) porte l’évaluation d’une
expression logique (par exemple X ≤ Y). Cette expression est soit vraie soit
fausse. Les expressions logiques les plus courantes sont des comparaisons, par
exemple ≠, =, <, >, …
Notons que X+2=3*X+1 n’est jamais une équation mais bien
une expression logique qui selon la valeur de la variable X est soit vraie soit
fausse.
X. L’itération :
I
Examinons le processus qui consiste à chercher, dans une liste de noms et
d’adresses, l’adresse d’une personne (le nom de la personne étant indiqué) :
Examiner le premier nom de la liste
si ce nom est le nom donné
alors extraire l’adresse correspondante S1
sinon examiner le nom suivant
si ce nom est le nom donné
alors extraire l’adresse correspondante
15
Algorithmique

sinon examiner le nom suivant


si ce nom est le nom donné
alors …

L’inconvénient de cet algorithme tient au fait que l’auteur ne sait pas quand il doit
s’arrêter d’écrire. L’auteur ne sait pas quand il doit s’arrêter d’écrire. L’auteur ne
sait pas combien de fois il doit écrire l’instruction S1 afin de s’assurer que le
nom est celui recherché.
Cet exemple montre que les séquences et les sélections ne sont pas suffisantes
pour exprimer des algorithmes dont la longueur peut varier selon les
circonstances. Ce qui est nécessaire, c’est le moyen de répéter certaines
instructions dans un algorithme un nombre quelconque de fois.

Pour cela, nous allons introduire l’instruction :


Répéter … jusqu’à ce que
On réécrit l’algorithme précédent ainsi :

Examiner le premier nom de la liste


Répéter
si ce nom est le nom donné
alors extraire l’adresse correspondante
sinon examiner le nom suivant
jusqu’à ce que le nom soit trouvé ou la liste épuisée
Cet exemple illustre la répétition ou l’itération ;
La forme générale de l’itération est :
répéter
Partie de l’algorithme
jusqu’à ce que condition
ce qui signifie que la partie de l’algorithme comprise entre les mots répéter et
jusqu’à ce que doit être répétée tant que la condition spécifiée après jusqu’à ce
que n’est pas vraie.
L’occurrence d’itération est généralement appelée une boucle et la partie qui est
répétée (c’est­à­dire celle qui est comprise entre les mots répéter et jusqu’à ce

16
Algorithmique

que) est connue sous le nom de corps de la boucle. La condition qui apparaît
après jusqu’à ce que est appelée la condition d’achèvement de la boucle.
L’intérêt de l’itération est la description d’un processus de durée indéterminée
par un algorithme de longueur finie. Cela implique qu’il faut s’assurer d’un
achèvement effectif de l’itération au moment prévu. L’algorithme précédent se
termine parce que la condition de clôture (non trouvé ou liste épuisée) devient
vraie. A noter cependant que l’oubli de la deuxième partie de la condition de fin
(liste épuisée) peut provoquer un désastre puisque si le nom recherché n’est pas
présent, le processeur continue sa recherche au­delà de la fin de liste.
Une des erreurs les plus courantes dans la conception des algorithmes est
l’omission des conditions de fin.
Certains processus ne sont pas censés se terminer.
Ex : contrôler des feux de signalisation routière.
Un algorithme décrivant un tel processus doit contenir une boucle sans fin. Une
telle boucle peut être écrite ainsi :
Répéter
Corps de boucle
A l’infini
Il existe de nombreux cas où les boucles répéter … jusqu’à ce que ne
conviennent pas pour exprimer l’itération désirée, et ce pour les raisons
suivantes.
Une boucle répéter … jusqu’à ce que comporte une condition d’achèvement
après le jusqu’à ce que qui suit le corps delà boucle. Cela implique que dans
tous les cas le corps de la boucle est exécuté au moins une fois puisque la
condition d’achèvement ne peut intervenir qu’à la suite de son exécution.
L’exemple suivant montre l’importance de cette remarque.
Exemple : nous voulons écrire un algorithme qui détermine le nombre le plus
grand d’une liste.
Un tel algorithme peut se réaliser par un examen de la liste en comparant chaque
nombre avec la plus grand déjà trouvé et en remettant cette opération à jour à
chaque apparition d’un nombre supérieur.

Appeler le premier nombre de la liste le plus grand nombre connu


répéter
lire le nombre suivant
si ce nombre > au plus grand nombre connu
17
Algorithmique

alors appeler ce nombre le plus grand nombre connu


jusqu’à ce que la liste soit entièrement parcourue
afficher le plus grand nombre connu
Cet algorithme semble correct à première vue. Pourtant il comporte une erreur
monumentale si la liste ne comporte qu’un seul nombre. Dans ce cas le
processeur atteint le bout de la liste dès qu’il exécute le corps de la boucle pour
la première fois. Même si la condition d’achèvement « fin de liste » est prévue,
dans ce cas elle n’est pas effective puisque le processeur n’effectue aucun test
de fin avant qu’il n’est exécuté le corps de la boucle au moins une fois. Il faut
donc mettre la condition d’achèvement au début de la boucle plutôt qu’à la fin
pour permettre au processeur d’omettre le corps de la boucle si la condition
d’achèvement est vraie. Et donc l’algorithme précédent peut s’écrire :

Appeler le premier nombre de la liste le plus grand nombre connu


tant que la liste n’est pas entièrement parcourue faire
lire le nombre suivant
si ce nombre > au plus grand nombre connu
alors appeler ce nombre le plus grand nombre connu
afficher le plus grand nombre connu.
Le corps de la boucle contient tout ce qui se trouve au dessous de tant que et la
condition d’achèvement apparaît avant le corps de la boucle entre tant que et
faire.
La forme générale de ce type d’instruction est :
tant que condition faire
Corps de la boucle
Elle signifie que le corps de la boucle doit être répété aussi longtemps que la
condition est vraie. Du fait que la condition est testée avant l’exécution du corps
de la boucle, ce type de boucle est dit pré testé. Le type de la boucle répéter est
dit post­testé. Le corps de la boucle post­testé est toujours exécuté au moins une
fois, alors que le corps d’une boucle pré testé peut ne pas être exécuté du tout.
Une boucle pré testée peut être considérée comme une boucle de « précaution »,
alors que la boucle post­testée est une boucle « d’imprudence ».
Avant d’achever cette section il nous faut aborder une autre forme d’itération.
C’est une forme particulièrement simple dans laquelle le nombre de répétition est
connu avant l’exécution de la boucle.
Par exemple, un algorithme pour calculer la puissance N d’un nombre X (càd
18
Algorithmique

XN) comportera une boucle dans laquelle X sera multiplié par lui­même, et il est
relativement clair que cette boucle doit être exécutée N fois.
Entrer X et N
Affecter 1 à produit
Répéter N fois
Multiplier produit par X
Afficher produit
Cet algorithme illustre un type itération de forme générale :
Répéter N fois
Corps de la boucle
où N est un entier positif quelconque, et où le corps de la boucle est sous
répéter.
Ce type d’itération s’appelle itération définie à l’avance par opposition à
l’itération indéfinie dans laquelle le nombre de répétitions dépend de ce qui arrive
quand la boucle est exécutée.
L’itération définie peut naturellement être transformée en itération indéfinie :
Mettre le compteur de répétitions à 0
Tant que compteur < N faire
Corps de la boucle
Ajouter 1 au compteur
Des exemples quotidiens d’itérations définies ou indéfinies peuvent nous
permettre de les distinguer plus clairement.
Définie indéfinie
(a) rejouer un morceau jouer jusqu’à ce que ce soit juste
(b) rouler pendant 2 Km rouler jusqu’au bout de la route
(c) attendre ici 5 mn attendre ici mon retour
(d) laisser cuire 20 mn laisser cuire jusqu’à ce que ce soit doré
La durée de l’itération définie est déterminée au moment de l’entrée dans la
boucle et l’achèvement est assuré. L’itération indéfinie dépend de la réalisation
de la condition d’achèvement. Il est clair que l’itération définie est la forme la
plus fiable mais elle n’a naturellement pas la même puissance.
Pour illustrer un autre point de notation voici deux exemples :
Répéter pour chaque roue

19
Algorithmique

Vérifier la pression du pneu


et
Répéter pour chaque joueur
Distribuer une carte
Dans chaque exemple le corps de la boucle est exécuté une fois pour chaque
objet.
La forme générale de l’instruction est :
Répéter pour chacun des objets Pour chacun des objets
Corps de la boucle corps de la boucle

Exemple :
Algorithme qui calcule le produit des N premiers nombres entiers (càd la
factorielle de N)
Entrer N
Affecter 1 à produit
Répéter pour chaque entier de 1 à N
Multiplier produit par entier
Afficher produit

Si E est l’énoncé à répéter, I une variable appelée variable de contrôle, et a et b


des expressions du même type que I alors l’énoncé :
Pour I = a à b faire E
indique que les deux énoncés I := x et E doivent être répétés, une fois
pour chaque valeur x de l’intervalle de a à b. les répétitions sont exécutées
séquentiellement, avec les valeurs x allant en croissant de a à b. l’énoncé
précédent peut être considéré comme équivalent à la suite d’énoncés suivante :
Début
I := i 1
E
I := i 2
E
I := i n

20
Algorithmique

E
Fin
où i 1 = a et i n = b et i j = successeur (i j­1)
Il est nécessaire d’identifier explicitement la variable de contrôle, car la
proposition Pour demande implicitement une affectation à cette variable.
La règle suivante résume les cas où il est à propos d’utiliser une formulation
comprenant une proposition Pour.
Si un énoncé doit être répété, on recommande d’utiliser l’énoncé Pour quand le
nombre de répétitions nécessaires est connu a priori. Si ce nombre n’est établi
qu’au cours des répétitions, il vaut mieux utiliser les propositions Tant que et
Répéter.

X. Représentation graphique des algorithmes : les organigrammes


Il est courant d’utiliser des symboles graphiques pour représenter des
algorithmes. Ces représentations portent le nom d’organigramme, et sont
construites à partir d’un nombre limité de symboles représentant les opérations
élémentaires, le passage en séquence étant parfois matérialisé par des traits
orientés joignant ces symboles.

Représentation usuelle des organigrammes :

Condition
entrée/sortie Traitement Fin

On remarquera l’absence de représentation de la primitive Tant que. Ce qui est


très préjudiciable à la bonne lecture d’un organigramme construit à l’aide de ces
symboles.
Primitives de la programmation structurée

21
Algorithmique

tant que P faire A

Répéter A jusqu’à P Si P alors A sinon B

Pour i := a à b faire
Corps de la boucle

22
Algorithmique

Exemples :
1. Organigramme de l’algorithme qui détermine la valeur absolue de X – Y

2. Ecrire un algorithme qui permet de multiplier deux nombres entiers naturels x et y


et appelle z leur produit.

3. Supposons que le processeur disponible ne peut pas multiplier, mais seulement


additionner, réécrire l’algorithme précédent pour ce processeur.
Entrer x, y {« on suppose x ≠ 0 »}
z:=0
u:=x
Répéter z:=z+y
u:=u–1
Jusqu’à ce que u=0
Organigramme

23
Algorithmique

Que pensez­vous de cet algorithme ? N’y a­t­il pas moyen d’écrire un algorithme plus
efficace ?

24
Algorithmique

Structures de données élémentaires

a. Les types de données :

1. Introduction :
Il est important lors de l’écriture d’un programme de spécifier
explicitement toutes les variables en tête du programme. Cela contribue de façon
significative à la lisibilité du programme.
Et en particulier, l’introduction d’une nouvelle variable doit être
accompagnée de l’indication du domaine des valeurs qu’elle peut prendre.
Il est d’ordinaire difficile, sans indication explicite, de déterminer quelle
sorte d’objet la variable représente. Or dans la plupart des cas, l’exactitude d’un
programme dépend des domaines des valeurs des variables.
Les opérateurs qui apparaissent dans les expressions ne sont
habituellement définis que pour certains domaines de valeurs des variables
utilisées.
Le choix des instructions de l’ordinateur qui exécutent une opération
arithmétique diffère, en général, suivant que les variables parcourent les nombres
réels ou seulement les entiers.
Remarque : le formalisme introduit ici est largement inspiré par le langage
Pascal.

2. Le type d’une variable :


L’ensemble des valeurs qu’une variable peut prendre est appelé son type.
Le type attribué aux différentes constantes, variables ou fonctions est
rendu explicite dans une déclaration.
Une déclaration de variable est notée :
Var v:T
où v est l’identificateur d’une nouvelle variable et la valeur de v est une
valeur de type T.
Pour déclarer plusieurs variables du même type, on peut utiliser la forme
abrégée :
Var v1, v2, …, vm : T
où v1, v2, …, vm sont les identificateurs des variables déclarées.
Comment peut­on introduire un type de données dans un programme de

25
Algorithmique

façon appropriée ?
[Link] type scalaire :
La première étape consiste à distinguer certaines catégories de types de
données. La distinction la plus importante est de savoir si les valeurs d’un certain
type sont structurées.
Si une valeur est non structurée (c’est­à­dire non décomposable en
constituants) on l’appelle un scalaire.
Le type scalaire correspond à la définition mathématique d’un ensemble en
extension : on indique le nom de l’ensemble puis celui de ses éléments.
La forme générale d’une définition de type est :
Type t = T
où t est l’identificateur nouvellement introduit et T une description du type.
Un type scalaire se décrit par l’énumération de ses composants.
Type t = (w1, w2, …, wn )
Une telle définition introduit l’identificateur de type t ainsi que n
identificateurs de constantes w1, w2, …, wn .
Exemples :
Type couleur = (rouge, jaune, vert, bleu)
Type prénom = (Ali, Jamal, Fatima)
Avec une variable x de type prénom on pourra écrire :
x : = Ali ou encore si x ≠ Fatima alors
Notons que si des variables sont d’un type qu’il est superflu de nommer
explicitement, on peut laisser ce type anonyme en combinant la déclaration de
variable et la définition de type :
Var v1, v2, …, vm : (w1, w2, …, wn )
Un type scalaire est automatiquement ordonné. L’ordre est celui dans
lequel les éléments sont indiqués. Les éléments sont distincts.
D’une manière générale, les principales caractéristiques du concept de type
retenu ici sont :
­ un type définit l’ensemble des valeurs possibles des données de ce type ;
­ le type de la valeur d’une constante, d’une variable ou du résultat d’une fonction
doit se déduire de sa forme ou de sa déclaration sans qu’il soit nécessaire de
procéder à une exécution ;
­ les opérateurs ou fonctions sont définis sur des arguments et ont des résultats

26
Algorithmique

d’un type déterminé.


La cardinalité d’un type de données est le cardinal de l’ensemble des
valeurs de ce type.
Deux opérateurs sont supposés définis a priori sur les éléments d’un
même type quelconque. Ce sont :
Le test d’égalité x = y
L’affectation à une variable x x:=y
[Link] types primitifs standards :
Les types primitifs standards ne sont pas soumis à une déclaration. La
cardinalité de chacun de ces types est supposée finie. Ainsi le type entier ne
recouvre pas l’ensemble des entiers mais seulement une partie d’entre eux.
. Le type booléen :
i
Il dénote le domaine des valeurs logiques, formé des deux éléments vrai et
faux.
X et y étant deux variables de type booléen, on définit les opérateurs
standard de ce type de la manière suivante :
Λ x et y contient vrai si x et y contiennent vrai
V x ou y contient vrai si x ou y contient vrai
¬ non x contient vrai si x contient faux et réciproquement

x y xVy xΛy ¬x
faux faux faux faux vrai
vrai faux vrai faux faux
faux vrai vrai faux vrai
vrai vrai vrai vrai faux

Les valeurs vrai et faux sont ordonnées de telle façon que : faux < vrai
Tous les opérateurs de relation fournissent un résultat de type booléen.
L’expression x = y, par exemple, a pour valeur vrai si x est égale à y, faux
sinon. Les opérateurs de relations ordinaires sont = , ≠ , < , ≤ , et >. Mais
les quatre derniers ne sont évidemment applicables qu’à des types ordonnés
(c’est­à­dire scalaires).
Affectation d’une valeur à une variable logique :
27
Algorithmique

Soit en donnant explicitement la valeur vrai ou faux, soit en donnant la


valeur logique d’une expression.
Exemples :
p : = vrai p : = (2 < 3)
q : = faux q : = non (2 < 3)

. Le type entier :
i
i
Ce type représente l’ensemble des nombres entiers relatifs. Les
bornes dépendent du matériel employé, le plus classique étant
l’intervalle ­32768 .. 32767. C’est l’ensemble des « entiers courts ».
Un autre type d’entiers est l’ensemble des « entiers longs » qui
peuvent varier de ­2 147 483 648 (­2^31) à 2 147 483 647.
Si x et y sont deux variables de type entier alors les opérations
suivantes ont un sens et le résultat est de type entier :
Addition x+y
Soustraction x–y
Multiplication x*y
Division tronquée x div y
Reste de la division x mod y
Deux variables de type entier peuvent être comparées à l’aide des
symboles = , ≠ , < , ≤ , et >.
Enfin il existe des procédures prédéfinies qui sont en général :
ABS telle que ABS (x) contient la valeur absolue de x
SQR telle que SQR (x) contient le carré de x
Succ telle que Succ (x) contient la valeur x + 1
(sauf pour x maximum)
Pred telle que Pred (x) contient la valeur x ­ 1
(sauf pour x maximum)
Odd telle que Odd(x) contient vrai ou faux selon que x est
impair ou non.

i
i
i. Le type réel :
Le fait que les domaines de valeurs représentables par l’ordinateur
28
Algorithmique

sont toujours des ensembles finis a des conséquences contraignantes


et dignes d’attention dans le traitement des nombres réels. Dans le
cas des nombres entiers, il était raisonnable de considérer comme
établi que les opérations arithmétiques produisent des résultats exacts
en toutes circonstances, (sauf en cas de débordement) mais cela
devient impossible à réaliser pour l’arithmétique des nombres réels.
La raison en est que tout intervalle si petit soit­il sur l’axe des
nombres réels contient un nombre infini de valeurs : l’axe réel a la
puissance du continu. En programmation, par conséquent, le type
réel ne représente pas un ensemble de nombres réels infini et non
dénombrable ; ce n’est qu’un ensemble fini de représentants
d’intervalles du continuum réel. L’exécution de calculs avec des
valeurs approchées au lieu de valeurs exactes a des effets qui
dépendent beaucoup du problème à résoudre et de l’algorithme
choisi. Au mieux les résultats calculés seront des approximations
entachées d’erreur des résultats réels. C’est le sujet des
« mathématiques numériques » que d’estimer (ce qui est très difficile)
ces erreurs.
Nous allons voir la forme de représentation utilisée pour les nombres
réels avec un nombre de chiffres finis. Dans les ordinateurs
modernes, on utilise d’ordinaire la représentation dite en virgule
flottante (ou point flottant), dans laquelle un nombre réel x s’exprime
au moyen de deux entiers e et m ayant un nombre fini de chiffres et
tels que :
x = m * Be ­E < e < E ­M < m < M
m est la mantisse (ou coefficient) et e l’exposant ; B, E et M
sont des constantes caractéristiques.
Exemples :
15. 15.04 15.04 E2 15.04 E­2
sont des nombres réels valant respectivement
0,15.10² 0,1504.10² 0,1504.104 0,1504
L’exposant est un entier.
Les réels de simple précision disposent d’une précision de six
chiffres parmi des nombres qui varient de 10^­38 à 10^38.
Les réels de double précision disposent d’une précision de 16
chiffres parmi des nombres qui varient de 10^­307 à 10^308.
Si x et y sont deux variables de type réel, alors les opérations
suivantes ont un sens et le résultat est de type réel :
29
Algorithmique

Addition x+y
Soustraction x–y
Multiplication x*y
Division x/y
Deux variables de type réel peuvent être comparées à l’aide des
symboles = , ≠ , < , ≤ , et >., qui ont le même résultat que pour
le type entier.
Bien que, au sens mathématique, les entiers soient un sous­ensemble
des nombres réels, il est habituel de considérer les types entier et réel
comme « disjoints ».
Il est, cependant, permis de « mélanger » des variables des deux
types dans une expression mathématique, et le résultat est de type
réel.
Exemple : 1. + 1 vaut 2. et non 2
Si on utilise un argument à valeur réelle là où seul un entier est
autorisé, il faut utiliser une fonction de transfert du type réel au type
entier.
Fonctions de transfert standard :
TRONC(x) qui représente l’entier obtenu en tronquant la partie
fractionnaire de x
ex : TRONC(5,8) = 5 TRONC(­1,414) = ­1
TRONC(­1,732) = ­1

TRONC(x + 0.5) si x 0
ARRONDI(x) =
­TRONC(0.5 – x) si x < 0

v. Le type caractère :
i
Ce type dénote un ensemble de caractères fini et ordonné. De la
même façon qu’il définit un certain domaine de nombres
manipulables, tout ordinateur définit aussi un jeu de caractères grâce
auquel il communique avec le monde extérieur. Ces caractères sont
disponibles sur les périphériques d’entrées et de sortie. Il est
fortement souhaitable de normaliser les jeux de caractères. C’est
même indispensable si l’on veut connecter des ordinateurs différents
30
Algorithmique

pour des communications mutuelles, des transmissions de données,


du traitement de données à distance, etc.… Quoique une coordination
complète dans la normalisation des jeux de caractères semble difficile,
il est communément admis que les jeux de caractères utilisés par les
ordinateurs doivent comprendre les 26 lettres latines, les 10 chiffres
arabes décimaux et un certain nombre de caractères spéciaux tels que
des signes de ponctuation. De plus un jeu de caractères défini par
l’Organisation Internationale de Normalisation (ISO) et son
homologue américain l’ASCII (American Standard Code for
Information Interchange) sont largement acceptés et utilisés.
Le jeu ASCII comprend 128 caractères. Chaque caractère peut être
représenté et codifié comme une combinaison unique de 7 bits. La
correspondance entre les combinaisons de bits et le jeu de caractères
est un code : c’est le code ASCII.
Deux fonctions standard permettent d’établir une correspondance
entre un jeu de caractères et un sous­ensemble des nombres naturels,
et vice et versa. Ces fonctions, appelées fonctions de transfert, sont
définies comme suit :
ORD(c) donne le numéro d’ordre du caractère c dans le jeu de
caractères ordonné (avec la table ASCII)
CHR(i) a pour valeur le caractère dont le numéro d’ordre est i.
En conséquence, on a les relations :
CHR(ORD(c)) = c ORD(CHR(i)) = i
et l’ordre de l’ensemble des caractères est défini par:
c1 < c2 ORD (c1) < ORD (c2)
Pour dénoter une constante de type CHAR, on enclot le caractère
entre apostrophes (Pascal). Par exemple, pour affecter un point
d’interrogation à la variable c de type Char :
C := ‘?’

v. Le type intervalle :
On va pouvoir définir des types intervalles (sous­ensembles) de la
façon suivante :
Type
Semaine = (lundi, mardi, mercredi, jeudi, vendredi, samedi,
dimanche) ;
31
Algorithmique

jourboulot = lundi .. vendredi ;


chiffre = 0 .. 9 ;
Une variable de type jourboulot ne peut prendre que des valeurs x,
telles que ORD (lundi) ≤ ORD (x) ≤ ORD ( vendredi).
Une variable de type chiffre ne peut prendre que des valeurs y telles
que : 0 ≤ y ≤ 9 .
Le type intervalle se définit par :
valeur minimale .. valeur maximale
Si une variable voit son contenu sortir de l’intervalle (ce qui ne peut
se produire que lors de l’exécution) alors il se produit une erreur.

3. Les types structurés


Les données simples, par exemple un entier ou un réel, ne sont pas
nécessairement considérés isolément mais peuvent être regroupés en
un tout complexe formant une donnée structurée. L’exemple le plus
largement connu est sans doute le tableau de valeurs d’un même type
et la plupart des langages offrent des facilités pour introduire de tels
tableaux.
[Link] type tableau :
● Le tableau unidimensionnel
Un tableau se compose d’au moins deux éléments, chaque élément
du tableau étant d’un même type.
Chaque composant d’un tableau peut être désigné explicitement, ce
qui le rend directement accessible.
Le nombre de composants est défini à la déclaration du tableau et ne
change plus par la suite.
Les composants sont accessibles au moyen du nom de la variable et
d’un indice, qui désigne de façon unique l’élément désiré. L’indice
doit donc être de l’un des types disponibles, qui s’appelle alors le
type d’indice du tableau. Nous restreignons les types d’indice à ceux
qui sont scalaires, pour qu’il existe un ordre linéaire parmi les
composants du tableau.
Exemples :
1. La mémoire de l’ordinateur est du type tableau : chaque élément
contient par exemple 8 valeurs binaires, un élément est repéré par son
indice dans le tableau.

32
Algorithmique

2. Le drapeau marocain est composé de deux valeurs


drapeau [1] = rouge
drapeau [2] = vert
Il est à une seule dimension, l’indice est numérique, chaque
composant est d’un même type :
Couleur = (rouge, vert)
Type drapeau = tableau [1 .. 2] de couleur

Le type d’un tableau composé d’un ensemble de valeurs du même


type T0 et indicé par les valeurs du type I est défini par la déclaration :
Type T = tableau [I] de T0
Le type de l’indice a une certaine cardinalité, c’est­à­dire qu’on sait à
l’avance le nombre de valeurs pouvant être prises par une variable de
ce type.
La taille d’un tableau = card(T0) card(I)
= nbre de valeurs pouvant être prises par l’indice * taille de chaque
composant
Exemple : un tableau x destiné à compter le nombre de véhicules de
chaque sorte parmi : avion, automobile, autobus, train, bateau.
Type véhicule = (avion, automobile, autobus, train, bateau) ;
T = tableau [ véhicule] de entier ;
Soit la déclaration d’un tableau x de type T
Var x:T;
L’élément indicé par i, où i est du type I (type de l’indice) est
sélectionné par x[i].
La valeur de x[i] est du type T0.
Par définition, deux tableaux sont égaux si et seulement si ils sont du
même type et que leurs composants correspondants sont égaux.
● Tableaux à plusieurs dimensions :
Les composants d’un tableau ne sont pas nécessairement des
scalaires, ils peuvent eux­mêmes être structurés.
Si ces composants sont des tableaux, le tableau originaire T est dit à
plusieurs dimensions. Si les composants des tableaux composants
33
Algorithmique

sont des scalaires, T est appelée une matrice.


Exemple : considérons une matrice réelle à 10 lignes et 5 colonnes.
Type _ ligne = tableau [1 .. 5] de réel ;
Matrice = tableau [1 .. 10] de ligne ;
Var M : matrice
Généralement, la déclaration d’une variable de type tableau à
plusieurs dimensions suit le modèle suivant :

(1) Var M : tableau [a .. b] de tableau [c .. d] de T0

M est déclaré comme comprenant b – a + 1 composants (souvent


appelés les lignes de la matrice), dont les indices sont a, …, b et
dont chacun est un tableau de d – c + 1 composants de type T0 et
d’indices c, …,d.
Pour noter le ième composant (la ligne) de M, on utilise la notation
habituelle :
M [i] a ≤ i ≤ b
et pour son j­ième composant de type T0 :
(2) M [i] [j] a ≤ i ≤ b , c ≤ j ≤ d
Il est courant et commode d’utiliser les abréviations suivantes qui
sont entièrement équivalentes à (1) et (2) respectivement :
Var M : tableau [a .. b , c .. d] de T0
M [i , j]
Manipulations de tableaux :
Les opérateurs ← , = , < > peuvent être appliqués entre deux
variables de même type Tableau.
Exemple :
Type matrice = tableau [ 1 .. 10 , 1 .. 10 ] de réel
Var m1, m2 : matrice
On peut écrire m1 ← m2 , m1 = m2 , m1 < > m2.
Par contre, dans cet exemple­là :
Var m1 : tableau [ 1 .. 10 ] de tableau [ 1 .. 10 ] de réel
m2 : tableau [ 1 .. 10 , 1 .. 10 ] de réel
34
Algorithmique

On ne pourra pas écrire m1 ← m2 bien que les deux variables


soient des matrices carrées.
On pourra par contre écrire :
Pour i ← 1 à 10 faire
Pour j ← 1 à 10 faire
m1 [ i ] [ j ] ← m2 [ i , j ]
Exemple :
Représentons le championnat d’un sport quelconque.

Joueur 1 Joueur 2 ... Joueur n


Joueur 1
Joueur 2 xy
...
Joueur n
Le joueur indiqué dans la ligne REÇOIT,

Le joueur indiqué dans la colonne SE DEPLACE.

A l’intersection de la ligne l
et de la colonne con stocke le score
de la façon suivante :
Le premier nombre représente les points du joueur qui REÇOIT,
Le deuxième nombre représente les points du joueur qui SE
DEPLACE.

On pourra écrire :
Type joueurs = ( J1, J2 , J3 , J4 , J5 )
Score = tableau [ 1 .. 2 ] de entier
Championnat = tableau [ joueurs , joueurs ] de score
Var résultat : championnat
Résultat [J2 , J5 ] [ 1 ] ← 2 ( J2 recevait J5 et
Résultat [J2 , J5 ] [ 2 ] ← 0 a gagné par 2 – 0)

c. Le type chaîne de caractères (ou alphanumérique ou STRING) :

35
Algorithmique

Une chaîne est une suite de caractères.


Exemple : "GOURMETTE" est une chaîne de caractères.
Fort heureusement, les boîtes que sont les variables peuvent contenir bien
d’autres informations que des nombres. Sans cela, on serait un peu embêté dès
que l’on devrait stocker un nom de famille, par exemple.
On dispose donc également du type alphanumérique (également appelé type
caractère, type chaîne ou en anglais, le type string –
Dans une variable de ce type, on stocke des caractères, qu’il s’agisse de lettres,
de signes de ponctuation, d’espaces, ou même de chiffres. Le nombre maximal
de caractères pouvant être stockés dans une seule variable string dépend du
langage utilisé.
Un groupe de caractères (y compris un groupe de un, ou de zéro caractères),
qu’il soit ou non stocké dans une variable, d’ailleurs, est donc souvent appelé
chaîne de caractères.
En pseudo­code, une chaîne de caractères est toujours notée entre guillemets.
Pourquoi ? Pour éviter deux sources principales de possibles confusions :
la confusion entre des nombres et des suites de chiffres. Par exemple, 423 peut
représenter le nombre 423 (quatre cent vingt­trois), ou la suite de caractères 4, 2,
et 3. Et ce n’est pas du tout la même chose ! Avec le premier, on peut faire des
calculs, avec le second, point du tout. Dès lors, les guillemets permettent d’éviter
toute ambiguïté : s’il n’y en a pas, 423 est quatre cent vingt trois. S’il y en a,
"423" représente la suite des chiffres 4, 2, 3.
Mais ce n'est pas le pire. L'autre confusion, bien plus grave ­ et bien plus
fréquente – consiste à se mélanger les pinceaux entre le nom d'une variable et son
contenu. Pour parler simplement, cela consiste à confondre l'étiquette d'une
boîte et ce qu'il y a à l'intérieur….
Les constantes peuvent être de type chaîne de caractères, comme les variables.
Deux variables de ce type peuvent être comparées par les opérateurs :
< , = , > , ≤ , ≥ avec l’ordre lexicographique.

Opérateur alphanumérique : &


Cet opérateur permet de concaténer, autrement dit d’agglomérer, deux
chaînes de caractères. Par exemple :
Variables A, B, C : chaine de Caractère

36
Algorithmique

Début
A ← "Gloubi"
B ← "Boulga"
C←A&B
Fin
La valeur de C à la fin de l’algorithme est "GloubiBoulga"

[Link] type ensemble (ou Set) :


Le type scalaire peut être considéré comme la définition d’un ensemble en
extension. Une variable de type scalaire contenant un élément de cet
ensemble.
Soit T un type scalaire ou intervalle,
Notons T = (α,β,γ,δ) ou T = α .. δ
On définit le type Ensemble de T ( Set of T ) comme étant l’ensemble
des parties de T.
Autrement dit une variable de type Ensemble de T pourra prendre
n’importe quelle « valeur » V du moment que V Є T au sens
ensembliste.
Type E = Ensemble de type de base
où type de base est un type scalaire ou intervalle
Ex :
Type
Chiffres = ensemble de Caractères
LettreouChiffre = ensemble de Caractères
Nombrepremier = ensemble de Entier
Var chiffre : Chiffres
LouC : LettreouChiffre
Premier : Nombrepremier
Chiffre ← [‘0’ .. ‘9’]
LouC ← [‘A’ .. ‘Z’ , ‘0’ .. ‘9’]
Premier ← [ 2 , 3 , 5 , 7 , 11 , 13]
Opérations possibles entre deux variables de type Ensemble de même type de
37
Algorithmique

base :
+ Union
_ différence
* intersection
Type nombre = (un, deux, trois, quatre, cinq, six, sept, huit, neuf, dix, onze,
douze) ;
Var mult2, mult3, mult6, mult3ou3, mult2pas3 : ensemble de nombre ;
Mult2 ← [deux, quatre, six, huit, dix, douze] ;
Mult3 ← [trois, six, neuf, douze] ;
Mult2ou3 ← mult2 + mult3 ;
Mult6 ← mult2 * mult3 ;
Mult2pas3 ← mult2 ­ mult3 ;

Comparaison de deux variables de type ensemble de même type de base :


= pour égalité
<> pour inégalité
≤ pour inclusion
≥ pour contenance
L’opérateur « est dans » ( In ) représente le symbole Є.
Si x est une variable de type scalaire ou intervalle T et si E est une variable de
type ensemble de T alors x « est dans » E est une expression booléenne vraie ssi
x Є E.
Voici une application assez couramment utilisée :
On pose à l’utilisateur une question à laquelle il doit répondre par O (pour oui)
ou N (pour non), toute autre réponse entraîne le renouvellement de la question.
Var réponse : caractère
Début
Répéter
Ecrire (‘Etes­vous étudiant à l’ENS ?’) ;
Ecrire (‘répondez par O ou N’) ;
Lire (réponse) ;
Jusqu’à ce que réponse « est dans » [‘O’ ,’N’]
38
Algorithmique

Fin.

[Link] :

Il est courant et utile de structurer dans un même enregistrement des données de


types différents.

Ex1 : une notice individuelle comportant nom, prénom, date de naissance et


sexe. La date de naissance est elle­même une donnée structurée qui se compose
d’un jour, entier de 1 à 31 ; d’un mois, entier de 1 à 12 ; d’une année, entier de
1900 à 2009.
Type date = enregistrement jour : 1..31 ;
mois : 1..12 ;
année : 1900 ..2009 ;
fin ;

alpha = chaîne de caractère (20) ;


sex = (homme, femme) ;
personne = enregistrement nom : alpha ;
prénom : alpha ;
naissance : date ;
sexe : sex ;
fin ;
var p : personne ;

d : date
29
9
1994

p : personne
Ch

ma
â
El
Idr
iss
i

39
Algorithmique

29 9 1994
Fe
m
me

Le prénom de la personne enregistrée dans la variable p est sélectionné par :


[Link]énom
Le mois de sa naissance est sélectionné par :
[Link]

De façon plus générale, le type enregistrement est défini par :

Type T = enregistrement s1 : T1 ;
s2 : T2 ;
………
sn : Tn ;
Fin ;
où T1, T2, …, Tn sont des types préalablement définis. La cardinalité du type T
est égale à Πi card(Ti).

La construction d’une variable x du type T est notée :

Var x:T

La sélection de la composante si de l’enregistrement x de type T est notée :


[Link]

Ex2 : Soit a un tableau de 50 personnes.

Var a : tableau [1..50] de personne ;

Le prénom de la personne enregistrée au rang i est sélectionné par :


a[i].prénom
Son mois de naissance est sélectionné par :
a[i].[Link]

Parmi les informations figurant dans un enregistrement on peut inclure un lien


destiné à permettre le passage d’un enregistrement à un autre selon un ordre que
l’on s’est donné.

Ex 3 : redéfinissons le type personne en y incluant un lien :

Type personne = enregistrement

40
Algorithmique

Nom : alpha ;
Prénom : alpha ;
Naissance : date ;
Sexe : sex ;
Lien : entier ;
Fin ;

Var a : tableau [1..50] de personne ;

Soient i1, i2, …i50 les rangs des personnes enregistrées énumérées dans
l’ordre alphabétique des noms, c’est­à­dire
a[ik1].nom < a[ik2].nom si et seulement si k1 < k2.
On suppose que le lien satisfait l’égalité :
a[ik].lien = ik+1­ ik­1.

Connaissant les rangs ik­1 et ik des deux personnes qui se succèdent dans l’ordre
alphabétique, on peut en déduire le rang de la k+1ème personne :
ik+1 = ik­1 + a[ik].lien

Réciproquement on peut trouver le rang de la k–1 ème personne si l’on connaît ik


et ik+1 :
ik­1 = ik+1 – a[ik].lien

. Enregistrement à format variable


f
Il est utile de définir dans un même type des enregistrements dont la structure
peut varier en fonction du cas rencontré. On utilise à cette fin un indicateur de
cas.

Ex 1 : les coordonnées d’un point du plan peuvent être données soit en


cartésienne soit en polaire.
Type coordonnée = enregistrement
Cas sorte : (cartésien, polaire) de
Cartésien : ( x, y : réel) ;
Polaire : (r : réel, angle) ;
Fin ;
L’indicateur de cas est ‘sorte’ qui est du type (cartésien, polaire). Selon la
valeur de cet indicateur les composantes suivantes de l’enregistrement sont deux
réels x et y, ou, un réel r et un angle où l’on suppose que le type angle a été
défini préalablement.
La forme générale de la déclaration d’un type enregistrement à format variable
est :

Type T = enregistrement s1 : T1 ; s2 : T2 ; … ; sn­1 : Tn­1 ;


41
Algorithmique

Cas sn : Tn de
c1 : (s1,1 : T1,1 ; … ; s1,n1 : T1,n1) ;
…………………………….
cm : (sm,1 : Tm,1 ; …; sm,nm : Tm,nm)
Fin;
Où c1, c2, …, cm sont les valeurs du type Tn. L’indicateur de cas est sn.
Les composantes d’une variable x du type T sont :
x.s1, …, [Link], [Link],1, …, [Link],n k

si et seulement si la valeur de [Link] est ck.

Si l’indicateur de cas est une valeur logique, la déclaration peut


prendre la forme suivante :

Type T = enregistrement s1 : T1 ; s2 : T2 ; … ; sn­1 : Tn­1 ;


si t alors (s1,1 : T1,1 ; … ; s1,n1 : T1,n1)
sinon (s2,1 : T2,1 ; … ; s2,n2 : T2,n2)
Fin;

Ex 2 :
Le type coordonnée peut être défini en utilisant une variable logique ‘cart’ au
lieu de ‘sorte’, en supposant ‘cart’ vrai s’il s’agit de coordonnées cartésiennes et
faux sinon.

Type coordonnée = enregistrement


Si cart alors (x,y : réel)
sinon (r : réel ; :angle)
fin ;

42
Algorithmique

Techniques algorithmiques
. Notion de modularité :
I

1. Introduction et définitions
Dans ce qui précède nous avons montré comment développer des algorithmes
grâce à des procédures d’affinement des instructions. A chaque étape de cet
affinement, l’algorithme est divisé en composants plus petits qui peuvent être
définis à leur tour de manière plus détaillée. L’affinement s’achève lorsque
chaque élément de l’algorithme est exprimé de manière telle que le processeur
prévu peut l’interpréter.
Les composants rencontrés pendant l’affinement sont souvent totalement
indépendants de l’algorithme principal au sens où ils peuvent être conçus
extérieurement au contexte dans lequel ils doivent être utilisés. De tels éléments
peuvent être conçus par quelqu’un d’autre que le concepteur de l’algorithme
principal et peuvent également être utilisés comme éléments d’un autre
algorithme. Ils peuvent être considérés comme des éléments multifonctionnels et
intégrables à tout algorithme auquel ils seraient nécessaires.
Comme premier exemple, nous allons examiner la conception de certains
algorithmes destinés à être exécutés par un simple automate. La fonction de
l’automate consiste à dessiner : il est équipé de roulettes qui lui permettent des
déplacements sur la feuille de papier et d’un stylo qui peut s’abaisser dès qu’il
veut faire un trait. L’automate peut interpréter et exécuter des commandes de la
forme :
Déplace (x) se déplace en avant de x cm
Gauche (x) tourne de x degrés à gauche
Droite (x) tourne de x degrés à droite
Lèvestylo ôte le stylo du papier
Baissestylo pose le stylo sur le papier.
Supposons que nous voulions que l’automate dessine deux carrés concentriques
comme le montre la figure suivante, sa position initiale étant le point X, centre des
carrés, stylo levé.

On peut esquisser l’algorithme suivant :


Déplace en A
Dessine un carré de 10 cm de côté 1.1
43
Algorithmique

Déplace en B
Dessine un carré de 20 cm de côté

La seconde et la quatrième instruction de cet algorithme impliquent le dessin d’un


carré, qui est une procédure en soi indépendante du reste de l’algorithme, ce qui
implique que nous pouvons concevoir un algorithme pour dessiner un carré sans
référence au contexte de l’algorithme qui l’utilise. Une fois un tel algorithme
conçu, nous pouvons l’intégrer aux endroits prévus dans l’algorithme 1.1 ou à
tout autre algorithme pour lequel le tracé d’un carré serait nécessaire.
Un algorithme qui peut être intégré à un autre algorithme est un module (dans
certains langages de programmation, on parle de procédure ou fonction). Si la
procédure retourne une valeur résultante, on l’appelle une fonction. La définition
de l’abréviation est la déclaration de procédure. Son utilisation au cours d’un
algorithme est un appel de procédure.
Pour que le module de conception du carré soit globalement utilisable, il doit être
en mesure d’effectuer un tracé de n’importe quelle taille. Lorsque le module (la
procédure) est appelé, la taille spécifique requise doit être indiquée. Pour
l’algorithme 1.1, nous pouvons écrire :
Dessinercarré (10) et dessinercarré (20)
Ce qui signifierait qu’un des carrés doit avoir un coté de 10cm et l’autre, un côté
de 20cm.

Ce module peut être énoncé de la manière suivante :


Module dessinercarré (taille)
{dessine un carré dont le côté a une longueur "taille" en cm. Le carré est tracé dans le
sens inverse des aiguilles d’une montre en partant de la position actuelle de
l’automate. La première ligne est tracée selon l’orientation actuelle de l’automate.
L’automate revient à sa position et à son orientation initiale dès que le stylo est levé.}
baissestylo
répéter 4 fois
déplacer (taille)
gauche 90
lèvestylo
La "taille" représente un paramètre formel du module. Il est utilisé à l’intérieur du
module pour définir la taille du carré. Lorsque le module est appelé, le paramètre
actuel 10 (ou 20) donne une valeur spécifique de la taille et détermine ainsi la
grandeur du carré.
44
Algorithmique

Généralisation :
Module nomdumodule (paramètres formels)
Corps du module

On appelle un module de la manière suivante :


Nomdumodule (paramètres actuels)
C’est ce que le processeur interprète comme une directive pour exécuter le corps
du module appelé dont les paramètres formels ont été remplacés par les
paramètres actuels de l’appel. Les paramètres formels d’un module peuvent être
considérés comme la représentation de l’information nécessaire au module
lorsqu’il est appelé.
Les paramètres actuels sont des informations pour un appel particulier. Les
informations prennent la place des paramètres formels lorsque le module est
exécuté. Il doit naturellement y avoir le même nombre de paramètres actuels et
formels.
Un algorithme construit autour d’un nombre donné de modules est appelé un
algorithme modulaire. Chaque module est un composant en soi de l’algorithme et
agit comme un bloc dans la construction de l’algorithme.
En utilisant le module dessinercarré comme nous l’avons précédemment défini,
nous pouvons affiner l’algorithme 1.1 de la manière suivante :
Gauche (45)
Déplacer (√50) {déplacer en A}
Gauche (135)
Dessinercarré (10) {dessiner le carré intérieur}
Droite (135)
Déplacer (√50) {déplacer en B}
Gauche (135)
Dessinercarré (20) {dessiner le carré extérieur}

Il faut bien comprendre que le concepteur de l’algorithme 1.2 n’a pas besoin de
savoir comment le module dessinercarré travaille ; tout ce qu’il doit connaître est
l’effet de son exécution.
La procédure (ou le module) est l’un des rares outils fondamentaux de l’art de la
programmation dont la maîtrise influe de façon décisive sur le style et la qualité
du travail du programmeur. La procédure sert à abréger le texte, et de façon plus
significative, à partager et structurer un programme en composants fermés et
logiquement cohérents. La structuration en sous programmes est indispensable, à

45
Algorithmique

la fois, à la documentation et à la vérification du programme. Par conséquent, il


est souvent souhaitable de faire d’une suite d’énoncés une procédure, même si
elle n’apparaît qu’une seule fois et qu’il n’y a donc aucune possibilité de
raccourcir le texte par ce moyen.
Des informations supplémentaires sur les variables (celles dont la valeur est
utilisée ou modifiée par la procédure) ou sur les conditions que les arguments
doivent satisfaire, peuvent apparaître dans l’en­tête de la procédure.
4. Localisation :
Deux autres concepts de base de la programmation soulignent l’utilité de la
procédure, en particulier son rôle dans la structuration des programmes. Il arrive
souvent que certaines variables ou certains objets (appelées souvent variables
auxiliaires) soient utilisées dans une suite donnée d’énoncés sans avoir la
moindre signification en dehors de ces énoncés. La clarté du programme est
nettement améliorée si l’on peut faire apparaître clairement la région où ces objets
sont significatifs. La procédure apparaît comme l’unité textuelle naturelle pour
délimiter le domaine de validité de ces objets locaux.
Si un objet (une constante, une variable, une procédure, une fonction ou un type)
n’a de signification qu’à l’intérieur d’une certaine partie du programme, il est dit
local. Dans ce cas, il est opportun de donner un nom à cette section du
programme (càd d’en faire une procédure). Les objets locaux sont alors déclarés
dans l’en­tête de la procédure. Comme les procédures elles­mêmes peuvent être
définies de façon locale, on peut emboîter des déclarations de procédures.

Ex : Déclaration de procédure avec une déclaration de variable locale


Procédure P
Var t : entier ;
Début t ← r mod q ;
r ← q;
q ← t
Fin ;
A l’intérieur de la procédure, on utilise deux sortes d’objets : les objets locaux
(ici, t) et les objets non locaux. Ces derniers sont déclarés dans l’environnement
de la déclaration de procédure. S’ils sont définis dans le programme principal, ils
sont dits globaux, et s’ils sont définis dans le langage (càd dans le contexte dans
lequel le programme est inclus), ils sont dits standards. Le domaine de validité
des objets locaux est le texte de la procédure tout entier. Ceci implique qu’après
l’achèvement du processus décrit par une procédure, l’espace de mémoire utilisé
46
Algorithmique

par les variables locales devient à nouveau disponible et peut servir pour d’autres
variables. Evidemment, lors d’un prochain appel de la même procédure, les
valeurs de ces variables locales seront à nouveau non définies, comme au
moment du premier appel.
Quand on identifie les objets locaux, il est essentiel que l’on puisse choisir les
noms librement, sans se préoccuper de l’environnement. Mais il peut se produire
que la situation où l’identificateur (disons x) choisi pour une variable locale dans
la procédure P est identique à celui d’un objet de l’environnement de P.
Naturellement, cette situation n’est possible que si le x non local n’a pas de
signification pour P. nous adopterons la convention de base selon laquelle, en
cas de conflit de noms, x à l’intérieur de P désignera la variable locale, et x à
l’extérieur de P l’objet non local.

Ex :

Programme démofonct (output) ;


var x1 , x2 : entier ;
fonction quitteoudouble (nombre : entier) : entier ;
const deux = 2 ;
var x2 : entier ;
début
x2 ← 0 ;
écrire (‘dans quitteoudouble x2 vaut : ‘ , x2) ;
si nombre ≤ 0 alors quitteoudouble ← 0
sinon quitteoudouble ← deux *
nombre
fin ;
début {programme principal}
x2 ← 1 ;
écrire (‘x2 =’ , x2) ;
x1 ← quitteoudouble (10) ;
écrire (‘x1 =’ , x1, ‘x2 =’ , x2) ;
x1 ← quitteoudouble (­3) ;
écrire (‘x1 =’, x1 , ‘x2 =’ , x2)
fin.
L’exécution de ce programme donne :
x2 = 1
Dans quitteoudouble x2 vaut : 0
x1 = 20 x2 = 1
La variable x2 n’est pas la même suivant qu’on se place ou non dans la fonction.
Il est commode de considérer le programme principal comme une procédure
sans nom. Son environnement est le langage de programmation, et c’est là que
47
Algorithmique

sont définis tous les objets standards. Cette idée explique aussi pourquoi l’on
peut choisir des identificateurs sans s’occuper des noms standards déjà définis.
Aussi longtemps qu’un objet standard n’est pas utilisé dans le programme,
l’usage soit accidentel, soit intentionnel de son identificateur comme nom d’objet
local, n’a pas le moindre effet défavorable.
1. Paramètres de procédure :
Les paramètres formels ne sont qu’à l’intérieur du corps de procédure et lui
sont locaux. On précise les types des paramètres dans l’en­tête de procédure.
Le type du paramètre actuel ou effectif est déterminé par le type du paramètre
formel.
En plus de l’indication du type de paramètre, il faut aussi préciser la sorte de
substitution désirée, car l’on peut substituer soit la valeur actuelle soit
l’identité de la variable ou de l’expression effectives. On distingue trois sortes
de substitution de paramètres.
a)On évalue le paramètre effectif, et on substitue la valeur résultante au paramètre
formel correspondant. C’est la substitution de valeur, et c’est la plus connue.
b)Le paramètre effectif est une variable. On évalue les indices éventuels et on
substitue la variable ainsi identifiée à son correspondant formel. C’est la
substitution de variable ou par référence. Elle sert si le paramètre représente un
résultat de la procédure.
c) On substitue littéralement le paramètre effectif, sans faire d’évaluation. C’est la
substitution de nom qui n’apparaît que rarement dans les applications pratiques.
Ex : L’algorithme suivant sert à montrer l’effet des trois sortes de
substitution de paramètres. Nous examinons les conséquences de l’appel
de procédure P ( T [ i ] ).
Var i : entier ;
T : tableau [1 .. 2] de entier ;
Procédure P ( x : entier ) ;
Début i ← i+1; x ← x+2
Fin ; (1)
Début {programme principal}
T [ 1 ] ← 10 ; T [ 2 ] ← 20 ; i ← 1;
P (T [ i ] )
Fin.
1er cas : substitution de valeur

48
Algorithmique

x est une variable dont la valeur initiale est 10 ; la valeur finale de T est (10, 20).
2ème cas : substitution de variable
x ≡T[1]
L’énoncé x ← x + 2 signifie maintenantT [ 1 ] ← T [ 1 ] + 2
La valeur finale de T est (12, 20).
3ème cas : substitution de nom
x ≡T[i]
L’énoncé x ← x + 2 signifie maintenantT [ i ] ← T [ i ] + 2
La valeur finale de T est (10, 22).
Pour distinguer les différentes sortes de substitution, nous posons les différentes
règles de notation suivantes :
a) La substitution de valeur est la forme la plus fréquente ; elle est choisie à défaut
d’indication explicite.
b) La substitution de variable est indiquée par le symbole var en tête du ou des
paramètres formels.
c) Une procédure ou une fonction sert de paramètre à une procédure ou une
fonction.
Dans le cas de la substitution de valeur, le paramètre formel représente une
variable locale qui prend initialement comme valeur le résultat de l’évaluation du
paramètre effectif correspondant. Après cette affectation initiale, cependant, il
n’existe plus aucune « connexion » entre les objets effectif et formel. Ceci nous
permet d’énoncer deux règles générales qui gouvernent le choix des
substitutions :
1. si un paramètre est un argument mais non pas un résultat de la procédure (ou de
la fonction), c’est la substitution de valeur qui est (d’ordinaire) appropriée.
2. si un paramètre joue le rôle de résultat de la procédure, la substitution de variable
est nécessaire.
Certains pièges sont propres à la méthode de substitution de variable. Ils sont
dus au fait que la même variable peut être rendue accessible sous plus d’une
identité. Cela est particulièrement dangereux dans le cas de variables structurées
telles que les tableaux.
Dans ces cas­là, le programmeur doit absolument se conformer à la discipline de
programmation énoncée par l’importante règle suivante :
Tout paramètre variable doit être disjoint de tous les autres paramètres ;
Autrement dit, aucun paramètre ne doit représenter tout ou partie d’un autre
49
Algorithmique

paramètre. L’exemple suivant montre les dangers potentiels de la violation de


cette règle, sur une procédure simple de multiplication de matrices.
Ex :
Type matrice = tableau [ 1 .. 2 , 1 .. 2 ] de entier ;
Procédure mult (var x , y , z : matrice) ;
Début
z [ 1, 1 ] ← x [ 1, 1 ] * y [ 1, 1 ] + x [ 1, 2 ] * y [ 2, 1 ] ;
z [ 1, 2 ] ← x [ 1, 1 ] * y [ 1, 2 ] + x [ 1, 2 ] * y [ 2, 2 ] ;
z [ 2, 1 ] ← x [ 2, 1 ] * y [ 1, 1 ] + x [ 2, 2 ] * y [ 2, 1 ] ;
z [ 2, 2 ] ← x [ 2, 1 ] * y [ 1, 2 ] + x [ 2, 2 ] * y [ 2, 2 ] ;
fin ;
Etant données les matrices :
2 1 3 ­1
A = et B =
­1 3 1 2

Nous pouvons étudier l’effet des énoncés de procédures qui suivent :


7 0
1. mult (A , B , C) donne C =
0 7

7 ­5
2. mult (A , B , A) donne A=
0 6

7 0
3. mult (A , B , B) donne B=
­4 6
Notons que l’on peut obtenir dans les trois cas les résultats corrects
obtenus par 1. (mult (A , B , C)), si x et y sont des paramètres à substitution de
valeur.
2. Procédures et fonctions en paramètres :
Une procédure ou une fonction F sert de paramètre à une procédure ou fonction
G si F doit être évaluée pendant l’exécution de G, et que F sert à représenter des
procédures ou fonctions différentes lors d’appels différents de G. les
algorithmes qui calculent l’intégrale G d’une fonction F sont des exemples bien

50
Algorithmique

connus.
Ex : Intégrale de Simpson
Pour trouver une valeur approchée de l’intégrale

S = f(x) dx
nous calculons la somme d’un nombre fini de valeurs échantillons fi .
Sk = h/3 (f0 + 4 f1 + 2 f2 + 4 f3 + 2 f4 + … + 4 fn­3 + 2 fn­2 + 4 fn­1 + fn)
où fi = f (a + i * h) , h = (b – a)/n , et n = 2k.
Le nombre de points échantillons est n + 1, et h est la distance entre deux de
ces points adjacents. La suite S1, S2, S3, … donne alors une valeur approchée de
l’intégrale S, qui converge si la fonction a un comportement suffisamment bon
(lisse) et si l’arithmétique est supposée exacte.

A chaque pas le nombre de points échantillons double. Naturellement, un


programme bien conçu évitera d’évaluer la fonction 2k fois à chaque k­ième pas.
Au lieu de cela, il réutilisera les valeurs fi calculées dans les pas précédents. La
somme Sk est, par conséquent, représentée par les trois termes :
Sk = Sk(1) + Sk(2) + Sk(4)
qui représentent les sommes des points échantillons ayant respectivement les
poids 1, 2 et 4. On peut les définir grâce aux relations de récurrence suivantes
51
Algorithmique

(avec k > 1, et avec les valeurs initiales suivantes) :


Sk(1) = ½ Sk­1(2)

Sk(2) = ½ Sk­1(2) + ¼ Sk­1(4)

Sk(4) = 4h/3 ( f (a + h) + f ( a + 3h) + … + f ( a + (n – 1)h ) )

S1(1) = h/3 ( f(a) + f(b) )

S1(2) = 0

S1(4) = 4h/3 f ( ( a + b)/2 )


Ces relations conduisent au programme d’intégration, exprimé comme une
fonction avec f comme paramètre :

Fonction Simpson ( a, b : réel ; fonction f : réel ) : réel ;


Const =
Var i, n : entier ;
S, SS, S1, S2, S4, h : réel ;
{f(x) est une fonction à valeur réelle avec un unique paramètre à valeur réelle. La
fonction doit être bien définie dans l’intervalle a ≤ x ≤ b}
début n← 2; h ← (b – a) * 0,5 ;
S1 ← h * ( f(a) + f(b) ) ; S2 ← 0 ;
S4 ← 4 * h * f ( a + h ) ; S ← S1 + S2 + S4 ;
Répéter SS ← S ; n← 2*n; h ← h/2
;
S1 ← 0,5 * S2 ; S2 ← 0,5 * S2 + 0,25 * S4 ;
S4 ← 0 ; i ← 1 ;
Répéter S4 ← S4 + f( a + i * h) ; i ← i + 2 ;
Jusqu’à i > n;
S4 ← 4 * h * S4 ; S ← S1 + S2 + S4

52
Algorithmique

Jusqu’à abs ( S – SS ) < ;


Simpson ← S/3
Fin.

La fonction Simpson peut maintenant servir comme opérande dans une


expression réelle. Par exemple, l’énoncé :
u ← Simpson ( 0 , П/2 , sin )

représente l’affectation u = sin (x) dx

Cependant, seul un identificateur de fonction peut apparaître comme troisième


paramètre effectif. Par exemple, pour calculer :

u = dx / ( a2 cos2 x + b2 sin2 x ) ½

à l’aide de la fonction Simpson, il faut déclarer explicitement une autre fonction


F:
fonction F ( x : réel ) : réel ;
début
F ← 1 / SQRT ( SQR ( a * cos (x) + SQR ( b * sin (x)))
fin ;
C’est seulement que l’on peut exprimer ceci :
u ← Simpson ( 0 , П /2 , F )

. La récursivité (ou récursion):


XI

1. Définition et exemple :
Calcul de la factorielle de N
Programme Factorielle ;
Var I, N, P : entier ;
Début lire (N) ;
P ← 1;
Si N = 0 alors écrire (P) ;
53
Algorithmique

Sinon pour I ← 1 à N faire


P ← P * I
Ecrire (P)
Fin.
Une approche différente consiste à utiliser le fait que la factorielle de N est
simplement le produit de N par la factorielle de (N – 1)
Càd,
Factorielle (N) = N * factorielle (N – 1)
Cela est vrai pour tout N plus grand que 1. De plus, factorielle (0) = 1.
Ainsi, un algorithme possible pour calculer la factorielle de N est :
Fonction factorielle ( n : 0 .. 32767) : entier ;
Début
Si n = 0 alors factorielle ← 1
Sinon factorielle ← n * factorielle ( n – 1 )
Fin ;
La dernière ligne du programme (sinon …) pré suppose que le programme doit
être exécuté à nouveau, mais avec le paramètre actuel moins 1. La figure suivante
illustre l’exécution du programme lorsque N = 3.
Factorielle (3)
Si 3 = 0 alors …
sinon fact ← 3 * fact (2)
si 2 = 0 alors …
sinon fact ← 2 * fact (1)
si 1 = 0 alors …
sinon fact ←1 *fact(0)
si 0 = 0 alors fact ← 1

La figure montre comment le calcul de factorielle(3) implique le calcul de


factorielle (2) qui implique à son tour celui de factorielle(1) etc. les boites
illustrent les quatre exécutions requises de la fonction, et les nombres à gauche
représentent les résultats cumulatifs après chaque exécution.
L’algorithme utilisé pour calculer la factorielle de N est exprimé en terme d’un
algorithme pour calculer la factorielle de (N – 1). C’est pourquoi on peut
considérer que l’algorithme est exprimé en référence à lui­même, son entrée N

54
Algorithmique

devant être remplacée par (N – 1). Le terme utilisé pour cette forme d’expression
est la récursion : un algorithme récursif est un algorithme qui s’appelle lui­même.
On évite le mouvement apparent de retour sur soi­même de la récursion en
s’assurant que l’entrée des appels récursifs successifs devient progressivement
plus « simple ». On doit alors rencontrer un cas limite dans lequel l’entrée est si
« simple » que le traitement peut­être effectué sans qu’il n’ait plus à s’appeler
lui­même. Le cas limite peut­être considéré comme une issue de secours qui
assure en tout état de cause la fin de l’exécution. Tout algorithme récursif doit
comporter une issue de secours et l’entrée doit être simplifiée progressivement
jusqu’à pouvoir atteindre cette issue de secours.
5. La récursivité en arbre :
Un autre modèle courant de calcul est appelé récursivité en arbre. Prenons pour
exemple la suite de Fibonacci où chaque nombre est la somme des deux
précédents :
0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21, …
En général les nombres de Fibonacci peuvent être définis par la règle suivante :

0 si n = 0
Fib (n) = 1 si n = 1
Fib (n – 1) + Fib (n – 2) autrement
Cette définition se traduit immédiatement par une procédure récursive calculant
les nombres de Fibonacci :
Algorithme fib (n : 0 .. 32 767) : entier ;
Début
Si n = 0 alors fib ← 0
Sinon si n = 1 alors _ fib ← 1
Sinon fib ← fib (n – 1) + fib (n –
2)
Fin.

55
Algorithmique

Types de données abstraites

. Introduction
I
Dans les chapitres précédents, nous nous sommes penchés sur les méthodes de
calcul et le rôle des procédures dans la construction des programmes.
Nous avons vu comment employer des données primitives (les nombres) et des
opérateurs primitifs (les opérateurs arithmétiques) et comment combiner des
procédures pour former de nouvelles procédures par composition,
branchements conditionnels et emploi de paramètres.
Nous avons vu qu’une procédure peut être considérée comme un modèle local
décrivant l’évolution d’un processus et avons classé, étudié et effectué des
analyses algorithmiques simples de quelques modèles courant de processus
incarnés par des procédures. Nous avons vu que les procédures d’ordre
supérieur élèvent le niveau de notre langage en nous rendant capable de manipuler
et, par là­même, de résonner en termes de méthodes générales de calcul (ex :
calcul de l’intégrale d’une fonction).
Nous allons à présent nous intéresser à l’étude de données plus complexes. La
plupart des programmes opéraient sur des données numériques (ou autres)
simples. Or les données simples ne sont pas suffisantes pour de nombreux
problèmes que nous voudrions traiter par ordinateur.
Les programmes sont généralement conçus pour modéliser des phénomènes
complexes, et le plus souvent, nous devons construire des objets de calculs
composés de plusieurs parties afin de modéliser des phénomènes du monde
matériel ayant plusieurs aspects. Aussi, tandis que notre objectif, dans les
précédentes parties, était la construction d’abstractions en combinant des
procédures pour former des programmes composés, nous envisageons dans ce
qui suit, un autre point clé du langage de programmation : les ressources qu’il
offre pour construire des modèles abstraits en combinant des objets­données
pour former des données composées.
Pourquoi composer des données dans un langage de programmation ?
Pour les mêmes raisons qui nous ont conduits à composer des procédures :
élever le niveau des concepts qui nous permettent de concevoir nos
programmes, accroître la modularité de nos modèles et augmenter le pouvoir
d’expression de notre langage. De même que la possibilité de définir des
procédures nous permet de traiter des processus à un niveau plus élevé que celui
des opérateurs primitifs du langage, de même la possibilité de construire des
objets­données composés nous permet de traiter des données à un niveau plus

56
Algorithmique

élevé que celui des objets­donnée primitifs du langage.

XI
I. Abstraction des données et des procédures :
Considérons la conception d’un système permettant les calculs arithmétiques
avec les nombres rationnels. Nous pourrions imaginer un opérateur rationnel +rat
qui prenne deux nombres rationnels comme arguments et calcule leur somme.
Décomposé en données plus simples, un nombre rationnel peut être vu comme
un couple d’entiers : un numérateur et un dénominateur. Ainsi nous pourrions
concevoir un programme dans lequel chaque nombre rationnel serait représenté
par deux entiers (un numérateur et un dénominateur) et où +rat serait réalisé par
deux procédures (l’une produisant le numérateur de la somme et l’autre
produisant le dénominateur).
Mais ceci serait maladroit car il nous faudrait garder en mémoire quels
numérateurs sont associés à quels dénominateurs.
Dans un système devant effectuer de nombreuses opérations sur un grand
nombre de rationnels, la multiplication de ces détails encombrerait
substantiellement le programme, sans parler des conséquences fâcheuses qu’elle
aurait sur notre compréhension.
Il serait préférable de « coller ensemble » un numérateur et un dénominateur
pour former un couple – un objet­donnée composé – que nos programmes
pourraient manipuler d’une manière compatible avec notre idée de nombre
rationnel, comme une seule unité conceptuelle.
L’utilisation de données composées nous permet aussi d’accroître la modularité
de nos programmes. Si nous pouvons manipuler les nombres rationnels
directement comme des objets à part entière, alors nous pouvons séparer la
partie de notre programme qui traite des nombres rationnels eux­mêmes, de la
façon dont les nombres rationnels sont représentés par des couples d’entiers ;
La technique générale qui consiste les parties d’un programme définissant la
représentation des objets­donnée de celle exprimant leurs utilisations est une
méthode puissante de construction appelée abstraction des données.
Nous allons voir comment les abstractions de données rendent les programmes
plus faciles à créer, à mettre à jour et à modifier.
L’utilisation de données composées amène un réel progrès dans le pouvoir
d’expression d’un langage de programmation. Considérons l’idée de former une
« combinaison linéaire » ax+by. Nous aimerions écrire une procédure qui
accepterait a, b, x et y comme arguments et retournerait la valeur de ax+by. Ceci
ne présente pas de difficulté si les arguments sont des nombres parce que nous
pouvons immédiatement définir la procédure :
Pour combinaison_linéaire :a :b :x :y
57
Algorithmique

Rends a*x+b*y
Fin
Mais supposons que nous ne nous intéressons pas seulement aux nombres.
Nous voudrions, par exemple, exprimer en termes de procédure, l’idée de former
une combinaison linéaire chaque fois qu’on a défini l’addition et la multiplication
_ pour des nombres rationnels, des nombres complexes ou toute autre chose.
Nous pourrions l’exprimer par une procédure de la forme :
Pour combinaison_linéaire :a :b :x :y
Rends add (mult :a :x) (mult :b :y)
Fin
Où add et mult ne sont plus les opérateurs primitifs + et * mais des opérateurs
plus complexes qui réalisent les opérations appropriées sur toutes sortes de
données passées en argument a, b, x et y. Le point fondamental est que la seule
chose que combinaison_linéaire doit connaitre sur a, b, x et y est que les
opérateurs add et mult réalisent les opérations appropriées. Du point de vue de
la procédure combinaison_linéaire, peu importe ce que sont a, b, x et y et,
moins encore, peu importe la connaissance de leur représentation en terme de
données plus primitives.
On peut concevoir un type de données abstraites comme la connaissance d’un
modèle mathématique et des opérations qui lui sont associées.
Le même exemple montre pourquoi il est important que notre langage de
programmation fournisse la possibilité de manipuler directement des objets
composés. Sans cette possibilité, une procédure comme combinaison_linéaire
ne pourrait transmettre ses arguments à add et mult sans connaitre les détails de
leur structure.
Le possibilité de manipuler directement des procédures permet d’accroître de la
même manière le pouvoir d’expression d’un langage de programmation.
Nous avons vu que les procédures sont en fait des abstractions décrivant des
opérations composées sur des données.
Par exemple :
Pour Cube :x
Rends x * x * x
Fin
Ne définit pas le cube d’un nombre particulier, mais plutôt une méthode pour
obtenir le cube de n’importe quel nombre. Bien sûr, il est possible de se passer
de procédure, en écrivant des expressions telles que :

58
Algorithmique

3 * 3 * 3
x * x * x
y * y * y
sans jamais mentionner Cube explicitement. Ceci a pour inconvénient de nous
contraindre à toujours travailler au niveau des seules primitives du langage (ici la
multiplication) et non avec des procédures d’ordre supérieur. Nos programmes
pourraient calculer des cubes, mais sans que notre langage sache exprimer l’idée
d’évaluation au cube. La capacité de construire des abstractions en attribuant des
noms aux formes courantes est une des choses que l’on doit attendre d’un
langage de programmation puissant, ce qui permet ensuite de travailler
directement avec les abstractions. Les procédures nous donnent cette possibilité.
C’est pourquoi tous les langages de programmation, mis à part les plus primitifs,
offrent des mécanismes de définition de procédures.
Cependant, même dans le domaine du calcul numérique, nous serions
sérieusement limités dans notre capacité de créer des abstractions, si nous étions
restreints aux procédures dont les paramètres doivent être des nombres. Souvent
la même structure de programme sera utilisée par plusieurs procédures
différentes. Pour transformer de telles structures en concepts, nous aurons
besoin de construire des procédures qui peuvent prendre d’autres procédures
comme argument ou avoir pour résultat d’autres procédures. Les procédures qui
manipulent des procédures sont appelées des procédures d’ordre supérieur.
Considérons les trois procédures suivantes. La première calcule la somme des
entiers de a à b.
Pour Somme_entiers :a :b
Si a>b rends 0
Rends ( :a + Somme_entiers :a + 1 :b)
Fin
La deuxième calcule la somme des cubes des nombres entiers compris dans un
intervalle donné :
Pour Somme_cubes :a :b
Si a>b rends 0
Rends ((cube :a) + Somme_cubes :a + 1 :b)
Fin

La troisième calcule la somme d’une suite de termes de la série suivante qui


converge (très lentement) vers Π/8 :
59
Algorithmique

1/(1*3) + 1/(5*7) + 1/(9*11) + …


Pour Somme_pi :a :b
Si a>b rends 0
Rends ((1/( :a*( :a+2))) + (Somme_pi :a+4 :b))
Fin
Rq : b étant la borne supérieure de l’intervalle.
Ces trois procédures ont visiblement une structure commune. En grande partie
identiques, elles ne diffèrent que par le nom de procédure, la fonctionde a
utilisée pour calculer le terme courant et la fonction qui fournit la valeur suivante
de a.
Nous pouvons produire chacune de ces procédures en remplissant les cases
d’un même gabarit :
Pour <nom> :a :b
Si a>b rends 0
Rends (<f> :a + <nom> (<suivant> :a) :b))
Fin
Cette structure commune met en évidence l’existence d’une abstraction utile
sous­jacente. D’ailleurs, les mathématiciens ont depuis longtemps identifié
l’abstraction « somme d’une série » et inventé la notation « sigma » comme
dans :
b

∑ f(n) = f(a) + … + f(b)


n=a

pour exprimer ce concept.

De la même façon, en tant que programmeurs, nous aimerions que notre langage
soit assez puissant pour pouvoir écrire une procédure exprimant l’idée de
sommation elle­même et non pas des procédures calculant des sommes
particulières.
Pour Somme :f :a :suivant :b
Si a>b rends 0
Rends ( :f :a + Somme :f ( :suivant :a) :suivant :b)
Fin
Remarquons que Somme a pour arguments les bornes inférieure et supérieure a
et b, ainsi que les procédures f et suivant. Somme s’utilise comme n’importe
60
Algorithmique

quelle procédure, par exemple pour définir Somme_cubes :


Pour Somme_cubes :a :b
Somme :cube :a :suivant :b
Fin
Nous pouvons définir Somme_pi de la même façon :
Pour Somme_pi :a :b
Somme :f_pi :a :suivant_pi :b
Fin

Pour f_pi :x
Rends 1/( :x * ( :x + 2))
Fin

Pour Suivant_pi :x
Rends :x + 4
Fin

Ces procédures nous permettent de calculer une valeur approchée de Π :


8 * Somme_pi 1 1000
Somme peut servir pour bâtir d’autres concepts. Par exemple, la valeur
numérique approchée de l’intégrale définie d’une fonction f entre les bornes a et
b se calcule selon la formule :
b

∫ f(x) dx = [ f(a + dx/2) + f(a + dx +dx/2) + f(a + 2dx + dx/2) + …]dx


a
pour de petites valeurs de dx. Ce que nous pouvons exprimer directement dans
une procédure :

Pour Intégrale :f :a :b :dx


Rends (Somme :f :a + :dx/2 :add_dx :b) * :dx
Fin
Pour add_dx :x
Rends :dx + :x

61
Algorithmique

Fin

Exemple : Intégrale cube 0 1 0,01


⇨ 0,249987492

Conclusion :
Nous avons introduit la procédure Somme qui prend une procédure que nous
appellerons terme comme paramètre et calcule la somme des valeurs de terme sur
un intervalle spécifié.
Pour définir Somme, il est très important de pouvoir parler d’une procédure telle
que terme comme d’une entité de plein droit sans lien avec la façon d’exprimer
terme par des opérations plus primitives.
Revenons à la construction de l’arithmétique des nombres rationnels. Ceci
servira de base à notre exposé sur les données composées et les abstractions de
données. Comme pour les procédures composées, le but à atteindre est celui de
l’abstraction comme technique diminuant la complexité. Nous verrons comment
les abstractions de données nous permettent de tracer des barrières d’abstraction
appropriées entre les différentes parties d’un programme.
Nous verrons que la clé pour former des données composées est qu’un langage
de programmation fournisse une sorte de « glu » qui permette de combiner des
objets­donnée en objets–donnée plus complexes. En effet, nous découvrirons
comment former des données composées sans utiliser d’opérations « données »
spéciales mais seulement des procédures.
Quand nous traitions les procédures, nous disions qu’une procédure, employée
comme composant d’une procédure plus complexe, peut–être vue non
seulement comme un assemblage d’opérations particulières, mais aussi comme
une abstraction procédurale. Cela revient à dire que les détails de la réalisation de
la procédure peuvent être supprimés et qu’une procédure particulière peut,
elle­même, être remplacée par n’importe quelle autre procédure qui a le même
effet. Autrement dit, nous pourrions construire une abstraction qui séparerait la
façon d’utiliser la procédure des détails de sa réalisation en termes plus primitifs.
L’idée de base de l’abstraction de données est de structurer les programmes qui
utilisent des objets­données composés de façon qu’ils opèrent sur des
« données abstraites ». Ainsi, nos programmes emploieraient des données de
manière à ne pas prendre en compte que des hypothèses sur les données
strictement nécessaires à la réalisation de notre tâche. En même temps, une
représentation « concrète » des données est définie indépendamment des
programmes qui utilisent ces données. La liaison entre les deux parties de notre
système sera un ensemble de procédures appelées sél ec t
eur set cons truc t
eur s
qui fournissent aux données abstraites une représentation concrète. Pour illustrer
cette technique, nous allons voir comment construire un ensemble de procédures
pour manipuler les nombres rationnels.

62
Algorithmique

Supposons que nous désirions construire une arithmétique pour les nombres
rationnels. Nous devrions pouvoir les additionner, les soustraire, les multiplier,
les diviser et les comparer pour tester leur égalité.
Partons du principe que nous avons déjà un moyen de construire un nombre
rationnel à partir d’un numérateur et d’un dénominateur, et aussi, qu’à partir d’un
rationnel donné, nous savons extraire ou sélectionner son numérateur et son
dénominateur. Supposons, par ailleurs, que ce constructeur et ces sélecteurs
sont disponibles comme des procédures :
Créer_rat <n> <d> qui retourne le rationnel dont le numérateur est l’entier
<n> et le dénominateur est l’entier <d>.
Numér <x> qui retourne le numérateur du rationnel <x>
Dénom <x> qui retourne le dénominateur du rationnel <x>.

Si nous avons les trois procédures, nous pouvons alors ajouter, soustraire,
multiplier, diviser et tester l’égalité en employant les relations suivantes :
n1/d1 + n2/d2 = (n1d2 + n2d1) / d1d2
n1/d1 ­ n2/d2 = (n1d2 ­ n2d1) / d1d2
n1/d1 * n2/d2 = (n1 * n2) / d1*d2
n1/d1 / n2/d2 = (n1 * d2) / d1*n2
n1/d1 = n2/d2 ssi n1*d2 = n2*d1

Pour +rat :x :y
Créer_rat ((numér :x * dénom :y) + (dénom :x * numér :y)) (dénom :x *
dénom :y)
Fin

Pour ­ rat :x :y
Créer_rat ((numér :x * dénom :y) ­ (dénom :x * numér :y)) (dénom :x *
dénom :y)
Fin

Pour * rat :x :y
Créer_rat ((numér :x * numér :y) (dénom :x * dénom :y))
Fin

Pour /rat :x :y
Créer_rat ((numér :x * dénom :y) (dénom :x * numér :y))
Fin

Pour =rat :x :y
Rends ((numér :x * dénom :y) = (numér :y * dénom :x))
Fin

63
Algorithmique

Lar eprésent ationdesr at i


onnels:
Un nombre rationnel est représenté par un couple de deux entiers : le numérateur
et le dénominateur.
Alors, créer_rat, numér et dénom sont immédiatement construits ainsi :

Pour créer_rat :n :d
Rends Ph :n :d
Fin

Rq : Ph est la primitive phrase qui regroupe les deux entiers dans une structure
unique.

Pour numér:x
Rends Pr :x
Fin

Rq : Pr est la primitive premier qui retourne le premier élément d’une phrase.

Pour dénom :x
Rends Der :x
Fin

Rq : Der est la primitive dernier qui retourne le dernier élément d’une phrase.

64
Algorithmique

Structures de données dynamiques


Les types définis jusqu’à présent étaient tous de cardinalité finie. (Rappelons à ce
sujet que le type entier ne recouvre pas l’ensemble des entiers relatifs mais
uniquement une partie finie d’entre eux). Ceci a pour conséquence pratique que
l’espace nécessaire, mesuré en nombre de bits par exemple, pour mémoriser une
donnée d’un type fixé peut être déterminé à l’avance indépendamment de la
valeur qui lui sera associée.
Il n’en va pas toujours de même. Considérons, par exemple, la généalogie d’une
personne connue ou inconnue définie par :
La mention « inconnu » pour un individu qui ne l’est pas ;
La mention « connu » suivi de son « nom » et de la généalogie du père » et de
« celle de la mère », pour une personne connue.
Bien que l’on puisse prévoir que l’espace nécessaire pour ranger une telle
information sera fini quelque soit la personne considérée, il est par contre
impossible d’en connaître la taille à l’avance.
Pour répondre à ce genre de situation, on a recours à une allocation dynamique
de la mémoire, c’est­à­dire que la réservation de l’espace nécessaire se fait au
moment de l’exécution au fur et à mesure des besoins rencontrés. Ceci par
opposition à une allocation statique pour laquelle la réservation peut être faite
avant l’exécution. Les structures de données qui supposent une allocation
dynamique sont appelées des st r uctur esdy nami ques .

I. Données récursives :
Nous nous limitons à l’introduction de deux exemples.
Exemple1 :
Considérons la généalogie d’une personne mentionnée précédemment.
Type généalogie = enregistrement
Si connu alors
(nom : alpha ;
père, mère : généalogie)
fin ;
L’indicateur de cas connu est de type logique. Pour le cas d’une personne
inconnue, aucune information complémentaire n’est à prévoir et le sinon est
omis.
La valeur du type généalogie définie par :
(V, Mohammed, (V, Ali, (V, Brahim, (F), (F)), (F)), (V, Fatima, (F), (V, Aicha,
(F), (F)))) est représentée par la figure suivante. (V et F se lisent vrai et faux,
chaque paire de parenthèses associées se lit comme la construction d’une valeur
du type : généalogie ( ). )

65
Algorithmique

Figure 1.1

Exemple 2
Une « expression » consiste en : un terme suivi d’un opérateur suivi d’un terme.
Les deux termes sont les opérandes de l’opérateur qu’ils encadrent. Un terme est
soit une variable – représentée par un identificateur – soit une expression entre
parenthèses.
Un type de données propre à représenter de telles expressions peut être défini
par :
Type expression = enregistrement
Op : opérateur ;
op1, op2 : terme ;
fin ;
terme = enregistrement
si t alors (id : alpha)
sinon (sousexp : expression)
fin ;
où l’on suppose que les types « opérateur » et « alpha » ont été préalablement
définis. L’indicateur de type logique t est vrai ou faux selon que le terme est une
variable ou une sous­expression.

Les expressions suivantes :


1. x + y
2. x ­ (y * z)
66
Algorithmique

3. (x + y) * (z ­ u)
4. (x / (y + z)) * u

peuvent être représentées par les figures suivantes :

1 3 4
+ * *
V x F + F /
V y V x V x
V y F
2 F ­ +
­ V z V y
V x V u V z
F * V u
V y
V z

Figure 1.2.

II. Le type pointeur


Considérons la figure 2.1 qui représente sous une autre forme la généalogie de la
figure 1.1 du paragraphe précédent. L’indicateur Vrai ou Faux y est présent ainsi
que le nom, mais l’emboitement des généalogies y est figurée par deux flèches ou
pointeurs qui renvoient respectivement à la généalogie du père pour la plus à
gauche, à la généalogie de la mère pour la plus à droite.

(V, Mohammed, (V, Ali, (V, Brahim, (F), (F)), (F)), (V, Fatima, (F), (V, Aicha, (F), (F))))

V Mohammed

V Ali
V Fati
ma

V Brahim
F
F
V Aicha

67
Algorithmique

F
F
F
F

Figure 2.1

Si nous supprimons la généalogie d’une personne inconnue, nous pouvons du


même coup nous passer de l’indicateur. Certaines flèches, dans un tel cas, « ne
vont nulle part ». Nous pouvons le noter au moyen d’une flèche spéciale que
nous appellerons nil (null). C’est ce qui est fait dans la figure 2.2 où la flèche nil
est représentée par ╦.

Mohammed

Ali
Fati
ma

Brahim
Aicha

Figure 2.2

Supposons qu’après avoir établi la généalogie de Mohammed, nous découvrons


dans une étape ultérieure que la mère de Ali est Zahra dont la généalogie nous est
donnée par la figure 2.3. L’ajout de la flèche pointillée en remplacement de la
flèche nil (null) nous permet de compléter la généalogie de Mohammed, ceci par
une opération simple qui ne dépend pas du niveau de complexité de l’une ou de
l’autre généalogie.

Kenza

68
Algorithmique

Has
san
Ghit
a

Mina

Figure 2.3

La figure 2.4 nous suggère que le modèle proposé est parfaitement adapté pour
être représenté dans un espace linéaire. Bien que ce ne soit pas ici notre
préoccupation immédiate, c’est une arrière­pensée avouée que celle d’utiliser
pour le traitement un ordinateur dont nous savons que la mémoire est, en
première analyse, un espace linéaire. Insérer figure 2.4(voir page 18 polycop).
Ces considérations suffisent à justifier l’introduction d’un type de données,
pointeur, qui répond au fonctionnement des flèches.
Définition d'un pointeur :
Un pointeur est une variable contenant l'adresse d'une autre variable d'un type
donné. La notion de pointeur fait souvent peur car il s'agit d'une technique de
programmation très puissante, permettant de définir des structures dynamiques,
c'est­à­dire qui évoluent au cours du temps (par opposition aux tableaux par
exemple qui sont des structures de données statiques, dont la taille est figée à la
définition).
Un type de pointeur est défini par une déclaration de la forme :
Type Tp = ^T
Où la flèche verticale se lit « pointeur vers » et T est un type préalablement
défini.
Langage C :
Etant donné un type T quelconque, le type pointeur sur T s'écrit « T* »
Ainsi, la définition d'une variable p du type ««pointeur sur T » correspond à
l'instruction
T *p;
De fait, cette écriture ne fait qu'exprimer que la variable p pointe sur une zone

69
Algorithmique

mémoire dont la valeur *p est de type T.


Un type de pointeurs est donc associé à un type de données. L’information
figurée par un pointeur est plus riche que la seule flèche des schémas précédents
puisqu’elle comprend la connaissance du type des données visées par ces
pointeurs. Ceci différencie le pointeur des adresses du langage machine
(assembleur).
La valeur spéciale nil (null), mentionnée plus haut pour figurer la « flèche qui ne
va nulle part », est une valeur possible de tout pointeur p indépendamment de
son type. Elle lui est affectée par p := nil. (figure 2.5.a).
Hormis ce cas spécial, la valeur d’un pointeur p de type ^T est une variable
de type T. Cette variable est notée p^. Cependant, p^ n’est pas entièrement
assimilable à un identificateur de variable du type T. En effet, on ne suppose pas
a priori qu’un espace est réservé pour ranger la valeur de p^. La réservation d’un
tel espace est dynamique. Elle est explicitement demandée par l’instruction :
Nouveau (p) ;
Seule l’exécution préalable de cette instruction permet d’utiliser la référence p^
(figure 2.5.b).
Deux pointeurs p et q du même type ^T peuvent viser la même variable de
type T. C’est le cas après une affectation de la forme q := p (figure2.5.c).

p p
p := nil p^
Nouveau (p)
figure 2.5.a Figure2.5.b

p p^ p^
q q^
nouveau (p) ; q := p p
q q^
figure 2.5.c nouveau (p) ; q := p ; nouveau (p)
figure 2.5.d

nouveau(p) ; nouveau(p)
70
Algorithmique

p^

figure 2.5.e
L’espace alloué à un instant donné à la variable visée par un pointeur peut se
trouver libéré lors de l’exécution d’une instruction de la forme :
Nouveau(p) ou p := q
La gestion dynamique de la mémoire suppose que tout espace libre est pris en
compte et rendu disponible. Faute d’une telle disposition, qui doit être prévue
lors de la compilation, on pourrait, à l’exécution, déboucher sur une
neutralisation d’une partie importante de la mémoire.
Après l’exécution de la suite d’instructions :
Nouveau (p) ; q := p ; nouveau(p)
L’espace initialement réservé pour p^ reste réservé pour q^ (figure 2.5.d).
Une exécution ultérieure de q := p libérerait cet espace.
Après l’exécution de la suite d’instructions :
Nouveau (p) ; nouveau(p)
L’espace initialement réservé pour p^ devient libre. (figure 2.5.e).
Pour le reste, notamment lors des affectations de valeurs, l’identificateur p^
associé à un pointeur p du type ^T joue le rôle d’un identificateur de variable
du type T. On doit retenir que si un autre pointeur q du type ^T pointe vers la
même variable que p (figure 2.5.c) toute modification de la valeur de p^ est
une modification de la valeur de q^.

III. Représentation en mémoire


La représentation des données en mémoire doit satisfaire deux critères
principaux :
● Etre effective et non ambigüe ;
● Etre fidèle à l’organisation du type.
Le premier critère signifie non seulement que chacune des valeurs d’un type
donné est représentable, mais aussi que les différentes opérations définies sur le
type peuvent effectivement se faire sur les représentations.
Le second a pour sens que la complexité d’exécution d’une opération (ordre de
grandeur de la durée d’exécution) doit être celle qui apparaît sur la définition du
type. Par exemple, la référence à la ième composante x[i] d’un tableau x ne doit
pas se traduire par une exploration complète des éléments du tableau. (A

71
Algorithmique

compléter voir page 27 manuscrit)


1. Représentation des pointeurs
La notion de pointeur a été, dès son introduction, étroitement associée à celle
d’une représentation en mémoire. L’instruction nouveau(p), où p est un
pointeur de type ^T, a pour effet de réserver l’allocation mémoire pour une
variable de type T avec p pointant sur cette variable. La réservation consiste à
se donner à partir d’une adresse i le nombre de mots nécessaire pour la
représentation d’une variable de type T. Il suffit donc, pour représenter le
pointeur p de représenter l’adresse i de la variable créée suivie (éventuellement)
d’une indication du type. L’association du type au pointeur peut être prise ne
compte sous une autre forme lors de la traduction du programme. Dans ce cas,
la représentation se réduit à une simple adresse.

ip
i , type T p : ^T

i Variable
type T

iq i , type q : ^T

Connaissant l’adresse ip du pointeur p, on sait retrouver l’adresse i de la


variable pointée par p. Un second pointeur q de type ^T peut pointer vers la
même variable.

V. Structures fondamentales
I
Les structures fondamentales de base : tableau, enregistrement, pointeur ont été
définies comme des types de données.
D’autres structures : listes linéaires, arbres, …, jouent un rôle important. Mais il

72
Algorithmique

n’est pas nécessaire pour les définir d’introduire des types d’un caractère
entièrement nouveau.
1. Notion de suite (ou liste)
Dans la vie courante, on a souvent affaire à des collections homogènes d’objets
ou d’individus, dont la taille est susceptible de varier à la suite d’ajouts ou de
retraits d’éléments, organisées de la manière suivante :
● On sait repérer l’une, l’autre ou les deux extrémités de la structure ;
● Les éléments sont ordonnés, et l’on peut donc définir le suivant et le précédent
de tout élément qui n’est pas une extrémité ;
● Pour atteindre un élément particulier, on doit nécessairement parcourir
séquentiellement la structure à partir d’une extrémité, jusqu’à le trouver.
On peut citer comme exemples d’une telle organisation : une file de voyageurs
devant un guichet, une suite de fax arrivant sur un terminal, une file de voitures
devant un poste de douane, etc.
Nous appelons suite une telle organisation dynamique homogène. Selon le mode
d’exploitation d’une suite, on définit différentes structures de données que nous
allons présenter.
La notion de liste linéaire est celle d’une suite finie d’éléments qui est donnée
par un premier élément, et pour chaque élément un lien donnant accès à son
suivant. Une liste est une structure particulièrement souple : elle peut grandir ou
rétrécir à volonté, et ses éléments, qui sont accessibles à tout moment, peuvent
être insérés ou supprimés à n’importe quel endroit. Une liste peut être concaténée
à une autre ou divisée en deux sous­listes.
D’un point de vue mathématique, une liste est une suite, vide ou non d’éléments
d’un type donné (que nous désignerons par TypeElement). Il est habituel de
représenter une telle suite par :
a1, a2, …, an
Où n est positif, et où chaque ai est de type TypeElement. Le nombre n
d’éléments s’appelle la longueur de la liste. Si n>= 1, on dit que a1 est le
premier élément et an le dernier. Si n = 0, on parle de liste vide, sans
éléments.
Pour construire un type de données abstraites à partir de la notion
mathématique de liste, il nous faut définir un ensemble d’opérations sur des
objets de type LISTE. Comme c’est le cas pour les autres TDA que nous
présenterons, aucun ensemble unique d’opérations sur les listes ne peut
permettre de couvrir tous les besoins. Nous donnerons ici un sous ensemble
représentatifs d’opérations. Dans une section ultérieure, nous proposerons

73
Algorithmique

plusieurs structures de données pour représenter les listes.


Pour illustrer plusieurs opérations fréquentes sur les listes, considérons le cas
typique d’une liste de renseignements relatifs à une clientèle dont on désire
supprimer les éléments identiques. D’un point de vue conceptuel, ce problème
peut se résoudre assez simplement pour chaque élément de la liste, on retire de la
suite tous les éléments qui lui sont identiques. La mise en forme de l’algorithme
correspondant nécessite néanmoins que l’on définisse les opérations permettant
de trouver le premier élément d’une liste, de la parcourir, puis d’accéder à la
valeur d’éléments ou de les détruire.

6. Mise en œuvre des listes


Dans cette section, nous allons décrire quelques structures de données
permettant la représentation des listes : les tableaux, les pointeurs et les curseurs.
Nous essaierons de comparer l’efficacité des types de mise en œuvre dans la
réalisation des opérations sur les listes que chacun permet.
[Link] en œuvre des listes par tableau
Une telle mise en œuvre, où les éléments des listes sont mémorisés dans les
cellules contiguës d’un tableau, facilite le parcours de listes et l’insertion
d’éléments en fin de liste (nous dirons souvent « en queue »).
En revanche, l’insertion d’un élément au milieu d’une liste nécessite de déplacer
tous les éléments suivants dans le tableau pour créer la place nécessaire au
nouveau venu. De même, la suppression d’un élément quelconque du tableau
demande que l’on déplace tous ses successeurs pour « boucher le trou »
produit, sauf s’il s’agit du dernier élément de la liste.

1 Premier élément Liste


2 Deuxième élément

dernier
Dernier élément

vide
longmax
Figure 2.1

74
Algorithmique

Dans la mise en œuvre par tableau le type LISTE consiste en un enregistrement à


deux champs. Le premier est un tableau dont la taille est suffisante pour contenir
les listes les plus grandes que l’on désire traiter. Le second champ est un entier
dernier indiquant la position du dernier élément de la liste. Le ième élément de la
liste est placé dans la ièmecellule du tableau pour les valeurs :
1 <= i <= dernier (figure 2.1).
Les positions dans la liste sont représentées par des entiers. La ième position est
ainsi représentée par l’entier i. La fonction FIN (L) ne fait que renvoyer la valeur
dernier +1.

Const
Longmax = 100 ; {ou n’importe quelle constante appropriée}
Type
LISTE = enregistrement
Elements : tableau [1.. longmax] de TypeElement ;
Dernier : entier ;
Fin ;
Position = entier ;
Fonction Fin (var L : LISTE) : position ;
Début
Retourne ( [Link] + 1)
Fin ;

[Link] en œuvre des listes par pointeurs


Une liste est un ensemble de cellules « simplement chaînées ». C’est exactement
ce que traduit la mise en œuvre que nous allons décrire maintenant, où des
pointeurs sont utilisés pour lier (càd créer une chaîne simple entre) les éléments
successifs d’une même liste.
Grâce à cette mise en œuvre nous n’aurons plus à faire de la place ou combler
de vide pour insérer ou supprimer des éléments dans les listes, puisque nous
n’utiliserons plus comme zone de stockage un ensemble de cellules contiguës de
la mémoire de la machine. En revanche, nous aurons à payer ce confort en
allouant de la place supplémentaire pour chaque pointeur utilisé.
Dans cette représentation, une liste est constituée de cellules, chacune étant
composée d’un élément de la liste et d’un pointeur sur la cellule suivante. Si la
75
Algorithmique

liste est composée de la suite a1, a2, …, an, la cellule contenant aipointe sur celle
contenant ai+1, pour i = 1, 2, …, n­1. La cellule contenant an pointe sur nil. Il
existe aussi une cellule de tête, appelée en_tête, qui pointe sur a1; en_tête ne
contient pas d’élément. Dans le cas d’une liste vide, en_tête pointe sur nil et la
structure ne contient pas d’autre cellule.
Remarque :
L’utilisation de cellules à part entière pour les cellules de tête facilite la mise en
œuvre des opérations sur listes en Pascal. Il est possible d’utiliser des pointeurs
pour les têtes de listes quand on désire implanter ces opérations de manière que
les insertions et les suppressions en tête de liste soient traitées à part.

En­tête liste

Pour les listes simplement chaînées, il est habituel d’utiliser une définition du type
position quelque peu différente de celle que nous avons donné dans la mise en
œuvre par tableau. Ici, la position i est un pointeur sur la cellule contenant un
pointeur sur ai pour i = 2, 3, …, n. La position 1 est un pointeur sur l’en­tête,
et la position FIN(L) est un pointeur sur la dernière cellule de L.
Le type d’une liste est, pour cette raison, le même que celui de position : c’est un
pointeur sur une cellule, sur la tête de liste plus précisément.

Type
Typecellule = enregistrement
Element : typeElement ;
Suivant : ^typecellule ;
Fin ;
LISTE = ^typecellule ;
Position = ^typecellule ;

Fonction FIN (L : LISTE) : position


{FIN retourne un pointeur sur la dernière cellule de L}
var
q : position
début
q := L ;
tant que q^.suivant <> nil faire
q := q^.suivant ;
retourne (q) ;
fin ;
76
Algorithmique

La fonction FIN est représentée ci­dessus. Le pointeur q part de la tête de liste,


puis parcourt celle­ci jusqu’à la fin, donc jusqu’à ce que q pointe sur nil.
Remarquons que cette mise en ouvre n’est pas judicieuse car elle oblige à
parcourir la liste en entier chaque fois que l’on désire connaître FIN (L). Nous
pourrions représenter les listes en incluant un pointeur sur la dernière cellule.

Procédure d’insertion d’un élément dans une liste représentée par tableau :

Procédure Insérer (x : TypeElement ; p : position ; var L : LISTE) ;


{insérer x à la position p dans la liste L}
var q : position ;
début
Si [Link] >= longmax
alors écrire « la liste est pleine »
Sinon
Si (p > [Link] +1) ou (p < 1)
Alors écrire « position non valide »
Sinon
Début
Pour q := [Link] downto p faire
{décaler les éléments aux positions p, p+1,..
d’un rang vers la fin de L}
[Link] [q+1] := [Link] [q] ;
[Link] := [Link] + 1 ;
[Link] [p] := x
Fin ;
Fin ; {insérer}

7. Les piles

a.Définition
Une pile est un type de liste particulier dans lequel toute insertion ou
suppression d’élément se fait à une extrémité, appelée dessus ou sommet de
la pile. L’appellation anglo­saxonne consacrée pour les piles est « LIFO list »
ou liste « dernier entré premier sorti ». Ceci s’explique intuitivement en
comparant une pile à un jeu de cartes ou à une pile de livres sur une table. Si
l’on se refuse de manipuler plus d’un élément à la fois, les seules actions
possibles sont l’ajout (empilement) ou le retrait (dépilement) d’un
élément au sommet.
Un type de données abstraites de la famille des piles comprend souvent les cinq
77
Algorithmique

opérations suivantes :
[Link] (P) : vider le contenu de la pile P. Cette opération est identique à
l’opération correspondante pour les listes en général.
[Link] (P) : retourner (sans le dépiler) l’élément au sommet de la pile P.
[Link] (P) : supprimer physiquement l’élément au sommet de la pile P.
[Link] (x, P) : insérer l’élément x au sommet de la pile P. L’ancien élément le
plus haut devient le deuxième et ainsi de suite.
[Link] (P) : cette fonction retourne VRAI si P est vide et FAUX sinon.
Chaque mise en œuvre de liste que nous avons donnée est aussi valable pour les
piles. Une pile est un cas particulier de liste et la même réflexion s’applique aux
opérations associées.
L’implantation par pointeurs d’une pile est facilitée par le fait que EMPILER et
DEPILER n’opèrent que sur la tête de liste et la première cellule de la liste.
En fait les têtes de listes peuvent être des pointeurs (ou des curseurs), et on plus
des cellules à part entière, puisque la notion de « position » est inutile pour les
piles.
Néanmoins la mise en œuvre par tableau que nous avons donnée précédemment
n’est pas vraiment judicieuse : les opérations DEPILER et EMPILER
provoquent le déplacement de toute la liste dans un sens ou dans l’autre,
entraînant un temps d’exécution proportionnel au nombre d’éléments dans la
pile.
En remarquant que les insertions comme les suppressions n’ont lieu qu’au
sommet de la pile, on peut imaginer une mise en œuvre plus efficace : on
« attache » le fond de la pile à l’indice le plus fort du tableau et on empile les
éléments en direction des indices décroissants. La pile croît donc vers le haut et
décroît vers le bas. Un curseur appelé sommet indique la position à tout
instant du premier élément de la pile.

sommet 1er élément de la pile

2ème élément

longmax Dernier élément

Pour cette mise en œuvre des piles à partir d’un tableau, il faut définir le type de
données abstraites PILE par :
Type

78
Algorithmique

PILE = enregistrement
Sommet : 1..longmax ;
Elements : tableau [1 .. longmax] de TypeElement
Fin ;

Une variable de type PILE sera donc entièrement déterminée par la donnée de la
suite elements [sommet] , elements[sommet + 1], …, elements [longmax].
Remarquez que si sommet = longmax + 1, la pile est vide.

[Link]édures et fonctions sur les piles :

Procédure RAZ (var P : PILE) ;


Début
[Link] := longmax +1
Fin ;

Fonction VIDE (P : PILE) : booléen ;


Début
Si [Link] > longmax alors retourne (VRAI)
Sinon retourne (FAUX)
Fin ;

Fonction SOMMET (var P : PILE) : TypeElement ;


Début
Si VIDE(P) alors écrire (‘la pile est vide’)
Sinon retourne ([Link] [[Link]])
Fin ;

Procédure DEPILER (var P : PILE) ;


Début
Si VIDE(P) alors écrire (‘la pile est vide’)
Sinon [Link] := [Link] + 1
Fin ;

Procédure EMPILER (x : TypeElement ; var P : PILE) ;


Début
Si [Link] = 1 alors écrire (‘la pile est pleine’)
Sinon
Début
[Link] := [Link] – 1
[Link] [[Link]] := x
Fin ;
Fin ;
79
Algorithmique

8. Les files
a.Définition

Une file est un autre type particulier de liste où les éléments sont insérés en
queue et supprimés en tête. Le nom vient des files d’attente à un guichet, où le
« premier arrivé est le premier parti ». Ce qui justifie le terme anglo­saxon
« FIFO list ». Les opérations sur une file sont analogues à celles définies sur les
piles, la principale différence provenant du fait que les insertions se font en fin
de liste plutôt qu’en début.
Donnons la liste des opérations dont nous souhaitons disposer sur les files :
[Link] (F) : transforme F en file vide.
[Link] (F) : fonction qui retourne l’élément en tête de la file F.
[Link] (x,F) : insère l’élément x à la fin de la file F.
[Link] (F) : supprime le premier élément de la file F.
[Link] (F) : fonction booléenne qui retourne VRAI si la file F est vide et FAUX
sinon.

[Link] en œuvre des files par pointeurs


Comme pour les piles, toute mise en œuvre de liste est valable pour les files. Il
est possible toutefois de tirer partie du fait que les insertions n’ont lieu qu’en
queue de file pour améliorer l’efficacité de l’opération ENFILER.
Plutôt que de parcourir la file d’un bout à l’autre pour insérer un nouvel élément
en queue de toute façon, nous pouvons garder un pointeur (ou un curseur) sur le
dernier élément. Comme pour toutes les listes nous avons aussi besoin d’un
pointeur sur le début de la liste. Pour les files ce pointeurs est indispensable aux
opérations TETE et DEFILER.
Nous allons étudier, à présent, une implantation de file à partir de pointeurs.

Type
TypeCellule = enregistrement
Element : TypeElement ;
Suivant : ^TypeCellule
Fin ;

Nous pouvons ensuite définir une file comme la donnée de deux pointeurs :
un premier sur la tête de liste et un second sur la queue. La première cellule
d’une file est la cellule factice dont le champ element n’est pas à prendre en
compte. Cette convention, comme nous l’avons dit plus haut, permet une
représentation simplifiée des files vides.

80
Algorithmique

Définissons le type FILE :

Type
FILE = enregistrement
Tete, queue : ^TypeCellule
Fin ;

h. Opérations sur les files :

Procédure RAZ (var F : FILE) ;


Début
Nouveau ([Link]) ; {création d’une cellule de tête}
[Link]^.suivant := nil ;
[Link] := [Link] ; {l’en­tête est à la fois la dernière et la première cellule}
Fin ;

Fonction VIDE (F : FILE) : booléen ;


Début
Si [Link] = [Link] alors retourne (VRAI)
Sinon retourne (FAUX)
Fin ;

Fonction TETE (F : PILE) : TypeElement ;


Début
Si VIDE(F) alors écrire (‘la file est vide’)
Sinon retourne ([Link]^.suivant^.elements)
Fin ;

Procédure DEFILER (var F : FILE) ;


Début
Si VIDE(F) alors écrire (‘la File est vide’)
Sinon [Link] := [Link]^.suivant
Fin ;

Procédure ENFILER (x : TypeElement ; var F : FILE) ;


Début
Nouveau ([Link]^.suivant) ; {ajouter une nouvelle cellule en queue de file}
[Link] := [Link]^.suivant ;
[Link]^.element := x ;
[Link]^.suivant := nil
Fin ;

81

Vous aimerez peut-être aussi