Vous êtes sur la page 1sur 65

EI1

Algorithmique
Équipe pédagogique

École Centrale de Nantes


École Centrale de Nantes, — Algorithmique et programmation — 2
Table des matières

1 Introduction 5

I PRÉAMBULE 7
2 Les machines 9
2.1 Bref historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Schéma et fonctionnement sommaire d’un ordinateur . . . . . . . . . . . . . . . . . . 10
2.3 Codage de l’information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

3 Les logiciels 12
3.1 Un logiciel indispensable : le système d’exploitation . . . . . . . . . . . . . . . . . . . 12
3.2 Les langages évolués . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.3 Transformation du texte écrit en un code exécutable . . . . . . . . . . . . . . . . . . 13
3.3.1 Interprétation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.3.2 Compilation, édition de liens . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

4 Représentation des nombres 15


4.1 Représentation des entiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.1.1 Les entiers positifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.1.2 Les entiers négatifs : complément à 2 . . . . . . . . . . . . . . . . . . . . . . . 15
4.2 Représentation d’un réel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4.2.1 Conversion de la partie fractionnaire d’un réel . . . . . . . . . . . . . . . . . . 16
4.2.2 Représentation en virgule flottante . . . . . . . . . . . . . . . . . . . . . . . . 16
4.3 Problèmes numériques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.3.1 Problèmes de débordement . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.3.2 Problèmes d’imprécision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.3.3 Une notion importante : le zéro « numérique » . . . . . . . . . . . . . . . . . 18
4.3.4 Résolution numérique de problèmes mathématiques . . . . . . . . . . . . . . 19

II ALGORITHMIQUE 20
5 Les structures de données 22
5.1 Les types de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.1.1 Les types simples (prédéfinis) . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.1.2 Les types composés . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
5.2 Les données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.2.1 Les constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.2.2 Les variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
5.2.3 Les expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.3 Structures de données particulières . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
5.3.1 Structures Linéaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

École Centrale de Nantes, — Algorithmique et programmation — 3


TABLE DES MATIÈRES TABLE DES MATIÈRES

5.3.2 Structures Arborescentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

6 Les structures algorithmiques 32


6.1 Les instructions simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
6.1.1 Les commentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
6.1.2 Les instructions d’affectation . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
6.1.3 Les lectures, écritures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.1.4 Les séquences d’instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.2 Les structures algorithmiques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
6.2.1 Les structures de choix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6.2.2 Les structures itératives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

7 Analyse descendante - Fonctions 37


7.1 première analyse - Fonction principale . . . . . . . . . . . . . . . . . . . . . . . . . . 37
7.2 Analyse des sous-problèmes - Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.3 Appel d’une fonction - Passage des arguments / paramètres . . . . . . . . . . . . . . 44

8 Algorithmes de traitement de files 46


8.1 Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
8.2 Traitement de files de longueur inconnue . . . . . . . . . . . . . . . . . . . . . . . . . 46
8.3 Traitement de file de longueur connue . . . . . . . . . . . . . . . . . . . . . . . . . . 47
8.4 Exemple d’algorithme de traitement de files . . . . . . . . . . . . . . . . . . . . . . . 48

9 Les algorithmes de tri 49


9.1 Tri par sélection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
9.2 Tri par insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
9.3 Tri à bulles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

10 Récursivité, manipulation de listes et d’arbres 53


10.1 Récursivité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
10.2 Manipulation de liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
10.2.1 Parcours d’une liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
10.2.2 Ajout d’un élément à la fin d’une liste . . . . . . . . . . . . . . . . . . . . . . 55
10.2.3 Suppression d’un élément dans la liste . . . . . . . . . . . . . . . . . . . . . . 56
10.2.4 Calcul de la longueur d’une liste . . . . . . . . . . . . . . . . . . . . . . . . . 57
10.2.5 Recherche d’un élément dans une liste . . . . . . . . . . . . . . . . . . . . . . 58
10.3 Manipulation d’un arbre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
10.3.1 Parcours d’un arbre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
10.3.2 Calcul de la hauteur d’un arbre . . . . . . . . . . . . . . . . . . . . . . . . . . 63

Ce polycopié a été élaboré avec l’aide des enseignants des équipes pédagogiques
successives de l’enseignement d’Algorithmique et Programmation.

Cours de l’école Centrale de Nantes « Algorithmique et Programmation » (ALGPR),



c
revu et corrigé en septembre 2013.

École Centrale de Nantes, — Algorithmique et programmation — 4


Chapitre 1

Introduction

L’algorithmique et la programmation font partie d’une science appelée Informatique.


La définition de l’informatique, donnée en 1966 par l’Académie française, est la suivante :
« Science du traitement rationnel, notamment par machines automatiques, de l’information
considérée comme le support des connaissances humaines et des communications dans les
domaines techniques, économiques et sociaux. »
L’information est donc considérée comme le support des connaissances et des commu-
nications, et l’ordinateur est une des machines automatiques qui permettent d’exécuter le
traitement rationnel de l’information. Un des objectifs de l’informatique est de procéder au
traitement des informations par l’ordinateur.
L’algorithmique est l’étude des algorithmes. Un algorithme est la description d’un
traitement rationnel de l’information, à savoir la suite des opérations nécessaires pour ré-
soudre un problème donné. Cet algorithme est issu d’une phase d’analyse du problème. Il
décrit comment est réalisée une fonction qui travaille sur un ensemble de valeurs, appelées
données, fournies à l’algorithme. L’algorithme de la fonction produit un ou plusieurs ré-
sultats. Il est écrit dans un formalisme suffisamment général pour ne pas tenir compte du
langage et de la machine qui seront utilisés pour la programmation et l’exécution.
La programmation, appelée phase de codage, est la traduction d’un algorithme dans
un langage compréhensible par l’ordinateur : le langage de programmation. Le résultat de
cette phase de programmation est un code source qui est compilé en un programme
exécutable.
Ce cours comporte deux parties :
– le préambule présente quelques généralités sur les ordinateurs, les logiciels, ainsi que
les problèmes induits par la représentation des nombres,
– la partie algorithmique contient les structures algorithmiques de base, explique com-
ment analyser et découper un problème en sous-problèmes.
Des compléments au contenu de ce cours seront accessibles sur le serveur pédagogique.

École Centrale de Nantes, — Algorithmique et programmation — 5


Chapitre 1. Introduction

École Centrale de Nantes, — Algorithmique et programmation — 6


Première partie

PRÉAMBULE : QUELQUES
GÉNÉRALITÉS SUR LES
ORDINATEURS

École Centrale de Nantes, — Algorithmique et programmation — 7


Dans ce préambule, un minimum d’éléments sont donnés pour permettre de comprendre
à quoi servent les différentes phases de construction et d’exécution d’un programme. Cette
démarche devra être appliquée pour pouvoir résoudre sur machine les problèmes vus en al-
gorithmique. Les explications données ici sont très simplifiées et schématisées. De nombreux
ouvrages décrivent ces techniques en détail (ouvrages sur l’histoire de l’informatique, sur
les ordinateurs et leurs langages, et aussi les premiers chapitres de nombreux livres sur la
programmation).
Une section est consacrée au problème de la représentation des nombres en machine et
des incidences de cette représentation sur la qualité des résultats obtenus.

École Centrale de Nantes, — Algorithmique et programmation — 8


Chapitre 2

Les machines

Le traitement de l’information nécessite l’utilisation de machines pour créer, stocker,


consulter, modifier, utiliser des informations de plus en plus volumineuses et de natures de
plus en plus diverses (textes, images, sons, . . . ). Dans ce paragraphe, on présente succinc-
tement l’histoire et l’évolution de ces machines numériques, les ordinateurs, puis la manière
dont ils traitent et stockent les informations.

2.1 Bref historique


Le principe de fonctionnement et la technologie des ordinateurs actuels héritent des
évolutions de plusieurs sciences : physique (électricité, magnétisme), automatique, électro-
nique (transistors, circuits imprimés, circuits intégrés, processeurs). Les ordinateurs ont été
eux-mêmes à leur tour à l’origine d’une nouvelle science : l’informatique.
Ils ont intégré :
– les principes des machines à calculer (les premières furent réalisées au XVIIe siècle à
l’aide de roues dentées et d’engrenages),
– l’automatisme et la notion de programme sous-jacente au fonctionnement des machines
à tisser (utilisation de cartes perforées au XVIIIe siècle),
– l’architecture des machines à différences et de la machine analytique conçues par
Babbage au milieu du XIXe siècle.
L’apparition de l’électricité vers la fin des années 1880 permet à Hollerith, statisticien
américain, de créer le premier ensemble de machines de traitement automatique de l’informa-
tion, utilisant la technique des cartes perforées. La société d’Hollerith prit le nom d’I.B.M.
(International Business Machines) en 1911.
En 1945, Von Neumann propose une architecture de machine qui est celle couramment
utilisée encore de nos jours. Il conçoit une machine stockant les données et fonctionnant
à l’aide d’un programme enregistré, sans avoir à modifier ses circuits, c’est un des tous
premiers ordinateurs (l’UNIVAC).
En 1946, l’ENIAC (Electronic Numerical Interfactor and Automatic Computer) est
construit aux USA. Ses caractéristiques sont imposantes : 30 tonnes, 167 m2 au sol, 17.468
tubes électroniques, 5.000 commutateurs ! Ce calculateur pouvait effectuer des calculs com-
plexes, mais le chargement du programme était fait par câblage manuel.

École Centrale de Nantes, — Algorithmique et programmation — 9


2.2. Schéma et fonctionnement sommaire d’un ordinateur Chapitre 2. Les machines

Les grands progrès que va faire ensuite l’électronique (transistor vers 1948, circuit im-
primé puis circuit intégré VLSI vers 1963) permettent la commercialisation à grande échelle
des ordinateurs : elle commence en 1958 avec la machine IBM 650, premier ordinateur
à équiper l’ENSM (qui deviendra ensuite l’ECN) ! La miniaturisation des composants et
les micro-processeurs vont rendre possible l’utilisation d’ordinateurs dans les entreprises (à
partir de 1965). Le premier ordinateur personnel vendu monté est le Micral de la société
française R2E en 1973 (plus tard rachetée par Bull) : l’ère de la mini et micro-informatique
est arrivée ! Dix ans plus tard, Apple propose le Macintosh dont l’interface graphique révo-
lutionnaire va mettre la micro-informatique à la portée de tous. Les dix années suivantes
verront l’avènement de Internet : réseau géré au départ par des bénévoles, destiné à ses
débuts aux chercheurs, il s’ouvre au grand public en 1994.

2.2 Schéma et fonctionnement sommaire d’un ordinateur


Un ordinateur est schématiquement composé de plusieurs périphériques et d’une unité
centrale.

Mémoire centrale

Mémoires auxiliaires
(souris, clavier, écran, etc.)

Unité de Unité de
traitement commande

Périphériques Unité centrale

Figure 2.1 – Principaux composants d’un ordinateur

Les périphériques comprennent les organes d’entrée/sortie : clavier, souris, scanner,


écran, imprimante, . . . et aussi toutes les mémoires auxiliaires dites de masse ou secon-
daires comme les disques durs, les clés USB, les DVD. Ces organes permettent de commu-
niquer avec l’unité centrale de l’ordinateur qui comporte au moins une mémoire centrale,
comportant une mémoire vive et une mémoire morte, une unité de traitement et une unité
de commande qui vont lui permettre d’exécuter des instructions et de faire des calculs.

École Centrale de Nantes, — Algorithmique et programmation — 10


Chapitre 2. Les machines 2.3. Codage de l’information

