Algorithmique Cours
Algorithmique Cours
Introduction
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. Celleci 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
. 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 ellemême. On doit
3
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.
5
Algorithmique
6
Algorithmique
[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
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.
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
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
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 audelà 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.
XN) comportera une boucle dans laquelle X sera multiplié par luimê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
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
20
Algorithmique
E
Fin
où i 1 = a et i n = b et i j = successeur (i j1)
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.
Condition
entrée/sortie Traitement Fin
21
Algorithmique
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
23
Algorithmique
Que pensezvous de cet algorithme ? N’y atil pas moyen d’écrire un algorithme plus
efficace ?
24
Algorithmique
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.
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
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
. 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
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 sousensemble
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
v. Le type intervalle :
On va pouvoir définir des types intervalles (sousensembles) de la
façon suivante :
Type
Semaine = (lundi, mardi, mercredi, jeudi, vendredi, samedi,
dimanche) ;
31
Algorithmique
32
Algorithmique
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)
35
Algorithmique
36
Algorithmique
Début
A ← "Gloubi"
B ← "Boulga"
C←A&B
Fin
La valeur de C à la fin de l’algorithme est "GloubiBoulga"
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 ;
Fin.
[Link] :
d : date
29
9
1994
p : personne
Ch
aï
ma
â
El
Idr
iss
i
39
Algorithmique
29 9 1994
Fe
m
me
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).
Var x:T
40
Algorithmique
Nom : alpha ;
Prénom : alpha ;
Naissance : date ;
Sexe : sex ;
Lien : entier ;
Fin ;
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 ik1.
Connaissant les rangs ik1 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 = ik1 + a[ik].lien
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
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.
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é.
Déplace en B
Dessine un carré de 20 cm de côté
Généralisation :
Module nomdumodule (paramètres formels)
Corps du module
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
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 :
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’entê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 caslà, 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
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 fn3 + 2 fn2 + 4 fn1 + 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.
S1(2) = 0
52
Algorithmique
u = dx / ( a2 cos2 x + b2 sin2 x ) ½
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
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 luimême.
On évite le mouvement apparent de retour sur soimê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
luimê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
. 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 objetsdonné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
objetsdonnées composés nous permet de traiter des données à un niveau plus
56
Algorithmique
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 objetdonné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 euxmê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 objetsdonné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
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 ellemê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
Pour f_pi :x
Rends 1/( :x * ( :x + 2))
Fin
Pour Suivant_pi :x
Rends :x + 4
Fin
61
Algorithmique
Fin
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
objetsdonné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,
ellemê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 objetsdonné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
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
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
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 sousexpression.
3. (x + y) * (z u)
4. (x / (y + z)) * u
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.
(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
Mohammed
Ali
Fati
ma
Brahim
Aicha
Figure 2.2
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èrepensé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
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^.
71
Algorithmique
ip
i , type T p : ^T
i Variable
type T
iq i , type q : ^T
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 souslistes.
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
dernier
Dernier élément
vide
longmax
Figure 2.1
74
Algorithmique
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 ;
liste est composée de la suite a1, a2, …, an, la cellule contenant aipointe sur celle
contenant ai+1, pour i = 1, 2, …, n1. 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.
Entê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’entê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 ;
Procédure d’insertion d’un élément dans une liste représentée par tableau :
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 anglosaxonne 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.
2ème é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.
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 anglosaxon
« 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.
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
Type
FILE = enregistrement
Tete, queue : ^TypeCellule
Fin ;
81