Documentos de Académico
Documentos de Profesional
Documentos de Cultura
CHAPITRE 1
La principale raison pour laquelle nous écrivons des programmes informatiques (logiciels) est
de chercher à utiliser les ordinateurs pour résoudre les problèmes qui nous intéressent. Les
programmes informatiques sont le seul moyen par lequel nous pouvons apprendre à la
machine à traiter un problème donné. C’est l’utilisateur qui doit communiquer à l’ordinateur
les capacités requises pour effectuer un certain traitement : L’ordinateur ne sait faire que ce
qu’on lui a appris à faire !
Les problèmes à résoudre peuvent être simples ou complexes, comme par exemple :
Cette liste n’est pas exhaustive mais elle nous permet tout de même d’avoir une idée de la
diversité des problèmes qui peuvent nous intéresser.
Nous résolvons tous de nombreux problèmes chaque jour. Par exemple, nous nous habillons
correctement chaque matin pour aller au travail ou à l’école, nous préparons nos repas tous les
jours, nous apprenons nos leçons tous les soirs, et ainsi de suite. Les méthodes mises en
œuvre pour résoudre de tels problèmes sont généralement informelles parce que ne reposant
pas une démarche logique rigoureuse. Il faut cependant souligner que la formulation des
solutions préconisées implique la définition de la liste des actions à entreprendre ainsi que
l’ordre dans lequel ces actions doivent être entreprises, même si ces actions et cet ordre
peuvent ne pas être exprimés avec beaucoup de précision. Malgré cette souplesse apparente,
les méthodes informelles que nous utilisons pour résoudre nos problèmes quotidiens sont
parfaitement satisfaisantes.
Cependant, lorsque nous utilisons l’ordinateur pour résoudre un problème, nous devons être
davantage plus précis. Lorsque les ordinateurs sont impliqués, la résolution d’un problème
passe généralement par le développement d'un algorithme. De façon simple, un algorithme
est la description des opérations à effectuer pour accomplir un certain traitement. Cette
description doit spécifier la liste des opérations à effectuer actions à entreprendre une suite
d’instructions organisées et suffisamment détaillées conduisant à la résolution d’un problème.
La signification de ce terme est assez proche de celle de recette, procédé ou méthode.
Toutefois, le terme algorithme nécessite une définition plus précise lorsqu’on aborde la
programmation des ordinateurs. Les instructions qui indiquent à l’ordinateur comment
effectuer une tâche sont nécessairement plus précises que celles qui indiquent à un cuisinier
comment préparer un gâteau ou à un fabricant de vélos comment les assembler.
Ainsi, le processus de résolution des problèmes à l’aide des ordinateurs est souvent plus
formel que le procédé que nous utilisons pour la résolution de nos problèmes quotidiens. Le
processus de résolution des problèmes à l’aide des ordinateurs se décline en trois grandes
étapes distinctes :
Figure 1.1. Étapes du processus de résolution d’un problème par les ordinateurs
Problème
Analyse informatique
Algorithmique
Algorithmique
Structures de données
Algorithme
Programme
Exploitation Maintenance
Définition. Algorithme
Une algorithme est une suite d’opérations que devra effecteur un ordinateur pour arriver, en
un temps fini, à un résultat donné, que nous définirons par une postcondition, à partir d’une
situation donnée, que nous définirons par une précondition. La suite d’opérations sera
composée d’actions élémentaires appelées instructions que nos allons présenter par la suite.
Les directives ou commandes qui indiquent à la machine ce qu’elle doit faire pour obtenir la
solution désirée sont appelées des instructions. Nous pouvons donc en première analyse
représenter un algorithme par une suite d’instructions de la forme :
instruction1 ;
instruction2 ;
…
instructionn ;
Par exemple,
algorithme Cercle ;
{ Programme pour calculer la surface et le périmètre d’un cercle }
const
pi = 3.14159 ;
var
Rayon, Circonférence, Surface : réel ;
début
écrire('Quelle est la valeur du rayon ? ')
lire(Rayon) ;
Circonférence 2*pi*Rayon ;
Surface pi * Rayon * Rayon ;
écrire('Rayon : ', Rayon) ;
écrire('Circonférence : ', Circonférence) ;
écrire('Surface : ', Surface)
fin.
algorithme Cercle ;
Elle commence par le mot réservé algorithme et se termine par le nom de l’algorithme qui est
ici Cercle. Le corps de l’algorithme comporte trois sections. Les deux premières décrivent les
données utilisées par l’algorithme ; elles constituent la partie des déclarations de l’algorithme.
La première section de ce bloc définit la constante symbolique pi et lui attribue la valeur
3.14159.
La deuxième section du bloc déclare les variables Rayon, Circonférence et Surface comme
étant des variables de type réel. Déclarer une variable signifie donner un nom et réserver un
emplacement dans la mémoire centrale de l’ordinateur pour pouvoir y stocker une information
du type spécifié. C’est à travers le nom donné à l’emplacement mémoire réservé que l’on peut
accéder à l’information qui s’y trouve. Nous avons donc dans notre exemple réservé trois
emplacements mémoire pour pouvoir y stocker des informations de type réel.
Mémoire centrale
La troisième section du bloc décrit les actions devant être effectuées par l’algorithme et est
appelée la partie instruction de l’algorithme. Celle-ci consiste en la description de sept
instructions (actions) se terminant chacune par un point virgule et délimitées par les mots
réservés début et fin. D’abord l’ordinateur demande à l’utilisateur de communiquer la valeur
du rayon du cercle. Cette valeur est lue et stockée dans une variable appelée Rayon. Ensuite
on calcule la circonférence et la surface du cercle en utilisant les formules habituelles. Les
deux résultats sont respectivement stockés dans les variables appelées Circonférence et
Surface. Enfin, on affiche à l’écran les valeurs des variables Rayon, Circonférence et Surface.
L’algorithme se termine par un point.
Ce paramètre est ici une chaîne de caractères entre apostrophes. Elle a pour objet de
reproduire textuellement à l’écran ou sur l’imprimante le texte :
Un caractère peut être une lettre majuscule ou minuscule, un chiffre, un signe de ponctuation,
ou un espace. Une chaîne de caractères, ou chaîne pour abréger, est une suite quelconque de
caractères entre apostrophes. Cette suite peut être vide ou de longueur quelconque. Si elle
contient une apostrophe, celle-ci sera doublée.
Ce paramètre est une variable de type réel. Elle a pour objet de récupérer la valeur du rayon
saisie au clavier par l’utilisateur et de la ranger dans un emplacement de la mémoire centrale
de l’ordinateur que nous avons nommé Rayon.
La troisième et la quatrième instructions ont la même structure. Elles sont formées d’une
expression à droite et du nom d’une variable à gauche, les deux étant séparées par le symbole
« » appelé opérateur d’affectation. Par exemple, la troisième a pour objet de calculer la
valeur de l’expression 2 * pi * Rayon et de ranger le résultat dans un emplacement de la
mémoire centrale de l’ordinateur que nous avons nommé Circonférence.
Les trois dernières instructions ont également la même structure. Elles sont chacune formées :
Le premier paramètre est une chaîne de caractères qui sera reproduite textuellement à l’écran
ou sur l’imprimante ; le deuxième est une variable (expression) dont la valeur sera reproduite
à l’écran ou sur l’imprimante. Par exemple, l’exécution de la sixième instruction affichera à
l’écran quelque chose du genre :
Circonférence : ddddd.dd
Voici d’autres exemples d’algorithmes. Nous laissons le soin au lecteur de comprendre ce que
fait chacun de ces algorithmes.
Exemple 1
algorithme Addition ;
var
Nombre1, Nombre2, Somme : entier ;
début
écrire('Quelle est la valeur du premier nombre ? ') ;
lire(Nombre1) ;
écrire('Quelle est la valeur du deuxième nombre ? ') ;
lire(Nombre2) ;
Somme Nombre1 + Nombre2 ;
écrire('Somme : ', Somme) ;
fin.
Exemple 2
algorithme Bienvenue ;
début
écrire('Bienvenue dans le monde des programmeurs ! ') ;
fin.
Exemple 3
algorithme Conversion ;
const
FacteurConversion = 2.54 ;
var
pouces, centimètres : réel ;
début
écrire('Quel est le nombre de pouces ? ') ;
lire(Pouces) ;
centimètres FacteurConversion*Pouces ;
écrire(pouces, 'pouces = ' , centimètres, 'centimètres') ;
fin.
Exemple 4
algorithme Bonjour ;
var
Nom : chaîne ;
début
écrire('Comment vous appelez-vous ? ') ;
lire(Nom) ;
écrire('Bonjour', Nom) ;
fin.
Exemple 5
algorithme Bonjour2 ;
var
nom : chaîne ;
sexe : caractère ;
début
écrire('Quel est votre nom ? ') ;
lire(nom) ;
écrire('Quel est votre sexe (M ou F) ? ') ;
lire(sexe) ;
si sexe = 'M' alors
Un algorithme comporte en général une ou plusieurs des sections énumérées dans la figure 1.3
ci-dessous. Ces sections, à l’exception de l’en-tête et de la partie instruction, peuvent ou ne
pas être présentes dans un algorithme donné.
Partie déclaration
Nous allons dans la suite examiner en détail le rôle et la structure des différentes sections d’un
début une explication plus précise des différents concepts
algorithme. Nous donnerons également
instruction1 ;
que nous avons introduits précédemment.
instruction2 ;
Les identificateurs .
.
.
Tous les objets manipulés dans un algorithme doivent avoir chacun un nom différent. Dans
instructionn ;
l’en-tête d’un algorithme nous avons
fin. nommé l’algorithme ; dans la section de définition des
constantes nous avons nommé des constantes ; dans la section de déclaration des variables
nous avons nommé des variables. Nous verrons dans les prochains chapitres que les types de
données, les procédures et les fonctions doivent aussi être nommés.
Définition. Identificateur
La chaîne de caractères qui constitue le nom d’un algorithme, d’une constante, d’un type de
données, d’une variable, d’une procédure ou d’une fonction est appelée un identificateur. Un
identificateur consiste en une lettre suivie par une suite de lettres ou de chiffres, aucune
distinction n’étant faite entre les lettres minuscules et majuscules. De cette façon les chaînes
Matricule et MATRICULE seront regardés comme le même identificateur.
Identificateur Signification
DateNaissance Date de naissance d’un étudiant
TauxHoraire Taux horaire d’un employé
Rayon Rayon d’un cercle
LieuNaissance Lieu de naissance d’un étudiant
Le langage algorithmique a certains mots clés ou mots réservés qui ne peuvent pas être
utilisés comme identificateurs. Ces mots ont une signification prédéfinie et ne peuvent donc
pas être utilisés à d’autres fins. Les mots réservés les plus courants du langage algorithmique
sont algorithme, var, début, fin, si, alors, sinon, tantque, faire, répéter, jusquà, pour,
haut et bas pour ne citer que les ceux-là. Les autres mots réservés du langage algorithme
seront présentés au fur et à mesure que nous progresserons dans notre exposé.
Le langage algorithmique offre aussi des identificateurs standards ayant une signification
prédéfinie. Ceux-ci peuvent si nécessaire être redéfinis pour avoir une signification différente,
mais la redéfinition d’un identificateur standard peut conduire à des confusions pour le lecteur
de l’algorithme. Ceci n’est donc pas recommandé ! Quelques exemples d’identificateurs
standards du langage algorithmique sont :
Même lorsqu’un programmeur est suffisamment averti ou expérimenté pour utiliser des
identificateurs expressifs, il est toujours très difficile de lire un algorithme écrit par une tierce
personne, et de comprendre facilement ce que font les différentes parties sans certains
commentaires explicatifs. Les commentaires peuvent facilement être insérés dans un
algorithme. Un commentaire a la forme générale suivante :
Les commentaires ne sont pas des instructions exécutables ; ils servent uniquement à la
documentation de l’algorithme et sont par conséquent ignorés par le système d’exécution.
Lorsqu’on écrit des algorithmes, il arrive souvent que l’on ait besoin d’utiliser des valeurs qui
sont connues d’avance et qui restent inchangées tout au long de l’algorithme. De telles valeurs
sont par conséquent appelées des constantes. Un exemple d’une telle valeur est la valeur
3.14159 que nous avons utilisée précédemment.
Quand on utilise une constante dans un algorithme, on peut soit utiliser la valeur littérale de la
constante, soit donner un nom symbolique à la constante et utiliser par la suite ce nom pour
faire référence à la constante en question. Pour créer une constante symbolique dans un
algorithme on utilise une instruction de définition de constante. La forme générale d’une
section de définition de constantes est la suivante :
const
identificateur1 = constante1 ;
identificateur2 = constante2 ;
…
identificateurn = constanten ;
Par exemple,
const
pi = 3.14159 ;
epsilon = 0.0001 ;
définit les constante symbolique pi, e et précision pour être utilisée à travers le programme.
Les variables
Définition. Variable
Comme nous pouvons le voir dans la figure 4.1, il est important de savoir faire la différence
entre le nom d’une variable et sa valeur courante.
Nom de la variable
Pour déclarer les variables , on utilise une instruction de déclaration dont la forme générale est
var
identifiacteur1 : type1 ;
identificateur2 : type2 ;
...
identificateurn : typen ;
Une déclaration de variable spécifie le nom de la variable ainsi que le type des valeurs que
cette variable peut prendre. Le type d’une variable correspondant au nom d’ensemble des
valeurs que cette variable peut prendre. Par exemple, l’instruction
var
nombre : entier ;
déclare une variable appelé nombre pouvant prendre des valeurs entières.
Un type de données spécifie un ensemble de valeurs de même nature pouvant être prises par
des variables dans un algorithme. Le langage algorithmique offre une grande variété de types
de données allant des types simples à des structures de données très complexes. Les types de
données les plus simples du langage algorithmique sont appelés les types scalaires. Ceci les
types de base à partir desquels tous les autres types de données sont construits. Les types de
données scalaires se divisent eux-mêmes en deux groupes : les types scalaires standards et les
types scalaires définis par l’utilisateur.
le type entier qui est l’ensemble des valeurs entiers relatifs pouvant être représentées
dans la machine,
le type réel qui est l’ensemble des nombres réels pouvant être représentés dans la
machine,
le type caractère qui est l’ensemble des caractères du code utilisé par la machine (code
ASCII par exemple),
le type booléen qui est l’ensemble des deux valeurs {vrai, faux}
Les types de données scalaires définis par l’utilisateur sont définis par l’utilisateur pour aider
à résoudre un problème particulier. Des exemples de types de données simples définis par
l’utilisateur sont :
Les types de données scalaires sont non structurés. Cela signifie qu’il consistent en de simples
valeurs distinctes. Le langage algorithmique offre également des types de données structurés
qui sont composés à partir des types scalaires et pouvant prendre un ensemble ou groupe de
valeurs. Les principaux types de données structurés sont :
Une expression est une construction algorithmique qui permettant d’effectuer des calculs dans
un algorithme. Une expression arithmétique représente une donnée numérique élémentaire.
Elle peut être constituée d’un seul terme tel qu’une constante, une variable, un élément de
tableau ou un désignateur de fonction. Elle est peut aussi être composée par un certain nombre
de termes (ou opérandes) et des opérateurs au moyen desquels ces opérandes sont combinés
pour produire une valeur numérique élémentaire.
2*pi*rayon
Nombre1 + Nombre2 ;
FacteurConversion * Pouces
Le langage algorithmique offre un certain nombre d’opérateurs arithmétiques qui prennent des
opérandes entiers et produisent des résultats entiers. Ces opérateurs sont :
Opérateur Signification
+ Addition
- Soustraction
* Multiplication
div Division entière
mod Reste de la division euclidienne
Ces opérateurs sont des opérateurs binaires infixés, ce qui signifie qu’ils s’écrivent entre
leurs deux opérandes.
Opération Résultat
9+5 14
9–5 4
9*5 45
9 div 5 1
9 mod 5 4
Les opérateurs + et – peuvent aussi être utilisés avec des valeurs telles que +a et –a pour
indiquer le signe d’une expression. L’opérateur + utilisé avec un seul opérande est appelé
l’opérateur + unaire et un – utilisé avec un seul opérande est appelé un opérateur – unaire.
Bien entendu, les opérateurs entiers peuvent aussi être utilisés avec des variables. Ainsi, si on
a les déclarations suivantes :
var
compteur, milieu, pluspetit : entier ;
compteur + milieu
compteur – milieu * pluspetit
milieu div 10 + pluspetit
5 div 2 donnera 2
2 div 2 donnera 1
14 div 3 donnera 4
5 div 7 donnera 0
21 mod 5 donnera 1
13 mod 10 donnera 3
5 mod 6 donnera 5
Opérateur Signification
+ Addition
- Soustraction
* Multiplication
/ Division réelle
Comme pour les opérateurs entiers, ces opérateurs sont des opérateurs binaires infixés. Les
opérateurs + et – peuvent aussi être utilisés comme des opérateurs + et – unaires, s’ils sont
utilisés avec un seul opérande.
Quelques exemples d’expressions réelles sont donnés ci-dessous :
Expression Résultat
3.5 + 4.3 7.8
3.5 – 4.3 -0.8
3.5 * 4.3 15.05
3.5 / 4.3 0.81395
Les opérateurs binaires +, - et * sont les mêmes que les opérateurs entiers correspondants.
L’opérateur binaire / est utilisé pour la division réelle. Comme on le verra dans le cours sur la
représentation des informations en machine, la représentation des nombres réels dans la
machine n’est pas exacte. Comme conséquence, il est possible que, pour des valeurs réelles a
et b, la relation (a/b)*b ne soit pas toujours vraie dans la machine.
Lorsqu’une expression implique plusieurs opérateurs, l’ordre dans lequel les opérateurs seront
évalués peut être ambiguë. Par exemple, la valeur de 2 + 3 * 4 est-elle égale à 20 ou à 14 ?
C’est-à-dire 2 + 3 * 4 sera-t-il interprétée comme (2 + 3) * 4 ou 2 + (3 * 4) ? A cause de ce
genre d’ambiguïtés, tous les langages de programmation ont adopté des règles spécifiant
l’ordre dans lequel les opérations seront effectuées. Ces règles sont appelées les règles de
précédence des opérateurs, elles correspondent aux règles utilisées en arithmétique. La
précédence des opérateurs que nous avons présentés ci-dessus, de la plus grande à la plus
petite, sont :
Précédence Opérateur
1 *, /, div, mod
2 +, -
Les règles d’évaluation des expressions reflètent les différentes classes de précédence des
opérateurs et la capacité d’exprimer ces précédences en utilisant les parenthèses.
Règle1. Évaluer d’abord les expressions entre parenthèses, en commençant par les
parenthèses les plus internes.
Règle 2. Effectuer les multiplications et/ou les divisions (*, /, div, mod) de la gauche vers la
droite.
Règle 3. Effectuer les additions et/ou les soustractions de la gauche vers la droite.
(Cost – Salvage)/Life
Supposons que Cost = 500.00, Salvage = 100.00 et Life = 10.0. Alors en utilisant les règles
d’évaluation des expressions, on a
Si on n’est pas sûr des règles de précédence dans un calcul particulier, on peut toujours forcer
l’ordre d’évaluation à appliquer dans la séquence que l’on veut en utilisant les parenthèses.
En mathématiques, les entiers peuvent être considérés comme des nombres réels. Dans une
machine, ils sont effectivement représentés différemment. Le langage algorithmique nous
permet cependant d’ignorer partiellement cette différence en convertissant automatiquement
chaque entier, qui apparaît là où un réel est attendu, en un nombre réel équivalent. La
réciproque n’est cependant pas vrai. On n’acceptera pas un nombre réel là où un nombre
entier est attendu. Enfin, on peut mélanger les entiers et les réels mais le résultat d’une telle
expression sera toujours réel.
Par exemple, supposons que nous avons déclaré les variables suivantes :
var
valeur1, valeur2 : réel ;
nombre : entier ;
valeur1 + valeur2
nombre * valeur1
valeur1 – 5
(valeur1 + nombre)/5
nombre + 5
nombre div 5
produisent des résultats entiers. Seules les expressions formées uniquement d’opérandes
entiers produisent des résultats entiers. Les expressions
ne sont pas valides car seuls les entiers sont attendus pour les opérateurs div et mod ; les
valeurs réelles ne seront donc pas acceptées.
Les instructions
Les instructions permettent de décrire les opérations qui doivent être effectuées dans un
algorithme. Il s’agit en quelque sorte des directives ou ordres que l’on donne au calculateur
pour lui permettre d’accomplir un certain traitement. Une instruction peut être simple ou
composée. Une instruction simple exprime une action élémentaire tandis qu’une instruction
composée est une séquence d’instructions simples ou composées devant être traitées comme
un tout. La forme générale d’une instruction composée est :
début
instruction1 ;
instruction2 ;
…
instructionn ;
fin ;
Les mots réservés début et fin marquent le début et la fin d’une instruction composée. Les
instructions sont habituellement organisées dans des structures algorithmiques particulières ,
appelées structures de contrôle, permettant de décrire avec précision la suite des opérations
à effectuer pour réaliser un certain traitement. Les principales structures de contrôle du
langage algorithmique sont :
Certaines instructions sont si fondamentales qu’il est indispensable de savoir comment elles
fonctionnent avant de continuer.
L’ordinateur a très souvent amené à communiquer une information à l’utilisateur. Pour cela, il
devra reproduire un texte ou une valeur numérique à l’écran ou sur l’imprimante. Cette
opération sera spécifiée par une instruction d’écriture dont la forme la plus simple consiste à
reproduire à l’écran ou sur l’imprimante la valeur d’une seule expression :
écrire(expression) ;
où expression est une expression numérique, logique ou une chaîne de caractères. Cette
instruction indique à l’ordinateur d’afficher à l’écran la valeur de expression.
Une expression de type chaîne de caractères est une suite de caractères entre apostrophes. Par
exemple l’instruction
Il est cependant possible de reproduire à l’écran les valeurs de plusieurs expressions par le
moyen d’une seule instruction d’écriture. Dans ce cas, la forme générale d’une instruction
d’écriture devient :
écrire(liste-expressions);
où liste-expressions est une liste d’expressions éventuellement de types différents séparés par
des virgules. Les valeurs des expressions de la liste sont alors écrites sur une même ligne ; le
curseur restant positionné sur la même ligne.
imprime les valeurs de rayon, circonférence et surface sur une même ligne ; le curseur restant
positionné sur la même ligne à la fin de l’opération d’écriture.
Une autre variante de l’instruction d’écriture prend la forme écrireln(). Cette version a pour
effet d’écrire les valeurs de la liste des expressions sur la même ligne et de faire passer le
curseur à la ligne suivante. Utilisée sans argument, elle permet d’écrire une ligne blanche et
de passer à la ligne suivante.
Commande l’affichage des valeurs de rayon, circonférence et surface à l’écran sur la même
ligne et de passer à la ligne suivante.
Une instruction de lecture permet au programme d’obtenir des informations à partir du monde
extérieur. Au fur et mesure que le programme s’exécute, des valeurs sont lues et affectées aux
variables. Ceci permet que des traitements soient appliqués à des données différentes sans
changer le programme. Seules les valeurs que nous présentons à l’entrée ont besoin d’être
changées.
Une opération de lecture sera spécifiée par une instruction de lecture dont la forme la plus
simple est :
lire(identificateur)
où identificateur est un nom de variable. Elle consiste à récupérer la valeur saisie au clavier et
à la ranger dans l’emplacement mémoire associé à la variable appelée identificateur. Par
exemple, l’instruction
lire(nombre)
lit la valeur entrée au clavier et la range dans l’emplacement mémoire associé à la variable
appelé nombre. Une instruction de lecture est bloquante en ce sens que lorsque le mécanisme
d’exécution atteint une instruction de lecture, le processus d’exécution du programme est
suspendue jusqu’à ce que l’utilisateur entre la donnée attendue.
Il est possible de lire en une seule instruction plusieurs valeurs. Dans ce cas, l’instruction de
lecture prend la forme
lire (liste-identificateurs)
lire(A, B)
L’instruction d’affectation
L’instruction d’affectation est sans doute l’instruction la plus utilisée en algorithmique et dans
la plupart des langages de programmation. C’est le moyen élémentaire de faire des calculs, de
déplacer des données et de changer les valeurs des variables. La forme générale d’une
instruction d’affectation dans le langage algorithmique est :
identificateur expression ;
où expression est une expression du même type que celui de la variable dont l’identificateur
apparaît à gauche. Le symbole « » est appelé l’opérateur d’affectation et se lit « est
remplacé par », ou « reçoit ». Des exemples d’instruction d’affectation que nous avons déjà
rencontrées sont :
Circonférence 2*pi*Rayon
Somme Nombre1 + Nombre2
Centimètres FacteurConversion * Pouces
Considérons par exemple le problème qui consiste à lire deux nombres et à permuter leurs
valeurs de telle sorte que le premier nombre contienne la valeur du deuxième et le deuxième
nombre contienne la valeur du premier. Pour effectuer cette permutation, on n’a pas besoin
seulement des deux nombres ; on doit aussi avoir un troisième nombre qui servira de réserve
pour qu’on ne perde pas l’une des valeurs.
algorithme Permute ;
var
A, B, C : réel ;
début
écrireln('Quelle est la valeur du premier nombre ? ') ;
lire(A) ;
écrireln('Quelle est la valeur du deuxième nombre ? ') ;
lire(A) ;
écrireln('Avant la permutation') ;
écrireln('Premier nombre : ', A) ;
écrireln('Deuxième nombre : ', B) ;
(*permutation des deux nombres*)
C A ;
A B ;
B C ;
écrireln('Après la permutation') ;
écrireln('Premier nombre : ', A) ;
écrireln('Deuxième nombre : ', B) ;
fin.
Certains calculs qui apparaissent assez souvent dans les algorithmes, comme le calcul de la
valeur absolue d’un nombre, ne peuvent pas être effectués facilement en utilisant les
opérateurs arithmétiques classiques +, -, *, /, div ou mod. Pour rendre de tels calculs faciles à
effectuer, le langage algorithmique ainsi que la plupart des langages de programmation offrent
un certain nombre de fonctions mathématiques standards. Par exemple, pour calculer la valeur
absolue de –10 et affecter le résultat à la variable nombre, on a juste besoin d’écrire
nombre abs(-10) ;
La valeur calculée par l’instance de fonction abs(-10) est +10. Cette valeur est affectée à la
variable nombre. La valeur -10 est appelée l’argument ou paramètre effectif de la fonction.
Étant donné un argument, une fonction calcule toujours une valeur élémentaire appelée la
valeur de la fonction.
La fonction abs() est directement offerte par le langage algorithmique et est par conséquent
appelée une fonction prédéfinie ou préconçue. Le tableau 1.5 ci-dessous donne les noms et
la description de quelques fonctions prédéfinies du langage algorithmique.
Pour utiliser une fonction, on écrit le nom de la fonction suivi par son argument ou paramètre
effectif entre parenthèses. Une telle expression est appelée un désignateur de fonction. Toute
expression légale peut être utilisée comme paramètre effectif à condition que le type de
Quand on écrit un algorithme, on utilise une notation qui exprime mieux la solution proposée
plutôt que les règles d’un langage de programmation particulier. Le principal avantage de
cette approche est que lorsqu’on résout un problème, on n’a pas de se préoccuper des détails
d’un langage particulier. On veut se sentir libre d’écrire de façon non ambiguë ce que chaque
instruction sans se être préoccupé par les aspects tel que la validité des identificateurs, l’utilisa
correcte des signes de ponctuations et d’autres détails du langage. Mais il est souvent
avantageux d’utiliser dans la description de l’algorithme, des notations proches du langage de
programme cible.
En général, pour écrire les instructions d’un algorithme, on utilise habituellement un mélange
de français, d’anglais ainsi que des notations et des structures d’un langage de programmation
particulier. Un tel dialecte constitue un pseudo langage qu’il convient d’appeler le langage
algorithmique.
Les organigrammes sont une autre forme d’expression des algorithmes. Les organigrammes
utilisent un ensemble de symboles géométriques standards pour représenter les différentes
opérations d’un algorithme. Un organigramme décrit figurativement la suite des opérations
nécessaires pour la résolution du problème.
Boîte de calcul
Boîte de commentaire
Boîte de lecture
Le pseudo langage et les organigrammes sont utilisés pour le même but. A priori, aucune des
deux approches n’est supérieure à l’autre pour représenter les algorithmes même si le pseudo
langage est en général plus proche des langages de programmation de haut niveau. La
technique utilisée n’est pas importante ; il faut seulement être confortable dans une proche et
être capable de développer des algorithmes corrects. Dans cet ouvrage, nous utilisons le
pseudo langage pour tous nos algorithmes. Périodiquement, nous utiliserons également les
organigrammes pour illustrer une instruction particulière.
début
Rayon
Surface 2*pi*Rayon
Circonférence pi * sqr(Rayon)
Rayon ,Surface,
Circonférence
fin
Exercices d’apprentissage
Exercice 1.1
Définir le vocable variable
Exercice 1.2
Choisir des identificateurs expressifs et pour déclarer des variables pouvant être utilisées pour
représenter les informations suivantes :
Information Information
Prix de vente Nature du baccalauréat
Moyenne des notes Mention obtenu au baccalauréat
Lieu de naissance Lieu obtention
Prix de revient Note examen final
Numéro matricule d’un étudiant Note examen partiel
Année obtention du baccalauréat Nombre d’heures de travail effectuées
Nombre de pages d’un livre Numéro de téléphone
Âge d’un étudiant en années Nom d’un employé
Salaire de base Cotisation CNPS
Date de naissance Code de l’unité de valeur
Les programmeurs débutants pensent souvent que la définition du problème à résoudre est
triviale. Rien ne peut aller au delà de la vérité. En effet, une définition attentive et précise du
problème peut souvent constituer plus de la moitié de la solution complète du problème. Le
défaut de développer des solutions attentives, bien que laborieuses, peut conduire non
seulement à la résolution d’un problème mal défini, mais très souvent conduit à la résolution
d’une partie seulement du problème posé ou à une solution beaucoup plus complexe que celle
qui est nécessaire pour résoudre le problème.
Nous perdons souvent beaucoup de notre énergie à résoudre des problème mal définis, et les
résultats obtenus sont bien évidemment inutiles. Une solution partielle à un problème, à cause
d’une définition incomplète du problème, peut également être inutile, particulièrement si on
tente de résoudre avec précision la partie du problème dont la définition a été omise. Enfin, si
notre solution est plus complexe que celle qui est nécessaire pour résoudre le problème posé,
des efforts non nécessaires ont été consentis pour la développer.
Certains problèmes sont plus faciles à analyser avec précision que d’autres. Évidemment, la
conversion des grammes en kilogrammes est plus facile que la gestion du service de la
scolarité d’un collège ou d’une université. Il n’y a pas une approche universelle bien
organisée pour analyser les problèmes. Toutefois, nos chances de succès peuvent être
améliorées si nous suivons rigoureusement certaines étapes importantes. Au bout du compte,
une bonne définition du problème implique un travail ardu. Nous devons résister à la tentation
de sauter l’étape de la définition du problème pour commencer directement le travail de
développement des algorithmes.
La définition d’un problème peut être divisée en deux phases : (1) la spécification des entrées
et (2) la spécification des sorties.
Une étape importante de la définition d’un problème est la description des données qui
constituent les entrées du problème. Pour se faire, on doit répondre aux différentes questions
suivantes :
De manière analogue, on a besoin de décrire les sorties désirées. La spécification des sorties
consiste généralement à répondre aux questions suivantes :
Essayons maintenant de faire l’analyse d’un problème simple et voyons comment on effectue
la spécification des entrées et des sorties.
Description initiale
Nous voulons écrire un programme pour calculer la commission d’un vendeur en utilisant la
formule : Commission = 5000 F + 0.10*Ventes. La valeur Ventes sera fournie au programme.
Bien que ceci soit un problème tout fait simple, il n’est pas de cette façon bien défini. Nous
pouvons facilement améliorer sa description.
Premier raffinement
Nous avons maintenant une idée beaucoup plus claire de ce que le programme doit faire.
Nous pouvons même raffiner davantage notre description ; c’est-à-dire, définir avec plus de
précision le problème, les contraintes sur les données d’entrée et de sortie et les conditions qui
vont affecter la solution du problème.
Deuxième raffinement
Si une valeur de Ventes supérieure à 500000 francs est fournie en entrée, la sortie
suivante sera générée :
Étant donné cette dernière spécification du problème avec une description détaillée des
entrées, des sorties et du traitement des erreurs, on aura maintenant aucune difficulté pour
comprendre ce qui est attendu.
Une fois que le problème a été clairement défini et analysé, un algorithme doit être développé
pour résoudre le problème. Nous pouvons maintenant donner une définition plus précise du
vocable algorithme.
Chaque instruction doit décrire exactement quelle opération doit être effectuée,
Chaque instruction doit être écrite explicitement, car il n’y a pas d’étapes supposées
ou sous-entendues,
Les instructions doivent être écrites dans un ordre précis.
Par ailleurs, pour être utile, un algorithme doit résoudre un problème général. Un algorithme
qui calcule uniquement la moyenne des nombres 7, 10, 15 et 14 n’a aucune généralité. Un
algorithme pour calculer la moyenne de quatre nombres est un peu plus général mais un
algorithme pour calculer la moyenne de n (n quelconque) nombres est beaucoup plus général.
Une fois que l’on a une définition solide du problème, on conçoit l’algorithme, habituellement
par étapes. C’est-à-dire que l’on se concentre initialement sur les aspects critiques et globaux
qui sont significatifs pour la résolution du problème, en réservant les détails pour les étapes
inférieures. Chaque étape est raffinée jusqu’à ce que l’on obtienne un algorithme complet
pouvant être facilement transformé en programme. Ce procédé de conception des algorithmes
par étapes est appelé le raffinement par étapes.
Considérons l’exemple suivant qui utilise une version simplifiée du problème défini ci-dessus,
le calcul de la commission d’un vendeur. En développant cette solution, nous allons ignorer
pour des raisons de simplicité, tous les traitements d’erreur prévus dans la définition initiale
du problème.
début
Lire le montant des ventes
Calculer la commission
Imprimer le montant des ventes et de la commission
fin.
La description initiale de l’algorithme ne fait pas autre chose qu’identifier les composantes
majeures de la solution. La description de ces composantes est tout à fait générale à ce stade.
L’étape suivante consiste à raffiner chacune de ces composantes à son tour.
Raffinement de l’algorithme
début
Lire le montant des ventes
Calculer la commission en utilisant le barème 5000 + 0.10*ventes
Imprimer le montant des ventes
Imprimer le montant de la commission
fin ;
L’algorithme est maintenant suffisamment détaillé. Une expression complète dans le langage
algorithmique est la suivante :
algorithme Paie ;
var
Ventes, Commission : réel ;
début
/*Lecture du montant des ventes*/
Écrire('Quel est le montant des ventes ? ') ;
Lire(Ventes) ;
/*Calculer la commission*/
Commission 5000 + 0.10*Ventes,
/*Imprimer le montant des ventes et de la commission*/
Écrire('Ventes : ', Ventes) ;
Écrire('Commission : ', Commission) ;
fin.
Exercices d’apprentissage
Exercice 1.3
Écrire un algorithme qui lit six nombres réels A, B, C, D, E et F et calcule la solution du
système d’équations
Ax + By = C
Dx + Ey = F
en supposant que le système admet une solution unique (déterminant non nul).
Exercice 1.4
Écrire un algorithme qui lit deux nombres réels A et B (A différent de zéro) puis calcule la
solution de l’équation Ax + B = 0.
Exercice 1.5
Écrire un algorithme qui lit le rayon d’une sphère, calcule et affiche le volume de la sphère.
Exercice 1.6
Écrire un algorithme qui calcule et affiche la longueur de l’hypoténuse d’un triangle rectangle
connaissant les longueurs des côtés perpendiculaires.
Exercice 1.7
Écrire un algorithme qui lit la longueur et la largeur d’un rectangle, calcule et affiche le
périmètre et la surface du rectangle.
Exercice 1.8
Écrire un algorithme qui lit le nom et le prénom d’un employé, le nombre d’heures de travail
qu’il a effectuées et son taux horaire puis calcule et affiche, le nom, le prénom et le salaire de
l’employé.
Exercice 1.9
Écrire un algorithme qui lit la note de contrôle continu et la note de synthèse d’un étudiant
dans une certaine unité de valeur puis calcule la moyenne de l’étudiant dans cette unité de
valeur sachant que le contrôle continu compte pour 30% et la note de synthèse pour 70%.
Exercice 1.10
Écrire un algorithme qui convertit les degrés Fahrenheit (F) en degrés Kelvin (K) en utilisant
la relation K = (5/9)(F – 32) + 273.
Exercice 1.11
Écrire un algorithme qui un montant exprimé en francs CFA puis calcule et imprime ses
équivalents en dollars américains et en dollars américains.
Exercice 1.12
Une compagnie a quatre divisions qui vendent une variété de produits. La direction de la
compagnie veut connaître quel pourcentage des ventes totales est généré par chaque division.
Écrire un algorithme qui lit le montant des ventes générées par chaque division et imprime le
montant total des ventes ainsi que le pourcentage des ventes de chacune des divisions.
CHAPITRE 2
Au chapitre précédent, nous avons présentée certaines notions de base de l’algorithmique qui
nous ont permis d’écrire des algorithmes simples impliquant la lecture des données, les
calculs et l’écriture des résultats. Dans les algorithmes que nous avons écrits, les instructions
étaient exécutées séquentiellement jusqu’à ce qu’on arrive à la fin du programme. Cependant,
on a souvent besoin de capacités additionnelles pour décrire complètement les opérations
devant être effectuées par un algorithme. Par exemple, il peut être utile de rendre l’exécution
d’une instruction dépendante d’une certaine condition, ou de choisir d’exécuter une ou
plusieurs instructions en fonction de la valeur d’une certaine condition. Ceci implique la
capacité de changer l’ordre dans lequel les instructions sont exécutées ou la capacité de
choisir, en fonction de ce que l’on veut faire, les instructions qui doivent être exécutées.
Considérons l’exemple suivant : on voudrait écrire un algorithme pour lire deux nombres
réels, diviser le premier par le second et imprimer le quotient.
début
Lire le dividende et le diviseur
Diviser le dividende par le diviseur
Écrire le résultat
fin.
algorithme Division1 ;
var
A, B, Q : réel ;
début
lire(A) ;
lire(B) ;
Q A/B ;
Écrire(Q) ;
fin.
L’algorithme ci-dessus marchera correctement aussi longtemps que l’on prendra la précaution
de ne pas donner au diviseur la valeur zéro. Comme la division par zéro n’est pas permise,
l’exécution de l’algorithme avec des données de ce genre va résulter en une erreur de
condition. On peut améliorer l’algorithme ci-dessus en testant d’abord la valeur du diviseur
pour voir si elle est égale à zéro et effectuer ensuite la division uniquement si elle n’est pas
nulle. L’algorithme va maintenant ressembler à quelque chose du genre :
début
Lire le dividende et le diviseur
si diviseur = 0 alors
Écrire un message indiquant une mauvaise entrée
sinon
Diviser le dividende par le diviseur et écrire le résultat
fin.
La structure si…alors…sinon
Pour écrire l’algorithme ci-dessus, nous avons eu besoin d’un moyen de représenter une
condition devant être testée ; le résultat du test devant déterminer ce que l’on doit faire par la
suite. Nous l’avons fait en utilisant l’instruction si…alors…sinon. Dans le langage
algorithmique, cette instruction prend la forme générale suivante :
si (condition) alors
opérations
sinon
opérations;
La condition entre les mots réservés si et alors est une expression qui peut prendre soit la
valeur vrai soit la valeur faux. Si elle prend la valeur « vrai » alors les opérations qui suivent
la clause alors sont effectuées. Si elle prend la valeur faux alors les opérations qui suivent la
clause sinon sont effectuées.
Diviseur = 0
Pour écrire correctement cet algorithme, nous avons utilisé l’instruction si… alors…sinon
dont la forme générale est la suivante :
si (condition) alors
instruction1
sinon
instruction2 ;
si (condition) alors
début
instruction1 ;
instruction2 ;
…
instructionn ;
fin
sinon
début
instruction1 ;
instruction2 ;
…
instructionm ;
fin ;
1. La condition qui se trouve entre les mots réservés si et alors est évaluée.
2. Si elle a la valeur « vrai », on exécute l’instruction qui suit la clause alors et l’exécution
continue avec l’instruction suivante (celle qui suit l’instruction si). Si elle a la valeur
« faux », on exécute l’instruction qui suit la clause sinon et l’exécution continue avec
l’instruction suivante.
instruction 1 instruction 2
algorithme Division2 ;
var
A, B : réel ;
Début
écrire('Quelle est la valeur du dividende ? ') ;
lire(A) ;
écrire('Quelle est la valeur du diviseur ? ') ;
lire(B) ;
si B = 0 alors
Écrire('Division impossible')
sinon
Écrire(A, ' / ', B, ' = ', A/B) ;
fin.
Les variables qui ne peuvent prendre que ces deux valeurs sont souvent appelées des variables
logiques ou booléennes, en l’honneur du mathématicien anglais du XVII ème siècle, George
Boole, qui a développé l’algèbre de la logique.
Pour déclarer une variable de type booléen ou logique, on utilise une déclaration de variable
de la forme suivante :
var
possible, trouvé : booléen ;
Les variables possible et trouvé ont été déclarées comme des variables logiques et peuvent
maintenant recevoir les valeurs vrai ou faux. Les affectations
possible vrai ;
trouvé faux ;
sont toutes valides. Pour imprimer la valeur d’une variable logique, on l’inclut dans la liste de
sortie d’une instruction d’écriture. Si la variable possible a la valeur vrai, l’instruction :
écrire(possible)
produira le résultat :
vrai
Les variables booléennes ne peuvent toutefois pas être utilisées dans la liste d’entrée d’une
instruction de lecture. L’instruction de lecture suivant n’est pas valide si trouvé a été déclarée
comme étant de type booléen, comme ci-dessus.
lire(trouvé) ;
Il existe cependant des astuces que l’on peut utiliser pour lire un caractère et l’interpréter
comme une valeur booléenne de telle sorte que des affectations indirectes puissent être
effectuées aux variables booléennes. Considérons par exemple le problème qui consiste à lire
le nom de l’utilisateur et à lui demander s’il est marié ou non. S’il est marié, on imprime le
message « … est marié ». S’il est célibataire on imprime le message « … est célibataire ».
algorithme salutation ;
var
nom : chaîne ;
marié : booléen ;
Ch : char ;
début
écrire('Quel est votre nom ? ') ;
lire(nom) ;
écrire('Êtes-vous marié (O ou N) ? ') ;
lire(Ch) ;
si Ch = 'O' alors
marié vrai
sinon
marié faux ;
si marié alors
écrire(nom, 'est marié')
sinon
écrire(nom, 'est célibataire') ;
fin.
Opérateur Signification
= est égal à
est différent de (n’est pas égal à)
est inférieur à
est supérieur à
est inférieur ou égal à
est supérieur ou égal à
Lorsque ces opérateurs sont utilisés avec des expressions entières ou réelles, elles produisent
des valeurs booléennes. Ainsi, on a :
Expression Valeur
5=6 faux
2<4 vrai
3.6 4.1 vrai
7.1 7.0 faux
3+5=6 faux
Les opérateurs relationnels ont une priorité inférieure à celle des opérateurs arithmétiques ;
ainsi lorsque les deux types d’opérateurs sont impliqués dans une expression, toutes les
opérations arithmétiques sont d’abord effectuées avant que les opérateurs relationnels ne
soient appliqués. Les règles de précédence des opérateurs arithmétiques et relationnels, pris
ensemble, sont :
Précédence Opérateurs
1 *, /, div, mod
2 +, -
3 =, , <, >, ,
Les exemples suivants illustrent l’évaluation d’expressions incluant les opérateurs relationnels
et arithmétiques :
Comme le résultat de l’évaluation d’une expression booléenne est une valeur booléenne, il
peut être affecté à une variable booléenne. Considérons par exemple le segment de code
suivant :
variable
p, q : booléen ;
x, y : entier ;
début
x 5 ;
y 0 ;
p x = y ;
q x + y x ;
….
fin ;
Après l’exécution de ces instructions p est faux et q est vrai. L’instruction d’affectation
p x = y ;
indique que l’expression à droite de l’opérateur d’affectation doit être évaluée et le résultat
affecté à p. Comme x = y est faux, la valeur faux est affectée à p.
Les conditions et les variables booléennes peuvent être combinées en utilisant des opérateurs
logiques ou booléens pour produire d’autres expressions logiques. Ces opérateurs sont :
Les opérateurs binaires « OU », « ET » et « XOR » sont utilisés pour combiner des conditions
afin d’obtenir des conditions composées. L’opérateur unaire « NON » est utilisé pour inverser
la valeur de vérité d’une expression ou d’une variable booléenne. Les tables de vérité de ces
quatre opérateurs sont définies de la manière suivante :
Voici quelques exemples où les opérateurs logiques utilisés pour construire des conditions
composées :
Expression Valeur
(15 20) ou (55 = 33) vrai
(15 20) et (55 = 33) faux
non (15 20) faux
La précédence des opérateurs logiques pris ensemble avec les opérateurs arithmétiques et
relationnels est :
Précédence Opérateurs
1 NON
2 *, /, div, mod, ET
3 +, -, OU, XOR
4 =, , <, >, ,
A cause de la précédence des opérateurs logiques, on utilisera des parenthèses pour écrire les
expressions arithmétiques ou logiques qui impliquent des opérateurs logiques.
L’exemple suivant illustre l’évaluation d’une expression booléenne impliquant les trois types
d’opérateurs.
Les expressions entre parenthèses sont évaluées en premier. Remarquer que l’expression
E = 12 < 14 et 2 = 5
est une expression invalide. D’après les règles de précédence décrites précédemment,
l’opérateur et a la plus grande précédence. Donc l’expression à évaluer en premier sera 14 et
12. Ceci est une expression non sens car et est un opérateur booléen alors que 14 et 2 ne sont
pas des valeurs booléennes.
Conditions composées
Les conditions composées sont souvent utilisées pour rendre plus compréhensibles les
algorithmes ayant des instructions si imbriquées complexes. Par exemple, la condition
imbriquée
si a < 4 alors
si b > 0 alors
w a*a – 2*b
sinon
si a = 3 alors
w a*a – 2*b
algorithme max3;
var
a, b, c: réel;
début
écrire('Entrez les valeurs de a, b et c') ;
lire(a, b, c) ;
si (a b) et (a c) alors
écrire('Le plus grand est : ', a)
sinon
si (b a) et (b c) alors
écrire('Le plus grand est : ', b)
sinon
écrire('Le plus grand est : ', c) ;
fin.
algorithme Vérification ;
var
montant : réel ;
tabac, carte : char ;
début
écrire('Quel est le montant des achats ? ') ;
lire(montant) ;
écrire('Possédez-vous votre carte de crédit (O ou N) ? ') ;
lire(Carte) ;
écrire('Avez-vous pris du tabac (O ou N) ? ') ;
lire(tabac) ;
si (montant < 100000) et (carte = 'O') et (tabac = 'N') alors
écrire('Achat approuvé')
sinon
écrire('Vérifier au niveau du département du crédit') ;
fin.
Exercices d’apprentissage
Exercice 2.1
Si une expression booléenne utilise des opérateurs logiques tels que ET et OU, ainsi que des
opérateurs relationnels, pourquoi doit on utiliser des parenthèses ?
Exercice 2.2
Lesquelles des expressions booléennes suivantes sont logiquement équivalentes ? p et q sont
deux variables booléennes.
(a) p q (b) (p et non q) ou (non p et q)
(c) non(p = q) (d) (p ou q) ou non(p et q)
Exercice 2.3
Réécrire chacune des instructions suivantes sans utiliser des conditions composées.
algorithme Paie ;
var
HoursWorked, TauxHoraire : réel ;
Overtime, Régulier : réel ;
Salaire : réel ;
début
écrire('Heures effectuées : ')
lire(HoursWorked) ;
écrire('Taux horaire : ')
lire(TauxHoraire) ;
si (HoursWorked > 40.0) alors
Overtime HoursWorked – 40.0
sinon
Overtime 0 ;
Régulier HoursWorked – Overtime ;
Salaire Régulier * Taux + Overtime * TauxHoraire * 1.5 ;
Écrire('Salaire : ', Salaire) ;
fin.
Exemple 2. Écrire un algorithme qui lit trois nombres réels et détermine si ces nombres
constituent les côtés d’un triangle rectangle.
algorithme TriangleRectangle ;
var
a, b, c : entier ;
début
écrire('Quelles sont les valeurs de a, b et c ? ')
lire(a, b, c) ;
si (a*a + b*b = c*c) ou (a*a + c*c = b*b) ou (b*b + c*c = a*a) alors
Écrire('Les nombres forment un triangle rectangle')
sinon
Écrire('Les nombres ne forment pas un triangle rectangle') ;
fin.
La structure si…alors
Comme nous l’avons vu, la construction si…alors…sinon est utilisée pour choisir laquelle de
deux instructions on doit exécuter sur la base de la valeur de vérité d’une expression logique.
Toutefois, l’instruction de sélection peut aussi être utilisée sans la clause sinon. Dans ce cas,
on parlera de la structure si…alors dont la forme générale est la suivante :
si (condition) alors
instruction ;
où instruction est une instruction simple ou composée. Dans ce dernier cas, on a :
si (condition) alors
début
instruction1 ;
instruction2
…
instructionn ;
fin ;
vrai
expression-logique
instruction faux
L’instruction si … alors est utile lorsqu’on veut effectuer une action si une certaine condition
est vraie et ignorer simplement cette action si la condition est fausse. Dans ce cas, la clause
sinon est superflue et peut être omise. Par exemple, considérons une autre version de
l’algorithme de paie ci-dessus.
algorithme Paie2 ;
var
HoursWorked, TauxHoraire : réel ;
Overtime, Régulier, Salaire : réel ;
début
écrire('Heures effectuées : ')
lireln(HoursWorked) ;
écrire('Taux horaire : ')
lireln(TauxHoraire) ;
Overtime 0 ;
si (HoursWorked > 40.0) alors
Overtime HoursWorked – 40.0;
Régulier HoursWorked – Overtime ;
Salaire Régulier * TauxHoraire + Overtime * TauxHoraire * 1.5 ;
Écrire('Salaire : ', Salaire) ;
fin.
On commence par initialiser Overtime à zéro, ensuite on vérifie si l’employé a effectué plus
de 40 heures de travail. Si l’employé a effectué plus de 40 heures, on calcule Complémentaire
sinon on ne fait rien. Quel que soit le résultat, on calcule ensuite le nombre d’heures
complémentaires effectuées par l’employé et enfin on calcule le salaire de ce dernier.
Comparer cet algorithme avec la version précédente où on utilise la construction si … alors
… sinon pour contrôler l’ordre d’exécution.
On parle d’instructions de sélection imbriquées lorsque dans une instruction si…alors ou si…
alors…sinon, l’instruction qui suit la clause alors ou la clause sinon est elle-même une
instruction de sélection. Les instructions de sélection imbriquées permettent de tester des
conditions en cascade afin de déterminer l’instruction ou le groupe d’instructions qui doit être
exécuté. Considérons par exemple le problème qui consiste à lire deux nombres entiers et
imprimer le plus grand de ces deux nombres. Si les deux nombres sont égaux, on imprime le
message « Les deux nombres sont égaux ». Un algorithme pour faire cela est le suivant :
algorithme Compare ;
var
x, y : entier ;
début
lire(x) ;
lire(y) ;
si (x > y) alors
écrire ('Le plus grand nombre est : ', x)
sinon
si (x = y) alors
écrire('Les deux nombres sont égaux')
sinon
écrire('Le plus grand nombre est : ', y)
fin.
Un examen de cet algorithme nous amène à remarquer que nous avons eu besoin d’utiliser
une structure de sélection qui se traduit par deux instructions si…alors…sinon écrites en
cascade. Il s’agit en réalité d’une seule instruction si…alors…sinon dans laquelle
l’instruction qui suit la clause sinon est elle-même une instruction si…alors…sinon. On dira
alors que l’on à fairevrai
à des instructions si…alors…sinon imbriquées.
faux
x>y
L’organigramme correspondant à cette instruction si … alors … sinon imbriquée est présenté
à la figure 2.3 ci-dessous.
vrai faux
FigureLe2.3. Organigramme de l’algorithme de comparaison x >deux
de y nombres.
plus grand est x
Les conditions composées sont particulièrement utiles dans les situations où on a besoins de
tester plusieurs conditions pour décider laquelle de deux instructions on doit effectuer.
Lorsqu’on doit choisir entre plusieurs instructions, on peut souvent utiliser l’instruction à
choix multiples. Cette instruction prend la forme générale :
case (expression) de
liste-valeurs1 : instruction1 ;
liste-valeurs2 : instruction2 ;
.
.
.
liste-valeursn : instructionn ;
fin;
L’expression comprise entre les mots réservés case et de est l’expression de sélection. C’est la
valeur prise par cette expression qui détermine l’instruction qui doit être exécutée. liste-
valeurs1, …, liste-valeurs1 énumèrent les différentes valeurs que peut prendre l’expression de
sélection.
si (opération = 1) alors
CA+B
sinon
si (opération = 2) alors
CA–B
sinon
si (opération = 3) alors
C A div B
sinon
si (opération = 4) alors
C A * B ;
En utilisant une instruction de sélection multiple, nous pouvons réécrire cette instruction de
la manière suivante :
case (opération) de
1 : C A + B ;
2:CA–B;
3 : C A div B;
4 : C A * B ;
fin ;
Quand on exécute une instruction de sélection multiple, l’expression de sélection est évaluée.
Dans notre exemple, la variable code est l’expression de sélection. La valeur de l’expression
de sélection doit être égale à l’une des valeurs énumérées dans le corps de l’instruction.
L’instruction qui sera exécutée est celle qui correspond à la liste dans laquelle se trouve la
valeur de l’expression de sélection. Si la valeur de l’expression de sélection ne se trouve dans
aucune des listes, aucune instruction ne sera exécutée. Pour prendre en compte ce genre de
situations, on peut ajouter une clause sinon dans une instruction de sélection multiple pour
spécifier une instruction qui prendre en charge ces cas particuliers.
case (expression) de
liste-valeurs1 : instruction1 ;
liste-valeurs2 : instruction2 ;
.
.
.
liste-valeursn : instructionn ;
sinon
autre-instruction ;
fin ;
On peut aussi utiliser la convention suivante : pour prévoir que l’expression de sélection
puisse prendre une valeur différente de toutes des valeurs attendues, on introduit une étiquette
spéciale, que nous appellerons par convention défaut, qui sera la valeur par défaut de
l’expression de sélection lorsque sa valeur effective ne sera pas égale à l’une quelconque des
étiquettes listées.
case (expression) de
liste-valeurs1 : instruction1 ;
liste-valeurs2 : instruction2 ;
.
.
.
liste-valeursn : instructionn ;
défaut : autre-instruction ;
fin ;
L’instruction de sélection multiple peut aussi être utilisée dans les situations où on travaille
avec un grand nombre de valeurs possibles. Par exemple, une mention est attribuée à un
étudiant sur la base du barème suivant :
On peut penser au premier abord que l’on doit lister toutes les valeurs possibles des notes (on
suppose que les notes varient de 0 à 100) dans les étiquettes. Cependant, ceci n’est pas
nécessaire. On peut réduire considérablement le nombre d’étiquettes nécessaires en utilisant la
division euclidienne par 10, ce qui permet de ne plus considérer que le reste de la division
euclidienne de la note par 10.
L’algorithme ci-dessous lit une note et affiche la mention correspondante en utilisant une
instruction de sélection multiple.
algorithme Mention ;
var
note : entier ;
début
écrire('Quelle est la note ? ') ;
lire(note) ;
si (note < 0) ou (note > 100) alors
Écrire('Mauvaise donnée')
sinon
case (note div 10) de
10, 9 : Écrire('A') ;
8 : Écrire('B') ;
7 : Écrire('C') ;
6 : Écrire('D') ;
5, 4, 3, 2, 1, 0 : Écrire('F') ;
fin ;
fin.
On peut réécrire cet algorithme en utilisant des instructions si … alors … sinon imbriquées.
L’algorithme devient alors :
algorithme Mention ;
var
note : entier ;
début
écrire('Quelle est la note ? ') ;
lire(note) ;
si (note < 0) ou (note > 100) alors
Écrire('Mauvaise donnée')
sinon
Dans les sections précédentes nous avons vu comment l’indentation aide à rendre les
instructions si…alors…sinon imbriquées faciles à comprendre. Nous avons en effet suivi des
conventions d’indentation dans tous nos algorithmes. Ces conventions sont strictement
réservées au lecteur humain car le compilateur ignore toutes ces indentations au moment de la
compilation du programme. Par exemple, le code
si (x < 5) alors
si (x = 2) alors
écrire (x)
sinon
écrire(x*x) ;
lire(x) ;
Cependant cette deuxième version est clairement plus facile à comprendre par une lecteur
humain. La deuxième fait ressortir la structure de l’instruction si…alors…sinon, alors que la
première ne fournit aucune information de ce genre.
Évidemment, il y a différentes façons d’indenter les instructions dans un algorithme pour les
rendre plus lisibles. Les conventions que nous avons utilisées sont les plus courantes. Nous
sommes libres d’utiliser nos propres conventions, mais il faut garder à l’esprit que le but de
l’indentation est d’améliorer la lisibilité des algorithmes. Les conventions que nous avons
utilisées sont les suivantes :
Convention 1. Aligner sur la marge gauche l’en-tête de l’algorithme, les mots réservés
annonçant les différentes sections de déclaration et de définition ainsi le premier début et le
dernier fin. Par exemple :
algorithme nomalgorithme ;
const
…
var
…
début
…
fin.
Convention 2. Indenter toutes les définitions et déclarations par rapport à aux mots réservés
correspondants. Par exemple
const
pi = 3.1416 ;
var
x, y : entier ;
p : booléen ;
début
lire(x) ;
écrire(x) ;
fin.
Convention 4. Ne pas écrire plusieurs instructions dans une même ligne. Par exemple,
l’écriture :
lire(x) ;
écrire(x) ;
lire(x) ; écrire(x) ;
si (condition) alors
instruction1
sinon
instruction2 ;
si (condition) alors
début
instruction1 ;
instruction2 ;
...
instructionm ;
fin
sinon
début
instruction1 ;
instruction2 ;
…
instructionn ;
fin ;
Au fur et mesure que nous étudierons de nouvelles instructions algorithmiques, nous allons
toujours utiliser ces conventions d’indentation pour les rendre aussi lisibles que possible.
Exercices d’apprentissage
Exercice 2.4
Les longueurs des côtés d’un triangle sont stockées dans les variables réelles a, b et c. Pour
que a, b et c soient effectivement correctes, la somme de deux quelconque des trois nombres
doit être strictement supérieure au troisième. Écrire un algorithme qui lit trois nombres réels a,
b et c et détermine si a, b et c sont effectivement les côtés d’un triangle. Si oui, alors écrire le
message « Triangle » dans le cas contraire écrire le message « Pas un triangle ».
Exercice 2.5
Une année est bissextile si son millésime est un multiple de 4, sauf les années de début de
siècle qui sont bissextiles si leur millésime est divisible par 400. Écrire un algorithme qui lit
un entier naturel et détermine si cet entier représente une année bissextile.
Exercice 2.6
Une boulangerie est ouverte de 7 heures à 13 heures et de 16 heures à 19 heures, sauf le
dimanche après-midi et le lundi toute la journée. Écrire un algorithme qui lit une heure (un
entier naturel) et un jour (une chaîne de caractère) et détermine si la boulangerie est ouverte le
jour et à l’heure indiqués.
Exercice 2.7
Un entier positif est stocké dans une variable nombre. Utiliser une instruction de sélection
multiple pour imprimer un message indiquant si le nombre est paire ou impaire.
Exercice 2.8
Écrire un programme pour résoudre le système d’équations linéaires d’inconnues x et y :
Ax + By = C
Dx + Ey = F
Exercice 2.9
Écrire un algorithme qui lit un nombre représentant l’âge d’une personne et affiche le
message « vous êtes mineur » si son âge est inférieur à 18 ans et le message « vous êtes
majeur » dans le cas contraire.
Exercice 2.10
Écrire un algorithme qui lit le nom et le sexe d’une personne et affiche le message « Bonjour,
Monsieur … » si la personne est de sexe masculin et le message « Bonjour, Madame … » si la
personne est de sexe féminin.
Exercice 2.11
Écrire un algorithme qui le nom, le sexe et le statut matrimonial d’une personne et affiche le
message « Bonjour monsieur … » si la personne est de sexe masculin, le message « Bonjour
mademoiselle … » si la personne est une demoiselle et le message « Bonjour madame … » si
la personne est une dame.
Exercice 2.12
Écrire un algorithme qui lit trois nombres et imprime le plus grand des trois nombres.
Exercice 2.13
Écrire un algorithme qui lit quatre nombres et imprime le plus grand des quatre nombres.
Exercice 2.14
Écrire un algorithme qui lit deux nombres réels A et B puis calcule la solution réelle de
l’équation Ax + B = 0.
Exercice 2.15
Écrire un algorithme qui lit trois nombres réels a, b et c puis calcule les solutions réelles de
l’équation quadratique ax 2 bx c 0 .
Exercice 2.16
Les tarifs d’affranchissement d’une lettre sont les suivants :
en-dessous de 20g : 280 FCFA,
à partir de 20g, mais en-dessous de 50g : 440 FCFA,
à partir de 50g : 670 FCFA.
Écrire un algorithme qui lit le poids d’une lettre et imprime le montant de l’affranchissement
de la lettre.
Exercice 2.17
La variable réelle Hours contient un nombre compris entre 0 et 100. Écrire un algorithme qui
lit une heure et affiche le message correspondant comme indiqué ci-dessous.
Hours Message
[0..30] Excessive absence
]30..50] Normal
]50..70] Excessive overtime
]70..100] Crazy
Exercice 2.18
On souhaite calculer le montant des impôts dus par un contribuable en fonction son revenu
imposable et de son nombre de parts fiscales. Les règles de calcul sont les suivantes :
le revenu par part fiscale est égale au quotient du revenu imposable par le nombre
de parts fiscales
l’impôt par part fiscale est calculé selon le barème suivant :
0 si le revenu par part fiscale est inférieur à 50000 F ;
10% sur la tranche du revenu par part fiscale comprise entre 50000 F et
100000 F ;
25% sur la tranche du revenu par part fiscale comprise entre 100000 F et
200000 F ;
50% sur le revenu par part fiscale est qui dépasse 200000 F
l’impôt total est égal au nombre de parts fiscales multiplié par l’impôt par part
fiscale.
Écrire un algorithme qui lit le revenu imposable et le nombre de parts fiscales d’un
contribuable puis calcule le montant des impôts dus par ce contribuable.
Le problème est d’écrire un algorithme pour calculer le montant des impôts dus par un
contribuable, connaissant son revenu imposable. Pour calculer les impôts, nous allons utiliser
le barème présenté dans le tableau ci-dessous
Inférieur à 25000 F 0F
Ceci est un barème allégé pour l’objectif du problème. Il ne sera pas difficile de le généraliser
pour traiter un barème plus complet, c’est-à-dire traiter des revenus supérieurs à 175000 F.
On veut écrire un algorithme pour calculer le jour de l’année connaissant le mois, le jour du
mois et l’année. Par exemple, si l’entrée de l’algorithme est « 2 7 1983 » la sortie de
l’algorithme serait « le 2/7/1983 est le 38 ème jour de 1983 ».
CHAPITRE 3
Les algorithmes que nous avons écrits jusque là ont une caractéristique commune : ils ne
peuvent traiter qu’un seul ensemble de données pour une exécution donnée. Par exemple,
l’algorithme de Paie que nous avons écrit ne peut calculer en une exécution que le salaire d’un
seul employé. Si on souhaite calculer le salaire de plusieurs employés, on devra exécuter le
programme autant de fois que nécessaire ! Nous serions plus à l’aise si nous avions la
possibilité de communiquer à la machine les informations sur la liste des employés dont on
veut calculer les salaires, et laisser le programme traiter automatiquement toute la liste. En un
mot, on aimerait que toutes ou certaines actions de l’algorithme soient exécutées plus d’une
fois, mais avec des données différentes.
Un ensemble d’étapes exécutées de façon répétitive dans un algorithme est appelé une boucle.
On peut donc définir une boucle comme une séquence d’instructions que l’on veut exécuter
une ou plusieurs fois.
On a souvent besoin de répéter l’exécution de certaines étapes d’un algorithme pour deux
raisons principales :
La situation que nous avons décrite avec l’algorithme de calcul de la paie des employés tombe
dans la première catégorie. On est intéressé par la répétition des opérations qui consistent à
lire le taux horaire et le nombre d’heures de travail effectuées par un employé, à calculer et à
imprimer son salaire. On rencontrera dans la suite des exemples de la deuxième catégorie où
on commence avec une seule valeur et on répète un ensemble de calculs jusqu’à ce que l’on
obtienne le résultat désiré.
Supposons que l’on veuille écrire un algorithme pour calculer la moyenne de 10 nombres.
Une première version de notre algorithme peut être la suivante :
début
Lire dix nombres
Calculer la moyenne des dix nombres
Écrire la moyenne
fin.
Cet algorithme est correct mais il nécessite que l’on déclare dix variables, une pour chacun
des nombres dont on veut calculer la moyenne, lire d’abord les valeurs de toutes les variables,
calculer ensuite la moyenne et écrire enfin le résultat. Cette approche n’est pas seulement non
nécessaire, mais elle peut devenir intolérable si on tente de généraliser cet algorithme pour
calculer la moyenne de mille nombres par exemple.
Une meilleure approche consiste à prendre avantage du fait que l’on n’a pas besoin que tous
les nombres soient disponibles en même temps. Tout ce que nous voulons faire c’est de
conserver un total courant des nombres dont on veut calculer la moyenne. Pour calculer la
moyenne, on divise le total final par le nombre de valeurs que l’on a. Ceci suggère que l’on
lise un nombre à la fois et que l’on ajoute ce nombre au total courant jusqu’à ce que tous les
nombres soient lus. Ensuite on divise le total par dix pour obtenir la moyenne.
Pour s’assurer que tous les nombres sont effectivement lus et que le total est correctement
calculé, on utilise deux variables :
une variable appelée total dont le rôle est de contenir le total courant des nombres
au fur et mesure qu’ils sont lus. Au départ, total est initialisé à zéro ;
une variable appelée compteur dont le rôle est de compter les nombres au fur et
mesure de leur lecture. Au départ, compteur est initialisé à zéro.
début
initialiser total à zéro
initialiser compteur à zéro
répéter
Lire un nombre
Ajouter le nombre au total
Ajouter 1 à compteur
jusquà compteur égal 10
Calculer la moyenne
Écrire la moyenne
fin.
Dans cet algorithme, la boucle conditionnelle est exprimée en délimitant les instructions à
répéter par les mots réservés répéter et jusquà. Cet algorithme spécifie que les trois actions,
Lire un nombre
Ajouter le nombre au total
Ajouter 1 à compteur
doivent être répétées jusqu’à ce que la condition « compteur égal 10 » soit vraie. Ces trois
opérations constituent le corps de la boucle et la condition « compteur égal 10 » est appelée la
condition de terminaison. D’abord, le corps de la boucle est exécuté, ensuite la condition de
terminaison est évaluée. Si elle a la valeur vrai, le processus d’exécution quitte la boucle et
continue avec l’instruction qui suit la boucle. Si elle a la valeur faux, les instructions du corps
de la boucle sont exécutées une fois de plus.
L’instruction répéter…jusquà
répéter
instruction
jusquà (condition) ;
où instruction est une instruction simple ou composée. Dans ce dernier cas, la boucle répéter
…jusquà prend la forme générale
répéter
instruction1 ;
instruction2 ;
…
instructionn ;
jusquà (condition) ;
Les mots réservés répéter et jusquà sont utilisés pour délimiter le corps de la boucle. Ainsi,
les instructions de la boucle constituent une instruction composée. Ceci est un exemple
d’instruction composée où on n’utilise pas début et fin pour regrouper les instructions. La
condition qui suit le mot réservé jusquà est appelée la condition de terminaison de la
boucle, parce que la boucle se termine lorsque cette condition prend la valeur « vrai ».
L’organigramme correspondant à la boucle répéter … jusquà est présenté dans la figure ci-
dessous.
Corps de la boucle
faux vrai
Expression logique
Remarques
La boucle ne se terminera jamais si la condition de terminaison reste indéfiniment
fausse. Il est donc important que le corps de la boucle contienne au moins une
instruction de mise à jour de la condition de terminaison pour garantir que celle-ci
deviendra vraie après un nombre fini d’étapes.
algorithme moyenne ;
var
nombre, total, moy : réel ;
i : entier ;
début
{Initialisation de compteur et total}
i 0 ;
total 0 ;
répéter
lire(nombre) ;
total total + nombre ;
i i +1 ;
jusquà (i = 10) ;
{Calcul de la moyenne}
moy total/10.0 ;
écrire('La moyenne est : ', moy) ;
fin.
Autres exemples
Exemple 1. Écrire un algorithme pour lire les noms d’une liste de 10 personnes et imprimer à
chaque fois le message « Bonjour … ».
algorithme Bonjour ;
var
nom : chaîne ;
i : entier ;
début
i 0 ;
répéter
écrire('Quel est votre nom ? ') ;
lire(nom) ;
écrire('Bonjour', nom) ;
i i + 1 ;
jusquà (i = 10) ;
fin.
Exemple 2. Écrire un algorithme pour résoudre une série de dix équations de la forme ax + b
= 0, où a et b sont des nombres réels.
algorithme Equation ;
var
a, b, x : real ;
i : entier ;
début
i 0 ;
répéter
écrire('Quel est la valeur de a ? ') ;
lire(a) ;
écrire('Quel est la valeur de b ? ') ;
lire(b) ;
i i + 1 ;
si (a 0) alors
debut
x -b/a ;
écrire('La solution est : ', x)
fin
sinon
si (b = 0) alors
écrire('Équation indéterminée')
sinon
écrire('Équation impossible') ;
jusquà (i = 10) ;
fin.
Exemple 3. Écrire un algorithme pour lire une suite de 100 nombres et déterminer le plus
grand de ces nombres.
L’approche consiste à traiter un nombre à la fois et garder trace de la plus grande valeur déjà
rencontrée. Chaque nouvelle valeur lue est comparée à la plus grande valeur courante. Si elle
est plus grande, elle devient le nouveau plus grand. Si elle est inférieure à la plus grande
valeur courante, on conserve la plus grande valeur courante et on lit la valeur suivante. Au
départ la plus grande valeur courante est initialisée au premier nombre lu.
algorithme maxliste ;
var
nombre, grand : real ;
i : entier ;
début
i 1 ;
lire(nombre) ;
grand nombre ;
répéter
lire(nombre) ;
i i + 1 ;
si (nombre > grand) alors
grand nombre ;
jusquà (i = 100) ;
écrire('Le plus grand nombre est : ', grand) ;
fin.
Exemple 4. Écrire un algorithme pour lire une suite de 100 nombres réels et déterminer le
plus petit de ces nombres.
algorithme minliste ;
var
nombre, min : real ;
i : entier ;
début
i 1 ;
lire(nombre) ;
min nombre ;
répéter
lire(nombre) ;
i i + 1 ;
si (nombre < min) alors
min nombre ;
jusquà (i = 100) ;
écrire('Le plus petit nombre est : ', min) ;
fin.
Exemple 5. Écrire un algorithme pour lire une suite de 100 nombres réels et déterminer le
plus petit et le plus grand de ces nombres.
algorithme maxmiliste ;
var
nombre, min, max : real ;
i : entier ;
début
i 1 ;
lire(nombre) ;
min nombre ;
max nombre ;
répéter
lire(nombre) ;
i i + 1 ;
si (nombre < min) alors
min nombre ;
si (nombre > max) alors
max nombre ;
jusquà (i = 100) ;
Exemple 6. Écrire un algorithme pour lire une suite de 100 nombres réels et déterminer le
plus petit de ces nombres ainsi que sa position dans la liste. On envisagera le cas de la
première occurrence et le cas de la dernière occurrence.
Algorithme posminliste ;
variable
nombre, min : real ;
i, indice : entier ;
début
i 1 ;
lire(nombre) ;
indice 1 ;
min nombre ;
répéter
lire(nombre) ;
i i + 1 ;
si nombre < min alors
début
min nombre ;
indice i ;
fin ;
jusquà (i = 100) ;
écrire('Le plus petit nombre est : ', min) ;
écrire('Sa position dans la liste est : ', indice) ;
fin.
Exemple 7. Écrire un algorithme pour traiter une liste de 10 étudiants. Pour chaque étudiant
l’algorithme lit le nom de l’étudiant, sa note de contrôle continu et sa note de synthèse dans
une certaine unité de valeur, calcule la moyenne de l’étudiant et affiche le message «… a pour
moyenne :…). La note de contrôle continu compte pour 30% et la note de synthèse pour 70%.
algorithme gestionote ;
var
nom : chaîne ;
note1, note2, moyenne : real ;
n: entier ;
début
n 0 ;
répéter
écrire('Quel est votre nom ? ') ;
lire(nom) ;
écrire('Votre note de contrôle continu ? ') ;
lire(note1) ;
écrire('Votre note de synthèse ? ') ;
lire(note2) ;
moyenne 0.3*note1 + 0.7*note2 ;
n n + 1 ;
écrire(nom, 'a pour moyenne : ', moyenne);
jusquà (n = 10) ;
fin.
Algorithme Paie ;
variable
nom : chaîne ;
Rate, HoursWorked : réel ;
Salary, Regular, Overtime : réel ;
i, n: entier ;
début
écrire('Nombre employés :') ;
lire(n) ;
i 0 ;
répéter
écrire('Nom employé :') ;
lire(nom) ;
écrire('Taux horaire : ') ;
lire(Rate) ;
écrire('Heures effectuées : ') ;
lire(HoursWorked) ;
i i + 1 ;
si HoursWorked > 40 alors
Overtime HoursWorked – 40.0
sinon
Overtime 0 ;
L’instruction faire…tantque
Le langage algorithmique offre une autre instruction de répétition très proche de l’instruction
répéter…jusquà. Il s’agit de l’instruction faire…tantque dont la forme générale est :
faire
instruction
tantque (condition) ;
où instruction est une instruction simple ou composée. Dans ce dernier cas, la boucle faire…
tantque prend la forme générale
faire
instruction1 ;
instruction2 ;
…
instructionn ;
tantque (condition) ;
Les mots réservés début et fin sont utilisés pour délimiter le corps de la boucle. La condition
qui suit la clause tantque est appelée la condition de répétition de la boucle parce que la
boucle est répétée tant que cette condition a la valeur vrai.
L’organigramme correspondant à la boucle faire…tantque est présenté dans la figure 3.2 ci-
dessous.
Corps de la boucle
vrai faux
Expression logique
Remarques
La boucle ne se terminera jamais si la condition de répétition reste indéfiniment vraie.
Il est donc important que le corps de la boucle contienne au moins une instruction de
mise à jour de la condition de répétition pour garantir que celle-ci deviendra fausse
après un nombre fini d’étapes.
Une propriété importante de la boucle faire…tantque est que le corps de la boucle est
exécuté au moins une fois. Ceci est vrai parce que la condition de répétition est testée
à la sortie de la boucle.
algorithme moyliste ;
variable
nombre, total, moy : réel ;
nb : entier ;
début
{Initialisation de compteur et total}
nb 0 ;
total 0 ;
faire
écrire(‘Entrez un nombre :’) ;
lire(nombre) ;
total total + nombre ;
nb nb +1 ;
tantque (nb < 10) ;
{Calcul de la moyenne}
moyenne total/10 ;
écrire('La moyenne est : ', moy) ;
fin.
Exercice 3.1
Généraliser l’algorithme ci-dessus pour lire une suite de n nombres et calculer la moyenne de
ces n nombres ; le nombre n de nombres devant être lu dans l’algorithme.
Autres exemples
Exemple 1. Écrire un algorithme pour lire les noms d’une liste de 10 personnes et écrire à
chaque fois le message « Bonjour … ».
algorithme Bonjour ;
var
nom : chaîne ;
i : entier ;
début
i 0 ;
faire
écrire('Quel est votre nom ? ') ;
lire(nom) ;
écrire('Bonjour', nom) ;
i i + 1 ;
tantque (i < 10) ;
fin.
algorithme Equation ;
var
a, b, x : real ;
i : entier ;
début
i 0 ;
faire
écrire('Quel est la valeur de a ? ') ;
lire(a) ;
écrire('Quel est la valeur de b ? ') ;
lire(b) ;
i i + 1 ;
si (a 0) alors
début
x -b/a ;
écrire('La solution est : ', x)
fin
sinon
si b = 0 alors
écrire('Équation indéterminée')
sinon
écrire('Équation impossible') ;
tantque (i < 10) ;
fin.
Exemple 3. Écrire un algorithme pour lire une suite de 100 nombres et déterminer le plus
grand de ces nombres.
L’approche consiste à traiter un nombre à la fois et garder trace de la plus grande valeur déjà
rencontrée. Chaque nouvelle valeur lue est comparée à la plus grande valeur courante. Si elle
est plus grande, elle devient le nouveau plus grand. Si elle est inférieure à la plus grande
valeur courante, on conserve la plus grande valeur courante et on lit la valeur suivante. Au
départ la plus grande valeur courante est initialisée au premier nombre lu.
algorithme maxliste ;
var
nombre, grand : real ;
i : entier ;
début
i 1 ;
écrire(‘Entrez un nombre :’) ;
lire(nombre) ;
grand nombre ;
faire
écrire(‘Entrez un nombre :’) ;
lire(nombre) ;
i i + 1 ;
si (nombre > grand) alors
grand nombre ;
tantque (i < 100) ;
écrire('Le plus grand nombre est : ', grand) ;
fin.
Exemple 4. Généraliser l’algorithme ci-dessus pour qu’il lise une suite de n nombres et
détermine le plus grand de ces n nombres ; le nombre n de nombres devant être lu dans
l’algorithme.
algorithme maxliste ;
var
nombre, grand : réel ;
n, i : entier ;
début
écrire('Quelle est la valeur de n ?') ;
lire(n) ;
si (n 0) alors
écrire('Valeur positive attendue ! ')
sinon
début
écrire('Entrez un nombre : ') ;
lire(nombre) ;
i 1 ;
grand nombre ;
faire
écrire(4ntrez un nombre :’) ;
lire(nombre) ;
i i + 1 ;
si (nombre > grand) alors
grand nombre ;
tantque (i < n) ;
Exemple 5. Écrire un algorithme pour lire une suite de 100 nombres réels et déterminer le
plus petit de ces nombres.
algorithme minliste ;
var
nombre, min: réel ;
i : entier ;
début
i 1 ;
écrire('Entrez un nombre : ') ;
lire(nombre) ;
min nombre ;
faire
écrire('Entrez un nombre : ') ;
lire(nombre) ;
i i + 1 ;
si (nombre < min) alors
min nombre ;
tantque (i < 100) ;
écrire('Le plus petit nombre est : ', min) ;
fin.
Exemple 6. Écrire un algorithme pour lire une suite de 100 nombres et déterminer le plus
petit et le plus grand de ces nombres.
algorithme maxminliste ;
var
nombre, min, max : réel ;
i : entier ;
début
i 1 ;
écrire('Entre un nombre : ') ;
lire(nombre) ;
min nombre ;
max nombre ;
faire
écrire('Entrez un nombre : ') ;
lire(nombre) ;
i i + 1 ;
si (nombre < min) alors
min nombre ;
si (nombre > max) alors
max nombre ;
tantque (i < 100) ;
Exemple 7. Écrire un algorithme pour lire une suite de 100 nombres et déterminer le plus
petit de ces nombres ainsi que sa position dans la liste. On envisagera le cas de la première
occurrence et le cas de la dernière occurrence.
algorithme posminliste ;
var
nombre, min : real ;
i, indice : entier ;
début
i 1 ;
écrire('Entrez un nombre : ') ;
lire(nombre) ;
indice 1 ;
min nombre ;
faire
écrire('Entre un nombre : ') ;
lire(nombre) ;
i i + 1 ;
si (nombre < min) alors
début
min nombre ;
indice i ;
fin ;
tantque (i < 100) ;
écrire('Le plus petit nombre est : ', min) ;
écrire('Sa position dans la liste est : ', indice) ;
fin.
Exemple 8. Écrire un algorithme pour traiter une liste de 10 étudiants. Pour chaque étudiant
l’algorithme lit le nom de l’étudiant, sa note de contrôle continu et sa note de synthèse dans
une certaine unité de valeur, calcule la moyenne de l’étudiant et affiche le message «… a pour
moyenne :…). Le note de contrôle continu compte pour 30% et la note de synthèse pour 70%.
algorithme gestionote;
var
nom : chaîne ;
note1, note2, moyenne : real ;
nb : entier ;
début
nb 0 ;
faire
écrire('Quel est votre nom ? ') ;
lire(nom) ;
écrire('Votre note de contrôle continu ? ') ;
lire(note1) ;
écrire('Votre note de synthèse ? ') ;
lire(note2) ;
moyenne 0.3*note1 + 0.7*note2 ;
nb nb + 1 ;
écrire(nom, 'a pour moyenne : ', moyenne);
tantque (nb < 10) ;
fin.
algorithme Paie ;
var
nom : chaîne ;
Rate, HoursWorked, Salary : réel ;
Regular, Overtime : réel ;
i, n : entier ;
début
écrire('Nombre employés :') ;
lire(n) ;
i 0 ;
faire
écrire('Nom employé : ') ;
lire(nom) ;
écrire('Taux horaire : ') ;
lire(Rate) ;
écrire('Heures effectuées : ') ;
lire(HoursWorked) ;
i i + 1 ;
si (HoursWorked > 40) alors
Overtime HoursWorked – 40.0
sinon
Overtime 0 ;
Regular HoursWorked – Overtime ;
Salary Rate * Regular + 1.5*Rate*Overtime ;
écrire('Nom : ', nom) ;
écrire('Salaire : ', Salary);
tantque (i < n) ;
fin.
Le langage algorithmique offre une autre forme d’instruction permettant d’exprimer les
boucles conditionnelles. Il s’agit de l’instruction tantque…faire dont la forme générale est :
où instruction est une instruction simple ou composée. Dans ce dernier cas, l’instruction
tanque … faire prend la forme générale :
L’instruction simple ou composée qui suit le mot réservé faire constitue le corps de la boucle.
La condition entre les mots réservés tantque et faire est la condition de répétition de la
boucle. La figure 2.3 ci-dessous présente l’organigramme de l’instruction tantque…faire.
faux
Expression logique
vrai
Corps de la boucle
En général, une boucle conditionnelle peut être exprimée soit par une instruction répéter…
jusquà, soit par une instruction faire…tantque, soit par une instruction tanque…faire. La
plupart des langages de programmation implémentent une ou deux de ces instructions.
L’avantage d’avoir les différentes variantes est que ceci permet d’utiliser celle qui semble la
plus appropriée pour un problème particulier. Réécrivons maintenant notre algorithme de
calcul de la moyenne de 10 nombres en utilisant la boucle tantque … faire. Dans ce cas
particulier, aucune des instructions n’est plus appropriée que les autres. Cependant, dans
plusieurs applications la boucle tantque … faire est plus appropriée que les deux autres
variantes parce qu’elle autorise que le corps de la boucle ne soit pas exécuté du tout.
algorithme moyliste ;
var
nombre, total, moyenne : réel ;
i : entier ;
début
(*Initialisation de compteur et total*)}
i 0 ;
total 0 ;
tantque i < 10 faire
début
écrire('Entrez un nombre : ') ;
lire(nombre) ;
total total + nombre ;
i i + 1 ;
fin ;
(*Calculer et écrire la moyenne*)
moyenne total/10.0 ;
écrire('La moyenne est : ', moyenne) ;
fin.
Autres exemples
Exemple 1. Écrire un algorithme pour lire les noms d’une liste de 10 personnes et imprimer à
chaque fois le message « Bonjour … ».
algorithme Bonjour ;
var
nom : chaîne ;
i : entier ;
début
i 0 ;
tantque (i < 10) faire
début
écrire('Quel est votre nom ? ') ;
lire(nom) ;
écrire('Bonjour', nom) ;
i i + 1 ;
fin ;
fin.
algorithme Equation ;
var
a, b, x : real ;
i : entier ;
début
i 0 ;
tantque i < 10 faire
début
écrire('Quel est la valeur de a ? ') ;
lire(a) ;
écrire('Quel est la valeur de b ? ') ;
lire(b) ;
i i + 1 ;
si (a 0) alors
début
x -b/a ;
écrire('La solution est : ', x)
fin
sinon
si b = 0 alors
écrire('Équation indéterminée')
sinon
écrire('Équation impossible') ;
fin ;
fin.
Exemple 3. Écrire un algorithme pour lire une suite de 100 nombres et déterminer le plus
grand de ces nombres.
L’approche consiste à traiter un nombre à la fois et garder trace de la plus grande valeur déjà
rencontrée. Chaque nouvelle valeur lue est comparée à la plus grande valeur courante. Si elle
est plus grande, elle devient le nouveau plus grand. Si elle est inférieure à la plus grande
valeur courante, on conserve la plus grande valeur courante et on lit la valeur suivante. Au
départ la plus grande valeur courante est initialisée au premier nombre lu.
algorithme maxliste ;
var
nombre, max : réel ;
i : entier ;
début
i 1 ;
écrire('Entrez un nombre : ') ;
lire(nombre) ;
max nombre ;
tantque (i < 100) faire
début
écrire('Entrez un nombre : ') ;
lire(nombre) ;
i i + 1 ;
si (nombre > max) alors
max nombre ;
fin ;
écrire('Le plus grand nombre est : ', max) ;
fin.
Exemple 4. Écrire un algorithme pour lire une suite de 100 nombres et déterminer le plus
petit de ces nombres.
algorithme minliste ;
var
nombre, min : réel ;
i : entier ;
début
i 1 ;
écrire('Entrez un nombre : ') ;
lire(nombre) ;
min nombre ;
tantque (i < 100) faire
début
écrire('Entrez un nombre : ') ;
lire(nombre) ;
i i + 1 ;
si (nombre < min) alors
min nombre ;
fin ;
écrire('Le plus petit nombre est : ', min) ;
fin.
Exemple 5. Écrire un algorithme pour lire une suite de 100 nombres et déterminer le plus
petit et le plus grand de ces nombres.
algorithme maxminliste ;
var
nombre, min, max : réel ;
i : entier ;
début
i 1 ;
écrire('Entrez un nombre : ') ;
lire(nombre) ;
min nombre ;
max nombre ;
tantque (i < 100) faire
début
écrire('Entrez un nombre : ') ;
lire(nombre) ;
i i + 1 ;
si (nombre < min) alors
min nombre ;
si (nombre > max) alors
max nombre ;
fin ;
écrire('Le plus petit nombre est : ', min) ;
écrire('Le plus grand nombre est : ', max) ;
fin.
Exemple 6. Écrire un algorithme pour lire une suite de 100 nombres et déterminer le plus
petit de ces nombres ainsi que sa position dans la liste. On envisagera le cas de la première
occurrence et le cas de la dernière occurrence.
algorithme posminliste ;
var
nombre, min : réel ;
i, indice : entier ;
début
i 1 ;
écrire('Entrez un nombre : ') ;
lire(nombre) ;
indice 1 ;
min nombre ;
tantque (i < 100) faire
début
écrire('Entrez un nombre : ') ;
lire(nombre) ;
i i + 1 ;
si (nombre < min) alors
début
min nombre ;
indice i ;
fin ;
fin ;
Exemple 7. Écrire un algorithme pour traiter une liste de 10 étudiants. Pour chaque étudiant
l’algorithme lit le nom de l’étudiant, sa note de contrôle continu et sa note de synthèse dans
une certaine unité de valeur, calcule la moyenne de l’étudiant et affiche le message «… a pour
moyenne :…). La note de contrôle continu compte pour 30% et la note de synthèse pour 70%.
algorithme gestionote ;
var
nom : chaîne ;
note1, note2, moyenne : real ;
n: entier ;
début
n 0 ;
tantque (n < 10) faire
début
écrire('Quel est votre nom ? ') ;
lire(nom) ;
écrire('Votre note de contrôle continu ? ') ;
lire(note1) ;
écrire('Votre note de synthèse ? ') ;
lire(note2) ;
moyenne 0.3*note1 + 0.7*note2 ;
n n + 1 ;
écrire(nom, 'a pour moyenne : ', moyenne);
fin ;
fin.
algorithme Paie ;
var
nom : chaîne ;
Rate, HoursWorked : réel;
Salary, Regular, Overtime : réel ;
i, n: entier ;
début
écrire('Nombre employés :') ;
lire(n)
i 0 ;
tantque (i < n) faire
début
écrire('Nom employé :') ;
lire(nom) ;
écrire('Taux horaire :') ;
lire(Rate) ;
écrire('Heures effectuées :') ;
lire(HoursWorked) ;
i i + 1 ;
si (HoursWorked > 40) alors
Overtime HoursWorked – 40.0
sinon
Overtime 0 ;
Regular HoursWorked – Overtime ;
Salary Rate* Regular + 1.5*Rate*Overtime ;
écrire('Nom :', Nom) ;
écrire('Salaire : ', Salary);
fin ;
fin.
Une première approche naïve consiste à compter manuellement les nombres et utiliser le
compte final comme dans le cas précédent. Cette approche trop simpliste est non seulement
encombrante mais inappropriée dans la plupart des cas. Une approche plus élégante pour
résoudre ce problème consiste à utiliser une sentinelle pour indiquer la fin des données. Les
données du problème sont préparées comme d’habitude mais une valeur spéciale, qui ne peut
pas être confondue avec les vraies données est ajoutée à la fin de la liste. Cette valeur, que
l’on appelle la sentinelle est là pour indiquer la fin des données. Par exemple, si on suppose
que les nombres dont on veut calculer la moyenne doivent tous être positifs, alors on peut
utiliser la valeur –1 comme sentinelle.
Toutes les valeurs à l’exception de –1 sont des données réelles et doivent être utilisées dans le
calcul de la moyenne. Le –1 est la sentinelle et ne devra pas être utilisé dans le calcul de la
moyenne. Une liste composée uniquement de la valeur –1 sera considérée comme une liste
vide.
Pour donc écrire un algorithme pour calculer la moyenne d’une liste de nombres positifs se
terminant –1, nous allons utiliser la valeur lue pour contrôler la boucle. En d’autres termes,
nous allons continuer à lire et à traiter les valeurs jusqu’à ce l’on lise la valeur de la sentinelle.
L’algorithme de calcul de la moyenne devient alors :
algorithme CalculMoyenne ;
var
nombre, total, moyenne : réel ;
nb : entier ;
début
(*Initialisation de compteur et total*)
nb 0 ;
total 0 ;
écrire('Entrez un nombre (-1 pour terminer) ') ;
lire(nombre) ;
tantque (nombre -1) faire
début
total total + nombre ;
nb nb +1 ;
écrire('Entrez un nombre (-1 pour terminer) ') ;
lire(nombre) ;
fin ;
Autres exemples
Exemple 1. Écrire un algorithme pour lire les noms et les années de naissance des enfants
d’une personne et imprimer à chaque fois le message, « … est âgé de … ans ». La répétition
prend fin quand on entre un nom égal à ‘*’.
algorithme agesenfants ;
var
nom : chaîne ;
naissance, age, année : entier ;
début
algorithme enfants ;
var
nom : chaîne ;
i, naissance, âge, année : entier ;
début
écrire('Quelle année de référence ? ') ;
lire(année) ;
i 0 ;
écrire('Entrez un nom (* pour terminer) ') ;
lire(nom) ;
tantque (nom '*') et (i < 10) faire
début
écrire('Quelle est son année de naissance ? ') ;
lire(naissance) ;
âge année – naissance ;
écrire(nom, 'est âgé de', âge, 'ans') ;
écrire('Entrez le prochain nom (* pour terminer) ') ;
lire(nom) ;
i i + 1 ;
fin ;
fin.
Exercices d’apprentissage
Exercice 3.3
Lesquelles des assertions suivantes s’appliquent à la structure répéter…jusquà seule ? A la
structure tantque…faire seule ? Aux deux à la fois ?
i. Le corps de la boucle est exécuté au moins une fois.
ii. L’expression logique qui contrôle la boucle est évaluée avant l’entrée de la boucle.
iii. Doit utiliser la construction début…fin si plusieurs instructions doivent être répétées.
iv. Le corps de la boucle peut ne pas être exécuté du tout.
v. Utilise une condition de terminaison.
vi. Utilise une condition de répétition.
Exercice 3.4
Écrire un algorithme qui lit le nom et le sexe d’une personne et affiche le message « Bonjour,
Monsieur … » si la personne est de sexe masculin. Si la personne est de sexe féminin, le
programme lui demande si elle est mariée ou non. Si la personne est mariée, le programme
affiche le message « Bonjour, madame … » sinon il affiche le message « Bonjour,
Mademoiselle … ».
Exercice 3.5
Écrire un algorithme qui va répéter le traitement ci-dessus pour un nombre quelconque de
personne. La répétition s’arrête lorsque l’utilisateur entre un nom égal à ‘*’.
Exercice 3.6
Écrire un algorithme qui demande à l’utilisateur de donner son nom, son sexe, son année de
naissance et une année de référence puis affiche le message « Monsieur … votre âge est de …
ans » si la personne est de sexe masculin et le message « Madame … votre âge est de … ans »
si la personne est de sexe féminin.
Exercice 3.7
Écrire un algorithme qui va répéter le traitement ci-dessus pour un nombre quelconque de
personne. La répétition s’arrête lorsque l’utilisateur entre un nom égal ‘*’.
Exercice 3.8
Écrire un algorithme qui va répéter le traitement ci-dessus pour n personnes au plus (n lu). La
répétition s’arrête lorsque l’utilisateur entre un nom égal à ‘*’ ou que le nombre de personnes
traitées devient supérieur à n.
Exercice 3.9
Écrire un algorithme qui lit une suite de nombres positifs se terminant par –1 et détermine le
plus grand de ces nombres.
Exercice 3.10
Écrire un algorithme qui lit une suite de nombres positifs se terminant par –1 et détermine le
plus grand de ces nombres ainsi que sa position dans la liste.
Dans certains cas où nous devons utiliser une instruction répéter…jusquà, faire…tantque
ou tantque…faire, le langage algorithmique nous offre une autre possibilité qui simplifie
davantage la construction des boucles. Nous pouvons le faire lorsque nous connaissons
d’avance combien de fois la boucle doit être exécutée. Considérons la boucle tantque…faire
suivante utilisée pour imprimer les 10 premiers entiers positifs :
nombre 1 ;
tantque nombre 10 faire
début
écrire(nombre) ;
nombre nombre + 1 ;
fin ;
La valeur de la variable nombre est utilisée pour contrôler la boucle. D’abord, nombre est
initialisé à sa valeur initiale (ici 1). La valeur courante de nombre est ensuite comparée à sa
valeur finale (ici 10). Si elle est inférieure ou égale à 10 le corps de la boucle est exécuté puis
la valeur de nombre est incrémentée de 1 et le processus, à l’exception de l’initialisation est
répété. Une seule instruction algorithmique permet de combiner certaines de ces étapes ; il
s’agit de l’instruction pour…haut…faire dont la forme générale est :
2. La valeur courante de la variable de contrôle est comparée à sa valeur finale. Si elle est
inférieure ou égale à la valeur finale, le corps de la boucle est exécuté.
3. La variable de contrôle est automatiquement incrémentée de 1.
4. Les étapes 2 et 3 sont répétées jusqu’à ce que la valeur de la variable de contrôle
devienne supérieure à la valeur finale.
La boucle pour…haut…faire est utile dans les problèmes où on connaît d’avance les valeurs
initiale et finale de la variable de contrôle. Ceci est généralement le cas lorsqu’on travaille
avec les vecteurs, un sujet que nous aborderons ultérieurement.
i valeurinitale
faux
i i +1 i valeurfinale
vrai
Corps de la boucle
Boîte
Considérons l’exemple additionnel suivant. Il s’agit de calculer et d’imprimer le produit des
termi
dix premiers entiers naturels non nuls. nal
produit 1 ;
pour i 1 haut 10 faire
produit produit * i ;
écrire(produit) ;
Comparer ce code à celui que l’on obtiendrait en utilisant la boucle répéter … jusquà, faire
… tantque ou tantque … faire.
Considérons maintenant le problème suivant : on Boîte veut imprimer les entiers de 1 à 100 dans
l’ordre inverse. Pour faire cela on peut utiliser la forme
de suivante de la boucle pour :
calcul
pour nombre 100 bas 1 faire
écrire(nombre) ;
pour …bas…faire pour calculer le produit des 10 premiers entiers naturels non nuls comme
illustré dans un exemple précédent :
produit 1 ;
pour i 10 bas 1 faire
produit produit * i ;
écrire(produit) ;
où instruction est une instruction simple ou composée. Dans ce dernier cas, l’instruction pour
prend la forme
La variable de contrôle, la valeur initiale et la valeur finale doivent être de type entier. Nous
allons assouplir cette condition plus tard, mais le point important est qu’elles ne peuvent pas
être de type réel. La valeur initiale de la variable de la variable de contrôle est affectée une
seule fois. Elle change au fur et à mesure que la boucle est répétée. Cependant, lorsque la
boucle se termine, la variable de contrôle est indéfinie. Ceci signifie que l’on ne peut pas
supposer que la variable de contrôle a une valeur égale à la valeurfinale + 1 (ou égale à la
valeurinitiale – 1). On traitera la variable de contrôle à la fin de la boucle pour comme une
variable indéfinie (non initialisée).
i valeurinitale
Figure 3.5. Organigramme de la structure pour…bas…faire.
faux
i i -1 i valeurfinale
Dr Ndi Nyoungui André
vrai
Corps de la boucle
Boîte
termi
nal
78 Algorithmique et structures de données
Boîte
de
calcul
On peut utiliser l’instruction pour…haut…faire pour la lecture des données à condition que
celles-ci soient correctement arrangées. Pour utiliser la boucle pour…haut…faire, on doit
connaître exactement d’avance combien de valeurs on va lire. Ceci peut être accompli en
comptant d’abord les valeurs à lire et en incluant le compte comme la première valeur à lire.
algorithme CalculMoyenne ;
var
nombre, total, moyenne : réel ;
i, n : entier ;
début
total 0 ;
écrire('Quelle est la valeur de n ?')
lire(n) ;
for i 1 haut n faire
début
écrire('Entrez un nombre : ') ;
lire(nombre) ;
total total + nombre ;
fin ;
moyenne total/n ;
écrire('La moyenne est : ', moyenne) ;
fin.
Exercices d’apprentissage
Exercice 3.11
Quelle sera la sortie du segment d’algorithme ci-dessous ?
Exercice 3.12
Quelle sera la sortie du segment d’algorithme ci-dessous ?
Exercice 3.13
Écrire un algorithme qui calcule la somme des carrés des n premiers entiers naturels non nuls.
Exercice 3.14
Écrire un algorithme qui lit un entier positif et calcule le factoriel de ce entier.
Exercice 3.15
Le chercheur médiéval Leonardo (Fibonacci) da Pisa, a proposé la suite infinie dans laquelle
chaque terme est la somme des deux termes précédents. Le n ème nombre de Fibonacci est
défini récursivement de la manière suivante :
Écrire un algorithme qui calcule et imprime les 100 premiers nombres de Fibonacci.
Exercice 3.16
Écrire un algorithme qui calcule et imprime les rapports entre les 100 premières paires de
nombres de Fibonacci consécutifs.
Exercice 3.17
La série de Maclaurin de la fonction exp(x) est donnée par :
x x2 xn xn
exp( x ) 1
1! 2! n! n 0 n!
Clairement, lorsque n devient grand, la contribution de chaque terme additionnel devient très
petite. Écrire un algorithme qui calcule et imprime les valeurs de exp(x) obtenues en utilisant
un nombre croissant de termes dans la série de Maclaurin de exp(x). L’itération s’arrête dès
que le terme courant devient inférieur à un certain epsilon.
Exercice 3.18
Lorsqu’un capital est déposé dans une banque qui paie les intérêts une fois par mois, et
lorsque le capital initial et les intérêts restent en dépôt, les intérêts sont dits composés. La
Écrire un algorithme qui calcule et imprime le capital en dépôt Pn lorsque le capital initial
P0 est placé au taux d’intérêt I à la fin de chacun des n premiers mois.
Jusqu’à ce moment nous n’avons considéré que les types de données entier, réel et booléen.
Nous introduisons maintenant les autres types scalaires du langage algorithmique. Les types
que nous allons étudier sont le type caractère, en abrégé char, et les types définis par
l’utilisateur. Ces deux types sont très utiles pour écrire les boucles.
Le type caractère
Même si nous avons utilisé des variables booléennes pour contrôler le cours de l’exécution
d’un algorithme, nous avons jusque là travaillé principalement avec des valeurs numériques.
Nous avons utilisé des chaînes de caractères pour formater les sorties dans les instructions
d’écriture ; cependant, ces chaînes de sont des constantes et nous ne pouvons pas changer
leurs valeurs. Il est important dans certaines situations d’avoir des variables qui prennent des
valeurs caractères. De nombreuses applications nécessitent la manipulations de données
caractères. Par exemple, un algorithme qui imprime les factures des clients doit être capable
de travailler non seulement avec des données numériques impliquant les charges et les
paiements, mais doit aussi traiter des données alphabétiques tels que le nom du client, son
adresse, et ainsi de suite.
Le langage algorithmique offre le type de données standard caractère. Les éléments du
caractère sont les caractères individuels qui sont représentables dans un code donné tel que le
code ASCII. Une constante caractère est une chaîne de caractère entre apostrophes.
Pour déclarer une variable de type caractère, on utilise une instruction de la forme :
var
identificateur : char ;
Pour lire une variable de type caractère, on utilise une instruction de la forme :
lire(lettre)
si lettre est une variable de type caractère. Cette instruction un caractère et affecte sa valeur à
la variable lettre.
Examinons maintenant un algorithme qui lit des caractères. L’algorithme ci-dessous lit une
suite de caractères et compte le nombre de blancs contenus dans la chaîne.
algorithme CompteBlancs ;
const
Blank = ' ' ;
var
nbre : entier ;
Ch: char ;
début
nbre 0 ;
lire(Ch) ;
tantque (Ch '.') faire
début
si (Ch = Blank) alors
nbre nbre + 1;
lire(Ch) ;
fin;
écrireln('Nombre de blancs :', nbre) ;
fin.
L’algorithme ci-dessous lit une suite caractère se terminant par un point et imprime chaque
mot sur une ligne séparer. On suppose que les mots sont séparés les uns des autres par un seul
caractère blanc.
algorithme Words;
const
Blank = ' ';
var
Ch : char;
début
lire(Ch);
tantque (Ch '.') faire
début
si (Ch = Blank) alors
écrireln
sinon
écrire(Ch) ;
lire(Ch) ;
fin;
fin.
« Ceci est une question sur les caractères, cependant, elle n’est pas difficile. »
algorithme Words;
var
Ch: char;
début
répéter
lire(Ch);
si (Ch ',') alors
écrire(Ch)
sinon
écrireln;
jusquà (Ch = '.');
fin.
algorithme Words;
const
Blank = ' ';
var
Ch: char;
début
lire(Ch);
tantque (Ch Blank) faire
début
tantque (Ch Blank) faire
début
écrire(Ch) ;
lire(Ch) ;
fin ;
lire(Ch) ;
fin;
fin.
Les types de données que nous étudiés jusque là (entier, réel, caractère, booléen) sont tous des
types standards. Le langage algorithmique donne aussi aux programmeurs la possibilité de
définir leurs propres types de données. Ceci est souvent utile pour simplifier les structures de
contrôle et donc améliorer la lisibilité des algorithmes. Un programmeur peut définir un
nouveau type scalaire de deux manières :
Supposons que l’on ait besoin d’une boucle pour calculer le revenu hebdomadaire d’un
employé connaissant son revenu journalier pour les jours allant de lundi à vendredi. Une
solution peut prendre la forme suivante :
total 0 ;
pour day 1 haut 5 faire
début
lire(dailypay) ;
total total + dailypay ;
fin;
On suppose bien entendu que des déclarations appropriées ont été effectuées auparavant. Plus
spécifiquement, on suppose que la variable day a été déclarée de la manière suivante :
var
day : entier ;
var
day : (dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi) ;
Ce que cette déclaration fait est d’énumérer toutes les valeurs possibles que la variable day
peut prendre. On peut maintenant réécrire la boucle comme suit :
total 0 ;
pour day lundi haut vendredi faire
début
lire(dailypay) ;
total total + dailypay ;
fin;
Le sens de la boucle est clairement plus apparent dans cette deuxième expression. La variable
day dans cette deuxième illustration est dite être de type défini par l’utilisateur. On aurait pu
rendre ce type plus explicite en utilisant une définition de type pour avoir un type avec son
propre identificateur :
type
tweekday = (dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi) ;
var
Day : tweekday ;
La définition de type ci-dessus rend disponible un identificateur pour le nouveau type scalaire
tweekday. On peut ensuite utiliser ce nouveau type pour déclarer des variables de ce type
comme nous l’avons fait avec la variable Day.
type
identificateur = (identificateur1, identificateur2, …, identificateurn) ;
On se rappellera que lorsqu’on utilise une définition de type, l’identificateur que l’on choisit
pour le type n’est pas un nom de variable et que l’on ne peut par conséquent pas lui affecter
des valeurs. Dans l’exemple ci-dessus, le rôle joué par l’identificateur WeekDay est
exactement le même que celui joué par les identificateurs prédéfinis entier, réel, caractère ou
booléen. En effet, le type booléen est un type énuméré prédéfini. Sa définition est :
type
booléen = (faux, vrai) ;
Nous n’avons bien entendu pas besoin de spécifier cette définition dans nos algorithmes, car
elle est directement offerte par le langage algorithmique.
De même que l’identificateur de type n’est pas une variable, il en est de même des
identificateurs qui représentent les différentes valeurs possibles de ce type. Nous ne pouvons
pas affecter de valeur à lundi ou à mardi, de même que nous ne pouvons pas affecter de valeur
à vrai ou à faux. La définition d’un type scalaire énuméré introduit non seulement un nouvel
identificateur de type mais fait aussi la chose suivante :
L’ordre dans lequel les valeurs sont listées dans la définition de type établit l’ordre pour ce
type. Dans l’exemple ci-dessus, dimanche a été listé le premier. Ceci rend dimanche inférieur
à toutes les autres valeurs de la liste. De même, lundi est inférieur à toutes les autres valeurs
qui le suivent. A cause de cet ordre, tout identificateur qui est utilisé pour spécifier une valeur
dans un type énuméré ne peut apparaître que dans une seule définition de type à la fois. Les
deux définitions de type suivantes, prises ensemble, sont invalides :
type
tcouleur = (rouge, bleu, jaune) ;
tnuance = (rose, gris, jaune, rouge);
Si les deux définitions sont admises, on ne pourra pas décider si jaune < rouge ou rouge <
jaune.
Les types de données scalaires énumérés sont à usage interne uniquement. On peut affecter et
tester de telles valeurs dans un algorithme, mais on ne peut pas les lire ou écrire en utilisant
une instruction de lecture ou une instruction d’écriture. Il existe cependant des astuces que
nous pouvons utiliser pour lire ou écrire indirectement de telles valeurs. Supposons par
exemple que la variable sexe soit déclarée de la manière suivante :
var
sexe : (Féminin, Masculin) ;
L’instruction ci-dessous peut être utilisée pour affecter une valeur à la variable sexe.
lire(Ch) ;
si Ch = 'M' alors
sexe Masculin
sinon
si Ch = 'F' alors
sexe Féminin ;
De même, l’instruction ci-dessous peut être utilisée pour écrire la valeur de la variable sexe
sinon
écrire('Féminin') ;
Il arrive souvent qu’une valeur soit destinée à recevoir des valeurs d’un certain type dans
un intervalle spécifique uniquement. Par exemple, on peut avoir la déclaration suivante :
var
année : entier ;
Cependant dans l’algorithme, les valeurs de la variable année seront toujours comprises entre
1900 et 2002. La déclaration
var
année : 1900..2020 ;
réalise cette restriction. Par ailleurs, le système génère un code pour vérifier qu’aucune valeur
en dehors de cet intervalle n’est affectée à année lorsque le programme s’exécute. Si une telle
affectation est tentée, elle résulte en une erreur d’exécution et le programme s’arrête. Ce genre
de vérification peut être utile lorsque le programme est entrain d’être exécuté.
Tout comme dans le cas des types scalaires énumérés, on peut définir explicitement un type
intervalle en utilisant une instruction de définition de type. La forme générale d’une définition
de type intervalle est :
type
identificateur = valeurinf .. valeursup ;
type
tettre = 'A'..'Z' ;
tpériode = 1900..2220 ;
tworkday = lundi..vendredi ;
La dernière définition de type est uniquement valide si le type énuméré WeekDay, comme
nous l’avons vu précédemment, a été défini avant la définition de tworkday. Les types
intervalles peuvent être définis pour les entiers, les caractères et les types énumérés. Les types
intervalles ne sont pas autorisés pour les réels.
Le langage algorithmique a des fonctions prédéfinies qui sont utiles quand on travaille avec
des types scalaires. Ces fonctions sont les suivantes :
Les fonctions succ et pred sont utilisées avec n’importe lequel type scalaire, à l’exception du
type réel, pour obtenir le successeur ou le prédécesseur d’une valeur donnée. Quand elles sont
utilisées avec les entiers, les fonctions pred et succ peuvent être employées pour incrémenter
ou décrémenter les compteurs.
Le prédécesseur de la première valeur et le successeur de la dernière valeur dans un type
énuméré ne sont pas définis. L’ordre des valeurs dans un type énuméré est défini par l’ordre
dans lequel les constantes de ce type sont énumérées.
Chaque valeur dans un type énuméré est associée à un nombre qui indique sa position dans la
liste. La première valeur est associée à la position 0, la deuxième à la position 1, et ainsi de
suite. Les nombres qui indiquent la position des constantes dans la liste sont appelés les
nombres ordinaux des constantes correspondantes. La fonction prédéfinie ord retourne le
nombre ordinal de son argument.
Nous avons maintenant étudié toutes les structures de contrôle du langage algorithmique. Il y
a trois structures de contrôle de base au moyen desquelles on peut contrôler l’exécution des
instructions dans un algorithme. Ces structures sont : la séquence, la sélection et la répétition.
Ce sont les blocs de construction de base de tous les algorithmes.
Dans une structure séquentielle, les instructions sont écrites les unes à la suite des autres.
Dans le langage algorithmique le prototype d’une structure séquentielle est une instruction
composée. Une telle structure est délimitée par les mots réservés début et fin. De cette façon,
une structure séquentielle peut s’écrire de la manière suivante :
début
instruction1 ;
instruction2 ;
...
instruction2 ;
fin ;
vrai faux
Condition
instruction 1 instruction 2
si (condition) alors
instruction1
sinon
instruction2 ;
si (condition) alors
début
instruction1 ;
instruction ;
…
instructionn ;
fin
sinon
début
instruction1 ;
instruction2 ;
…
instructionm ;
fin ;
vrai
expression-logique
faux
instruction
si (condition) alors
instruction ;
où instruction est une instruction simple ou composée. Dans ce dernier cas, elle prend la
forme suivante :
si (condition) alors
début
instruction ;
instruction2 ;
…
instructionn ;
fin ;
Enfin, l’instruction de sélection multiple est illustrée par l’organigramme de la figure 3.9 ci-
dessous.
expression
case (expression) de
liste-valeur1 : instrcution1 ;
liste-valeur2 : instruction2 ;
...
liste-valeurn : instruction n ;
fin ;
ou encore
case (expression) de
liste-valeur1 : instrcution1 ;
liste-valeur2 : instruction2 ;
…
liste-valeurn : instruction n ;
sinon
autre-instruction
fin ;
instruction
faux vrai
Expression
logique
répéter
instruction
jusquà (condition) ;
où instruction est une instruction simple ou composée. Dans ce dernier cas, elle prend la
forme suivante :
répéter
instruction1 ;
instruction2 ;
…
instructionn ;
jusquà (condition) ;
faire
instruction
tantque (expression-logique) ;
où instruction est une instruction simple ou composée. Dans ce dernier cas, elle prend la
forme suivante :
faire
instruction1 ;
instruction2 ;
…
instruction ;
tantque (expression-logique) ;
faux
expression logique
vrai
instruction
où instruction est une instruction simple ou composée. Dans ce dernier cas, elle prend la
forme suivante :
Enfin, l’instruction pour…haut…faire est illustrée par l’organigramme de la figure 3.12 ci-
dessous :
i valeurinitiale
faux
ii+1 i valeurfinale
vrai
instruction
où instruction est une instruction simple ou composée. Dans ce dernier cas, elle prend la
forme suivante :
Enfin, pour illustrer les différentes structures de contrôle que nous venons de présenter,
considérons l’algorithme ci-dessous. Il s’agit d’un algorithme qui calcule et imprime l’âge
moyen des femmes ainsi que l’âge moyen des hommes présents dans une liste quelconque de
personnes.
algorithme AgeMoyen ;
var
sexe : char ;
age, rotalfem, totalhom, nbfem, nbhom : entier ;
moyfem, moyhom : réel ;
début
totalfem 0 ;
nbfem 0 ;
totalhom 0 ;
nbhom 0 ;
écrire('Quel est votre sexe (M ou F)?') ;
lire(sexe) ;
tantque sexe '*' faire
début
écrire('Quel est votre âge ?') ;
lire(age) ;
si sexe = 'F' alors
début
totalfem totalfem + age ;
nbfem nbfem + 1 ;
fin
sinon
début
totalhom totalhom + age ;
nbhom nbhom + 1 ;
fin ;
écrire('Quel est votre sexe (M ou F)?') ;
lire(sexe) ;
fin ;
si nbfem = 0 alors
écrire('Aucune femme dans la liste') ;
sinon
début
moyfem totalfem/nbfem ;
écrire('Âge moyen des femmes : ', moyfem) ;
fin ;
si nbhom = 0 alors
écrire('Aucun homme dans la liste') ;
sinon
début
moyhom totalhom/nbhom ;
écrire('Âge moyen des hommes : ', moyhom) ;
fin ;
fin.
Examinons maintenant une application utilisant une boucle qui commence par une valeur
singulière et répète un ensemble d’opérations jusqu’à ce que la solution désirée soit obtenue.
Nous allons examiner un problème mathématique classique : le calcul de la racine carrée d’un
nombre. Il y a plusieurs algorithmes pour calculer les racines carrées. Nous allons utiliser
celui que l’on attribue à Isaac Newton. La méthode de Newton utilise l’approche suivante : si
r est une approximation de la racine carrée d’un nombre x, alors (x + x/r)/2 est une meilleure
approximation de la racine carrée de x. On commence donc par une approximation arbitraire,
par exemple 1, et on répète le calcul de l’approximation jusqu’à ce que la différence entre le
carré de l’approximation et le nombre devienne inférieure à un epsilon donné. Écrire un
algorithme qui lit un nombre et calcule sa racine carrée.
CHAPITRE 4
PROCEDURES ET FONCTIONS
4.1 Introduction
Dans les algorithmes que nous avons écrits jusque là, nous avons utilisé des procédures et des
fonctions qui sont directement offertes par le langage algorithmique. On les appelle ainsi des
fonctions ou procédures prédéfinies. Ces fonctions ou procédures implémentent la plupart des
opérations usuelles que nous utilisons très souvent dans les algorithmes.
Nom Description
abs(x) Calcule la valeur absolue de x
arctan(x) Calcule arc tangente de x
cos(x) Calcule le cosinus de x
cosh(x) Calcule le cosinus hyperbolique de x
exp(x) Calcule l’exponentiel de x
ln(x) Calcule le logarithme naturel de x
log(x) Calcule le logarithme décimal de x
pred(x) Calcule le prédécesseur de x dans une énumération
round(x) Calcule la partie entière de x
sin(x) Calcule le sinus de x
sinh(x) Calcule le sinus hyperbolique de x
Sqr(x) Calcule le carré de x
sqrt(x) Calcule la racine carrée de x
succ(x) Calcule le successeur de x dans une énumération
tan(x) Calcule la tangente de x
Nom Description
écrire Imprimer des données à l’écran
écrireln Imprimer des données à l’écran
libérer Libérer la mémoire occupée par une variable dynamique
Lire Lire des données au clavier
lireln Lire des données au clavier
nouveau Initialiser une variable dynamique
réécrire Ouvrir un fichier en écriture
relire Ouvrir un fichier en lecture
Les fonctions prédéfinies du langage algorithmique que nous avons étudiées dans les
chapitres précédents, effectuent des opérations qui ne sont pas facilement réalisables avec les
opérateurs usuels. En outre, les programmeurs peuvent définir leurs propres fonctions pour
étendre les capacités du langage algorithmique. Comme exemple, supposons que nous avons
besoin d’une fonction pour calculer le factoriel d’un entier positif. Nous pouvons créer cette
fonction en utilisant la définition de fonction suivante :
En général, la liste des paramètres formels peut contenir un nombre quelconque de paramètres
de types différents. Le corps de la fonction, délimité par les mots réservés début et fin, décrit
les actions et/ou les traitements qui doivent être effectuées sur les données d’entrée lorsque la
fonction est exécutée. Pour communiquer le résultat des calculs à l’algorithme appelant, ce
résultat doit être affecté au nom de la fonction. Dans notre exemple, le corps de la fonction
contient une boucle qui calcule le factoriel de n et une instruction qui affecte cette valeur au
nom de la fonction. Cette valeur peut ensuite être communiquée à la portion de l’algorithme
qui a appelé la fonction.
Voyons maintenant comment une fonction définie par l’utilisateur fonctionne. L’exemple ci-
dessous illustre la définition et l’utilisation d’une fonction dans un algorithme.
algorithme AppelFonction ;
variable
nombre, n : entier ;
(*déclaration de la fonction*)
fonction factoriel(x : entier) : entier ;
var
fact : entier ;
début
fact 1 ;
pour i 1 haut x faire
fact fact*i ;
factoriel fact ;
fin ;
(*algorithme principal*)
début
n 0 ;
tantque n 10 faire
début
nombre factoriel(n) ;
écrire(n, ' ! = ', nombre) ;
n n + 1 ;
fin ;
fin.
On remarquera que les fonctions sont définies après la déclaration des variables. On notera
aussi que le mot réservé fin qui termine la définition d’une fonction est suivi par un point
virgule et non un point. Seul l’algorithme principal se termine par un point. La forme générale
d’une définition de fonction est la suivante :
Type-résultat est le type de la valeur que la fonction délivre (entier, réel, caractère, booléen,
chaîne, pointeur ou un type énuméré défini par l’utilisateur). On notera que le corps de la
fonction doit contenir au moins une instruction qui affecte une valeur au nom de la fonction.
Cette valeur est retournée comme le résultat de la fonction. La section de déclaration des
variables locales de la fonction est utilisée pour déclarer les variables utilisées uniquement par
la fonction et nulle part ailleurs dans l’algorithme.
Le but d’une définition de fonction est de décrire les calculs qui doivent être effectués sur les
données de la liste des paramètres formels. Il est important de souligner qu’aucun traitement
n’est effectué à la suite de la définition d’une fonction. L’exécution effective des instructions
du corps de la fonction intervient uniquement lorsque la fonction est invoquée dans un
algorithme. Dans l’exemple ci-dessus, l’instruction
nombre factoriel(n) ;
Nous avons vu qu’une fonction définie par l’utilisateur peut avoir plusieurs paramètres
formels. Considérons la fonction ci-dessous qui calcule le plus grand entre deux nombres.
Le désignateur de fonction
plusgrand(7, 15)
plusgrand(7, 15)
le même que celui du paramètre formel correspondant. Par exemple, les expressions suivantes
sont toutes des invocations valides de la fonction plusgrand :
Dans les deux derniers exemples, un des paramètres effectifs est lui-même un désignateur de
fonction. Lorsqu’une expression contient des appels imbriqués à des fonctions, l’appel le plus
interne est exécuté le premier et le résultat de cet appel est utilisé comme paramètre effectif
dans l’exécution de l’appel supérieur.
La liste des paramètres formels d’une définition fonction peut contenir paramètres scalaires
aussi que des paramètres structurés, tels que des vecteurs, des articles ou des listes chaînées.
Exemple 1. Écrire une fonction qui prend en entrée trois nombres réels et retourne le plus
grand de ces nombres.
Exemple 2. Écrire une fonction qui prend en entrée quatre nombres réels et retourne le plus
grand de ces nombres.
Première solution
Cette solution utilise une instruction si…alors…sinon imbriquée pour calculer le maximum
des quatre nombres.
sinon
si c > d alors
max4 c
sinon
max4 d
sinon
si b > c alors
si b > d alors
max4 b
sinon
max4 d
sinon
si c > d alors
max4 c
sinon
max4 d ;
fin ;
Deuxième version
On peut éviter d’utiliser une imbrication très profonde des instructions si…alors … sinon en
utilisant une succession d‘instruction si…alors. L’algorithme devient alors.
Troisième version
On peut éviter d’utiliser une imbrication très profonde des instructions si…alors … sinon en
utilisant les conditions composées. L’algorithme devient alors.
max4 b
sinon
si (c a) et (c b) et (c d) alors
max4 c
sinon
max4 d ;
fin ;
Exercices d’apprentissage
Exercice 4.1
Écrire une fonction qui prend en entrée deux entiers positifs n et p et retourne Anp , le nombre
d’arrangements p à p de n éléments.
Exercice 4.2
Une année est bissextile si son millésime est un multiple de 4, sauf les années de début de
siècle qui sont bissextiles si leur millésime est divisible par 400.
Écrire une fonction booléenne qui reçoit en entrée un entier naturel et détermine si cet entier
représente une année bissextile.
Exercice 4.3
Une boutique est ouverte de 7 heures à 13 heures et de 16 heures à 22 heures, sauf le
dimanche après-midi et le lundi toute la journée. Écrire un fonction booléenne qui reçoit en
entrée une heure (un entier naturel) et un jour (une chaîne de caractère) et détermine si la
boutique est ouverte le jour et à l’heure indiqués..
Exercice 4.4
Les tarifs d’affranchissement d’une lettre sont les suivants :
en-dessous de 20g : 280 FCFA,
à partir de 20g, mais en-dessous de 50g : 440 FCFA,
à partir de 50g : 670 FCFA.
Écrire une fonction qui prend en entrée le poids d’une lettre et retourne le montant de
l’affranchissement de la lettre.
Exercice 4.5
Un entier positif est premier s’il est supérieur à deux et s’il n’est divisible que par un et par
lui-même. Écrire une fonction qui prend en entrée un entier positif et détermine si cet entier
est un nombre premier.
Exercice 4.6
On souhaite calculer le montant des impôts dus par un contribuable en fonction son revenu
imposable et de son nombre de parts fiscales. Les règles de calcul sont les suivantes :
- le revenu par part fiscale est égale au quotient du revenu imposable par le nombre de parts
fiscales
- l’impôt par part fiscale est calculé selon le barème suivant :
0 si le revenu par part fiscale est inférieur à 50 000 F ;
10% sur la tranche du revenu par part comprise entre 50 000 F et 100 000 F ;
25% sur la tranche du revenu par part comprise entre 100 000 F et 200 000 F
50% sur le revenu par part fiscale est qui dépasse 200 000 F
- l’impôt total est égal au nombre de parts fiscales multiplié par l’impôt par part fiscale
Écrire une fonction qui prend en entrée le revenu imposable et le nombre de parts fiscales
d’un contribuable et retourne le montant des impôts dus par ce contribuable.
Exercice 4.7
Lorsqu’un capital est déposé dans une banque qui paie les intérêts une fois par mois et que le
capital initial et les intérêts restent en dépôt, on dit que les intérêts sont composés. La formule
qui donne le capital courant P lorsque le capital initial est A et que les intérêts sont payés au
taux annuel I au bout de N mois est :
P A1 I N
Écrire une fonction qui prend en entrée le capital initial A, le nombre d’années N et calcule le
capital courant P au bout des N mois.
Les principales différences entre une définition de procédure et une définition de fonction sont
les suivantes :
On ne spécifie pas dans l’en-tête de la procédure un type pour la procédure elle même.
Ceci n’est pas nécessaire car une procédure peut ne pas retourner de résultat du tout,
ou elle peut retourner plusieurs valeurs de différents types.
Il n’est pas permis d’affecter une valeur au nom de la procédure dans le corps de la
procédure. Le nom de la procédure est utilisé uniquement pour identifier la procédure.
Enfin, le mot réservé procédure, au lieu de fonction, est utilisé dans l’en-tête de la
procédure.
y produit ;
fin ;
L’algorithme ci-dessous montre comment la procédure factoriel est déclarée et invoquée dans
un algorithme.
algorithme AppelProcédure ;
var
n, p, nombre : entier ;
(*déclaration de la procédure*)
procédure factoriel(x : entier ; var y : entier) ;
variable
i, produit : entier ;
début
produit 1 ;
pour i 1 haut x faire
produit produit*i ;
y produit ;
fin ;
(*algorithme principal*)
début
factoriel(10, b) ;
écrire('10! = ', b) ;
n 0 ;
tantque n 10 faire
début
factoriel(n, nombre) ;
écrire(n, ' ! = ', nombre) ;
n n + 1 ;
fin ;
fin.
factoriel(10, b)
factoriel(n, nombre)
Ici, la valeur de n est passée à la procédure, qui calcule le factoriel de cette valeur et retourne
le résultat comme la valeur de la variable nombre. On notera que les appels de procédure sont
écrits comme des instructions indépendantes alors que les désignateurs de fonction sont
toujours utilisés comme des composantes d’une expression.
Pour comprendre comment les valeurs sont transmises à une procédure et retournées par la
procédure, examinons maintenant en détail le mécanisme de transmission des paramètres.
factoriel(10, b)
et l’en-tête de procédure
factoriel(10, b)
Lorsque les données sont communiquées du programme appelant à une procédure en utilisant
des paramètres transmis par valeur, ces valeurs sont affectées aux paramètres formels
correspondants. Conceptuellement, les paramètres transmis par valeur offrent un mécanisme
pour passer des données à la procédure. Un paramètre formel qui est un paramètre transmis
par valeur est distinct du paramètre effectif correspondant. C’est une nouvelle variable avec
un identificateur et un emplacement mémoire propres. Quand une procédure est appelée, le
système affecte les valeurs des paramètres effectifs correspondants aux paramètres transmis
par valeur appropriés. Lorsque la procédure se termine, la mémoire allouée aux paramètres
transmis par valeur est libérée et la valeur du paramètre transmis par valeur n’existe plus.
factoriel(10, b) ;
factoriel(n, nombre) ;
est exécutée, la valeur de n est affectée à x. La procédure effectue ensuite tous ses calculs
avec x. La valeur de n reste inchangée même si x est modifiée dans la procédure. En effet, elle
reste encore inchangée lorsque la procédure se termine, et x cesse d’exister. C’est précisément
parce que les paramètres transmis par valeur obtiennent leurs valeurs par une affectation des
valeurs des paramètres effectifs aux paramètres formels correspondants qu’on les appelle des
paramètres transmis par valeur. Comme les affectations sont utilisées pour communiquer les
valeurs aux paramètres formels transmis par valeur, les paramètres effectifs correspondants
aux paramètres transmis par valeur peuvent être des constantes, des variables ou des
expressions. Si une expression est utilisée comme paramètre effectif, elle est d’abord évaluée
et sa valeur est ensuite affectée au paramètre formel.
Comme les paramètres transmis par référence communiquent en établissant une équivalence
entre les identificateurs des variables correspondantes, les paramètres effectifs correspondants
aux paramètres transmis par référence doivent être des variables. Ils ne peuvent pas être des
constantes ou des expressions. En général, on utilise des paramètres transmis par valeur pour
retourner les résultats. On peut bien entendu utiliser les paramètres transmis par référence à la
fois pour passer des données à la procédure et retourner les résultats. Toutefois, comme les
paramètres transmis par référence modifieront automatiquement les paramètres effectifs
correspondants, si les paramètres formels sont modifiés dans la procédure, on évitera de les
utiliser lorsque les valeurs des paramètres effectifs doivent être protégées.
Autres exemples
Exemple 1. écrire une procédure qui reçoit en entrée un entier n, lit n nombres et le retourne
la somme de ces nombres.
Exemple 2. écrire une procédure qui reçoit en entrée un entier n, lit n nombres calcule la
différence entre la somme des nombres de rang impair et la somme des nombres de rang pair.
Exercices d’apprentissage
Exercice 4.8
Écrire une procédure qui reçoit en entrée un entier naturel n, lit n nombre réels et retourne le
plus grand de ces nombres.
Exercice 4.9
Écrire une procédure qui prend en entrée un entier naturel n, lit n nombres et retourne le plus
grand de ces nombres ainsi que sa position dans la liste.
Exercice 4.10
Écrire une procédure qui prend en entrée un entier n, lit n nombres et retourne le plus grand et
le plus petit de ces nombres ainsi que leurs positions respectives dans la liste.
Exercice 4.11
Écrire une procédure qui teste si un nombre n, non négatif, est un carré parfait. Cette
procédure fournit deux résultats : un booléen qui est vrai si et seulement si n est un carré
parfait, et un entier égal à la partie entière de la racine carrée de n.
Exercice 4.12
Utiliser la procédure ci-dessus dans une autre procédure, qui, pour un nombre entier, imprime
la racine de sa racine carrée ou de la partie entière de sa racine carrée, ou un message d’erreur
si le nombre est négatif.
Exercice 4.13
Écrire une fonction qui calcule n p, où p est un entier naturel et n un entier quelconque.
Exercice 4.14
Écrire une procédure qui prend en entrée trois nombres a, b et c et les permute de telle sorte
que l’on obtienne a b c.
Un outil important pour créer des algorithmes fiables est de préciser clairement les contraintes
sur les paramètres de la fonction ou de la procédure. On peut prédire ce que fera une
procédure ou une fonction si et seulement si toutes les préconditions de la procédure ou de la
fonction sont satisfaites. Inversement, une postcondition est la spécification de ce que fera
une fonction ou une procédure lorsque toutes les préconditions sont satisfaites.
instruction2 ;
…
instructionn ;
fin ;
Il est aussi possible de combiner les préconditions et les postconditions dans une seule
instruction de spécification. Dans ce cas, on insère la spécification à la suite de l’en-tête de la
procédure ou de la fonction : le mot spécification, suivi d’une précondition, d’une flèche
() et d’une postcondition.
Par exemple, la fonction factoriel marche correctement lorsque le paramètre n reçoit une
valeur positive. Nous pouvons donc réécrire cette fonction de la manière suivante :
Mais il n’est pas préciser comment on va procéder pour que ces contraintes soient respectées.
Les commentaires du code source peuvent être facilement ignorés, et peuvent devenir
obsolètes à cause des modifications opérées dans l’algorithme. Pour cela, certains langages de
programmation, à l’instar du C++, offre un moyen facile d’inclure des macros de vérification
des préconditions dans le code source des fonctions, un sujet qui sera examiné dans le cadre
du cours de programmation en C++.
4.6 La récursivité
Une fonction ou procédure récursive est une fonction ou procédure qui s’appelle elle-même
directement ou indirectement. Par exemple, rappelons que le factoriel d’un entier naturel est
défini par :
1, si n 0
n!
n( n 1 )! , sinon
C’est à ce genre de problèmes que la récursivité s’applique avec succès. Ceci parce que :
1. La solution est facile à calculer pour certains cas simples appelés cas de base. (Dans
notre exemple, le cas de base est le calcul de 0 ! qui est égal à 1).
2. Pour les autres cas, il existe un ensemble bien défini de règles qui conduisent
éventuellement à un cas de base. Ce sont les étapes de la récursivité. (Dans notre
exemple, pour n > 0 nous avons une règle qui nous permet de redéfinir le problème
avec une valeur de n plus petite. Ceci conduit éventuellement à une définition du
problème avec une valeur de 1 pour n!, et ainsi on a atteint le cas de base).
À partir de cette définition récursive, une version récursive de la fonction qui calcule le
factoriel d’un entier naturel est la suivante :
Comme nous l’avons déjà vu, une version itérative de la fonction factoriel est :
On a donc le choix entre une approche itérative et une approche récursive. La question est
maintenant de savoir laquelle des approches est supérieure à l’autre ? Dans la plupart des cas,
l’approche itérative est toujours plus efficace. L’ordinateur prend beaucoup plus de temps et
utilise beaucoup plus de mémoire pour effectuer des appels et des retours récursifs plus qu’il
n’en prend lorsqu’il exécute les boucles. Toutefois, la récursivité est très utile lorsqu’on ne
peut pas trouver directement une solution au problème alors que l’on peut trouver un moyen
de transformer le problème original en un problème identique plus simple ou plus petit. La
récursivité est aussi utile lorsque le problème est initialement exprimé de façon récursive. De
nombreux problèmes mathématiques sont de cette nature.
b , si b a et a mod b 0
GCD( a ,b ) GCD( b , a ), si a b
GCD( b , a mod b ), sinon
Il est donc maintenant facile de trouver une version récursive de la fonction qui calcule le plus
grand diviseur commun de deux entiers positifs.
Exemple 2. Le deuxième exemple de problème récursif que nous présentons est celui du
calcul des nombres de Fibonacci. Le nème nombre de Fibonacci est défini récursivement de la
manière suivante :
À partir de cette définition, on obtient facilement la fonction récursive suivante qui calcule le
nème nombre de Fibonacci.
fin ;
n n 1 n 1 n n n
, 1 , 0 , si n p .
p p p 1 0 n p
À partir de cette définition, on obtient facilement la fonction récursive suivante qui calcule les
coefficients binomiaux..
Exemple 4. Le quatrième exemple de problème récursif concerne le calcul des nombres Anp .
En effet, on démontre que le nombre d’arrangements de p éléments d’un ensemble à n
éléments est donné par la relation de récurrence :
Anp Anp1 pAnp11 ;
An0 1;
Ann n!
À partir de cette définition, on obtient facilement la fonction récursive suivante qui calcule les
nombres Anp .
Exercices d’apprentissage
Exercice 4.15
Considérons la fonction suivante :
Quelle est la sortie de cette fonction en utilisant les paramètres effectifs suivants ?
(a) 0 (b) –1
(c) 5 (d) 10
Exercice 4.16
Réécrire la fonction ci-dessus sous forme itérative.
Exercice 4.17
Écrire une version itérative de la fonction qui calcule le plus grand diviseur commun de deux
entiers positifs.
Exercice 4.18
Écrire une version itérative de la fonction qui calcule le nième nombre de Fibonacci.
Exercice 4.19
Écrire une version itérative de la fonction qui calcule les coefficients binomiaux.
La modularité
Nous avons examiné en détail dans les chapitres précédents, le processus de conception des
algorithmes pour des problèmes plus ou moins complexes. Nous allons voir maintenant
comment un algorithme complexe peut être décomposés en unités fonctionnelles plus petites
appelées modules et comment on peut utiliser ces modules pour faciliter la conception et la
validation de gros algorithmes.
Un module est une partie d’un système plus grand, plus complexe qui effectue une tâche
spécifique bien définie. Quand on parle de système complexe, que ce soit une pièce dans une
machine ou un algorithme, on peut le comprendre plus facilement si on le regarde comme
étant composé d’unités fonctionnelles distinctes. Par exemple, on peut regarder une
automobile comme consistant en un corps, un train de puissance (moteur et transmission), un
système de refroidissement, un système de suspension et ainsi de suite. Chacune de ces
composantes est un module.
Quand on conçoit une automobile, on peut le faire en termes de ces modules et ensuite
concevoir les modules eux-mêmes en termes d’autres modules. Par exemple, le train de
puissance, comme nous l’avons dit, consiste en un moteur et un système de transmission. Le
moteur lui-même peut être subdivisé en composants.
Module
Niveau 1
Le module de niveau 1 représente le système entier. Les modules de niveau 2 sont les
composantes majeures du système. Les modules du niveau 3 sont les blocs de construction à
partir desquels les modules du niveau 2 sont construits, et ainsi de suite.
Conception descendante
Une fois que le module de niveau 1 à été conçu et validé, on commence à travailler sur les
modules de niveau 2. Ici encore, on procède un module à la fois et on conçoit chaque module
en fonctions des modules de niveau 3 qui lui sont apparentés. Une fois de plus, on n’est pas
intéressé par les détails de ces sous-modules, même pas des autres modules du même niveau.
Dans des gros projets, il est probable que des équipes différentes travaillent sur les autres
modules.
Une première approche pour effecteur le test d’un algorithme consiste à commencer par tester
en premier lieu le module de niveau 1. On teste ensuite les modules du niveau 2, et ainsi de
suite, jusqu’à ce que tous les modules du programme soient testés. Les modules sont ici testés
en suivant l’ordre de décomposition de l’algorithme. Cette approche est appelée le « test
descendant ». Dans cette approche, chaque module est testé dans le même environnement
dans lequel il sera utilisé dans le programme final. Il y a cependant un problème. Comment
peut-on tester le module de niveau 1 alors que les modules de niveau 2 (qui sont appelés par
le module de niveau 1) ne sont pas encore écrits ? La réponse consiste à remplacer ces
modules pas encore écrits par des maquettes. Une maquette est un module factice qui prend la
place du module effectif pendant le test des modules de niveau supérieur. Ce qu’une maquette
fait dépend de la tâche que le module qu’il remplace devra accomplir.
Une deuxième approche consiste à tester les modules dans l’ordre inverse de décomposition.
On commence alors par tester les modules de plus bas niveau, puis les modules de niveau
juste au-dessus et ainsi de suite jusqu’à ce que le module de niveau 1 soit testé. Une telle
approche est appelée le « test ascendant ».
4.7 Conclusion
Nous avons vu comment le programmeur peut définir ses propres fonctions et procédures
pour enrichir le langage algorithmique. Ces fonctions et procédures sont en général écrites
pour répondre à un besoin bien précis.
Une fois rendues disponibles, ces fonctions et procédures peuvent être utilisées pour écrire
des algorithmes plus complexes, conformément au concept de modularité que nous venons de
présenter. Les algorithmes que nous allons écrire dans le cadre des cours qui vont suivre
seront exprimés exclusivement sous forme de fonction ou de procédure, sous-entendu qu’on
pourra les mettre ensemble pour résoudre des problèmes plus complexes.
La récursivité est une technique de programmation par laquelle une procédure ou fonction
s’appelle elle-même, directement ou indirectement. L’utilisation de la récursivité a deux
inconvénients majeurs : primo, elle a un impact négatif sur la vitesse d’exécution à cause de la
surcharge qui découle des appels et des retours répétés de la même fonction ; secundo, les
appels récursifs tendent à utiliser abondamment le tas, car le programme sauvegarde l’adresse
de retour de chaque appel de fonction avec les arguments et les variables locales. Mais ces
désavantages sont souvent supplantés par un avantage clair pour le programmeur : une
solution récursive à un problème peut être la plus simple, et par conséquent plus facile à
débugger et à comprendre.
Dans le but de calculer les taxes, les biens économiques peuvent être dépréciés en utilisant
l’une de trois méthodes différentes. L’approche la plus simple est appelée la dépréciation
linéaire « straight-line depreciation ». En utilisant cette méthode, la valeur du bien décroît de
la même quantité chaque année sur la période totale. Ainsi, si un article est déprécié sur une
période de n années, 1/n de la valeur totale est déprécié chaque année.
Une deuxième méthode utilise le « double-declining balance depreciation ». Dans cette
méthode, la dépréciation autorisée chaque année est égale à la valeur courante multipliée par
2/n, où n est le nombre d’années au cours desquelles l’article doit être déprécié.
Une troisième méthode, appelée la méthode de la somme des chiffres « sum-of-the-digits
method », fonctionne de la manière suivante : D’abord, la somme des nombres de 1 à n est
calculée, où n est le nombre d’années au cours desquelles le bien est déprécié. Ensuite, la
dépréciation autorisée à la i-ème année est égale à la valeur originale multipliée par (n – i) + 1
divisée par la somme des nombres.
Écrire trois fonctions différentes pour calculer la dépréciation d’un article en utilisant chacune
des méthodes ci-dessus.
On veut écrire une procédure pour construire les n premières lignes du triangle de Pascal. Le
triangle de Pascal est un tableau de nombres dont les entrées sont les coefficients binomiaux
p n!
n p! ( n p )!
Les entrées du triangle sont telles que les nombres situés sur une ligne correspondent à la
même valeur de n tandis que les nombres situés sur une colonne correspondent à la même
valeur de p.
Chapitre 5
5.1 Introduction
Toutes les variables utilisées dans un algorithme doivent être déclarées et leur types doivent
être spécifiés. Un type de données spécifie un ensemble de valeurs de même nature pouvant
être prises par des variables dans un algorithme. Le langage algorithmique offre une variété de
types de données allant des types de données simples aux structures de données très
complexes. Les types de données simples du langage algorithmique sont appelés les types
scalaires. Ce sont les types de base à partir desquels tous les autres types sont construits. Les
types de données scalaires sont eux mêmes divisés en deux groupes : les types scalaires
standards et les types scalaires définis par l’utilisateur.
Les types de données définis par l’utilisateur sont définis par le programmeur pour aider à la
résolution d’un problème particulier. Les types de données scalaires sont des types non
structurés, c’est-à-dire qu’ils consistent en des valeurs simples distinctes. Une variable de type
simple ne peut prendre qu’une seule valeur de ce type à un point donné de l’algorithme. Ainsi,
une variable de type entier ne peut recevoir qu’une seule valeur de type entier. De même une
variable de type caractère ne peut recevoir qu’une seule valeur de type caractère. Le langage
algorithmique offre aussi des types de données structurés qui sont construits à partir d’autres
types de donnés et dont les éléments sont des collections ou groupes valeurs.
le type entier qui est l’ensemble des valeurs entières (positives ou négatives) qui
peuvent être représentés dans la machine.
le type réel qui est l’ensemble des valeurs de nombres réels.
le type caractère qui est l’ensemble des caractères représentés par le code utilisé
par la machine (code ASCII par exemple).
le type booléen qui est l’ensemble formé par les deux valeurs vrai et faux
Les types de données scalaires définis par l’utilisateur sont définis par l’utilisateur pour aider
à résoudre un problème particulier. Des exemples de types de données scalaires définis par
l’utilisateur sont :
Les types de données structurés sont construits à partir d’autres types données, scalaires ou
structurés. Une donnée structurée est une collection de valeurs scalaires ou structurées. Un
type de données structuré est un type de données dans lequel les valeurs sont des collections
Une structure de données qui est capable de stocker des informations de différents types a de
nombreuses applications pratiques. Par exemple, quand on travaille avec les informations de
paie d’un employé, on a besoin de stocker et de manipuler les variables suivantes :
Il sera plus commode de regrouper toutes ces informations sous un seul identificateur pouvant
être manipulé comme une entité unique. En informatique, une structure de données qui permet
de regrouper et de manipuler différents types de données comme une entité unique est appelée
un article ou un enregistrement. Plus formellement, un article est une structure consistant en
un nombre fixe de composantes appelées champs ou membres pouvant être de types
différents.
Les informations de paie ci-dessus peuvent être regroupées dans un article de la manière
suivante :
type
temployé = article
nom : chaîne ;
matricule : entier ;
salaire : réel ;
fin ;
Les mots réservés article et fin sont utilisés pour délimiter la définition. Une fois qu’un type
d’article a été défini et rendu disponible, il peut ensuite être utilisé pour déclarer des variables
de ce type :
variable
employé : temployé ;
Comme autre exemple de type d’articles, considérons la représentation d’une date. On peut
l’envisager comme un article comprenant trois champs : une variable définie par l’utilisateur
pour le mois et deux entiers pour le jour et l’année :
type
tmois = (janvier, février, mars avril, mai, juin , juillet, août, septembre,
octobre, novembre, décembre) ;
tdate = article
mois : tmois;
jour : 1..31 ;
année : entier;
end;
variable
Today, Birthday : tdate;
Les variables Today et Birtday sont de type tdate et chacune est capable de stocker toutes les
composantes de cette structure.
type
télément = article
champ1 : Type1 ;
champ2 : Type2 ;
.
.
.
champn : Typen ;
fin ;
où champ1, …, champn sont les différents champs de l’article avec leurs types respectifs.
Type1, …, Typen peuvent être n’importe quel type scalaire (entier, réel, caractère, booléen,
énuméré) ou un type structuré (vecteur, article, pointeur).
type
tabonné = article
nom : chaîne20 ;
adresse : chaîne30 ;
numéro : chaîne8 ;
fin ;
tpersonne = article
nom : chaîne20 ;
Le type article tabonné a trois champs. Chacun des champ est une chaîne de caractères de
longueur différente. Le type article tpersonne a quatre champs. Noter qu’un type défini par
l’utilisateur peut être spécifié dans une définition d’article, comme cela est le cas avec les
champs Sexe et Statut. Les champs d’un article ne sont pas nécessairement de type scalaire.
Nous allons voir dans la suite qu’ils peuvent souvent être eux-mêmes des articles.
Étant donné les définitions ci-dessus, on peut maintenant déclarer des variables de ce type.
Par exemple, des variables de type tpersonne peuvent être déclarées de la manière suivante :
variable
Myself, Yourself : tpersonne ;
Les champs d’un article sont les composantes d’une même variable et sont référencés en
spécifiant le nom de la variable, suivi par l’identificateur de champ, séparés par un point. Une
telle expression est appelée un désignateur de champ. Un désignateur de champ désigne
l’emplacement mémoire associé à la composante correspondante de l’article. Les instructions
d’affectation
Les désignateurs de champ peuvent être utilisés comme tous les autres identificateurs de
variables. Ainsi, l’instruction
Écrire(Myself.nom)
Lire(Yourself.age)
lit une valeur pour le champ Age de la variable Yourself. Enfin, l’instruction
La valeur d’un article entier peut être affectée à un autre article de même type en utilisant une
seule instruction d’affectation. L’instruction d’affectation
Yourself Myself
Yourself.nom Myself.nom ;
Yourself.sexe Myself.sexe;
Yourself.age Myself.age;
Yourself.statut Yourself.statut;
La syntaxe d’un article en algorithmique n’interdit pas qu’un champ dans un article soit lui-
même un autre article. Considérons les définitions de types suivantes :
type
tdéduction = article
CFC : réel ;
CRTV : réel ;
CNPS : réel ;
fin ;
temployé = article
Nom : chaîne20 ;
Matricule : entier ;
SalaireBrute : réel ;
Taxes : tdéduction ;
SalaireNet : réel ;
fin ;
variable
déd : tdéduction ;
employé : temployé;
La variable Employé est un article ayant cinq champs. Le champ « Taxes » est lui-même un
article ayant ses propres champs. C’est un sous-article de temployé. Pour accéder à un champ
quelque de Taxes, on doit spécifier toutes les variables impliquées. Pour affecter des valeurs à
tous les champs de employé, on écrit :
Employé.Nom 'Mohamadou' ;
Employé.Matricule 101498092 ;
Employé.SalaireBrute 3132.50 ;
Employé.Taxes.CFC 650.75 ;
Employé.Taxes.CRTV 139.17 ;
Employé.Taxes.CNPS 133.55 ;
Emploué.SalaireNet 2794.03
Un champ d’un sous-article de Employé est référencé par un désignateur de champ impliquant
deux identificateurs. Le désignateur de champ
Employé.Taxes.CFC
référence le champ CFC du sous-article employé.Taxes. Noter que pour accéder à un champ
tel que SalaireBrute, un seul identificateur de champ est utilisé car aucun sous-raticle n’est
impliqué. Le désignateur de champ
employé.Taxes
référence un article de type tdéduction et peut par conséquent recevoir une valeur de ce type.
Exercices d’apprentissage
Exercice 5.1
Écrire des définitions d’articles ,pour représenter les données suivantes :
(a) Un employé ayant des champs pour le nom, le numéro matricule, le salaire brute et le net à
percevoir.
(b) Une équipe de football ayant les champs pour le nom de l’équipe, le nombre de matchs
joués, le nombre de matchs gagnés, le nombre matchs nuls, le nombre de matchs perdus et le
pourcentage de matchs gagnés.
(c) Un compte bancaire ayant des champs pour le numéro de compte, le nom du titulaire du
compte, l’adresse du titulaire, le solde courant et les intérêts cumulés.
(d) Un numéro de téléphone ayant des champs pour la code de région, le préfixe et le numéro.
Exercice 5.2
Écrire des procédures pour lire et écrire des articles de type temployé.
Les articles que nous avons considérés jusque là sont constitués uniquement de champs fixes.
Cela signifie que une fois que l’on a défini un type d’articles consistant en un nombre de
champs de différents types, toutes les variables de ce type contiennent toutes ces mêmes
champs. Souvent, il est utile d’avoir des articles contenant seulement quelques champs
identiques. Cela signifie que l’on a parfois besoin de considérer des articles de même type
même si leurs structures peuvent différer d’une certaine manière, comme par exemple avoir
un certain nombre de champs différents, possiblement de types différents. Pour illustrer cela,
considérons les définitions suivantes :
type
toption = (GBIO, MIP, GTE, GIN) ;
tétudiant = article
Nom : chaîne20 ;
Dans cette définition nous avons rendu l’existence de certains champs dépendante de la valeur
d’un autre champ. Tous les étudiants ont certaines informations en commun : Nom, Sexe,
Age, Option. Cependant, dépendant de la valeur de Option, on voit que les informations
restantes sont différentes. Les étudiants de l’option Mathématiques ont en plus les champs
Analyse et Algèbre. Pour les étudiants de l’option Informatique on a en plus des champs
communs, les champs Langages, Réseaux et Infographie. Enfin les étudiants de l’option
Physique ont en plus les champs Optique, Electricité et Mécanique. Un article qui permet de
telles variations dans sa structure est appelé un article avec variantes.
À la suite du mot réservé of, on a une liste de déclarations de champs variables, la partie
variable de l’article. La déclaration de chaque champ variable consiste en une constante de
même type que le champ de sélection, suivi par deux points, suivi par une liste de déclarations
de champs entre parenthèses. La liste de déclarations de champs peut être vide, signifiant que
cette variante de l’article n’a pas besoin de champs additionnels autres que ceux listés dans la
partie fixe.
Dans les articles variables, les identificateurs de champs doivent être distincts. Un même
identificateur de champ ne peut pas apparaître dans deux listes de champs de la partie variable
ou dans la partie fixe et dans la partie variable. Une définition d’article ne peut avoir qu’une
seule partie variable. Cependant, les parties variables peuvent être imbriquées. Remarquer
qu’un seul mot réservé fin termine la partie variable et la définition de l’article.
Une variable déclarée comme étant une variable de type article avec variantes doit toujours
contenir tous les champs fixes, y compris le champ de sélection. Ainsi, étant donné la
déclaration :
variable
étudiant : tétudiant ;
étudiant.Option GIN
étudiant.Algorithmique 68 ;
étudiant.Analyse 78
sont maintenant valides. Toutefois, c’est une erreur de référencer un champ variable non
associé à la valeur courante du champ de sélection. On utilise habituellement l’instruction de
sélection multiple pour éviter ces erreurs. L’utilisation de cette approche est illustrée dans les
exemples ci-dessous qui lise et écrive un article de type tétudiant.
GIN : début
écrireln('Algorithmique : ', étudiant.Algorithmique) ;
écrireln('Programmation : ', étudiant.Programmation) ;
écrireln('Analyse : ', étudiant.Analyse) ;
écrireln('Infographie : ', étudiant.Infographie) ;
fin ;
fin ;
lireln(étudiant.Automatique) ;
écrire('Electricité : ') ;
lireln(étudiant.Electricité) ;
fin ;
GTE : début
écrire('Electromécanique : ') ;
lireln(étudiant.Electromécannique) ;
écrire('Thermique : ') ;
lireln(étudiant.Thermique) ;
écrire('Régulation : ') ;
lireln(étudiant.Régulation) ;
fin ;
GIN : début
écrire('Algorithmique : ') ;
lireln(étudiant.Algorithmique) ;
écrire('Programmation : ') ;
lireln(étudiant.Programmation) ;
écrire('Analyse : ') ;
lireln(étudiant.Analyse) ;
écrire('Infographie : ') ;
lireln(étudiant.Infographie) ;
fin ;
end ;
end ;
Exercices d’apprentissage
Exercice 5.3
Définir un type d’article avec variantes qui pourrait être utilisée pour stocker les informations
concernant une figure géométrique. L’article doit stocker la forme (carré, triangle, rectangle
ou cercle) et les dimensions appropriées. Écrire ensuite une procédure pour lire les
informations sur une figure et une fonction qui prend en entrée une figure géométrique et
retourne sa surface.
Exercice 5.4
Soit le type article date, formé de trois nombres entiers qui indiquent respectivement le jour,
le mois et l’année.
type
tdate = article
jour, mois, année : entier ;
fin ;
Écrire une fonction booléenne qui prend entrée deux dates date1 et date2 et détermine si la
date date1 vient avant la date date2.
On a besoin d’un moyen de représenter ces informations tel que l’on puisse facilement ajouter
ou supprimer des intérêts, déterminer si une personne particulière est intéressée par un certain
sujet, comparer les intérêts de différentes personnes et travailler avec ces données.
Une approche consiste à représenter ces informations dans un vecteur de type booléen de la
manière suivante :
type
tdomaine = (Art, Théâtre, Tennis, Baseball, Football, Politique, Lecture) ;
tintérêts : vecteur[Domaine] de booléen ;
En utilisant le vecteur booléen Intérêts, on peut stocker les informations sur le questionnaire
de la manière suivante :
Avec cette méthode de représentation, on peut ajouter des intérêts en remplaçant un faux par
un vrai ou supprimer un intérêt en remplaçant un vrai par un faux. On peut aussi déterminer si
une personne a un intérêt particulier en examinant l’emplacement approprié du vecteur pour
déterminer s’il contient un vrai ou un faux.
Les vecteurs booléens sont un moyen convenable de représenter ce genre de données. Le
langage algorithmique a cependant un moyen plus convenable de faire cela, la structure de
données ensemble.
En mathématiques, un ensemble est une collection d’objets. En mathématiques, un ensemble
est représenté par des accolades contenant la liste des objets qui constituent la collection. Par
exemple, l’ensemble des entiers positifs impaires inférieurs à 10 est représenté par
{1, 3, 5, 7, 9}
[1, 3, 5, 7, 9]
Les objets qui appartiennent à un ensemble sont appelés ses éléments. Ce qui rend les
ensembles utiles est le fait que les opérations telles que l’ajout ou la suppression des éléments
sont faciles à effectuer. Il est aussi facile de déterminer si un objet particulier est oui ou non
un élément d’un ensemble donné.
type
identifiacteur = ensemble de télément ;
type
tAfrique = (Cameroun, Gabon, Guinée, Tchad, Centrafrique, Congo, Nigéria, Ghana) ;
tCommunauté = ensemble de tAfrique ;
variable
AfriqueCentrale : tCommunauté ;
Les valeurs possibles de la variable Communauté sont les différents sous-ensembles de Pays.
Par exemple, un valeur possible de AfriqueCentrale est [Cameroun, Gabon, Guinée].
Avant qu’un ensemble ne soit manipulé, il doit recevoir une valeur. La forme générale d’une
instruction d’affectation ensembliste est :
identificateur expression-ensembliste ;
L’expression ensembliste peut être une constante de type approprié, un autre identificateur
d’ensemble, ou elle peut spécifier une ou plusieurs opérations ensemblistes. La forme la plus
simple d’une instruction d’affectation consiste en un identificateur à gauche de l’opérateur
d’affectation et une constante ensembliste à droite. Les constantes ensemblistes sont créées
par le moyen des constructeurs ensemblistes. Des exemples de constructeurs ensemblistes
sont :
[Cameroun]
[Gabon, Nigéria, Guinée]
[Cameroun..Centrafrique]
[Cameroun..Guinéé, Congo..Ghana]
Une des raisons de l’utilité des ensembles en algorithmique est le fait que les opérations de
réunion, d’intersection et de différence ensembliste sont disponibles comme des opérations
primitives. Ceci rend évidentes les opérations d’insertion et de suppression des éléments dans
un ensemble.
Opérateur Signification
+ Union
* Intersection
- Différence ensembliste
Les priorités des opérateurs +, * et - , quand ils sont utilisés comme opérateurs ensemblistes,
sont les mêmes que lorsqu’ils sont utilisés comme opérateurs arithmétiques. Les parenthèses
peuvent être utilisées dans les expressions ensemblistes de la manière que dans les
expressions arithmétiques.
Le langage algorithmique offre cinq opérateurs relationnels qui peuvent être utilisés pour
vérifier la relation entre des ensembles, et l’appartenance d’un élément à un ensemble.
Opérateur Signification
= Égalité entre ensemble
Inégalité entre ensembles
Inclusion ensembliste
Contenance ensembliste
in Appartenance
L’opérateur dans est utilisé pour tester l’appartenance d’un élément à un ensemble. Le
premier opérande doit être un type scalaire, le type de base, et le deuxième opérande doit être
le type ensemble associé. Soit X un ensemble a une valeur appartenant au type de base de X,
alors l’expression « a in X » est vrai si a appartient à X et faux sinon.
L’opérateur in est habituellement utilisé pour simplifier l’écriture des structures de sélection
dans les algorithmes qui impliquent les ensembles. Par exemple,
ou
L’opérateur dans est aussi utilisé pour simplifier des expressions booléennes complexes. Par
exemple, supposons que l’on souhaite tester si une variable entière nombre a l’une des valeurs
suivantes : 1, 3, 4, 6 ou 9. Une manière de le faire est d’utiliser l’instruction si…alors, avec
une condition composée :
Une instruction si…alors plus simple qui donne le même résultat est :
Une certaine attention doit être exercée lorsqu’on utilise l’opérateur in pour simplifier les
expressions booléennes. Une utilisation correcte de l’opérateur in nécessite que l’opérateur
scalaire à gauche de in soit une valeur qui appartient au type de base de l’ensemble à droite de
l’opérateur in.
L’opérateur booléen non peut être utilisé avec les opérateurs relationnels ensemblistes. Il faut
cependant se rappeler l’opérateur non doit toujours précéder une expression booléenne. Ainsi,
l’expression
non (5 in [1..20])
est valide, et sa valeur est faux. Dans cet exemple, l’expression booléenne qui est l’opérande
de non est 5 in [1..20].
Les opérateurs relationnels ensemblistes ont tous le même niveau précédence, et ce niveau de
précédence est le même que celui des opérateurs arithmétiques. Donc, tous les opérateurs
relationnels ont une précédence inférieure à celle des opérateurs ensemblistes *, + et -.
Conclusion
Nous avons présenté les types de données les plus courants du langage algorithmique.
Certains d’entre eux sont implémentés par différents langages de programmation. D’autres
par contre ne sont implémentés que par des langages particuliers.
Exercice 5.6
Le service informatique d’un centre touristique conserve les informations sur les clients en
utilisant la structure suivante :
type
tdomaine = (Cinéma, Musique, Sport, Lecture) ;
tclient = article
nom : chaîne20 ;
sexe : (féminin, masculin) ;
domaine : ensemble de tdomaine ;
âge : entier ;
fin ;
1. Écrire une procédure qui lit les informations sur un nouveau client et crée un article
pour ce client.
2. Écrire une procédure qui imprime les informations d’un client.
3. Écrire une fonction booléenne qui vérifie que deux clients sont compatibles. Pour être
compatibles, les clients doivent être de sexe opposé, avoir un écart d’âge de six ans au
plus et avoir au moins deux intérêts en commun.
4. Écrire une procédure qui prend en entrée les informations sur un client et une liste de n
clients puis imprime la liste des clients qui lui sont compatibles.
Algorithme d’Eratosthene
On veut écrire une procédure pour imprimer tous les entiers premiers compris entre 1 et un
certain entier positif arbitraire, nombremax. Pour obtenir les nombres premiers, nous allons
utiliser un algorithme classique connu comme l’algorithme d’Eratosthènes. L’algorithme peut
être décrit de la manière suivante :
1. Commencer avec l’ensemble des entiers consécutifs compris entre 1 et nombremax.
2. Supprimer de cet ensemble tous les multiples de 2. Ceux-ci ne sont pas certainement
des nombres premiers.
3. Trouver le prochain entier restant dans l’ensemble supérieur à celui dont les multiples
viennent d’être supprimés et supprimer tous ses multiples. Ceux-ci ne peuvent pas être
des nombres premiers.
4. Répéter l’étape 3 jusqu’à ce que l’entier dont les multiples viennent d’être supprimés
soit supérieur ou égal à la racine carrée de nombremax. Cette condition de terminaison
pour la suppression des entiers non premiers est basée sur le fait que les deux facteurs
dans un produit ne peuvent pas être tous les deux supérieurs à la racine carrée du
produit.
5. Les nombres qui restent dans l’ensemble après le processus de suppression sont les
entiers premiers compris entre 1 et nombremax.
Chapitre 6
Les vecteurs
Ici, on n’a pas besoin seulement de calculer la moyenne d’un ensemble de notes, mais on doit
aussi calculer la différence entre chaque note et la moyenne. Pour cela, on doit soit lire toutes
les notes deux fois, une première fois pour le calcul de la moyenne et une deuxième fois pour
le calcul des différences à la moyenne, ou alors stocker toutes les notes de manière qu’elles
soient disponibles au moment où on a besoin de calculer les différences. En utilisant les
structures de données que nous avons étudiées jusque là, on aura besoin de noms de variables
séparées pour chaque note. Trente cinq variables, telles que note1, note2, …, note35 de type
approprié doivent être déclarées. Cela est bien possible mais l’algorithme résultant sera lourd.
Il impliquera trente cinq instructions de lecture séparées pour les trente cinq notes, le calcul de
la moyenne et ensuite le calcul de la différence entre chaque note et la moyenne. On aura
besoin de 35 instructions d’affectation pour calculer la différence entre chaque note et la
moyenne comme l’illustre le segment d’algorithme ci-dessous :
……
Différence note1 – moyenne ;
Écrire(Différence) ;
Différence note2 – moyenne ;
Écrire(Différence) ;
.
.
.
Différence note35 – moyenne ;
Écrire(Différence) ;
…...
Cette solution sera bien entendu impraticable si on doit effectuer les mêmes calculs pour 100
ou 1000 notes.
Le fait que les variables note1, note2, …, note35 soient distinctes nous force à utiliser des
instructions d’affectation individuelles pour calculer chaque différence bien que les calculs
effectués pour chaque variable soient exactement les mêmes. Ainsi, la possibilité d’utiliser la
répétition dans ce qui apparaît être une situation répétitive est empêchée par le fait que les
variables impliquées ne sont pas liées les unes aux autres.
Une meilleure approche consiste à considérer toutes ces variables comme étant les
composantes d’une variable unique appelée un vecteur et utiliser un indice pour distinguer les
composantes individuelles. Ceci est illustré par la figure ci-dessous où on a une seule variable
vecteur appelée note ayant trente cinq composantes ou éléments.
78 95 100 … 65
note
Soient :
un ensemble quelconque de valeurs E,
un entier positif n (n 0),
un intervalle I de N tel que I = [1..n].
Un vecteur est une application V de I dans E qui à chaque élément i de I fait correspondre un
élément V[i] de E ; I est appelé l’ensemble des indices du vecteur.
Pour déclarer un vecteur note de 35 éléments pouvant contenir chacun un entier, on utilise la
déclaration suivante :
variable
note : vecteur[1..35] de entier ;
Cette déclaration mentionne deux types de données : le type de données des composantes du
vecteur, entier dans notre exemple, et le type de données des indices, le type intervalle 1..35
dans notre exemple.
variable
liste : vecteur[typeindice] de télément ;
où télément est le type des éléments du vecteur et typeindice le type des indices du vecteur.
Type des éléments : Le type des éléments décrit chaque élément du vecteur. Çà peut être
n’importe lequel des types scalaires que nous avons étudiés jusque là (entier, réel, caractère,
booléen) ou même un type structuré comme un article ou un vecteur. On notera que tous les
éléments d’un vecteur sont de même type.
Le type des indices : Le type de l’indice peut être soit l’un des types standards booléen ou
caractère, un type scalaire énuméré défini par l’utilisateur, ou un type intervalle. Aucun des
types scalaires entier ou réel ne peut être utilisé comme ensemble des indices d’un vecteur car
ceci créera un vecteur ayant un nombre infini de composantes. Toutefois, un intervalle type
entier peut être utilisé.
Pour des raisons de simplicité, l’ensemble des indices sera toujours noté [1..n] si on a un
vecteur de n éléments. Il peut être vide (si n = 0, [1..n] est vide). Le vecteur est alors dit vide.
L’indice le plus petit (ici 1) est appelé la borne inférieure, le plus grand (ici n) est appelé la
borne supérieure.
Notations : Un vecteur liste pourra être noté liste[1..n] et même simplement liste lorsqu’il n’y
a pas d’ambiguïté.
1 i n
liste[1] … liste[i] … liste[n]
liste
Indiçage : Les indices sont utilisés pour distinguer les composantes individuelles d’un
vecteur. On référence une composante particulière d’un vecteur en donnant le nom du vecteur,
suivi par un indice entre des crochets carrés. L’indice peut être une constante, une variable ou
même une expression. La seule restriction est que le type de l’expression doit correspondre au
type de l’indice spécifié dans la déclaration du vecteur.
Comme avec les autres types de données, il est possible de définir un type de données vecteur
en utilisant une définition de type de la forme :
constante
taillemax = 100 ;
type
vélément = vecteur[1..taillemax] de télément ;
où télément est un identificateur de type simple (entier, réel, caractère, booléen) ou structuré
(article, vecteur). La constante taillemax représente le nombre maximum d’éléments possibles
que le vecteur peut contenir. En effet, il faut prévoir le nombre maximum d’éléments du
vecteur afin de réserver la place mémoire nécessaire.
Avec cette définition de type, on peut déclarer n’importe quel vecteur liste[1..n] tel que la
borne supérieure n du vecteur vérifie la relation 0 < n taillemax. Par exemple,
variable
liste : vélément ;
Avec cette déclaration, le vecteur liste peut contenir jusqu’à 100 valeurs de type ELEMENT.
Cent emplacements mémoire sont donc réservés pour contenir les éléments du vecteur liste.
On dit qu’on a une gestion statique de la mémoire. Mais il peut arriver dans la pratique que
l’on utilise que n emplacements seulement, avec 0 n < taillemax. Ce qui peut résulter en un
gâchis de la mémoire si on utilise effectivement très peu de place par rapport à la place
réservée lors de la définition du vecteur.
1 n taillemax
… …
Sous-vecteur
Relation d’ordre
On est parfois amené à définir une relation d’ordre sur l’ensemble E des valeurs d’un vecteur.
Cette relation est noté, pour des raisons de simplification d’écriture, par le symbole d’inégalité
stricte (<) quel que soit l’ensemble E. Il est évident que dans chaque cas particulier, on est
conduit à définir le prédicat associé à cette relation d’ordre.
Avec un seul vecteur, nous traitons soit les problèmes où l’on est amené à parcourir
l’ensemble du vecteur, soit au contraire ceux où l’on devra s’arrêter dès qu’une certaine
condition est vérifiée : il s’agira alors plus spécialement des problèmes d’accès à une valeur
donnée dans un vecteur.
Soit un vecteur liste de n éléments. L’approche la plus simple consiste à parcourir le vecteur
dans l’ordre croissant des indices. On obtient alors un schéma d’énumération séquentiel de la
gauche vers la droite des éléments du vecteur :
On peut également définir un parcours séquentiel dans l’ordre décroissant des indices. On
obtient alors un schéma d’énumération séquentiel de la droite vers la gauche des éléments du
vecteur :
On peut aussi écrire le parcours d’un vecteur liste[i..n] sous forme récursive.
fin;
ou encore à :
L’emploi de méthodes récursives pour traiter les parcours simples de vecteurs est souvent
inutilement lourd et lent à l’exécution. C’est pourquoi les algorithmes sur les vecteurs sont le
plus souvent traités de manière itérative, en réservant la récursivité aux cas où elle s’impose.
Exemples d’application
Exemple 6.1
Écrire une procédure pour lire un vecteur de n éléments.
Exemple 6.2
Écrire une procédure qui affiche les éléments d’un vecteur dans l’ordre des indices croissants.
fin ;
fin;
Exemple 6.3
Écrire une fonction qui calcule la somme des éléments d’un vecteur de n nombres réels. On
admet que la somme des éléments est égale à zéro si le vecteur est vide.
Version itérative
sommeliste total ;
fin ;
Version récursive
si n = 0 alors
sommeliste 0
sinon
si n = 1 alors
sommeliste liste[n]
sinon
sommeliste liste[n] + sommeliste(liste, n - 1) ;
fin ;
Exemple 6.4
Écrire une fonction qui calcule la moyenne des éléments d’un vecteur de n nombres réels.
moyenneliste total/n ;
fin ;
fin ;
Exemple 6.5
Écrire une fonction qui calcule le maximum des éléments d’un vecteur de n nombres réels.
maximum max ;
fin ;
Exemple 6.6
Écrire une fonction qui calcule la position du plus grand élément d’un vecteur de n nombres
réels. On suppose que la plus grande valeur est unique.
Exemple 6.7
Écrire une procédure qui calcule le plus grand élément d’un vecteur de n nombres réels ainsi
que sa position dans le vecteur. On suppose que la plus grande valeur est unique.
procédure maxpos(liste : vélément ; n : entier ; var grand : réel ; var k : entier) ;
variable
i : entier ;
début
grand liste[1] ;
k 1 ;
pour i 2 haut n faire
si grand < liste[i] alors
début
grand liste[i] ;
k i ;
fin ;
fin ;
Exercices d’apprentissage
Exercice 6.1
Écrire une procédure qui affiche les éléments d’un vecteur dans l’ordre décroissant des
indices.
Exercice 6.2
Écrire la même procédure sous forme récursive.
Exercice 6.3
Écrire une fonction qui calcule le nombre d’occurrences d’une valeur val dans un vecteur de n
éléments.
Exercice 6.4
On considère un vecteur de n entiers. Écrire une procédure qui imprime uniquement les
éléments de rang impaire.
Exercice 6.5
Écrire une procédure qui reçoit en entrée un vecteur de n entiers et remplace chaque élément
du vecteur par la somme des éléments qui le précèdent y compris lui-même.
Exercice 6.6
On à demandé à n étudiants d’évaluer la qualité de la nourriture offerte à la cafétéria sur une
échelle de 1 à 10 (1 signifiant immangeable et 10, excellent). Écrire un algorithme pour lire
les n réponses dans un vecteur d’entiers et résumer le résultat du sondage.
Exercice 6.7
Une des techniques utilisées par les cryptographes pour déchiffrer les codes est d’utiliser la
fréquence avec laquelle les différents caractères apparaissent dans un texte encodé. Écrire une
procédure qui lit une chaîne de caractères se terminant par un point et calcule la fréquence
d’apparition de chaque caractère dans le texte. On suppose que seules les lettres de l’alphabet
anglais sont concernées. La ponctuation est ignorée et aucune différence n’est faite entre les
lettres minuscules et majuscules.
Exercice 6.8
Écrire une procédure qui reçoit en entrée un vecteur de n nombres réels et détermine le plus
grand et le second plus grand de ces nombres.
Exercice 6.9
On suppose que l’on a les définition suivantes :
type
tdate = article
jour, mois, année : entier
fin ;
toption = (GBIO, MIP, GTE, GIN) ;
tétudiant = article
nom, chaîne ;
prénom : chaîne ;
sexe : (féminin, masculin) ;
matricule : chaîne ;
datenais : tdate ;
lieunais : chaîne ;
option : toption ;
fin ;
vétudiant = vecteur[1..100] de tétudiant ;
Une des applications les plus courantes implique la recherche dans une liste pour déterminer
si un élément particulier est présent dans la liste. Il y a plusieurs raisons pour lesquelles on a
souvent besoin de faire la recherche dans les listes. On peut simplement vouloir déterminer si
une valeur particulière apparaît comme une composante (recherche associative), on peut
vouloir déterminer là où elle apparaît (recherche par position) ou on peut vouloir localiser
une composante pour la modifier ou la remplacer. On a aussi très souvent besoin de faire la
recherche dans un vecteur pour retrouver la composante correspondante dans un autre vecteur.
L’accès par position est défini par la primitive d’indiçage liste[i]. L’accès associatif est le plus
courant. Soit elem une variable contenant une valeur du même type que celui des éléments de
la liste, on veut déterminer s’il existe un indice i [1..n] tel que élément = liste[i].
La recherche dans les listes peut se faire de différentes manières. Ici, nous présentons le sujet
en examinant les deux algorithmes les plus courants, la recherche linaire ou recherche
séquentielle et la recherche dichotomique ou recherche binaire.
Dans une recherche linéaire ou séquentielle, on cherche une valeur particulière dans une liste
en examinant chaque élément dans l’ordre, en commençant par le début de la liste. Quand on
trouve l’élément que l’on cherche, on arrête. Bien entendu, il est possible que l’élément
recherché ne soit pas présent dans la liste. On détermine cette situation en atteignant la fin de
la liste sans trouver la valeur recherchée.
rechséquentielle i n ;
fin ;
Une analyse attentive de la boucle de cette fonction montre que si elem liste[1..n], i atteint à
la dernière itération la valeur n + 1, et on est alors amené à effectuer un test sur liste[n + 1] qui
est une valeur indéfinie. On introduit donc l’opérateur booléen et alors qui est en fait une
évaluation optimisée du et classique. Il s’agit simplement de constater que si le premier terme
du et est faux, il est inutile d’examiner le second terme car le résultat de la conjonction sera
faux.
A B A et alors B
vrai vrai vrai
vrai faux faux
faux non examiné faux
si A alors
CB
sinon
C faux ;
A B A ou sinon B
vrai non examiné vrai
faux vrai vrai
faux faux faux
si A alors
C vrai
sinon
C B ;
Remarques
i 1 ;
tantque (i n) et alors (liste[i] elem) faire
i i + 1 ;
rechséquentielle i n ;
fin ;
Pour éviter l’utilisation du connecteur « et alors », on introduit une variable booléenne
trouvé. L’algorithme devient alors :
rechséquentielle trouvé ;
fin ;
On peut aussi donner une version récursive de cet algorithme. Dans ce cas, il faut introduire
une variable i dans la liste des paramètres en tant que borne inférieure du sous-vecteur que
l’on étudie. On obtient alors l’algorithme suivant :
On peut également donner une version récursive de cet algorithme pour effectuer un parcours
de la droite vers la gauche. Dans ce cas, on recherche élément dans liste[1..n] et on n’a plus
besoin de la variable auxiliaire i, puisque la borne inférieure du vecteur est toujours égale à 1.
rechrécursive faux
sinon
si liste[i] = elem alors
rechrécursive vrai
sinon
rechrécursive rechrécursive(liste, n - 1, elem) ;
fin ;
Avec le connecteur et alors, on pourrait éviter de vérifier dans le tantque si liste[i] est égal à
elem lorsque i = n, mais nous préférons présenter une version de l’algorithme qui évite
l’emploi du et alors même au prix d’un test supplémentaire lorsque liste[n] = elem.
Exercices d’apprentissage
Exercice 6.11
Écrire une fonction qui calcule le produit des éléments d’un vecteur de n nombres réels
(penser au cas où un élément serait nul)
Exercice 6.12
Écrire de plusieurs manières différentes une fonction qui délivre l’indice de la valeur val dans
un vecteur liste[1..n] si val appartient au vecteur et 0 si val n’appartient pas au vecteur.
Préciser dans chaque cas si c’est la première ou la dernière occurrence que l’on a trouvée.
Si tous les couples d’éléments consécutifs d’un vecteur vérifient la relation liste[i-1] liste[i],
on dit que le vecteur est trié ou ordonné par ordre croissant. Si par contre tous les couples
d’éléments consécutifs vérifient la relation liste[i] liste[i-1], on dira que le vecteur est trié ou
ordonné par ordre décroissant.
Dans toute la suite du cours, sauf avis contraire, on appellera vecteur trié un vecteur trié par
ordre croissant. On admettra que le vecteur vide et que le vecteur ne contenant qu’un seul
élément sont triés.
D’où la définition :
Cette définition récursive est à rapprocher des définitions récursives que nous connaissons en
mathématiques , telles que la définition de n!
Une application immédiate de cette définition est la fonction qui vérifie qu’un vecteur est trié.
La méthode consiste à comparer tous les couples de deux éléments consécutifs en s’arrêtant
dès qu’un couple ne vérifie pas la relation d’ordre liste[i-1] liste[i]. Le vecteur est trié si on
termine la parcours avec i = n + 1.
On peut également réécrire cette fonction sans utiliser le « et alors ». L’algorithme devient
alors :
vecteurtrié i > n
fin;
La méthode consiste à parcourir le vecteur à la recherche d’un indice i [1..n] tel que la
relation liste[1..i-1] < elem liste[i...n] soit vérifiée.
Ensuite, il ne reste plus qu’à tester l’égalité elem = liste[i] pour savoir si elem est présent ou
non dans le vecteur trié.
D’autres solutions sont possibles, comme nous l’avons vu précédemment afin d’éviter
l’emploi du « et alors ». L’algorithme devient alors :
si i n alors
accèstrié2 liste[i] = elem
sinon
accèstrié2 faux ;
fin ;
D’autres solutions sont possibles, comme nous l’avons vu précédemment pour les vecteurs
non triés.
Si le vecteur n’est pas vide, on peut s’arrêter sur l’avant-dernier élément, afin d’éviter
l’emploi de l’opérateur « et alors » :
Si le vecteur n’est pas vide, on peut aussi comparer l’élément que l’on cherche avec
celui du dernier élément de la liste. Dans ce cas, si valeur > liste[n], l’algorithme est
terminé, l’élément n’est pas présent dans la liste. Si valeur liste[n], on peut effectuer
un parcours de gauche à droite avec une seule condition (liste[i] < valeur) au niveau
du tantque car on est certain que i sera borné par n (puisque liste[n] valeur).
Exercices d’apprentissage
Exercice 6.13
Écrire une fonction entière qui retourne la place de la première occurrence de la valeur val
dans un vecteur trié liste[1..n] si val appartient au vecteur liste, et 0 si val n’appartient à liste.
Exercice 6.14
Même question pour la dernière occurrence.
La recherche dichotomique (on dit aussi recherche binaire ou recherche par bissection) est une
méthode classique qui permet de faire une recherche associative dans un vecteur ordonné. Le
problème est le suivant : on dispose d’un vecteur liste[1..n] trié par ordre croissant. On veut
déterminer si on peut trouver dans ce vecteur un élément ayant une valeur donnée elem. La
recherche dichotomique s’exécute de la manière suivante :
On peut notablement accélérer l’exécution de cet algorithme en ajoutant un test initial pour
savoir si elem est bien compris entre les deux éléments extrêmes du vecteur.
Sous forme récursive, on cherche elem dans liste[inf..sup]. L’algorithme devient alors :
Exercices d’apprentissage
Exercice 6.15
Modifier les fonctions dichotomie ci-dessus afin qu’elles retournent la place de élément dans
le vecteur V si élément est présent dans le vecteur et 0 sinon (on supposera que le vecteur est
trié sans répétition)
Autre version
une fonction booléenne, nous écrivons une fonction qui délivre la place de elem dans le
vecteur. On admettra que cette place est égale à zéro dans le cas où elem n’est pas présent
dans la liste.
Ces algorithmes mettent en œuvre les techniques décrites précédemment : accès par position,
accès associatif par une méthode séquentielle ou dichotomique si le vecteur est trié. Nous
nous limiterons à des algorithmes d’insertion ou de suppression d’un seul élément dans un
vecteur.
S’il n’y a aucun critère d’insertion, il suffit d’ajouter le nouvel élément à la fin du vecteur et
de ne pas oublier de mettre à jour la taille du vecteur.
n n + 1 ;
liste[n] valeur ;
fin ;
Si au contraire, on veut ajouter un élément à la kème place, ou après (ou avant) l’occurrence
d’une valeur, alors les algorithmes sont du même type que celui de l’insertion d’une valeur
dans un vecteur trié, que nous présentons ci-dessous.
On considère le problème qui consiste à insérer un nouvel élément dans un vecteur trié de n
éléments de manière à obtenir un vecteur trié de n+1 éléments contenant tous les éléments du
vecteur initial plus l’élément à insérer.
On va donc définir :
une fonction entière qui prend en entrée un vecteur trié liste de n éléments, une valeur
elem de type télément et retourne la place de elem dans la liste.
une procédure qui prend en entrée un vecteur trié liste de n éléments, un entier p, une
valeur de type ELEMENT et insère cette valeur à la position p de manière à obtenir un
vecteur trié de n + 1 éléments.
procédure insertplace(var liste : vélément ; var n : entier ; p : entier ; elem : télément) ;
En effet, on cherche une place le plus à droite possible afin d’avoir à effectuer le moins
possible de décalages pour l’insertion.
Cette recherche peut être effectuée par une méthode séquentielle ou par une méthode
dichotomique.
Méthode séquentielle
On commence par regarder si p = 1 ; sinon il reste à chercher p dans l’intervalle [2..n+1]. Pour
cela on effectue un parcours de droite à gauche.
position i + 1 ;
fin ;
fin ;
Méthode dichotomique
sup n ;
tantque inf < sup faire
début
m (inf + sup) div 2 ;
si liste [m] elem alors
inf m + 1
sinon
sup m ;
fin ;
position sup ;
fin ;
fin ;
liste[p] elem ;
fin ;
Le problème est analogue à celui de l’insertion d’un nouvel élement. Toutefois, la valeur à
supprimer n’appartient pas forcément au vecteur liste, la suppression est donc impossible dans
ce cas. On définira alors un paramètre booléen permettant de savoir si la suppression a été
possible ou non. Nous nous intéresserons à la suppression d’un élément dans un vecteur trié.
On cherche un indice p tel que : d’une part p [0..n] et d’autre part elem liste[1..n], p = 0
ou bien liste[p] = elem, elem < liste[p+1..n], p [1..n].
Méthode séquentielle
Méthode dichotomique
p n ;
sinon
si liste[n] > elem alors
début
inf 1 ;
sup n;
tantque inf < sup faire
début
m (inf + sup) div 2 ;
si liste[m] elem alors
inf m + 1
sinon
sup m ;
fin ;
Pour supprimer l’élément d’indice p, il suffit de décaler d’une position vers la gauche tous les
éléments ayant un indice supérieur à p. A la fin du décalage, on diminue la taille du vecteur de
un. On obtient alors l’algorithme :
n n – 1;
fin;
Au lieu de supprimer directement un élément dans un vecteur, on peut remplacer la valeur que
l’on veut supprimer par une valeur non significative appelée bidon. Au bout d’un certain
nombre de suppressions, le vecteur contient un certain nombre de valeurs non significatives
que l’on supprimera par un retassement unique.
Dans la pratique, on parlera de suppression logique : marquage des éléments par un booléen
mis à faux par exemple, suivie da la suppression physique des éléments marqués : retassement
avec suppression de tous les éléments marqués.
On peut remarquer que l’algorithme ci-dessus respecte l’ordre des éléments significatifs du
vecteur d’origine. Si cet ordre n’est pas d’importance, nous pouvons donner une autre version
un peu plus performant.
j j – 1;
fin
sinon
i i + 1 ;
n i –1 ;
fin ;
Exercices d’apprentissage
Exercice 6.16
Écrire un algorithme pour supprimer un sous-vecteur donnée dans un vecteur.
Exercice 6.17
Écrire un algorithme pour remplacer un sous-vecteur donné dans un vecteur par un autre.
Exercice 6.18
Écrire un algorithme pour remplacer toutes les occurrences d’un sous-vecteur donnée dans un
vecteur par un autre.
Exercice 6.19
Écrire un algorithme pour nettoyer un texte (vecteur de caractères), en éliminant les espaces
superflus : chaque fois que l’on trouve une suites d’espaces, on les supprime tous sauf un.
Il s’agit de diviser un vecteur en trois zones, selon un critère qui peut prendre trois valeurs
différentes. Dans chaque zone, tous les éléments présenteront la même valeur du critère. On
demande d’effectuer cette partition en un seul parcours du vecteur, chaque élément ne devant
être examiné qu’une seule fois. On s’efforcera également de minimiser le nombre de
permutations.
Le problème peut être exprimé de la manière suivante : un vecteur contient n boules qui
peuvent être bleues, blanches ou rouges. On demande de trier le vecteur de telle sorte qu’il
contienne d’abord les boules bleues, ensuite les boules blanches et enfin les boules rouges.
sinon
si bleu(liste[i]) alors
début
permute(liste[b], liste[i]) ;
b b + 1;
i i + 1;
fin
sinon
début
si i < r alors
permute(liste[r], liste[i]) ;
r r – 1 ;
fin ;
fin ;
On constate que le nombre de permutations n’est pas optimal dans cette version. En effet, lors
de la permutation (liste[r], liste[i]), si liste[r] est une boule rouge, on permutera de nouveau
cette boule au pas suivant. On peut donc, afin de minimiser le nombre de permutations,
s’assurer que lorsque liste[i] est une boule rouge, qu’on ne permute pas avec une autre boule
rouge liste[r].
fin ;
Un problème classique dans le traitement des données est celui du tri des vecteurs. Étant
donné un vecteur liste[1..n] de n éléments d’un ensemble totalement ordonné E, trier ces n
éléments revient à les réorganiser de telle sorte qu’ils soient classés soit par ordre croissant
soit par ordre décroissant.
Il existe de nombreux algorithmes de tri. Dans cette section, nous allons nous intéresser
uniquement aux méthodes de tri les plus classiques qui sont :
Méthode : Le tri par bulles est une méthode de tri très simple mais très peu efficace. Pour
trier par ordre croissant un vecteur liste[1..n], le tri par bulles s’exécute en (n - 1) étapes de la
manière suivante :
Étape 1 : Parcourir le vecteur de liste[1] à liste[n] en faisant des comparaisons échanges pour
amener dans liste(n) le plus grand élément du vecteur liste[1..n] ;
Étape 2 : Parcourir le vecteur de liste[1] à liste[n – 1] en faisant des comparaisons échanges
pour amener dans liste[n – 1] le plus grand élément du sous-vecteur liste[1..n - 1] ;
.
.
.
Étape k : Parcourir le vecteur de liste[1] à liste[n-k+1] en faisant des comparaisons échanges
pour amener dans liste[n – k + 1] le plus grand élément du sous-vecteur liste[1..n – k + 1] ;
.
.
.
A la dernière étape, les deux premiers sont comparés et échangés si nécessaire, et le tri est
terminé.
Optimisation de l’algorithme
Quand on utilise le tri par bulles, on n’a pas souvent besoin de faire toutes les n-1 passes. Il y
a un moyen simple de déterminer si un vecteur est déjà complètement trié. Si une passe trouve
chaque couple d’éléments consécutifs dans un bon ordre et n’effectue donc aucune
permutation, alors le vecteur est déjà trié. On peut donc modifier l’algorithme ci-dessus pour
prendre avantage de cette information. Pour cela, on introduit un indicateur booléen que l’on
met à vrai au début de chaque passe et on le met à faux lorsqu’une permutation est effectuée.,
Si cet indicateur est toujours à vrai à la sortie de la passe, alors on considère que le vecteur est
déjà trié et on arrête.
i i + 1 ;
fin;
k k + 1 ;
fin;
fin;
Méthode : Le tri par sélection est une méthode de tri très simple mais très peu efficace. Pour
trier par ordre croissant un vecteur liste[1..n] de n éléments, le tri par sélection s’exécute en (n
- 1) étapes de la façon suivante :
Étape 1 : Déterminer et ranger dans liste[1] le plus petit élément du sous-vecteur liste[1..n] ;
Étape 2 : Déterminer et ranger dans liste[2] le plus petit élément du sous-vecteur liste[2..n] ;
.
.
.
Étape k : Déterminer et ranger dans liste[k] le plus petit élément du sous-vecteur liste[k..n] ;
.
.
.
À la dernière étape (n - 1) les deux derniers éléments sont comparés et échangés si nécessaire
et le tri est terminé.
On a donc besoin d’une fonction qui délivre à chaque étape k, l’indice du plus petit élément
du sous-vecteur liste[k..n].
k k + 1 ;
fin;
fin;
Méthode : Le tri par insertion directe est une méthode de tri très efficace pour trier des
vecteurs de taille moyenne. Pour trier par ordre croissant un vecteur liste[1..n], le tri par
insertion directe procède de la manière suivante :
si (j < i) alors
début
elem liste[i];
pour k i bas j + 1 faire
liste[k] liste[k - 1];
liste[j] elem;
fin;
i i + 1 ;
fin;
fin;
En observant qu’à chaque étape i, le sous-vecteur liste[1..i] est déjà trié, on peut utiliser la
procédure d’insertion dans un vecteur trié que nous avons présentée précédemment pour
placer l’élément liste[i + 1]. La procédure de tri par insertion directe devient alors :
C’est une version du tri par insertion directe dans laquelle on utilise la recherche binaire pour
trouver la place de l’élément liste[i] dans le sous-vecteur liste[1..i-1] à l’étape i. On a donc
besoin d’une version de l’algorithme de recherche dichotomique qui délivre la place de
l’élément dans le vecteur.
On peut maintenant écrire l’algorithme de tri par insertion par bissection de la manière
suivante :
elem : télément;
début
pour i 2 haut n faire
début
j indichotomie(liste, i – 1, liste[i]) ;
si j < i alors
début
elem liste[i];
pour k i bas j + 1 faire
liste[k] liste[k - 1];
liste[j] elem ;
fin;
fin;
fin;
début
si le vecteur à trier a encore plus de un élément alors
début
Sélectionner le pivot
Réorganiser le vecteur pour placer le pivot à sa position finale
Trier le sous-vecteur à gauche du pivot
Trier le sous-vecteur à droite du pivot
fin ;
fin ;
Nous allons donc commencer par écrire une fonction qui permet de sélectionner le pivot avant
de voir comment on procède pour amener le pivot dans sa position finale. Le pivot peut être
choisi de différentes manières. On peut par exemple, prendre comme pivot le premier élément
du vecteur à trier. Une autre approche consiste à choisir le plus grand entre les deux premiers
éléments comme pivot. Lorsque le vecteur est presque trié, il peut paraître plus logique de
prendre comme pivot l’élément milieu du vecteur (pivot central).
Une fois que la valeur du pivot a été choisi, on a besoin d’un algorithme pour placer le pivot
dans sa position finale. Pour faire cela on utilise deux indices dans le vecteur : un indice L se
déplaçant de la gauche vers la droite et un autre indice R se déplaçant de la droite vers la
gauche. À tout moment les valeurs se trouvant à gauche de L sont plus petites que le pivot
tandis que celles qui se trouvent à droite de R sont supérieures ou égales au pivot. Pour
obtenir cela, L se déplace en dépassant tout élément qui est inférieur au pivot. Il arrête son
mouvement quand il rencontre une valeur supérieure ou égale au pivot. Si L croise R, alors on
a fini et le pivot est positionné. Si L arrête son mouvement à gauche de R, R commence à se
déplacer de la droite vers la gauche. R se déplace en passant toute valeur supérieure ou égale
au pivot. Lorsque R arrête à son tour son mouvement, les valeurs se trouvant en position L et
R sont permutées et L reprend son mouvement vers la droite. Pour trier le reste du vecteur, on
répète le processus avec les sous-vecteurs à gauche et à droite du pivot.
Algorithmes
Compte tenu de l’importance du tri rapide, nous donnons ci-dessous plusieurs versions de cet
algorithme de tri qui a, dans le cas général, de meilleures performances que les algorithmes
précédents.
Première version
permute(liste[L], liste[R])
fin ;
permute(liste[place], liste[R]) ;
Quicksort(liste, inf, R – 1) ;
Quicksort(liste, R + 1, sup)
fin;
fin;
Deuxième version
Elle est basée sur le principe de « diviser pour régner » que nous avons déjà utilisé dans
l’algorithme de recherche dichotomique. Si on appelle partition la procédure qui permet de
trouver et de placer le pivot dans sa place finale, le Quicksort s’écrit récursivement :
On choisit une valeur appartenant au vecteur liste[inf..sup] que l’on appelle pivot. Ensuite on
effectue des permutations afin de classer les éléments du vecteur par rapport à ce pivot)
procédure partition(var liste : vélément ; inf, sup : entier ; var place : entier) ;
variable
L, R : entier ;
Pivot : télément;
début
pivot liste[inf];
L inf + 1;
R sup;
tantque L R faire
si liste[L] pivot alors
LL+1
sinon
début
permute(liste[L], liste[R]) ;
R R – 1 ;
fin ;
permute(liste[inf], liste[R])
place R;
fin ;
En analysant cet algorithme, on constate que l’on effectue des permutations inutiles. En effet,
en permutant liste[L] et liste[R], si liste[R] est supérieur au pivot, on permutera de nouveau
cet élément liste[R] qui est en position i pour le remettre en position R – 1.
Version optimisée
Afin d’éviter ces permutations, nous donnons ci-dessous une nouvelle version qui ne permute
liste[L] et liste[R] que dans la cas où liste[L] > pivot et liste[R] pivot. L’algorithme est alors
le suivant :
procédure partition(var liste : vélément ; inf, sup : entier ; var place : entier) ;
variable
L, R : entier ;
pivot : télément;
début
pivot liste[inf];
L inf + 1;
R sup;
tantque L R faire
si liste[L] pivot alors
LL+1
sinon
si liste[R] > pivot alors
R R – 1 ;
sinon
si L < R alors
début
permute(liste[L], liste[R]) ;
L L + 1
R R – 1 ;
fin ;
permute(liste[inf], liste[R])
place R ;
fin ;
Il peut paraître plus logique, lorsque le vecteur est presque trié, de prendre le pivot au milieu
du vecteur (pivot central). Nous donnons ci-dessous une version du Quicksort qui inclut les
deux appels récursifs dans une procédure unique.
si L R alors
début
si L < R alors
permute(liste[L], liste[R]) ;
L L + 1
R R – 1 ;
fin ;
fin ;
Exercices d’apprentissage
Exercice 6.20
Modifier le tri par bulles pour obtenir un vecteur trié par ordre décroissant.
Exercice 6.21
Modifier le tri par sélection pour obtenir un vecteur trié par ordre décroissant.
Exercice 6.22
Modifier le tri par insertion pour obtenir un vecteur trié par ordre décroissant.
Exercice 6.23
Modifier le tri par insertion par bissection pour obtenir un vecteur trié par ordres décroissant.
Exercice 6.24
Modifier le Quicksort pour obtenir un vecteur trié par ordre décroissant.
Ce problème consiste à construire, à partir de plusieurs vecteurs triés, un seul vecteur suivant
un certain critère de fusion. Nous prenons comme exemple celui de l’interclassement de deux
vecteurs triés.
Soient deux vecteurs triés liste1[1..n1] et liste[1..n2] à valeurs dans un même ensemble E et
munis de la même relation d’ordre. On veut construire un vecteur liste3[1..n3] trié et
contenant tous les éléments de liste1 et liste2. Il est évident qu’on aura n3 = n1 + n2.
L’algorithme d’interclassement peut être utilisé dans un algorithme de tri qui s’exprime
récursivement de manière assez semblable au Quicksort. Il paraît ressembler aussi à une
recherche dichotomique, mais oblige à parcourir chaque fois l’ensemble du vecteur non
seulement une moitié.
À chaque étape, on trie la première moitié du vecteur et la seconde moitié, puis on interclasse
les deux sous-vecteurs triés.
Exercice 6.25
Écrire un algorithme pour calculer la réunion de deux ensembles représentés par des vecteurs.
Exercice 6.26
Écrire un algorithme pour calculer l’intersection de deux ensembles représentés par des
vecteurs.
Exercice 6.27
Écrire un algorithme pour calculer la différence ensembliste de deux ensembles représentés
par des vecteurs.
Exercice 6.28
Écrire un algorithme pour calculer la différence symétrique de deux ensembles représentés
par des vecteurs.
Dans les exemples précédents, nous avons utilisé les vecteurs pour traiter des valeurs
numériques. Cependant, comme nous l’avons indiqué au début du chapitre, les éléments d’un
vecteur ne sont pas nécessairement de type numérique. Nous pouvons déclarer et utiliser des
vecteurs dont les composantes sont des caractères. Les instructions suivantes déclarent les
vecteurs de caractères nom et prénom.
variable
nom : vecteur[1..20] de caractère ;
prénom : vecteur[1..12] de caractère ;
Comme avec les composantes des autres vecteurs, on peut utiliser des boucles pour manipuler
les caractères stockés dans un vecteur. Par exemple, le segment d’algorithme ci-dessous lit 20
caractères et les stocke dans le vecteur de caractères nom. Il lit ensuite 12 caractères et les
stocke dans le vecteur de caractères prénom.
Cependant, le traitement des données caractères un caractère à la fois est très encombrant car
les boucles doivent être utilisées pour toutes les manipulations. Heureusement, le langage
algorithmique offre un type spécial de vecteur de caractères pour faciliter la manipulation de
plusieurs caractères à la fois. Dans les chapitres précédents, nous avons introduit des objets de
la forme ‘Bonjour’. Ces objets sont des chaînes de caractères. Bien que nous ayons utilisé
plusieurs fois les chaînes de caractères, nous n’avons pas indiqué leur type de donnée. Toutes
les chaînes de caractères sont des constantes du type :
chaîne[1..n] de caractère
L‘avantage d’utiliser le type chaîne de caractères est qu’il offre la possibilité de manipuler
l’ensemble des caractères d’une chaîne comme un tout. Par exemple, si on déclare les
variables nom et prénom par :
variable
nom : chaîne[1.20] de caractère
prénom : chaîne[1.12] de caractère
les instructions suivantes lisent puis impriment les variables nom et prénom :
lire(nom) ;
lire(prénom) ;
écrire(‘nom :’, nom) ;
écrire(‘prénom :’, prénom) ;
Exercice 6.29
Un palindrome est une chaîne de caractères qui se lit de la manière de la gauche vers la droite
et de la droite vers la gauche. Écrire une fonction qui prend en entrée une chaîne de caractères
et détermine si c’est un palindrome ou non.
On a souvent besoin de tableaux qui correspondent aux entrées d’une table plutôt qu’à une
liste. Par exemple, les données de la table suivante
Test
Étudiant 1 2 3 4
1 68 75 78 65
2 100 91 87 95
3 85 87 78 96
Numéros de colonne
1 2 3 4
1 68 75 78 65
2 100 91 87 95
Comme avec les tableaux à3 une85 dimension
87 ou78 vecteurs,
96 un tableau à deux dimensions est
dénoté par un identificateur qui référence le tableau entier. Pour localiser une composante
dans un tableau à deux dimensions, deux indices doivent être utilisés, un pour le numéro de
ligne et l’autre pour le numéro de colonne. Par exemple, supposons que le tableau ci-dessus
soit appelé notes. La composante notes[1, 3] référence la valeur se trouvant dans la ligne 1,
colonne 3. C’est-à-dire que la valeur de notes[1, 3] est 78. De même, la valeur de notes[3, 2]
est 87.
Tout tableau à deux dimensions utilisé dans un algorithme doit être déclaré. La déclaration
d’un tel tableau est semblable à celle d’un vecteur. Par exemple, pour déclarer le tableau notes
correspondant à la figure ci-dessus, on peut utiliser la déclaration :
variable
notes : tableau[1..3, 1..4] de entier ;
l’identificateur du type,
l’ensemble des indices de ligne et l’ensemble des indices de colonne,
le type des éléments du tableau.
type
identificateur = tableau[typeindiceligne, typeindicecolonne] de télément ;
où télément est le type des éléments du tableau, typeindiceligne le type des indices de ligne et
typeindicecolonne le type des indices de colonne.
Par exemple, pour définir un type tableau à deux dimensions de m lignes et de n colonnes, on
utilise une définition de type de la forme :
type
tmatrice = tableau[1..m, 1..n] de télément ;
Chaque élément X[i, j] d’un tableau à deux dimensions est caractérisé par son indice de ligne i
et par son indice de colonne j. La manipulation d’un tableau à deux dimensions nécessite donc
habituellement l’utilisation des boucles imbriquées. Par exemple, la procédure ci-dessus lit
une matrice ligne par ligne.
De même, la procédure ci-dessous imprime les élément d’une une matrice ligne par ligne.
Il est bien entendu possible de déclarer et d’utiliser des tableaux à trois dimensions ou plus.
Le langage algorithmique nous permet d’avoir des tableaux de n’importe quel type. Jusque
maintenant nous avons travaillé avec des tableaux dont le type de base est un type scalaire
(entier, réel, caractère, booléen) ou un type défini par l’utilisateur. Étant donné que nous
pouvons définir un type de tableau, nous pouvons aussi avoir des tableaux dont les éléments
sont eux-mêmes des tableaux. Par exemple, considérons les déclarations suivantes :
type
tétudiant = tableau[1..5] de entier ;
variable
Classe : tableau[1..35] de tétudiant ;
troisième étudiant est stockée dans Classe[3][2]. Le premier indice [3], est utilisé pour
sélectionner l’étudiant qui dans ce cas est un tableau contenant 5 entiers représentant les notes
de cet étudiant. Le deuxième indice [2] est utilisé pour sélectionner la deuxième note de cet
étudiant.
La déclaration ci-dessus, bien entendu, est équivalente à :
variable
Notes : tableau[1..35, 1..5] de entier ;
Une application importante des tableaux dont les éléments sont des tableaux implique les
chaînes de caractères. Un tableau dont les éléments sont des chaînes de caractères peut être
déclaré comme suit :
type
tnom = chaîne[1..15] de caractère ;
variable
Clients : tableau[1..100] de tnom ;
Le tableau Clients a 100 composantes, chacune d’elle est une chaîne de caractères de longueur
15. Comme avec un tableau dont les éléments sont des tableaux, nous pouvons référencer le
nom d’un client particulier en utilisant un simple indice. La composante Clients[i] référence le
nom du client numéro i. Toutes les opérations qui peuvent être utilisées avec les chaînes
peuvent aussi être utilisées avec les composantes du tableau Clients. L’instruction
Clients[j] Clients[i]
Écrire(Clients[i])
imprime la chaîne de caractères qui constitue le nom du client numéro i. Enfin, l’expression
logique
Clients[i] = Clients[j]
Exercices d’apprentissage
Exercice 6.30
Écrire une procédure qui reçoit en entrée deux matrices réelles de m lignes et n colonnes et
retourne la somme des deux matrices.
Exercice 6.31
Écrire une procédure qui reçoit en entrée une matrice réelle A de m lignes et n colonnes et une
matrice B réelle de n lignes de p colonnes retourne le produit AB de ces deux matrices.
Exercice 6.32
Une matrice de N lignes et N colonnes est dite symétrique si A[i, j] = [j, i] pour couple (i, j).
Écrire une fonction qui prend en une matrice réelle de N lignes et N colonnes et détermine si
la matrice est symétrique.
Exercice 6.33
Écrire une fonction qui prend en entrée une matrice réelle de m lignes et n colonnes et
retourne le plus grand élément de cette matrice.
Exercice 6.34
Écrire une procédure qui prend en entrée une matrice réelle de m lignes et n colonnes et
retourne le plus grand élément de cette matrice ainsi sa position ligne/colonne.
Exercice 6.35
Écrire une procédure qui calcule la transposée d’une matrice réelle.
Exercice 6.36
Un carré magique est un tableau de N lignes et N colonnes d’entiers de l’intervalle [1.. N*N]
tel que les sommes des termes de chaque ligne, chaque colonne et de chaque diagonale
principale sont égales. Écrire une fonction qui prend en entrée une matrice de N lignes et N
colonnes d’entiers de l’intervalle [1.. N*N] et détermine si la matrice est un carré magique.
Exercice 6.37
Écrire une fonction qui prend en entrée une matrice réelle de m lignes et n colonnes et
retourne le numéro de la ligne dont la somme des coefficients est maximum.
Exercice 6.38
Écrire une procédure qui prend en entrée un entier n et retourne la matrice des n premières
lignes du triangle de Pascal.
Dans ces études de cas, nous allons appliquer les algorithmes sur les vecteurs (création,
parcours, recherche, insertion, suppression, tri) à des structures de données plus ou moins
complexes.
On souhaite gérer une liste d’étudiants à l’aide de quelques algorithmes simples sur les
vecteurs (parcours, recherche, insertion, suppression, tri). Les informations retenues pour
chaque étudiant sont le numéro matricule, le nom (nom et prénom), le sexe , la date de
naissance, le lieu de naissance, la filière, le nombre et la liste des cours de la filière. Bien
entendu, le numéro matricule sera différent pour chaque étudiant.
constante
taillemax = 100 ;
nbmax = 20 ;
type
tdate = article
jour, mois, année : entier ;
fin ;
tcours = article
code : entier ;
intitulé : chaîne ;
examenpartiel : réel ;
examenfinal : réel ;
fin ;
vcours = vecteur[1..nbmax] de tcours ;
tétudiant = article
matricule : chaîne ;
nom : chaîne ;
sexe : (Féminin, Masculin) ;
annéenais : tdate ;
lieunaiss : chaîne ;
filière : chaîne ;
nbcours : entier ;
courspris : vcours ;
fin ;
vétudiant = vecteur[1..taillemax] de tétudiant ;
On suppose que le vecteur est trié par ordre alphabétique et que tous étudiants de la même
filière ont les mêmes cours.
2°/ Écrire un algorithme qui imprime le nom, la date de naissance, le lieu de naissance et la
spécialité de chacun des étudiants enregistrés dans un vecteur de n étudiants.
3°/ Écrire une fonction qui retourne le nombre d’étudiants d’une filière donnée contenus dans
un vecteur de n étudiants.
4°/ Écrire une procédure qui retourne le nombre de filles et le nombre de garçons présents
dans un vecteur de n étudiants.
5°/ Écrire un algorithme pour afficher le matricule, le nom, le lieu de naissance et la spécialité
de chacun des étudiants dont l’année de naissance est comprise entre aninf et ansup dans un
vecteur de n étudiants.
6°/ Écrire une fonction qui retourne la position d’un étudiant de matricule donné dans un
vecteur de n étudiants. La fonction retourne zéro si l’étudiant concerné n’est pas présent dans
la liste.
7°/ Écrire une procédure pour insérer un nouvel étudiant dans un vecteur de n étudiants. La
procédure doit d’abord vérifier qu’il n’existe pas encore un étudiant ayant le même matricule.
8°/ Écrire une procédure qui supprime un étudiant de matricule donné dans un vecteur de n
étudiants.
9°/ Écrire un algorithme qui calcule le nombre d’unités de valeur validées par un étudiant de
matricule donné.
10°/ Écrire une fonction qui retourne la moyenne des notes d’un étudiant de matricule donné
dans une unité de valeur de code donné.
11°/ Écrire une fonction qui retourne la moyenne générale des notes d’un étudiant de
matricule donné.
12°/ Écrire un algorithme qui classe par ordre de mérite, par rapport à la moyenne générale
des notes, les étudiants d’une filière donnée. À moyenne égale, les étudiants seront classés par
ordre alphabétique.
Stock de voitures
Un marchand de véhicules d’occasion souhaite gérer son stock à l’aide de procédures simples
sur les vecteurs. On suppose que le stock de véhicules, de 100 voitures au plus, peut être
représenté à l’aide d’un tableau en mémoire.
Les seules informations retenues pour chaque voiture seront le numéro d’immatriculation et
son année de mise en service, sa marque et son modèle, ainsi que le prix. Bien entendu, le
numéro d’immatriculation sera différent pour chaque voiture.
type
tvoiture = article
numéro : chaîne ;
année : entier ;
marque, modèle : chaîne ;
prix : réel ;
fin ;
vvoiture = vecteur[1..100] de tvoiture ;
On suppose que le vecteur est trié par ordre croissant sur les numéros des véhicules.
1°/ Écrire une procédure qui prend en entrée un vecteur de n voitures, une marque et modèle
et affiche l’année et le prix de chacune des voitures de le marque et du modèle demandés
2°/ Écrire une procédure qui prend en entrée un vecteur de n voitures, un prix inférieur et un
prix supérieur et affiche l’année, la marque et le modèle de chacune des voitures dont le prix
est compris entre les deux prix.
3°/ Écrire une procédure qui prend en entrée un vecteur de n voitures et un numéro et retourne
position du vecteur dans le vecteur. La procédure retourne également un indicateur booléen
permettant de déterminer si la voiture est présente dans la liste.
4°/ Écrire une procédure qui prend entrée un vecteur de n voitures et une nouvelle voiture et
procède à l’insertion de la nouvelle voiture dans la liste ; le vecteur de sortie devant être trié
sur les numéros. La procédure retourne également un indicateur booléen permettant de
déterminer si l’insertion a effectivement eu lieu.
5°/ Écrire une procédure qui prend entrée un vecteur de n voitures et un numéro et procède à
la suppression de la voiture de numéro demandé. La procédure retourne également un
indicateur booléen permettant de déterminer si la suppression a effectivement eu lieu.
6°/ Écrire une procédure qui reçoit en entrée un vecteur de n voitures et procède au tri du
vecteur par ordre alphabétique sur les marques, et à marque égale, sur les prix décroissants.
Librairie
On souhaite gérer les ouvrages vendus dans une librairie à l’aide d’une structure composée
d’un vecteur liste[1..taillemax]. Chaque composante du vecteur est une variable article
composé des champs suivants : le nom de l’auteur, le titre de l’ouvrage, la discipline, le nom
de l’éditeur, l’année d’édition et le prix du livre.
Le vecteur des ouvrages est supposé trié par ordre alphabétique sur les noms des auteurs.
constante
taillemax = 100 ;
type
touvrage = article
auteur : chaîne ;
titre : chaîne ;
discipline : chaîne ;
éditeur : chaîne ;
prix : réel ;
quantité : entier ;
fin ;
vouvrage = vecteur[1..taillemax] de touvrage ;
On supposera que les titres de tous les livres écrits par un même auteur sont différents.
1°/ Écrire une procédure qui crée un vecteur de n livres et le trie par ordre alphabétique sur les
noms des auteurs.
3°/ Écrire une fonction qui déterminer si un auteur de nom donné a écrit au moins un livre.
4°/ Écrire une fonction qui délivre l’indice d’un ouvrage de titre et d’auteur donnés dans la
liste si cet ouvrage se trouve dans la liste et 0 sinon.
5°/ Écrire une fonction qui délivre la quantité en stock d’un ouvrage de titre et d’auteur
donnés.
6°/ Écrire une procédure qui imprime la liste des ouvrages dont la quantité en stock est égale à
zéro.
7°/ Écrire une procédure qui imprime la liste des ouvrages édités par un éditeur donné.
8°/ Écrire une procédure qui imprime la liste des ouvrages disponibles dans une discipline
donnée.
9°/ Écrire une procédure qui ajoute un nouvel ouvrage dans la liste.
10°/ Écrire une procédure qui supprime un ouvrage de titre et d’auteur donnés.
12°/ Écrire une procédure qui supprime tous les livres dont la quantité en stock est nulle.