La mémoire centrale de taille limitée, d’accès très rapide est un passage obligé pour toute
instruction ou donnée que l’ordinateur doit traiter.
Les mémoires secondaires servent à stocker de façon permanente des informations, de
taille très importante ; elles sont d’accès plus lent. Toutes les mémoires sont divisées en
cases élémentaires, appelées « mots » mémoire, de taille fixe différente selon les machines,
possédant chacune son adresse. Chacune de ces cases peut être lue (sans être modifiée) ou
écrite (avec effacement de ce qui y était précédemment).
L’unité centrale fonctionne selon le principe de Von Neumann : la suite séquentielle
d’instructions est gérée par un registre appelé compteur ordinal qui indique la prochaine ins-
truction à exécuter. Pour effectuer les diverses instructions, l’unité centrale dispose en outre
d’un certain nombre de « cases mémoires » rapprochées appelées registres et/ou mémoire
cache. Le jeu d’instructions élémentaires de la machine permet de manipuler les données
par l’intermédiaire de ces registres.

2.3 Codage de l’information


L’unité d’information est le bit (binary digit) dont les valeurs possibles sont 0 ou 1. Toutes
les informations sont codées à l’aide de bits en binaire, depuis les nombres et les caractères
jusqu’aux textes, images, sons ou programmes. Ces informations sont stockées sous forme
de mots mémoire dont la taille est exprimée en bits ou en octets (8 bits). La taille d’un mot
mémoire varie de 2 à 16 octets selon l’ordinateur, et se situe à 64 bits pour les ordinateurs
courants.
Pour le codage des caractères alphanumériques, un standard américain a été longtemps
utilisé : le codage ASCII (American Standard Code for Information Interchange) sur 1 octet.
Mais ce codage ne supporte pas les caractères accentués. Le codage ISO-8859-1 (Latin1) est
venu corriger cela par l’ajout des caractères accentués pour le français, entre autres. Ce-
pendant l’échange de documents au niveau international a rendu nécessaire la création d’un
nouvel encodage pour encoder tous les caractères existants : l’unicode, avec des caractères
sur 2 octets. Cette augmentation importante de la taille de stockage est résolue avec l’enco-
dage UTF-8, qui ne stocke les caractères en unicode que lorsque c’est nécessaire. Ce codage
est désormais adopté universellement. Mais pour les autres types d’informations (données
numériques, instructions, . . . ) le codage diffère souvent d’une machine à l’autre.

École Centrale de Nantes, — Algorithmique et programmation — 11


Chapitre 3

Les logiciels

3.1 Un logiciel indispensable : le système d’exploitation


Pour pouvoir faire fonctionner l’ensemble des composants électroniques qui constituent
un ordinateur, il faut au moins un programme pour enchainer les diverses tâches : gérer
la mémoire principale et l’enchainement des instructions, gérer les mémoires secondaires,
exécuter les instructions d’entrée/sortie. Le partage du temps, les problèmes de sécurité sont
également à la charge de ce programme si l’ordinateur est partagé par plusieurs utilisateurs.
Ce programme de base est appelé système d’exploitation (exemples : GNU/Linux, MacOSX,
Windows,), tout ordinateur en possède un. Une petite partie de ce système est résident en
mémoire morte de l’ordinateur et lui permet de démarrer correctement lors de la mise sous
tension. Les programmes, textes, images, . . . , autrement dit tout ce qui est stocké sur les
mémoires secondaires est organisé en répertoires et fichiers qui sont gérés par le système
d’exploitation. Le langage de commande du système d’exploitation permet à l’utilisateur de
dialoguer directement avec la machine.

3.2 Les langages évolués


Le codage des instructions est donc binaire. Programmer directement en binaire la résolu-
tion d’un problème complexe serait très fastidieux ! La nécessité de programmer en langages
plus évolués est vite apparue. Le premier de ces langages, le plus proche de la machine,
est l’assembleur. Mais pour tout type de langage, il faut un traducteur qui transforme les
instructions écrites dans ce langage en instructions compréhensibles par la machine : ce
traducteur est appelé un compilateur. Pour rendre la programmation plus aisée et moins
dépendante de la machine, des langages de plus en plus évolués ont été inventés, les compi-
lateurs correspondants devenant de plus en plus complexes.
Le premier de ces langages, dit évolués, fut le FORTRAN destiné au calcul scientifique.
Puis de nombreux autres langages ont été développés. Ils peuvent être regroupés en diffé-
rentes classes :
– les langages impératifs qui décrivent la suite des instructions à exécuter pour résoudre
le problème (exemples : FORTRAN, C, PASCAL, ADA, . . . ),
– les langages fonctionnels où toutes les relations entre les données sont écrites sous forme
de fonctions (exemples : LISP, le plus ancien, est aussi un langage de manipulation de

École Centrale de Nantes, — Algorithmique et programmation — 12


Chapitre 3. Les logiciels 3.3. Transformation du texte écrit en un code exécutable

symboles, CAML, . . . ),
– les langages déclaratifs où les relations logiques entre les objets manipulés sont décla-
rées, sans décrire la méthode de résolution du problème, la solution est trouvée par
des déductions effectuées par le « moteur » associé au langage (exemple : PROLOG),
– les langages orientés objets où données et actions sur ces données sont regroupées sous
les concepts de classe et d’objet, l’utilisateur d’un objet n’ayant qu’une vue partielle de
cet objet (son interface), le reste ne lui étant pas accessible ; par ailleurs les différentes
classes et objets sont liés par des liens d’héritage (exemples : SMALLTALK, EIFFEL,
C++, JAVA, . . . ),
– les langages avec contraintes, souvent associés aux langages déclaratifs, les relations
entre les objets manipulés sont exprimées sous forme de contraintes numériques, lo-
giques, ensemblistes, . . . (exemples : PROLOG IV, SCHEME, CHARME, . . . ).
De nouveaux langages sont régulièrement inventés, spécifiques pour la programmation
de noyaux d’exécutif temps-réel, pour l’interrogation de bases de données, pour la program-
mation d’applications sur Internet, etc.

3.3 Transformation du texte écrit en un code exécutable


Un programme, écrit dans un langage évolué à l’aide d’un éditeur de texte, est stocké
sur la machine dans un « fichier texte » : c’est le code source. Même s’il est stocké sous
forme de 0 et de 1, il n’est pas compréhensible pour la machine, car il ne correspond pas à
des instructions exécutables. Il faut donc traduire le contenu de ce fichier texte en un code
exécutable. Cette transformation peut se faire de deux façon différentes selon le langage
utilisé.

3.3.1 Interprétation
Le logiciel (un programme !) qui réalise cette interprétation est appelé un interpréteur.
Il lit une instruction, vérifie sa correction syntaxique, la traduit en binaire puis lance son
exécution avant de passer à l’instruction suivante qui sera à son tour lue, analysée puis
exécutée.
Exemples de langages interprétés : certains BASIC, LISP, PROLOG, SQL, . . .

3.3.2 Compilation, édition de liens


Le compilateur transforme le code source en code objet binaire : cette phase, appe-
lée compilation, consiste à vérifier que l’unité de code source écrite est syntaxiquement et
sémantiquement correcte vis à vis de règles du langage utilisé, puis à traduire en binaire.

École Centrale de Nantes, — Algorithmique et programmation — 13


3.3. Transformation du texte écrit en un code exécutable Chapitre 3. Les logiciels

Codes source Codes objet

void main () 1101010111010


compilation 0010010001011 Code exécutable
{
... 1010101000111
} 1000101001010
10100010110101001100101010010
10001010010010001110101101101
...... ...... 00001010011010100100010110101
10100101000101010101010101010
11111010010101001010101010100
int fct (int n) 0001101010101 10010010001001010101010110010
compilation 1001001101110
{ 01001010101010101000100001111
... 1010000100111 01001001010100100101000111001
} 00001000101110110101011001101

bibliothèques déjà compilées 1010111100101


0010001100010 édition de liens
0101000100111
1011001110001

Figure 3.1 – Schéma de transformation de code source en code exécutable

Il faut ensuite lier les différents éléments qui constituent le programme : le programme
(ou fonction) principal(e) et les sous-programmes (ou fonctions) déjà compilé(e)s et les
bibliothèques utilisées. Cette phase, appelée « édition de liens », est réalisée à l’aide d’un
nouveau logiciel (éditeur de liens). Cette phase est nécessaire même si tout le programme
est écrit d’un seul tenant (tout le code source dans un seul fichier) : en effet le code des
instructions d’entrée/sortie par exemple, qui est écrit à part dans des bibliothèques, doit
être lié au code obtenu par compilation.
Exemple :
Vous trouverez les explications des commandes de compilation et d’édition de liens dans la
section ??, p. ??, et un exemple à la page ??.

École Centrale de Nantes, — Algorithmique et programmation — 14


Chapitre 4

Représentation des nombres

Le codage des nombres n’est pas le même sur toutes les machines, mais il a un certain
nombre de caractéristiques qui se retrouvent partout et permettent d’expliquer les problèmes
liés à cette représentation.

4.1 Représentation des entiers


4.1.1 Les entiers positifs
Un entier positif est représenté par sa valeur en base 2 (obtenue par divisions successives
par 2).
Exemple :
Représentation de 25 sur un octet :
25 = 12 × 2 + 1 12 = 6 × 2 + 0 6 = 3 × 2 + 0 3 = 1 × 2 + 1 1 = 0 × 2 + 1
25 = 1 × 24 + 1 × 23 + 0 × 22 + 0 × 21 + 1 × 20 → la représentation de 25 sur un octet est
00011001.

4.1.2 Les entiers négatifs : complément à 2


La représentation des nombres négatifs diffère selon les machines. La plus courante est
celle dite « en complément à 2 ». Cette représentation consiste à convertir la valeur absolue
du nombre en base 2, à prendre le complément à 1 de chacun de ses bits et à ajouter 1 au
nouveau nombre obtenu.
Exemple :
Représentation de −25 sur un octet :
– représentation de 25 : 00011001,
– prendre les compléments à 1 : 11100110,
– ajouter 1 : 11100110 + 00000001 = 11100111,
→ la représentation de −25 sur un octet est : 11100111.

Exemple :
6 est représenté par 00000110, −6 par 11111001 + 00000001 = 11111010.

École Centrale de Nantes, — Algorithmique et programmation — 15


4.2. Représentation d’un réel Chapitre 4. Représentation des nombres

Remarques :
– Le premier bit de la représentation en complément à 2 d’un entier positif vaut 0, et
pour un entier négatif, il vaut 1.
– Le plus grand entier positif qui peut être représenté sur 2 octets est 216−1 − 1 = 32767.
– Le plus petit entier négatif qui peut être représenté sur 2 octets est −216−1 = −32768.
– Avec une telle représentation sur un octet par exemple, la somme d’un entier et de
son opposé donne bien 0 :
00000110 + 11111010 = 1 00000000, la retenue étant en dehors du mot permettant
de représenter l’entier, elle est perdue.

4.2 Représentation d’un réel


4.2.1 Conversion de la partie fractionnaire d’un réel
Il faut écrire la partie fractionnaire comme une somme de termes de la forme ni × 2−i
où les ni valent 0 ou 1.
Exemples :

– Représentation de 0, 625
0, 625 × 2 = 1, 250
0, 250 × 2 = 0, 50
0, 50 × 2 = 1
0, 625 = 1 × 2−1 + 0 × 2−2 + 1 × 2−3 → la représentation de 0, 625 est : .101

– Représentation de 0, 1
0, 1 × 2 = 0, 2
0, 2 × 2 = 0, 4
0, 4 × 2 = 0, 8
0, 8 × 2 = 1, 6
0, 6 × 2 = 1, 2
0, 2 × 2 = 0, 4
...
→ la représentation binaire de 0, 1 est infinie : .0001100110011...
Pour la représentation en machine, il y a donc troncature : 0, 1 ne peut pas être
représenté exactement.

4.2.2 Représentation en virgule flottante


En « virgule flottante », le réel est représenté par un rationnel de la forme :
(signe) Mantisse × baseexposant
La norme IEEE 754 définit la façon de coder un nombre réel. Cette norme se propose
de coder le nombre sur 32 bits et définit trois composantes :
– le signe est représenté par un seul bit, le bit de poids fort (celui le plus à gauche)
– l’exposant est codé sur les 8 bits consécutifs au signe
– la mantisse (les bits situés après la virgule) sur les 23 bits restants

École Centrale de Nantes, — Algorithmique et programmation — 16


Chapitre 4. Représentation des nombres 4.2. Représentation d’un réel

Ainsi le codage se fait sous la forme suivante :


s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm
– le s représente le bit relatif au signe,
– les e représentent les bits relatifs à l’exposant,
– les m représentent les bits relatifs à la mantisse.
Certaines conditions sont toutefois à respecter pour les exposants :
– l’exposant 00000000 est interdit,
– l’exposant 11111111 est interdit. On s’en sert toutefois pour signaler des erreurs, on
appelle alors cette configuration du nombre NaN, ce qui signifie Not a number,
– Il faut rajouter 127 (01111111) à l’exposant pour une conversion de décimal vers un
nombre réel binaire. Les exposants peuvent ainsi aller de -254 à 255
La formule d’expression des nombres réels est ainsi la suivante :

(-1)^S * 2^( E - 127 ) * ( 1 + F )

où :
– S est le bit de signe et l’on comprend alors pourquoi 0 est positif
( -1^0=1 ).
– E est l’exposant auxquel on doit bien ajouter 127 pour obtenier son équivalent codé.
– F est la partie fractionnaire, la seule que l’on exprime et qui est ajoutée à 1 pour
effectuer le calcul.
Exemple :
Représentation de 525,5 :
– 525,5 est positif donc s = 0.
– Sa représentation en base 2 est la suivante : 1000001101,1
– En normalisant, on trouve :
1,0000011011*2^9
– On ajoute 127 à l’exposant qui vaut 9 ce qui donne 136, soit en base 2 : 10001000
– La mantisse est composée de la partie décimale de 525,5 en base 2 normalisée, c’est-
à-dire 0000011011.
– Comme la mantisse doit occuper 23 bits, il est nécessaire d’ajouter des zéros pour la
compléter : 00000110110000000000000
– La représentation du nombre 525,5 en binaire avec la norme IEEE est donc :
0 10001000 00000110110000000000000
0100 0100 0000 0011 0110 0000 0000 0000
Exemple :
Représentation de -0,625 :
– Le bit s vaut 1 car 0,625 est négatif
– 0,625 s’écrit en base 2 de la façon suivante : 0,101
– On souhaite l’écrire sous la forme 1.01 x 2-1
– Par conséquent l’exposant vaut 1111110 car 127 - 1 = 126 (soit 1111110 en binaire)
– la mantisse est 01000000000000000000000 (seuls les chiffres après la virgule sont re-
présentés, le nombre entier étant toujours égal à 1)
– La représentation du nombre 0,625 en binaire avec la norme IEEE est :
1 11111110 01000000000000000000000
1111 1111 0010 0000 0000 0000 0000 0000

École Centrale de Nantes, — Algorithmique et programmation — 17


4.3. Problèmes numériques Chapitre 4. Représentation des nombres

Cette représentation est celle qui permet d’avoir le plus grand nombre de chiffres signi-
ficatifs.
Le codage de la mantisse sur 23 bits assure une partie significative du nombre codée de
l’ordre de 2−23 soit 10−7 . On peut donc considérer que sur une machine codant ainsi un
réel, les réels codés vont, en valeur absolue, de 10−39 à 1038 avec une précision de 10−7 sur
la mantisse.

4.3 Problèmes numériques


4.3.1 Problèmes de débordement
Lorsque, au cours d’un traitement, les valeurs maximales ou minimales sont dépassées,
selon la machine et le logiciel utilisé, il pourra y avoir message d’erreur ou apparition de
résultats aberrants. Par exemple, dans une représentation sur 1 octet, le plus grand entier
positif représentable est 28−1 −1 = 127 représenté par 01111111, en lui ajoutant 1, le binaire
obtenu 10000000 représente −128.

4.3.2 Problèmes d’imprécision


La représentation des réels est en général erronée, puisque à une représentation infinie
on fait correspondre un codage fini. L’erreur ainsi commise sur les nombres va être propagée
à tous les calculs utilisant ces nombres. Il conviendra donc toujours de se poser la question
de la signification des résultats obtenus.
Exemple :
Addition de deux réels d’ordres de grandeur très différents : l’addition de 2 réels se fait en
prenant comme exposant le plus grand des exposants, en alignant les mantisses, puis en
additionnant les mantisses et en normalisant le résultat.
Ainsi si on calcule 104 + 10−4 avec des réels codés sur 4 octets, avec 24 bits pour la
mantisse, 10−4 sera ignoré :
En binaire, avec virgule flottante, 104 est représenté par :
0, 100111000100000000000000 × 21110 ,
et 10−4 est représenté par :
0, 110100011011011100010111 × 2−1101 .
En prenant l’exposant le plus grand, 10−4 est représenté comme suit :
0, 0000000000000000000000000001... × 21110
Les 24 premiers bits de la mantisse étant nuls, ce deuxième nombre sera ignoré dans l’addi-
tion.

4.3.3 Une notion importante : le zéro « numérique »


Tout calcul sur les réels étant a priori entaché d’erreurs, une valeur obtenue par un calcul
ne pourra jamais être comparée à une autre par une égalité stricte : écrire par exemple en
C : "if (x==0)", alors que x est un réel résultant d’un calcul, n’a pas de sens. Il convient
de regarder cette égalité à la précision prés de la machine. Il faudra écrire : "if (abs(X)
<eps)", où eps est une valeur qui dépend de la machine utilisée et des nombres manipulés.

École Centrale de Nantes, — Algorithmique et programmation — 18


Chapitre 4. Représentation des nombres 4.3. Problèmes numériques

Cette valeur de eps peut être considérée comme le « zéro numérique » de la machine : il
faut considérer que tout nombre inférieur à cette valeur n’est plus significatif. Par exemple,
il ne faudra pas faire une division par un nombre X tel que |X| < eps. Autre exemple, une
matrice théoriquement inversible pourrait ne pas l’être numériquement et vice-versa.
Exemple :
 
0, 5 1
0, 7 1, 4
pourrait être trouvée inversible, alors que :
 
1 1
1 + 10−7 1

ne le serait pas.
A titre indicatif, pour une machine où les réels ont une mantisse représentée sur 23 bits,
eps est de l’ordre de 10−7 .

4.3.4 Résolution numérique de problèmes mathématiques


De plus, il est à noter que la résolution de certains problèmes mathématiques (limite
d’une suite, calcul d’une série, par exemples) suppose que les calculs se fassent sur un
nombre infini de termes. Or, un calcul numérique se fait nécessairement sur un nombre
fini de termes. La notion de convergence numérique d’une suite, par exemple, sera donc une
adaptation d’un critère de convergence mathématique. Ces problèmes sont vus dans le cours
de Mathématiques pour l’ingénieur.

École Centrale de Nantes, — Algorithmique et programmation — 19


Deuxième partie

ALGORITHMIQUE

École Centrale de Nantes, — Algorithmique et programmation — 20


Remarque : Les symboles spécifiques, ou mots-clefs, du langage algorithmique sont écrits
dans ce document en style souligné.

Notion d’analyse descendante


Pour écrire l’algorithme permettant de résoudre le problème donné, celui-ci est examiné
dans son ensemble, puis est découpé en différents sous-problèmes : une sorte de plan du
futur algorithme est ainsi réalisé. Puis chaque sous-problème est à son tour analysé : si sa
réalisation n’est pas immédiate, il est à nouveau découpé en plusieurs sous-problèmes dont
la réalisation est reportée au niveau suivant d’analyse.
En descendant ainsi dans les niveaux de détail, on finit par arriver au niveau des ins-
tructions qui correspondent à un traitement non décomposable. Cette façon de procéder
est appelée analyse descendante.

Organisation de cette partie


Nous allons commencer par les types de données présentés dans le chapitre 5 : types
simples d’abord, puis les types composés.
Nous donnerons ensuite dans le chapitre 6 les instructions élémentaires, ou structures
élémentaires utilisables en algorithmique.
Dans le chapitre 7, nous verrons comment découper un problème en sous-problèmes
par l’analyse descendante et comment résoudre ces sous-problèmes en faisant appel à des
fonctions.
Le chapitre 8 présente les algorithmes classiques sur une file de données et le chapitre 9
les algorithmes élémentaires de tris.

École Centrale de Nantes, — Algorithmique et programmation — 21


Chapitre 5

Les structures de données


5.1 Les types de données
5.1.1 Les types simples (prédéfinis)
Les types simples de données utilisés sont entier, réel, caractère et booléen (ou
logique) :
– type entier , notation décimale (ex : 215), opérations usuelles (+, -, /, *, mod)
– type réel , notation de préférence en virgule fixe (ex : 3,14) (éventuellement en virgule
flottante : 0,314e+1) opérations usuelles (+, -, /, *)
– type caractère , noté entre apostrophes (ex : ‘A’, ‘&’, . . . )
– type booléen , noté vrai, faux ; opérations logiques (et, ou, non,. . . )

5.1.2 Les types composés


Les types composés sont utilisés lorsque plusieurs objets manipulés ont le même type (ta-
bleaux) ou lorsque sémantiquement il est naturel de regrouper des objets de types différents
dans une même structure (enregistrements).
La déclaration d’un type est à faire après l’en-tête de la fonction qui l’utilise la première,
en général, la fonction principale. Une convention utilisée à l’école consiste à repérer les
types construits en leur donnant un nom commençant par une majuscule systématiquement
précédée par ”t_”.

 Les types tableaux


Lorsque des données de même type, devant être traitées de manière identique, sont
nombreuses mais de nombre maximum connu, il est pratique de les stocker dans un
type de données appelé tableau. Un tableau est défini par son nom, sa dimension et la
taille maximale de chaque dimension. La taille maximale de chaque dimension ne sera pas
forcément précisée dans l’algorithme. Les tableaux les plus utilisés sont ceux de dimension
1 ou 2 : les vecteurs et les matrices. Les mots réservés pour leur déclaration sont : vecteur
, matrice , et plus généralement tableau pour les dimensions supérieures.

École Centrale de Nantes, — Algorithmique et programmation — 22


Chapitre 5. Les structures de données 5.1. Les types de données

Vecteurs :
Les vecteurs sont caractérisés de la façon suivante :
– Un vecteur est un ensemble ordonné d’éléments qui ont tous le même type,
– Une composante est un élément de cet ensemble identifié par un indice,
– Un indice est un entier. Il peut être exprimé par une constante, une variable ou une
expression numérique formée de constantes et/ou de variables entières.
Notation : soit v le vecteur, la ie composante est notée vi ou v[i].
Matrices :
Les matrices sont caractérisées de la façon suivante :
– Une matrice est un ensemble d’éléments, tous de même type, construits selon deux
directions (ou dimensions).
– Un élément est repéré par deux coordonnées : l’indice de ligne et l’indice de colonne.
Notation : soit M une matrice, l’élément de la ie ligne et de la j e colonne est noté Mij ou Mi,j
ou M[i, j].
Exemples :
// déclarations de types tableaux
types
t_VectEnt : vecteur d’entiers
t_MatReels : matrice de réels
t_MonTableau : tableau d’entiers de dimension 4

 Les types enregistrements


Dans certains problèmes, le regroupement de données de types différents peut être utile
et naturel. Il faut alors créer un nouveau type de données structuré, appelé enregistrement.
Par exemple, un article aura un libellé, un prix unitaire, une référence. Plutôt que de ma-
nipuler trois variables différentes, voire trois tableaux différents pour l’ensemble des articles,
on utilisera un enregistrement ayant trois champs.
Déclaration d’un type enregistrement :
type
nomduType : Enregistrement

type champ1, // de type simple ou composé


type champ2, // idem
...
Fin_enregistrement

Exemple :
// Déclarations de types tableaux et enregistrements
types
t_Chaine : vecteur de caractères

École Centrale de Nantes, — Algorithmique et programmation — 23


5.2. Les données Chapitre 5. Les structures de données

t_Article : Enregistrement
entier : reference,
t_Chaine : libelle, // type précédemment défini
réel : prix
Fin_enregistrement

t_VectArticles : vecteur de t_Article

5.2 Les données


5.2.1 Les constantes
Il est parfois pratique de pouvoir nommer certains objets qui gardent la même valeur au
cours de l’algorithme, par exemple le nombre π qui lors de la programmation sera exprimé
avec un nombre de décimales dépendant de la précision recherchée. De même certaines
valeurs vont dépendre du contexte d’utilisation (nombre maximal d’éléments, année en cours,
. . . ), l’utilisation d’une constante pour désigner cette valeur permettra une plus grande
lisibilité de l’algorithme et une plus grande facilité pour maintenir le programme qui lui
correspondra.
Les constantes sont déclarées par leur type et leur nom ; ce nom est écrit par convention
en lettres majuscules. Leur rôle doit être précisé par un commentaire (cf. section 6.1.1 p.
32).
Exemple :
constantes
réel PI // nombre π
entier NBMAXELTS // nombre maximum d’éléments à traiter

5.2.2 Les variables


Une variable est une sorte de boı̂te, zone mémoire dont le contenu peut être modifié
au cours de l’algorithme, mais dont le type est invariable. Le contenu de la variable est sa
valeur. Pour pouvoir être manipulée, la variable est désignée par un nom ou identificateur.
Cet identificateur doit être explicite. Il aura un sens intuitif (mnémotechnique) et par
convention sa première lettre sera en minuscule, ou bien correspondra à une notation habi-
tuelle (mathématique, physique, . . . ) en accord avec son rôle dans l’algorithme. Les noms
des indices de boucle ne comporteront qu’une seule lettre, minuscule : i, j, k par exemple.
La déclaration de la variable consiste à préciser son type, son nom, et son rôle sous
forme de commentaire (cf. section 6.1.1, p.32). Exemples :
variables
// déclaration de variables avec les types simples
entier nCour // élément courant d’une liste d’entiers
entier somme // somme des éléments de la liste d’entiers
réels x, y // coordonnées du point courant

École Centrale de Nantes, — Algorithmique et programmation — 24


Chapitre 5. Les structures de données 5.2. Les données

// déclaration de variables avec les types composés


t_VectEnt v1, v2 // valeurs des dés de deux joueurs
t_MatReels Rx // matrice de rotation autour de l’axe X

t_Article artCour // article courant


t_Article artPrec // article précédent
t_VectArticles mesArticles // liste des articles considérés

Remarque : L’accès aux champs d’une variable de type enregistrement se fait en appelant la
variable suivie du nom du champ choisi. Par exemple pour une variable unArticle de type
t_Article, l’accès au champ référence se fait par unArticle.reference, l’accès au champ
libellé se fait par unArticle.libelle et au champ prix par unArticle.prix.

5.2.3 Les expressions

Une expression est une combinaison d’objets de types simples et d’opérateurs. Pour
éviter toute ambiguı̈té, il convient de parenthéser convenablement les expressions.
Exemple :
les expressions (2 - 7) - 3 et 2 - (7 - 3) ne sont pas égales.

 Opérateurs de comparaison :

Les opérateurs < ≤ = 6= ≥ > sont utilisables pour des opérandes de


type entier ou réel, ou caractère. Le résultat est un booléen.
Exemple :
3 ≤ (7-1) // résultat vrai

Les opérateurs = et 6= sont utilisables pour tout type d’opérande, les deux opérandes
étant de même type. Le résultat est un booléen.
Exemples :
vrai 6= faux // résultat vrai
v1 = v2 // v1 et v2 étant des vecteurs, résultat vrai si tout élément de v1
est égal à l’élément de m^ eme indice de v2

 Opérateurs logiques :

Les opérateurs logiques et ou non s’appliquent à des opérandes booléens.


Exemples :
(5 + 2 > 1) et ((2 − 1) = 1) // résultat vrai
non (x=2) // résultat faux si la valeur de x est 2, vrai sinon
(c1 = ‘O’) ou (c1 = ‘o’) // c1 doit ^ etre de type caractère

École Centrale de Nantes, — Algorithmique et programmation — 25


5.3. Structures de données particulières Chapitre 5. Les structures de données

5.3 Structures de données particulières


Les structures de données permettent d’organiser les données afin de les traiter dans leur
ensemble. Ceci permet de faciliter notamment :
– le tri des données
– la recherche d’une donnée particulière
– la suppression d’une donnée particulière
– l’insertion d’une nouvelle donnée dans la structure
– etc.
Nous allons maintenant montrer les principes qui permettent d’utiliser ces structures de
données, indépendamment de leur implémentation dans un langage. Nous nous intéresserons
particulièrement à des structures linéaires et à des structures arborescentes.

5.3.1 Structures Linéaires


Liste

Une liste est une structure de données permettant de regrouper des données de même
type de manière à pouvoir y accéder librement. La liste est à la base de structures de données
plus complexes comme la pile, la file, les arbres, etc.
Plus formellement, une liste est un ensemble d’éléments liés par une relation de séquen-
tialité ayant les propriétés suivantes :
– tout élément, sauf le premier et le dernier, a un prédécesseur et un successeur ;
– le premier élément n’a pas de prédécesseur ;
– le dernier élément n’a pas de successeur.
Les primitives
EI2 communément utilisées pour manipuler des listes sont : logicielles (MELOG)”
“Méthodes
– Insérer : ajoute un élément dans la liste
– Retirer : retire un élément de la liste
Exemple d’une liste (non ordonnée) d’entiers
– Test de liste vide : renvoie vrai si la liste est vide, faux sinon
– Test du nombre d’éléments dans la liste : renvoie le nombre d’éléments dans la liste
successeur élément rien
On peut distinguer, de manière générale, deux grands types de listes selon leur organi-
sation en1 mémoire : 4 5 6 2 3

– les tableaux,
– les listesrien
chaı̂nées. prédécesseur

Figure 3 : Sous la forme d’une liste chaînée (ici doublement) : accès séquentiel
Tableau L’accès à un élément se fait à l’aide d’un index qui représente l’emplacement de
exclusivement
l’élément dans la structure (fig 5.1). Cette structure de données a déjà été présentée 5.1.2.
élément

1 0 4 1 5 2 6 3 2 4 3 5 6 ..... N
indice

Figure
Figure 4 :–Sous
5.1 la forme
Tableau. d’un direct
Accès tableauà(ici
unvecteur) : accès
élément direct
à l’aide depossible
son indice

c Olivier Roux, Guillaume Moreau, Morgan Magnin


! page 

École Centrale de Nantes, — Algorithmique et programmation — 26


Chapitre 5. Les structures de données 5.3. Structures de données particulières

Listes chaı̂nées La taille d’une liste chaı̂née n’a pas de limite autre que celle de la mémoire
disponible. Chaque élément peut pointer, suivant le type de liste chaı̂née, vers un ou plusieurs
éléments de la liste. Ainsi, pour augmenter la taille d’une liste chaı̂née, il suffit de créer un
nouvel élément et de faire pointer certains éléments, déjà présents au sein de la liste, vers le
nouvel élément.
Il existe deux grands types de liste chaı̂née :
– les listes simplement chaı̂nées : chaque élément dispose d’un pointeur sur l’élément
suivant (ou successeur) de la liste. Le parcours se fait dans un seul sens ;
– les listes doublement chaı̂nées : chaque élément dispose de deux pointeurs, respective-
ment sur l’élément suivant (ou successeur) et sur l’élément précédent (ou prédécesseur).
Le parcours peut alors se faire dans deux sens, mutuellement opposés : de successeur
en successeur, ou de prédécesseur en prédécesseur (fig 5.2).

Les listes simplement chaı̂nées se définissent comme :


type
listeSimplementChainee : Enregistrement

type valeur, // de type simple ou composé


listeSimplementChainee suivant, // pointe sur l’élément suivant
Fin_enregistrement

Par exemple pour une liste d’entier :


type
t_listeEntier : Enregistrement
entier valeur,
t_listeEntier suivant, // pointe sur l’élément suivant
Fin_enregistrement
EI2 “Méthodes logicielles (MELOG)”

Remarque : Le champ suivant du dernier élément d’une liste simplement chaı̂née pointe sur
l’élément NULL.
Exemple d’une liste (non ordonnée) d’entiers

successeur élément rien

1 4 5 6 2 3

rien prédécesseur

Figure 35.2
Figure : Sous la forme
– Liste d’une liste chaı̂née.
doublement chaînée (ici doublement)
Accès : accès
séquentiel séquentiel
à un élément.
exclusivement

Une liste d’entier doublement chaı̂née


élément se définit comme :
1 0 4 1 5 2 6 3 2 4 3 5 6 ..... N
indice

Figure 4 : Sous la forme d’un tableau (ici vecteur) : accès direct possible

c Olivier Roux, Guillaume Moreau, Morgan Magnin


! page 

École Centrale de Nantes, — Algorithmique et programmation — 27


5.3. Structures de données particulières Chapitre 5. Les structures de données

type
t_listeEntier : Enregistrement
entier valeur,
t_listeEntier suivant, // pointe sur l’élément suivant
t_listeEntier précédent, // pointe sur l’élément précédent
Fin_enregistrement

Remarque : Le champ suivant du dernier élément et le champ précédent du premier élément


d’une liste doublement chaı̂née pointent sur l’élément NULL.
Une liste chaı̂née est une structure de donnée récursive car elle se rappelle elle-même
dans sa définition (la récursivité sera vue plus en détail à la section 10.1).

Pile
Une pile est une structure de données fondée sur le principe ”dernier arrivé, premier
sorti” (ou LIFO pour Last In, First Out), ce qui veut dire que les derniers éléments ajoutés
à la pile seront les premiers à être récupérés.
Les primitives communément utilisées pour manipuler des piles sont :
– Empiler : ajoute un élément sur la pile (push)
– Dépiler : enlève un élément de la pile et le renvoie (Pop)
– Test de pile vide : renvoie vrai si la pile est vide, faux sinon.
– Nombre d’éléments de la pile : renvoie le nombre d’éléments dans la pile.

Figure 5.3 – Illustration de l’utilisation d’une file.

File
Une file est une structure de données basée sur le principe ”Premier arrivé, premier
servi !” en anglais FIFO (First In, First Out), ce qui veut dire que les premiers éléments
ajoutés à la file seront les premiers à être récupérés. Le fonctionnement ressemble à une file
d’attente : les premières personnes à arriver sont les premières personnes à sortir de la file.
Les primitives communément utilisées pour manipuler des files sont :
– Ajouter un élément dans la file
– Renvoyer le prochain élément de la file, et le retirer de la file
– Test de file vide : renvoie vrai si la file est vide, faux sinon

École Centrale de Nantes, — Algorithmique et programmation — 28


Chapitre 5. Les structures de données 5.3. Structures de données particulières

– Test du nombre d’éléments dans la file : renvoie le nombre d’éléments dans la file

5.3.2 Structures Arborescentes


Les arbres constituent une façon de concevoir les liens un peu plus complexes que des
liens de séquentialité, et qui peuvent exister entre les différents éléments considérés. Dans
la vie courante, on connaı̂t l’arbre généalogique, qui permet de mettre l’accent sur les liens
générationnels entre les membres d’une famille. Dans le monde de l’informatique, l’orga-
nisation des fichiers et répertoires (ou dossiers) dans les systèmes d’exploitation est aussi
une arborescence. De même, évidemment, la relation d’héritage des classes d’objets se prête
bien à une organisation en arbre. Nous verrons aussi que ce genre de structure facilite la
programmation de l’évaluation d’une expression mathématique.

Définition et vocabulaire
Un arbre est un ensemble fini d’éléments appelés nœuds liés par une relation de parenté
respectant les propriétés suivantes :
– il existe un unique élément n’ayant pas de père, appelé la racine ;
– à l’exception de la racine, tout élément a exactement un père ;
– tout élément est un descendant de la racine.

Figure 5.4 – Un exemple d’arbre

Un peu de vocabulaire :
– Un élément de l’arbre est appelé nœud
– Un nœud sans fils est appelé feuille
– La profondeur d’un nœud est son nombre d’ascendants + 1 (la racine est à la profon-
deur 1)

École Centrale de Nantes, — Algorithmique et programmation — 29


5.3. Structures de données particulières Chapitre 5. Les structures de données

– La hauteur d’un arbre est sa profondeur maximale (l’arbre vide a une hauteur de 0 ;
l’arbre réduit à sa racine a une profondeur de 1)
– Le nombre de nœuds d’un arbre est sa taille
– Un nœud et ses descendants forment un sous-arbre (la relation de parenté est respec-
tée)
– Un arbre n-aire a au plus n descendants

Les arbres sont des structures de données bien adaptées pour certains types de problèmes
comme :
– l’organisation des ‘répertoires’ d’un ordinateur
– la représentation de la relation d’héritage
– la représentation du personnel d’une entreprise (organigramme)
– l’évaluation d’une expression mathématique
– ...

Parfois, la structure de l’arbre est suffisante pour modéliser un problème, mais le plus
souvent on adjoint à chaque nœud une ‘étiquette’ ou ‘valeur’.
Par exemple, dans le cadre de l’évaluation d’une expression mathématique, chaque nœud
peut avoir pour étiquette soit un nombre, soit un opérateur. La structure de l’arbre donne
un ordre pour effectuer les opérations (et elle peut ainsi dispenser d’un parenthésage).

Figure 5.5 – Représentation de l’expression mathématique sin(x − yz)

Arbres binaires
Comme indiqué précédemment, un arbre n-aire est tel que chaque nœud a au plus n fils.
Dans la suite du document, nous nous intéresserons principalement aux arbres binaires dans
lesquels chaque nœud a alors au plus 2 fils : un ‘fils gauche’ et un ‘fils droit’.

École Centrale de Nantes, — Algorithmique et programmation — 30


Chapitre 5. Les structures de données 5.3. Structures de données particulières

Un arbre binaire se définit comme :


type
arbreBinaire : Enregistrement

type valeur, // de type simple ou composé


arbreBinaire filsGauche, // pointe sur le fils gauche du nœud
courant
arbreBinaire filsDroit, // pointe sur le fils droit du nœud
courant
Fin_enregistrement

Un arbre binaire dont chaque noeud contient un caractère se définit comme :


type
t_arbreCar : Enregistrement
caractères valeur,
t_arbreCar filsGauche, // pointe sur le fils gauche du nœud
courant
t_arbreCar filsDroit, // pointe sur le fils droit du nœud courant
Fin_enregistrement

Remarque : Si un nœud d’un arbre n’a pas de fils gauche ou pas de fils droit le champs
filsGauche et/ou fils droit du nœud en question pointe sur l’élément NULL.
Comme les listes chaı̂nées, les arbres sont des structures de données récursives car ils se
rappellent eux-même dans leur définition (la récursivité sera vue plus en détail à la section
10.1).

École Centrale de Nantes, — Algorithmique et programmation — 31


Chapitre 6

Les structures algorithmiques

6.1 Les instructions simples


6.1.1 Les commentaires
Les commentaires permettent de rendre l’algorithme plus lisible et compréhensible. Ils
doivent être nombreux et pertinents dans le cas où la méthode utilisée est complexe. Dans
l’algorithme, ils commencent par deux barres obliques successives et s’étendent jusqu’à la
fin de la ligne. Il est aussi possible d’écrire des commentaires sur plusieurs lignes en utilisant
/* et */ pour délimiter le commentaire.
Exemple :
// Ceci est un commentaire
/* Ceci est un commentaire
sur plusieurs lignes */

6.1.2 Les instructions d’affectation


cible ← source // se lit : cible prend pour valeur source
La cible de l’affectation, peut être de type simple quelconque : numérique, logique, caractère.
C’est une variable. La source exprime la valeur, de même type que la cible, qui doit lui être
donnée. Elle peut être sous la forme d’une valeur simple, d’une constante, d’une expression
ou d’une variable.
Exemples :
s ← sin(a + 3.*y) - y/(z - 1.41) // s est une variable de type réel
reponse ← ’O’ // reponse est de type caractère
continuer ← vrai // continuer est de type booléen (ou logique)
n ← n+2 // la nouvelle valeur de n est égale à sa valeur précédente plus 2

Pour des variables s1 et s2 de types composés :


– si s1 et s2 sont deux tableaux de même type, l’affectation s1← s2 signifie que chaque
élément de s1 prend pour valeur celle de l’élément de s2 de même indice,
– si s1 et s2 sont deux enregistrements de même type, l’affectation s1← s2 signifie que
chaque champ de s1 prend pour valeur celle du champ correspondant de s2.

École Centrale de Nantes, — Algorithmique et programmation — 32


Chapitre 6. Les structures algorithmiques 6.2. Les structures algorithmiques

6.1.3 Les lectures, écritures


Une lecture correspond à l’affectation dans une variable d’une valeur obtenue à partir
d’un support externe. Cette lecture se fait par défaut au clavier, mais on peut aussi préciser
un nom de fichier ou une autre source.
Exemples :
Avec A une variable de type matrice, dim un entier et B un vecteur :
{dim,A,B}← lire () // lire dim, A et B au clavier
{dim,A,B}← lire ("data.txt") // on précise le support si c’est utile

Une écriture correspond à l’affichage ou l’inscription de la valeur d’une variable sur un


support externe. Cette écriture se fait par défaut à l’écran, mais on peut aussi préciser une
autre destination.
Exemples :
écrire (A,B) // afficher la matrice A et le vecteur B à l’écran
écrire ("la matrice n’est pas inversible") // afficher un texte à l’écran
écrire (support ; A,B) // on précise le support si c’est utile

L’indication du support sera précisé seulement si c’est utile, par exemple pour
distinguer différents fichiers utilisés.
Quand on lit ou on écrit une variable de type composé, ne pas développer
élément par élément. Cela alourdirait inutilement l’algorithme.
Exemple :
Avec un vecteur listeArticles de type t_VectArticles (défini p. 24), l’instruction :
listeArticles ← lire ()
désigne la lecture de tous les éléments de type t_Article du vecteur listeArticles,
champ par champ. On ne détaille pas davantage. On peut ajouter toute précision utile en
commentaire, par exemple le nombre d’éléments à lire :
listeArticles ← lire () // lecture des nbArt composantes de listeArticles

6.1.4 Les séquences d’instructions


Exceptionnellement, plusieurs instructions peuvent être écrites sur une même ligne (no-
tamment lors d’initialisations), elles sont alors séparées par un point virgule.
Exemple :
X ← 0 ; Y ← 0 // initialisation des coordonnées du point courant

6.2 Les structures algorithmiques


Les structures algorithmiques sont contrôlées par des expressions logiques (prédicats)
pouvant prendre les valeurs vrai ou faux (cf. section 5.2.3 p. 25 pour des exemples). Ces
prédicats seront notés P, P1,... dans la suite.
Le corps des structures est composé de séquences d’instructions et/ou de struc-
tures telles que définies dans ces pages. Ces séquences seront notées instructions,

École Centrale de Nantes, — Algorithmique et programmation — 33


6.2. Les structures algorithmiques Chapitre 6. Les structures algorithmiques

instructions 1,....

6.2.1 Les structures de choix


Ces structures correspondent aux cas où le traitement à effectuer est différent selon le
résultat de certains tests, résultats de l’évaluation de conditions telles que vues précédem-
ment.

 Structure de choix simple :


si ( P ) alors
| instructions // à effectuer dans le cas où P est vrai
fin si

 Structure de choix alternatif :


si ( P ) alors
| instructions 1 // à effectuer dans le cas où P est vrai
sinon
| instructions 2 // à effectuer dans le cas où P est faux
fin si

 Structure de choix multiples :


choix selon :
| P1 : instructions 1 // à effectuer dans le cas où P1 est vrai
| P2 : instructions 2 // à effectuer dans le cas où P2 est vrai, P1 étant
faux
| ...
| PN : instructions N // à effectuer dans le cas oú PN est vrai, les Pi
précédents étant faux
|
| autre : instructions N+1 // à effectuer par défaut, aucun des Pi
n’étant vrai
fin choix selon

Seule une des suites d’instructions sera réalisée : celle correspondant au premier Pi vrai,
ou la dernière si aucun des Pi n’est vrai.

6.2.2 Les structures itératives


Dans le cas où une séquence d’instructions doit être effectuée un certain nombre de
fois (connu ou non), la répétition de cette suite est exprimée à l’aide d’une structure itéra-
tive. Selon le type de répétition (ou d’itération), il convient d’utiliser la structure itérative
appropriée.

École Centrale de Nantes, — Algorithmique et programmation — 34


Chapitre 6. Les structures algorithmiques 6.2. Les structures algorithmiques

 Structure tant que . . . faire :


C’est la structure itérative la plus générale. Si, à la suite des initialisations, P est faux,
alors instructions 2 n’est pas effectuée.
Exemple :
instructions 1 // initialisations, notamment de la valeur de P
tant que P faire
| instructions 2 // instructions 2 sera effectuée tant que P sera vrai
| // la valeur de P est nécessairement réévaluée dans
instructions 2
fin tant que

 Structure pour ... faire :


C’est la structure itérative à utiliser quand on sait exactement combien de fois
instructions doit être effectuée.
Vecteur :
pour tout élément a de V faire [indice i]
| instructions // instructions sera effectuée n fois
| // n étant le nombre d’éléments du vecteur
| // on peut faire référence à l’élément ‘a’
| // ou à son indice (si précisé) dans les instructions
fin pour

Matrice :
pour tout élément a de M faire [indices i,j]
| instructions // instructions sera effectuée n*m fois
| // n*m étant le nombre d’éléments de la matrice
| // on peut faire référence à l’élément ‘a’
| // ou à ses indices (si précisés) dans les instructions
fin pour

École Centrale de Nantes, — Algorithmique et programmation — 35


6.2. Les structures algorithmiques Chapitre 6. Les structures algorithmiques

Itération explicite :
pour i ← iDeb à iFin [pas iPas] faire
iFin-iDeb+1
| instructions // instructions sera effectuée iPas fois
fin pour

Remarques :
– si le pas est égal à 1, il n’est pas précisé.
Exemple :
pour i ← iDeb à iFin faire
| instructions // instructions sera effectuée (iFin-iDeb+1) fois
fin pour

– si on n’a pas besoin des indices des éléments dans les itérations sur les vecteurs ou les
matrices, on ne les note généralement pas.
– l’itération explicite peut permettre d’itérer sur un vecteur ou une matrice également,
en manipulant explicitement les indices.
Exemple :
pour i ← 0 à nbColonnes-1 faire
| pour j ← 0 à nbLignes-1 faire

| | instructions // ces instructions seront effectuées


| | // (nbLignes*nbColonnes) fois
| fin pour

fin pour

École Centrale de Nantes, — Algorithmique et programmation — 36


Chapitre 7

Analyse descendante - Fonctions

7.1 première analyse - Fonction principale


Face à un problème, plusieurs questions se posent :
– Quelles sont les données de ce problème ?
– Quels résultats sont attendus ?
– Comment obtenir ces résultats ?
L’étude de ces questions conduit à écrire la fonction principale qui résoudra ce problème,
suivant le plan suivant :

fonction principale
problème : ... // pour dire ce que détermine la fonction, à partir de quelles données
constantes
... // pour définir des valeurs particulières
types
... // pour définir des types composés utiles pour représenter des données
ou résultats de la fonction

algorithme
variables
... // pour représenter les données et résultats
début
| // obtenir les données
| ...
| // traiter les données
| ...
| // donner les résultats
| ...
fin

Remarque : la longueur entre les délimiteurs début et fin, ne doit pas excéder
une page (format A4 sans saut de page ! ! !) pour des questions de lisibilité et de
compréhension.

École Centrale de Nantes, — Algorithmique et programmation — 37


7.1. première analyse - Fonction principale Chapitre 7. Analyse descendante - Fonctions

Si l’algorithme d’une fonction ne tient pas sur une page, cela signifie très probablement
que cette dernière doit être découpée en sous-fonctions.
L’écriture des différentes parties de l’algorithme, en particulier la partie traiter les
données qui est en général la plus complexe, nécessite une phase d’analyse globale du
problème qui conduit à un découpage de ce problème en différents sous-problèmes. La réso-
lution de ces différents sous-problèmes doit pouvoir être étudiée de façon indépendante,
au niveau d’analyse suivant (cf. section 7.2, p. 40).

Exemple 1 :
Deux joueurs lancent le même nombre de dés chacun son tour. On veut savoir lequel totalise
le plus grand nombre avec les valeurs de ses dés.

fonction principale
problème : Étant données les valeurs des dés des 2 joueurs, déterminer lequel obtient la
plus grande somme des valeurs de ses dés.
type
t_VectEnt : vecteur d’entiers
algorithme
variables
entier nbD // nombre de dés lancés
t_VectEnt D1, D2 // valeurs des dés des deux joueurs
entiers score1, score2 // scores des deux joueurs
début
| // obtenir nbD et les valeurs D1 et D2 des dés de chaque joueur
| {nbD,D1,D2} ← lire ()
| // traiter les données
| // calcul de score1 avec D1
| ...
| // calcul de score2 avec D2
| ...
| // donner les résultats
| choix selon :
| | (score1 > score2) : écrire (‘le joueur 1 a gagné’)
| | (score1 < score2) : écrire (‘le joueur 2 a gagné’)
| | autre : écrire (‘les joueurs sont ex-equo’)
| fin choix selon
fin

École Centrale de Nantes, — Algorithmique et programmation — 38


Chapitre 7. Analyse descendante - Fonctions 7.1. première analyse - Fonction principale

Exemple 2 :
Soit une liste d’articles donnés avec leurs noms (libellés), références et prix unitaires. Trier
cette liste en ordre croissant de références et indiquer les prix minimum et maximum de
l’ensemble de ces articles.
fonction principale
problème : Étant donnée une liste d’articles avec le nom, la référence et le prix unitaire,
les redonner triés en ordre croissant de références. Donner aussi les valeurs minimum et
maximum des prix.
types
t_Chaine : vecteur de caractères
t_Article : Enregistrement
entier : reference,
t_Chaine : libelle,
réel : prix
Fin_enregistrement
t_VectArticles : vecteur de t_Article
algorithme
variables
entier nbArt // nombre d’articles de la liste
t_VectArticles listeArticles, listeTriee // liste initiale, liste triée
entiers prixMin, prixMax // prix minimum et maximum des articles
début
| // obtenir le nombre d’articles nbArt et la liste de ces articles
listeArticles
| {nbArt,listeArticles}← lire ()
| // traiter les données
| // tri des articles suivant le champ reference
| ...
| // recherche du min et du max des valeurs du champ prix
| ...
| // donner les résultats
| écrire (nbArt, listeTriee, prixMin, prixMax)
fin

Remarque : Les algorithmes n’ont pas toujours besoin d’être aussi formels. Ici, on pourrait
se contenter d’écrire :

fonction principale
problème : Étant donnée une liste d’articles avec le nom, la référence et le prix unitaire,
les redonner triés en ordre croissant de références. Donner aussi les valeurs minimum et
maximum des prix.

École Centrale de Nantes, — Algorithmique et programmation — 39


7.2. Analyse des sous-problèmes - Fonctions Chapitre 7. Analyse descendante - Fonctions

types
t_Chaine : vecteur de caractères
t_Article : Enregistrement
entier : reference,
t_Chaine : libelle,
réel : prix
Fin_enregistrement
t_VectArticles : vecteur de t_Article

algorithme

début
On lit et range les articles dans un t VectArticles. On trie ce vecteur suivant le champ de la
référence et on cherche les valeurs minimum et maximum du champ du prix. Finalement, on
écrit le nombre d’articles, le vecteur trié, le prix minimum et le prix maximum des articles.
fin

7.2 Analyse des sous-problèmes - Fonctions


Chaque sous-problème est caractérisé par :
– des entrées qui sont des données du problème initial ou des résultats intermédiaires,
– des sorties qui seront des résultats du problème initial ou des résultats intermédiaires,
– la façon d’obtenir ces résultats.

Un sous-problème sera résolu par une fonction définie par :


– son nom (première lettre en minuscule par convention),
– sa spécification :
. liste des paramètres : entrées du sous-problème données avec leurs types, noms et
rôles,
. liste des résultats : sorties du sous-problème données aussi avec leurs types, noms
et rôles,
– son algorithme qui décrit la façon d’obtenir les résultats.

L’écriture de l’algorithme de la fonction nécessite l’analyse du sous-problème qui


peut à nouveau être découpé en différents sous-problèmes à traiter à un niveau d’analyse
inférieur. L’algorithme de la fonction fera alors appel à d’autres fonctions résolvant ces
nouveaux sous-problèmes, et ainsi de suite, en descendant dans l’analyse autant que
nécessaire.

Le rôle des fonctions dans le principe de l’analyse descendante est en premier


lieu le moyen d’écrire des algorithmes courts et clairs, mettant bien en évidence
les étapes successives de la résolution d’un problème.

École Centrale de Nantes, — Algorithmique et programmation — 40


Chapitre 7. Analyse descendante - Fonctions 7.2. Analyse des sous-problèmes - Fonctions

En particulier, une fonction permet de regrouper des instructions qui sont


utilisées plusieurs fois et d’y faire appel à chaque fois que nécessaire, en l’adaptant au
contexte, comme cela sera illustré dans l’exemple 1 qui suit.
On présentera une fonction suivant le plan suivant :
fonction nomFonction
problème : ... // pour expliquer ce que fait la fonction
spécification :
fonction : {liste des résultats}← nomFonction(liste des paramètres)
paramètres : liste des paramètres donnés avec leurs types, noms et rôles
résultats : liste des résultats donnés avec leurs types, noms et rôles

algorithme

variables locales
... // pour déclarer les variables de la fonction
début
| // corps de la fonction
| ...
| // renvoi des résultats
| renvoyer liste des résultats
fin

Rappel :

La longueur d’un algorithme, entre les délimiteurs début et fin, ne doit pas
excéder une page (format A4 sans saut de page ! ! !).

Remarques :
– Il est important pour la compréhension de bien faire apparaı̂tre le rôle des pa-
ramètres et résultats. Le choix du nom est important pour cela et toute
précision utile devra être donnée en commentaire.
– Une fonction peut ne comporter aucun paramètre, par exemple si son rôle est de saisir
des valeurs au clavier.
– Une fonction peut avoir une liste de résultats vide, par exemple si son rôle est d’afficher
des valeurs à l’écran.
– Les paramètres d’une fonction peuvent faire partie des résultats.

École Centrale de Nantes, — Algorithmique et programmation — 41


7.2. Analyse des sous-problèmes - Fonctions Chapitre 7. Analyse descendante - Fonctions

Exemple 1 :

fonction sommeVectEnt

problème : Calculer la somme des composantes d’un vecteur d’entiers.

spécification :
fonction : somme ← sommeVectEnt(v,n)
paramètres : t VectEnt v // vecteur d’entiers (type défini précédemment)
entier n // longueur du vecteur
résultats : entier somme // somme des composantes du vecteur
algorithme

variables locales
entier e
début
| // initialisation
| somme ← 0
| // on ajoute à somme les valeurs des n composantes de v
| pour tout élément e de v faire
| | somme ← somme + e
| fin pour
| // renvoi des résultats
| renvoyer somme
fin

École Centrale de Nantes, — Algorithmique et programmation — 42


Chapitre 7. Analyse descendante - Fonctions 7.2. Analyse des sous-problèmes - Fonctions

Exemple 2 :

fonction trierListeArticles

problème : Ranger dans un nouveau vecteur les éléments d’un vecteur d’articles, en
les triant suivant l’ordre croissant du champ reference.

spécification :
fonction : listeTriee ← trierListeArticles(nbArt,listeArticles)
paramètres : entier nbArt
t VectArticles listeArticles
résultats : t VectArticles listeTriee

algorithme

variables locales
entier min // indice de l’article de plus petite référence
t_Article unArticle // intermédiaire pour échanger deux articles

début
| // initialisation
| listeTriee ← listeArticles
| // tri des articles
| pour i ← 1 à NbArt-1 faire
| | // recherche de l’indice de l’article de plus petite reference
| | min ←i
| | pour j ← i+1 à nbArt faire
| | | si ( listeTriee[j].reference < listeTriee[min].reference )
alors
| | | | min ← j
| | | fin si
| | fin pour
| | // permutation éventuelle des articles d’indices i et min
| | si ( min 6= i ) alors
| | | // Utilisation de la variable temporaire unArticle
| | | unArticle ← listeTriee[i]
| | | listeTriee[i] ← listeTriee[min]
| | | listeTriee[min] ← unArticle
| | fin si
| fin pour
| // renvoi des résultats
| renvoyer listeTriee
fin

Remarques : Le paramètre listeArticles pourrait aussi devenir un résultat si l’on souhaite


modifier cette liste sans en créer une nouvelle. Dans ce cas le résultat listeTriee est

École Centrale de Nantes, — Algorithmique et programmation — 43


7.2. Analyse des sous-problèmes - Fonctions Chapitre 7. Analyse descendante - Fonctions

remplacé par listeArticle dans la partie spécifications et dans l’algorithme.

École Centrale de Nantes, — Algorithmique et programmation — 44


Chapitre 7. Analyse descendante - Fonctions 7.3. Appel d’une fonction - Passage des arguments / paramètres

fonction prixMinMax

problème : Chercher les valeurs minimum et maximum des prix des articles

spécification :
fonction : {prixMin,prixMax} ← prixMinMax(nbArt,listeArticles)
paramètres : entier nbArt
t VectArticles listeArticles
résultats : réels prixMin, prixMax
algorithme

variable locale
entiers iMin,iMax // indices des articles de prix minimum et maximum

début
| // recherche des prix min et max, iMin et iMax sont les indices des
articles correspondants
| iMin ← 1 ; iMax ← 1
| prixMin ← listeArticles[1].prix ; prixMax ← prixMin
| pour i ← 2 à nbArt faire
| | si ( listeArticles[i].prix < prixMin ) alors
| | | iMin← i ; prixMin ← listeArticles[i].prix
| | fin si
| | si ( listeArticles[i].prix > prixMax ) alors
| | | iMax← i ; prixMax ← listeArticles[i].prix
| | fin si
| fin pour
| // renvoi des résultats
| prixMin, prixMax
fin

7.3 Appel d’une fonction - Passage des arguments / para-


mètres
Pour résoudre un sous-problème, il faut faire appel à la fonction le résolvant. Les échanges
entre la fonction appelante et la fonction appelée se font par :
– identification des arguments de la fonction appelante (qui sont des variables de la
fonction appelante), et des paramètres correspondants de la fonction appelée,
– récupération des résultats renvoyés par la fonction appelée.
Les arguments et paramètres associés doivent coı̈ncider en nombre, en ordre
et en types.
Exemple 1 :
Dans l’algorithme de la fonction principale, l’étape traiter les données peut alors être
réalisée par l’appel à la fonction sommeVectEnt et l’affectation aux variables score1 et

École Centrale de Nantes, — Algorithmique et programmation — 45


7.3. Appel d’une fonction - Passage des arguments / paramètres Chapitre 7. Analyse descendante - Fonctions

score2 :
algorithme
variables
entier nbD // nombre de dés lancés
t_VectEnt D1, D2 // valeurs des dés des deux joueurs
entiers score1, score2 // scores des deux joueurs
début
| // obtenir les données
| {nbD,D1,D2}← lire ()
| // traiter les données
| score1 ← sommeVectEnt(D1,nbD)
| score2 ← sommeVectEnt(D2,nbD)
| // donner les résultats
| choix selon :
| | (score1 > score2) : écrire (‘le joueur 1 a gagné’)
| | (score1 < score2) : écrire (‘le joueur 2 a gagné’)
| | autre : écrire (‘les joueurs sont ex-equo’)
| fin choix selon
fin

Exemple 2 :
L’étape traiter les données est réalisée par l’appel aux fonctions trierListeArticles et
prixMinMax :

algorithme
variables
entier nbArt // nombre d’articles de la liste
t_VectArticles listeArticles, listeTriee // liste initiale, liste triée
entiers prixMin, prixMax // prix minimum et maximum des articles
début
| // obtenir les données
| {nA,listeArticles}← lire ()
| // traiter les données
| listeTriee ← trierListeArticles(nA,listeArticles)
| {prixMin,prixMax}← prixMinMax(nA,listeArticles)
| // donner les résultats
| écrire (nA, listeTriee, prixMin, prixMax)
fin

Remarque : pour la liste d’articles, on pourrait n’utiliser que la variable listeArticles en


remplaçant la valeurs de la variable par le résultat de la fonction trierListesArticles :
listeArticles ← trierListeArticles(nA,listeArticles)

École Centrale de Nantes, — Algorithmique et programmation — 46


Chapitre 8

Algorithmes de traitement de files

8.1 Définition
Un ensemble d’éléments a une structure de file non vide si on peut définir :
– le premier élément de la file,
– un procédé permettant d’obtenir l’élément suivant de la file,
– un procédé permettant de détecter la fin de la file.
Exemples de file :
– file de caractères : une phrase, terminée par un point, dont les caractères sont lus un
à un,
– file de nombres : l’ensemble des notes d’un étudiant (stockées dans un fichier),
– ...

8.2 Traitement de files de longueur inconnue


Le nombre d’éléments d’une file n’est pas connu à priori mais l’élément final de la file
(généralement une butée) est supposé reconnaissable.
Dès qu’un élément de la file a été obtenu, il faut tester si cet élément n’est pas l’élément
final, avant de le traiter. Après avoir obtenu le premier élément de la file (qui peut être
l’élément final), tant que l’élément obtenu n’est pas l’élément final, on le traite, puis on
obtient l’élément suivant. Le schéma général de l’algorithme est donc le suivant :

École Centrale de Nantes, — Algorithmique et programmation — 47


8.3. Traitement de file de longueur connue Chapitre 8. Algorithmes de traitement de files

Schéma d’algorithme 1
début
| // initialisations des données
| .........
| // obtenir le premier élément de la file
| .........
| // itérations sur les éléments de la file à traiter
| tant que « élément non final » faire
| | // traitement de l’élément courant
| | .........
| | // obtenir l’élément suivant
| | .........
| fin tant que
| // traitement éventuel de l’élément final
| .........
| // traitement éventuel lié à la fin de file
| .........
fin

8.3 Traitement de file de longueur connue


Le nombre d’éléments de la file étant connu, pour chaque élément, il faut l’obtenir puis
le traiter. Le schéma d’algorithme est dans ce cas :
Schéma d’algorithme 2
début
| // initialisations des données : notamment obtenir le nombre d’éléments
de la file
| .........
| nbElt ← lire ()
| .........
| // nbElt 6= 0 si la file est non vide
| // itérations sur les éléments de la file à traiter si la file est non
vide
| pour i<-1 à nbElt faire
| | // obtenir l’élément courant
| | .........
| | // traitement de l’élément courant
| | .........
| fin pour
| // traitement éventuel lié au dernier élément et/ou à la fin de file
| .........
fin

Exemple :

École Centrale de Nantes, — Algorithmique et programmation — 48


Chapitre 8. Algorithmes de traitement de files 8.4. Exemple d’algorithme de traitement de files

Voir l’algorithme de la fonction sommeVectEnt, de l’exemple 1 de la section 7.2, p. 40.

8.4 Exemple d’algorithme de traitement de files


fonction exemple
problème : Une file de nombres entiers est lue élément par élément au clavier. Cette file
est terminée par une butée, nécessairement différente de 0, également lue au clavier. Ecrire
au fur et à mesure la file en éliminant les 0 de la file, la butée ne sera pas écrite. Donner le
nombre total d’éléments de la file et le nombre de 0 éliminés.

algorithme
variables locales
entiers butee, // valeur de l’élément final de la file
nbCour // élément courant de la file
nb0 // nombre d’éléments de la file égaux à 0
nbElt // nombre total d’éléments de la file
début
| // La file est de longueur inconnue : le schéma d’algorithme à suivre
| // est le premier schéma (tant que ... faire).
| // initialisations
| butee ← lire () // on suppose que la butée est non nulle
| nb0 ← 0 ; nbElt ← 0
| // obtenir le premier élément de la file
| nbCour ← lire ()
| // itérations sur les éléments de la file à traiter
| tant que nbCour 6= butee faire
| | // traitement de l’élément courant
| | si ( nbCour6=0 ) alors
| | | écrire (nbCour)
| | sinon
| | | nb0 ← nb0+1
| | fin si
| | nbElt ← nbElt+1
| | // obtenir l’élément suivant
| | nbCour ← lire ()
| fin tant que
| écrire (nb0,nbElt)
fin

École Centrale de Nantes, — Algorithmique et programmation — 49


Chapitre 9

Les algorithmes de tri

L’opération de tri de structures de données ordonnées est cruciale pour optimiser les
traitements sur ces structures. Le problème de tri est un problème classique et de nombreux
algorithmes ont vu le jour pour y répondre de la manière la plus efficace possible. Nous
présentons trois algorithmes de tri simples : tri par sélection, tri par insertion et tri à bulle.
Il existe des algorithmes plus efficaces, mais ils utilisent des techniques ou des structures de
données qui dépassent la portée de ce cours.
Pour en savoir plus sur les algorithmes de tri vous pouvez commencer par la page Wiki-
pédia http://fr.wikipedia.org/wiki/Algorithme_de_tri qui vous donnera un aperçu
des autres algorithmes de tri.
Dans les descriptions des tris ci-après, nous considérons un tableau de n éléments numé-
rotés de 0 à n-1.

École Centrale de Nantes, — Algorithmique et programmation — 50


Chapitre 9. Les algorithmes de tri 9.1. Tri par sélection

9.1 Tri par sélection


Principe :
– on recherche le plus petit élément du tableau,
– on l’échange avec le premier élément du tableau,
– on recommence en considérant la partie non triée du tableau.

fonction TriSelection

spécification :
fonction : tableau ← TriSelection(tableau, n)
paramètres : vecteur entier tableau
entier n
résultats : vecteur entier tableau
algorithme

variables locales
entier min // indice de la valeur minimale

début
| pour i ← 0 à n-2 faire
| | // recherche de l’indice de la valeur minimale
| | min ← i
| | pour j ← i+1 à n-1 faire
| | | si ( tableau[j] < tableau[min] ) alors
| | | | min ← j
| | | fin si
| | fin pour
| | // permutation éventuelle des valeurs d’indices i et min
| | si ( min 6= i ) alors
| | | échanger tableau[i] et tableau[min]
| | fin si
| fin pour
| renvoyer t ableau
fin

École Centrale de Nantes, — Algorithmique et programmation — 51


9.2. Tri par insertion Chapitre 9. Les algorithmes de tri

9.2 Tri par insertion


Principe (analogie du jeu de carte) :
– on considère que le premier élément est un sous-tableau de taille 1 déjà trié,
– on insère l’élément suivant à la bonne position dans la partie du tableau déjà triée,
– on recommence en insérant l’élément suivant.

fonction TriInsertion

spécification :
fonction : tableau ← TriInsertion(tableau, n)
paramètres : vecteur entier tableau
entier n
résultats : vecteur entier tableau
algorithme

variables locales
entier tmp // intermédiaire pour échanger deux valeurs

début
| pour i ← 1 à n-1 faire
| | tmp ← tableau[i]
| | j ← i
| | tant que j > 0 et tableau[j-1] > tmp faire
| | | tableau[j] ← tableau[j-1]
| | | j ← j-1
| | fin tant que
| | tableau[j] ← tmp
| fin pour
| renvoyer t ableau
fin

École Centrale de Nantes, — Algorithmique et programmation — 52


Chapitre 9. Les algorithmes de tri 9.3. Tri à bulles

9.3 Tri à bulles


Principe :
– on considère un sous-tableau de 2 éléments que l’on échange s’ils ne sont pas dans
l’ordre,
– on décale le sous-tableau d’une case pour parcourir tout le tableau, et on recommence
au début du tableau jusqu’à ce que tout les éléments soient dans l’ordre,

fonction TriABulle

spécification :
fonction : tableau ← TriABulles(tableau, n)
paramètres : vecteur entier tableau
entier n
résultats : vecteur entier tableau
algorithme

variables locales
entier tmp // intermédiaire pour échanger deux valeurs
booléen aucunEchange // Vrai s’il n’y a pas eu d’échange

début
| // initialisation
| aucunEchange ← faux
| tant que auncunEchange = faux faire
| | aucunEchange ← vrai
| | pour j ← 0 à n-2 faire
| | | si ( tableau[j] > tableau[j+1] ) alors
| | | | échanger tableau[j] et tableau[j+1]
| | | | auncunEchange ← faux
| | fin pour
| fin tant que
| renvoyer t ableau
fin

École Centrale de Nantes, — Algorithmique et programmation — 53


Chapitre 10

Récursivité, manipulation de listes


et d’arbres

10.1 Récursivité
La récursivité consiste à définir ou construire une chose en fonction d’elle même... Il
existe des définitions récursives de fonctions et de structures de données.
L’algorithme d’une fonction est récursif s’il contient des appels à cette même fonction.
Étudions par exemple la définition récursive de la fonction factorielle.
fonction factorielle

spécification :
fonction : res ← factorielle( n)
paramètres : entier n
résultats : entier res
algorithme

début
| si ( n = 0 ) alors
| | res ← 1
| sinon
| | res ← n × factorielle(n-1)
| fin si
| renvoyer res
fin

École Centrale de Nantes, — Algorithmique et programmation — 54


Chapitre 10. Récursivité, manipulation de listes et d’arbres 10.2. Manipulation de liste

L’exécution de factorielle(4) donnerait :

appel initial 4× factorielle(3)


2e appel 3× factorielle(2)
3e appel 2× factorielle(1)
4e appel 1× factorielle(0)
5e appel 1

retour 1
retour 2
retour 6
retour 24

A chaque appel de la fonction factorielle, l’exécution est mise en attente jusqu’à l’appel
de la fonction factorielle qui réalise la condition terminale (ici lorsque n = 0). Puis les
opérations en attente sont dépilées jusqu’à donner le résultat de l’appel initial.
Remarques :
– Il faut toujours prévoir une condition terminale.
– Les appels de la fonction ne doivent pas être infinis.

Il existe d’autres exemples de fonctions récursives, citons en particulier :


– les tours de Hanoı̈
– l’algorithme de la division euclidiennes de deux entiers
– le calcul des nombres de la suite de Fibonacci
– le calcul de la longueur d’une liste chaı̂née,
– l’algorithme de recherche d’un élément dans une liste chaı̂née,
– le mécanisme d’insertion d’un nouvel élément en tête d’une liste chaı̂née,
– le calcul de la hauteur d’un arbre,
– le calcul du nombre de nœuds d’un arbre,
– le calcul du nombre de feuilles d’un arbre,
– ...
Nous avons déjà défini deux structures de données particulières qui sont récursives, les
listes chaı̂nées (5.2) et les arbres (5.4).

10.2 Manipulation de liste


Nous allons étudier différents algorithmes de manipulation de listes sur des listes d’en-
tiers.

École Centrale de Nantes, — Algorithmique et programmation — 55


10.2. Manipulation de liste Chapitre 10. Récursivité, manipulation de listes et d’arbres

10.2.1 Parcours d’une liste


fonction afficherListe

spécification :
fonction : Ø← afficherListe(l)
paramètres : t listeEntier l
résultats : Ø
algorithme

variables locales
t_listeEntier élément // élément courant qui va permettre de parcourir
la liste élément par élément

début
| élément ← l
| // boucle tant le dernier élément de la liste n’est pas atteint
| tant que élément existe faire
| | écrire (élément.valeur)
| | élément ← élément.suivant
| fin tant que
fin

10.2.2 Ajout d’un élément à la fin d’une liste


L’ajout d’un élément dans la liste va nécessiter l’appel à une sous-fonction pour créer un
élément à partir d’un entier.
fonction créerElément

spécification :
fonction : nouvElement ← créerElément(val)
paramètres : entier val
résultats : t listeEntier nouvElement
algorithme

début
| nouvElement.valeur ← val
| // Le nouvel élément n’a pas de suivant
| nouvElement.suivant ← rien
| renvoyer nouvElement
fin

École Centrale de Nantes, — Algorithmique et programmation — 56


Chapitre 10. Récursivité, manipulation de listes et d’arbres 10.2. Manipulation de liste

fonction ajoutElement

spécification :
fonction : nouvL ← ajoutElement(l, val)
paramètres : t listeEntier l
entier val // valeur du nouvel élément
résultats : t listeEntier nouvL
algorithme

variables locales
t_listeEntier élément // élément courant qui va permettre de parcourir
la liste élément par élément

début
| nouvL ← l
| élément ← l
| // boucle tant le dernier élément de la liste n’est pas atteint
| tant que élément existe faire
| | écrire (élément.valeur)
| | élément ← élément.suivant
| fin tant que
| élément.suivant ← créerElément(val)
| renvoyer nouvL
fin

10.2.3 Suppression d’un élément dans la liste


Pour supprimer un élément de la liste, il va falloir parcourir la liste. Si il y a plusieurs
éléments de la valeur cherchée, seul le premier sera supprimé de la liste.
fonction supprimeElement

spécification :
fonction : nouvL ← supprimeElement(l, val)
paramètres : t listeEntier l
entier val //valeur de l’élément à supprimer
résultats : t listeEntier nouvL

École Centrale de Nantes, — Algorithmique et programmation — 57


10.2. Manipulation de liste Chapitre 10. Récursivité, manipulation de listes et d’arbres

algorithme

variables locales
t_listeEntier element // élément courant qui va permettre de parcourir
la liste élément par élément
t_listeEntier precedent
booléen trouvé

début
| si ( l non vide ) alors
| | trouvé ← faux
| | nouvL ← l
| | element ← l
| | // cas particulier : le premier élément doit ^
etre supprimé
| | si ( element.valeur = val ) alors
| | | nouvL ← nouvL.suivant
| | sinon
| | | precedent ← element
| | | element ← element.suivant
| | | // boucle tant le dernier élément de la liste n’est pas atteint
| | | tant que element existe et non trouvé faire
| | | | si ( element.valeur = val ) alors
| | | | | precedent.suivant ← element.suivant
| | | | | trouvé ← vrai
| | | | sinon
| | | | | precedent ← element
| | | | | element ← element.suivant
| | | | fin si
| | | fin tant que
| | fin si
| sinon
| | nouvL ← rien
| fin si
| renvoyer nouvL
fin

10.2.4 Calcul de la longueur d’une liste


Cette fonction, ici définie pour une liste d’entiers, permet de calculer récursivement la
longueur de la liste.

École Centrale de Nantes, — Algorithmique et programmation — 58


Chapitre 10. Récursivité, manipulation de listes et d’arbres 10.2. Manipulation de liste

fonction longueurListe

spécification :
fonction : longueur ← longueurListe(l)
paramètres : t listeEntier l
résultats : entier longueur
algorithme

début
| si ( l.suivant n’existe pas ) alors
| | longueur ← 1
| sinon
| | longueur ← 1+longueurListe(l.suivant)
| fin si
| renvoyer longueur
fin

10.2.5 Recherche d’un élément dans une liste

La fonction chercheElement renvoie vrai ou faux selon si l’entier cherché est présent
ou non dans la liste parcourue.
fonction chercheElement

spécification :
fonction : longueur ← chercheElement(l, val)
paramètres : t listeEntier l
entier val
résultats : booléen presence
algorithme

début
| si ( l.valeur = val ) alors
| | presence ← vrai
| sinon
| | si ( l.suivant n’existe pas ) alors
| | | presence ← faux
| | sinon
| | | presence ← chercherElement(l.suivant, val)
| | fin si
| fin si
| renvoyer presence
fin

École Centrale de Nantes, — Algorithmique et programmation — 59


10.3. Manipulation d’un arbre Chapitre 10. Récursivité, manipulation de listes et d’arbres

10.3 Manipulation d’un arbre


Cette section présente quelques algorithmes récursifs de manipulation d’arbres.

10.3.1 Parcours d’un arbre


Avec une structure linéaire, nous ne nous posons pas la question de savoir dans quel
ordre examiner les éléments. C’est la relation de séquentialité qui s’applique naturellement.
Avec les arbres, il n’est plus possible de simplement prendre les éléments ‘dans l’ordre’.
Pour être exact, il existe plusieurs ordres possibles dans lesquels considérer les éléments.
Une première façon de parcourir l’arbre consiste à considérer les éléments suivant un
mode appelé ‘en largeur d’abord’, c’est-à-dire prendre tous les éléments d’un même niveau de
profondeur, puis passer au niveau suivant. C’est souvent ce que l’on fait quand on construit
un arbre généalogique. cette méthode peut être illustrée par le schéma suivant :

Figure 10.1 – Exemple de parcours en largeur d’abord

Une autre façon de procéder consiste à explorer l’arbre suivant un mode appelé ‘en
profondeur d’abord’. On descend le long d’une branche jusqu’au bout, c’est-à-dire jusqu’à
ce qu’on ait atteint une feuille, puis on passe à la branche suivante, et ainsi de suite à
l’identique.

Figure 10.2 – Exemple de parcours en profondeur d’abord

Lorsqu’on parcourt un arbre binaire, on peut encore jouer sur l’ordre dans lequel on
prend un nœud par rapport à ses deux fils. On distingue l’ordre préfixe, l’ordre infixe et
l’ordre postfixe qui peuvent être représentés de la façon suivante :

École Centrale de Nantes, — Algorithmique et programmation — 60


II.3 Les arbres binaires
ur d’un arbre binaire : dans un arbre l’ordre
Chapitre 10. Récursivité, manipulation de listes et d’arbres 10.3. Manipulation d’un arbre

it être défini. infixe

prefixe

nfixe, ou postfixe postfixe

Figure 10.3 – Ordre de parcours d’un arbre binaire


e ces traitements :
Dans l’ordre préfixe, on prend d’abord le père puis le fils gauche puis le fils droit.
descendants gauche, descendants droit
Dans l’ordre infixe, on prend d’abord le fils gauche puis le père puis le fils droit.

auche, nœud, descendants droit


Dans l’ordre postfixe, on prend d’abord le fils gauche puis le fils droit, puis le père.

auche, arbre
descendants droit, nœud
Nous allons maintenant examiner le parcours dans l’ordre préfixe. Prenons le cas d’un
binaire dont chaque noeud contient un caractère (comme défini 5.3.2) :
type
t_arbreCar : Enregistrement
n page 
caractères valeur,
t_arbreCar filsGauche, // pointe sur le fils gauche du nœud
courant
t_arbreCar filsDroit, // pointe sur le fils droit du nœud courant
Fin_enregistrement

Nous pouvons définir la méthode lecturePréfixe sur cet arbre :


fonction lecturePréfixe

spécification :
fonction : Ø← lecturePréfixe(arbre)
paramètres : t arbreCar arbre
résultats : Ø

École Centrale de Nantes, — Algorithmique et programmation — 61


10.3. Manipulation d’un arbre Chapitre 10. Récursivité, manipulation de listes et d’arbres

algorithme

début
| écrire (arbre.valeur)
| si ( arbre.filsGauche existe ) alors
| | lecturePréfixe(arbre.filsGauche)
| fin si
| si ( arbre.filsDroit existe ) alors
| | lecturePréfixe(arbre.filsDroit)
| fin si
fin

On construit l’arbre suivant et on lui applique la méthode lecturePrefixe.

Le résultat sera :

A
B
C
D
E
F

On a donc bien effectivement parcouru l’arbre selon l’ordre préfixe qui consiste à afficher
d’abord un antécédent (A) devant ses descendants du sous arbre gauche (B, C, D), suivis
des descendants du sous arbre droit (E, F).

École Centrale de Nantes, — Algorithmique et programmation — 62


Chapitre 10. Récursivité, manipulation de listes et d’arbres 10.3. Manipulation d’un arbre

Figure 10.4 – Ordre de parcours préfixe d’un arbre binaire

0. La méthode est appliquée à l’arbre A : lecturePrefixe(A)


1. La méthode affiche ‘A’ et appelle lecturePrefixe(B) (lecturePrefixe(A) s’interrompt).
2 La méthode affiche ‘B’ et appelle lecturePrefixe(C) (lecturePrefixe(B) s’interrompt).
3. C est une feuille donc l’exécution se termine après lecture de ‘C’.
4. L’exécution reprend alors dans la méthode ayant appelé C, c’est-à-dire dans B.
5. De là, la méthode va dans le sous-arbre D.
6. La méthode affiche ‘D’ et l’exécution de lecturePrefixe(D) se termine.
7. L’exécution de lecturePrefixe(B) reprend pour se terminer aussitôt.
8. L’exécution de lecturePrefixe(A) reprend et appelle lecturePrefixe(E).
9. La méthode affiche ‘E’ et appelle lecturePrefixe(F).
10. La méthode affiche ‘F et l’exécution de lecturePrefixe(F) se termine.
11. L’exécution de lecturePrefixe(E) reprend pour se terminer aussitôt, faute de fils droit.
12. L’exécution de lecturePrefixe(A) reprend pour enfin se terminer.

La lecture que nous venons de décrire est dite ‘préfixe’ car elle lit la valeur de l’étiquette
avant d’évaluer le fils-gauche puis le fils-droit.
Si on évalue le fils gauche puis l’étiquette puis le fils droit, la lecture est dite ‘infixe’.
Si enfin la lecture de l’étiquette est faite après les fils gauche et droit, elle est dite
‘postfixe’.

Selon le problème que l’arbre modélise, une des ces trois lectures sera souvent mieux
adaptée que les deux autres.

École Centrale de Nantes, — Algorithmique et programmation — 63


10.3. Manipulation d’un arbre Chapitre 10. Récursivité, manipulation de listes et d’arbres

Figure 10.5 – Lecture préfixe : a b c d e f — Lecture infixe : c b d a f e — Lecture postfixe :


cdbfea

10.3.2 Calcul de la hauteur d’un arbre

fonction hauteur
spécification :
fonction : h ← hauteur(arbre)
paramètres : t arbreCar arbre
résultats : entier h
algorithme

variables locales
entier g
entier d

début
| si ( arbre.filsGauche existe ) alors
| | g ←hauteur(arbre.filsGauche)
| sinon
| | g ← 0
| fin si
| si ( arbre.filsDroit existe ) alors
| | d ←hauteur(arbre.filsDroit)
| sinon
| | d ← 0
| fin si
| h ← 1+max(g,d)
| renvoyer h
fin

École Centrale de Nantes, — Algorithmique et programmation — 64


Chapitre 10. Récursivité, manipulation de listes et d’arbres 10.3. Manipulation d’un arbre

Remarque : La fonction max renvoie le maximum de deux entiers passés en paramètres.

École Centrale de Nantes, — Algorithmique et programmation — 65

Vous aimerez peut-être aussi