Download - Ancien syllabus du cours

Transcript

Programmation

Thierry Massart

Nadjet Benseba Emmanuel Dall’Olio Martin De WulfGilles Geeraerts Joel Goossens Christophe Macq

Olivier MarkowitchPatrick Meyer

Universite Libre de BruxellesFacultes des Sciences et des Sciences Appliquees

Premiere annee de Bachelier en InformatiquePremiere annee de Bachelier en Sciences de l’ingenieur

Mai 2009

ii

iii

Avant-propos

Le but de ce cours et des exercices associes est de donner aux etudiants une connais-sance active de l’algorithmique de base et de la programmation structuree. Le for-malisme utilise pour decrire les programmes developpes dans ce cours est le langagede programmation C++.

Notons que, bien qu’une certaine partie du langage C++ soit etudiee, ce cours n’estpas simplement un manuel de programmation C++. De nombreuses parties de C++n’y sont d’ailleurs pas etudiees.

Les concepts de base C++ utilises dans le cadre de ce cours pour ecrire un algo-rithme ou un programme C++ resolvant un probleme algorithmique donne sont peunombreux.

Le cours theorique sert a expliquer ces concepts.

Il ne vous est pas seulement demande de comprendre ces notions de base, maisegalement de les assimiler et de pouvoir les utiliser pour concevoir et dans unemoindre mesure analyser, des programmes C++ resolvant les problemes poses.

La resolution de nombreux exercices ainsi que les travaux a e!ectuer vous permet-tront d’arriver a une connaissance de l’algorithmique beaucoup plus profonde, requiseau terme de ce cours.

Cette matiere doit donc etre pratiquee de facon constante a la fois pendant et endehors des seances d’exercices sur papier et sur machine. Une etude entierementcomprise dans une courte periode de temps ne peut pas aboutir au but final requis.

A propos des exercices A la fin de certains chapitres de ce syllabus, on trouverades exercices qui seront exploites lors des seances de travaux pratiques.

Ces exercices ont pour but de familiariser les etudiants avec les concepts theoriquesetudies au cours. Ils ont egalement ete penses dans le but de permettre aux etudiantsd’acquerir de bonnes pratiques de programmation ; de celles qui permettent d’ecriredes programmes e"caces et clairs.

iv

Tout au long du texte, certains exercices, auxquels les etudiants devraient accorderune importance toute particuliere, ont ete selectionnes. Ils sont signales par la miseen page suivante :

! b: 10 min. !

Enonce de l’exercice

Ces exercices ont ete juges particulierement importants, car ils resument a eux seulstout un pan de matiere precedemment etudie. Leur niveau a egalement ete juge apeu pres egal a celui des examens ou interrogations.

Nous conseillons donc fortement aux etudiants d’essayer de resoudre ces exercices<< en condition d’examen >> : cela leur permettra de jauger aisement leur niveau.Le temps normalement necessaire a la resolution du probleme est egalement donnea titre indicatif (il est signale par b). Les etudiants devraient etre capable de re-soudre les problemes poses endeans ce laps de temps. Dans le cas contraire, nousleur conseillons fortement de se mettre a jour, de consulter un assistant, la guidance,le professeur. . .

v

vi

Table des matieres

1 Introduction 1

2 Concepts de base 3

2.1 Notions fondamentales . . . . . . . . . . . . . . . . . . . . . . . . . . 3

2.2 Variables et constantes . . . . . . . . . . . . . . . . . . . . . . . . . . 4

2.3 Lecture et ecriture . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.4 Type des entites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

2.5 Les expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

3 Les instructions 23

3.1 Structuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

3.2 L’instruction simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

3.3 L’instruction composee ou bloc . . . . . . . . . . . . . . . . . . . . . 24

3.4 Les conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

3.5 L’instruction if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

3.6 L’instruction switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

3.7 L’instruction while . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

vii

viii TABLE DES MATIERES

3.8 L’instruction do . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

3.9 La boucle for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

3.10 Blocs et declaration d’entites . . . . . . . . . . . . . . . . . . . . . . . 44

3.11 Decomposition d’un probleme . . . . . . . . . . . . . . . . . . . . . . 48

3.12 Exercices : Instructions elementaires . . . . . . . . . . . . . . . . . . . 53

3.13 Exercices : Structures iteratives . . . . . . . . . . . . . . . . . . . . . 58

3.13.1 Boucles simples . . . . . . . . . . . . . . . . . . . . . . . . . . 58

3.13.2 Boucles imbriquees . . . . . . . . . . . . . . . . . . . . . . . . 65

4 Les fonctions 67

4.1 Les fonctions qui retournent une valeur . . . . . . . . . . . . . . . . . 68

4.2 Les fonctions qui ne retournent pas une valeur . . . . . . . . . . . . . 71

4.3 Execution du module appele . . . . . . . . . . . . . . . . . . . . . . . 72

4.4 Exercices : Les Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . 82

5 Definition de types 89

5.1 Les types enumeres . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90

5.2 Les pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

5.3 Les references . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

5.4 Les tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

5.5 typedef . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

5.6 Definition generale des tableaux . . . . . . . . . . . . . . . . . . . . . 102

6 Exemples d’utilisation de tableaux 105

TABLE DES MATIERES ix

6.1 La lecture et l’ecriture d’un vecteur . . . . . . . . . . . . . . . . . . . 105

6.2 La recherche du minimum . . . . . . . . . . . . . . . . . . . . . . . . 108

6.3 L’image miroir d’un vecteur . . . . . . . . . . . . . . . . . . . . . . . 109

6.4 Le glissement circulaire gauche . . . . . . . . . . . . . . . . . . . . . . 112

6.5 La recherche d’un element . . . . . . . . . . . . . . . . . . . . . . . . 113

6.6 Le produit de matrices . . . . . . . . . . . . . . . . . . . . . . . . . . 117

6.7 Exercices : Les tableaux . . . . . . . . . . . . . . . . . . . . . . . . . 120

6.7.1 Tableaux Simples . . . . . . . . . . . . . . . . . . . . . . . . . 120

6.7.2 Tableaux a Deux Dimensions : les Matrices . . . . . . . . . . . 122

6.8 Exercices : Manipulations de caracteres . . . . . . . . . . . . . . . . . 123

7 Decoupe d’un programme C++ 127

7.1 Definitions et declarations . . . . . . . . . . . . . . . . . . . . . . . . 127

7.2 Decoupe d’un programme . . . . . . . . . . . . . . . . . . . . . . . . 129

8 E!cacite d’un algorithme 133

8.1 Criteres de choix d’un algorithme . . . . . . . . . . . . . . . . . . . . 133

8.2 Exemple : la recherche du minimum . . . . . . . . . . . . . . . . . . . 135

8.3 Autre exemple : la recherche d’un element . . . . . . . . . . . . . . . 139

8.4 Le grand O . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140

8.5 Calcul du grand O . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144

8.5.1 Classes de complexite . . . . . . . . . . . . . . . . . . . . . . . 144

8.5.2 Regles de calcul . . . . . . . . . . . . . . . . . . . . . . . . . . 144

x TABLE DES MATIERES

8.5.3 Application des regles de calcul . . . . . . . . . . . . . . . . . 146

9 Exemples de complexite 149

9.1 La recherche dichotomique . . . . . . . . . . . . . . . . . . . . . . . . 149

9.2 La recherche dans un texte . . . . . . . . . . . . . . . . . . . . . . . . 152

9.3 Exercices : Complexite . . . . . . . . . . . . . . . . . . . . . . . . . . 155

10 Les tris 161

10.1 Le tri par selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163

10.1.1 Complexite du tri par selection . . . . . . . . . . . . . . . . . 165

10.2 Le tri par insertion . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165

10.2.1 Amelioration du tri par insertion . . . . . . . . . . . . . . . . 166

10.3 Le tri par echange (Bulle) . . . . . . . . . . . . . . . . . . . . . . . . 167

10.4 Le tri par enumeration (comptage) . . . . . . . . . . . . . . . . . . . 169

10.5 Autres methodes de tri . . . . . . . . . . . . . . . . . . . . . . . . . . 172

10.6 Exercices : Les tris de tableaux . . . . . . . . . . . . . . . . . . . . . 172

10.6.1 Tris simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172

10.6.2 Tri par enumeration . . . . . . . . . . . . . . . . . . . . . . . 173

10.6.3 Tri Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173

10.6.4 Tri Cocktail Shaker . . . . . . . . . . . . . . . . . . . . . . . . 175

10.6.5 Tri par ventilation . . . . . . . . . . . . . . . . . . . . . . . . 176

11 Verification d’un algorithme 177

11.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177

TABLE DES MATIERES xi

11.2 Rappels de logique . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178

11.2.1 Le calcul des propositions . . . . . . . . . . . . . . . . . . . . 178

11.2.2 La logique des predicats . . . . . . . . . . . . . . . . . . . . . 180

11.3 Preuve partielle et totale . . . . . . . . . . . . . . . . . . . . . . . . . 182

11.4 Exemples de preuves d’algorithmes . . . . . . . . . . . . . . . . . . . 185

11.5 Exercices : Les invariants . . . . . . . . . . . . . . . . . . . . . . . . . 188

12 Allocation dynamique de memoire 195

12.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195

12.2 New et delete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196

12.3 Notions de pile et tas . . . . . . . . . . . . . . . . . . . . . . . . . . . 199

12.3.1 Pile (Stack) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199

12.3.2 Tas (Heap) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199

12.4 Gestion de la memoire lors de l’execution d’un programme . . . . . . 200

12.4.1 Exemple d’execution d’un programme . . . . . . . . . . . . . . 201

12.5 Erreurs frequemment commises . . . . . . . . . . . . . . . . . . . . . 203

13 Les classes 207

13.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207

13.2 Un exemple de classe et d’objets sans methodes . . . . . . . . . . . . 208

13.3 Un exemple de classe avec methodes . . . . . . . . . . . . . . . . . . 208

13.4 Allocation dynamique d’objets . . . . . . . . . . . . . . . . . . . . . . 211

13.5 Canevas de declaration d’une classe . . . . . . . . . . . . . . . . . . . 212

xii TABLE DES MATIERES

13.6 Le pointeur this . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213

13.7 Exercices : Les pointeurs et les classes . . . . . . . . . . . . . . . . . . 216

13.7.1 Les pointeurs . . . . . . . . . . . . . . . . . . . . . . . . . . . 216

13.7.2 Les classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217

14 Les listes chaınees 221

14.1 Notion de liste chaınee . . . . . . . . . . . . . . . . . . . . . . . . . . 221

14.2 Liste simplement chaınee . . . . . . . . . . . . . . . . . . . . . . . . . 222

14.3 Liste doublement chaınee circulaire . . . . . . . . . . . . . . . . . . . 230

14.4 Exercices : Les listes . . . . . . . . . . . . . . . . . . . . . . . . . . . 236

14.4.1 Les listes lineaires simples : fonctions elementaires . . . . . . . 236

14.4.2 Listes circulaires avec element bidon au debut . . . . . . . . . 238

14.4.3 Listes lineaires simples triees . . . . . . . . . . . . . . . . . . . 238

14.4.4 Listes implementees dans des vecteurs . . . . . . . . . . . . . 238

14.4.5 Listes doublement liees . . . . . . . . . . . . . . . . . . . . . . 240

14.4.6 Intersection et di!erence symetrique . . . . . . . . . . . . . . 242

14.4.7 Liste triee scindee en plusieurs sous-listes . . . . . . . . . . . . 242

14.4.8 Listes cycliques . . . . . . . . . . . . . . . . . . . . . . . . . . 244

14.4.9 Parcours de 3 listes triees . . . . . . . . . . . . . . . . . . . . 245

14.4.10Listes a di!erents niveaux . . . . . . . . . . . . . . . . . . . . 246

Chapitre 1

Introduction

Le but de l’informatique est d’e!ectuer du traitement de l’information.

L’information est un ensemble d’elements qui ont une signification dans le contexteetudie. Les donnees d’un probleme sont representees par l’ensemble des informationsutilisees pour resoudre ce probleme en vue d’obtenir les resultats escomptes.

Un algorithme n’est pas concu uniquement pour obtenir un resultat pour une donneebien precise, mais constitue une methode qui permet, a partir de n’importe quelleautre donnee du meme type, d’obtenir le resultat correspondant.

Un probleme algorithmique peut etre vu comme une fonction f dont l’image detoute donnee d de l’ensemble D des donnees possibles est un resultat r (= f(d)) del’ensemble R des resultats possibles.

L’algorithme qui resout le probleme est la methode calculant f(d) en un temps fini ,pour toute donnee d valide.

Notons qu’il n’existe pas d’algorithme pour tout probleme (Il est donc interessant dedeterminer theoriquement l’existence d’un algorithme pour un probleme donne avantd’essayer de l’ecrire e!ectivement). A cote de cela, il existe souvent de nombreuxalgorithmes pour resoudre un probleme donne ; leurs qualites propres permettrontde les di!erencier.

Un algorithme doit etre implemente sur un ordinateur. Celui-ci ne possede jamaisqu’un nombre limite de memoire de stockage d’information dont la precision estlimitee. De ce fait pour resoudre certains problemes qui en theorie pourraient requerirun calcul trop long, ou une precision ou un stockage d’information trop importants,

1

2 CHAPITRE 1. INTRODUCTION

des algorithmes ne donnant qu’une valeur approchee du resultat doivent etre concus.

Un algorithme ecrit dans un langage de programmation “comprehensible” par unordinateur est un programme. C++ est un langage de programmation incluant denombreux concepts evolues de programmation. C’est pourquoi nous avons choisi del’utiliser dans ce cours.

Ecrire un petit programme ou a fortiori developper un gros logiciel demande unedemarche en plusieurs etapes, appelee processus de developpement d’un programme,qui peut etre divise en plusieurs phases (partiellement) successives.

1. Analyse de ce qui est requis et specification,

2. Conception,

3. Implementation,

4. Tests et installation,

5. Exploitation et maintenance.

Chaque phase produit des resultats ecrits soit sous forme de rapport : specificationde ce qui est requis (cahier de charges), manuel utilisateur, description du fonc-tionnement, description succinte ou detaillee de l’algorithme, programme dumentcommente, historique des modifications, ....

Ce cours n’abordera par tous ces points en details ; il est oriente vers l’algorithmiqueet les techniques de programmation, et n’etudiera pas, par exemple, comment realiserune interface graphique pour un programme.

Chapitre 2

Concepts de base

2.1 Notions fondamentales

Au chapitre precedent, nous avons decrit l’algorithme comme etant une methode decalcul d’une fonction f . Cette description donne une vue exterieure d’un algorithme.

Si nous regardons l’interieur d’un algorithme, la notion fondamentale est celled’action.

Une action a une duree finie et un e!et bien precis. Chaque action utilise une ou plu-sieurs entites ; les changements d’etats de ces entites sont les “marques” laisseespar les actions.

Pour pouvoir decrire cela, il faut que chaque action soit descriptible dans un langage.

– Soit une action est simple ; on peut alors la decrire sous forme d’une instruction.– Soit elle est trop complexe pour etre decrite en une instruction, elle doit alors

etre decomposee en plusieurs instructions : le groupement de ces instructions estappele processus. Si chaque instruction d’un processus se suit l’une l’autre dansson deroulement, le processus est dit sequentiel.Un algorithme ou programme est l’instruction, (au sens large) qui decrit un pro-cessus complet.L’ordre d’ecriture de l’ensemble des instructions d’un algorithme ou d’un pro-gramme est generalement di!erent de l’ordre d’execution de celles-ci.

3

4 CHAPITRE 2. CONCEPTS DE BASE

2.2 Variables et constantes

Les instructions elementaires d’un algorithme manipulent di!erents types d’entitescomme l’illustre le code C++ de la figure 2.1.

int main(){

int x,y,z;

x = 3; // 1y = 12; // 2z = x; // 3x = 2 * x; // 4y = 2 * y; // 5z = x * y; // 6

}

Fig. 2.1 – Exemple de manipulation de variables et de constantes

ou les caracteres ‘//’ marquent que ce qui suit jusqu’a la fin de la ligne, est uncommentaire uniquement destine au programmeur et inutile au bon fonctionnementde l’algorithme.

Le programme manipule deux types d’entites : des constantes (3,12,2) et des va-riables (x, y et z). On peut assigner des valeurs a ces variables. L’assignation estl’action la plus fondamentale lors de l’execution d’un algorithme (programme). Unevariable peut-etre comparee a une boıte (ou a un tiroir). Chaque variable a, a unmoment donne une et une seule valeur. On peut connaıtre sa valeur autant de foisque l’on veut. On peut egalement ecraser son ancienne valeur et en mettre une autrea la place ; l’ancienne valeur est alors perdue.

L’operation d’assignation d’une valeur w dans une variable v sera denotee

v = w

ou = est l’operateur d’assignation et w une expression. Dans l’exemple precedent,l’expression w est tantot une constante (3,12, . . . ) tantot une variable (x,y, . . . ),tantot un calcul (2*y, x*y, . . . ).

Ainsi les variables manipulees sont schematisees par la figure 2.2 ou le ’ ?’ specifieune valeur indefinie.

Chaque variable a un nom (identificateur) qui permet de la manipuler. Ce nom estchoisi par la personne qui ecrit l’algorithme et il est preferable que ce nom rappelle

2.2. VARIABLES ET CONSTANTES 5

? x ? y ? z

Fig. 2.2 – Shema des variables x, y et z

le sens de son utilisation. (Exemple : Tva, comme nom d’une variable qui contiendraune valeur de TVA).

Chaque variable a egalement un type qui permet de connaıtre le format du contenude la variable. Dans l’exemple, la valeur de chaque variable sera de type entier (int).Plus precisement chaque variable peut contenir une valeur parmi un ensemble finide valeurs possibles. Il existe un petit nombre de types predefinis : int, double,char, bool, . . . . Nous en reparlerons plus loin.

Sauf initialisation implicite, la valeur d’une variable est initialement indefinie. L’as-signation modifie cette valeur.

Dans l’exemple precedent, le programme s’execute en 7 etapes. La figure 2.3 donnela valeur de chaque variable apres chaque etape.

etape 0 ? x ? y ? z(initialement)

etape 1 3 x ? y ? z

etape 2 3 x 12 y ? z

etape 3 3 x 12 y 3 z

etape 4 6 x 12 y 3 z

etape 5 6 x 24 y 3 z

etape 6 6 x 24 y 144 z

Fig. 2.3 – Changement de valeur des variables x, y et z

Une facon plus commode de representer les changements d’etats subis par les va-riables est donnee par la representation en table d’etats. Celle-ci a une colonne re-presentant la sequence des instructions executees et une colonne par variable.

Ainsi l’exemple precedent est donne par la figure 2.4

Notons que pour identifier une entite (par exemple une variable), les majuscules etles minuscules sont vues en C++ comme des caracteres di!erents. Ainsi, int Tva,tva ; declare 2 variables entieres di!erentes. Pour eviter les erreurs, il ne faut pas

6 CHAPITRE 2. CONCEPTS DE BASE

Instruction Etatx y z

? ? ?x = 3 3 ? ?y = 12 3 12 ?z = x 3 12 3x = 2 * x 6 12 3y = 2 * y 6 24 3z = x * y 6 24 144

Fig. 2.4 – Table d’etat des variables x, y et z

declarer 2 entites ayant le meme nom a des nuances pres.

Une constante est une entite au meme titre qu’une variable qui possede un identi-ficateur, un type et une valeur. Dans l’exemple precedent 3 et 12 identifient deuxconstantes entieres ayant respectivement les valeurs 3 et 12 en base dix.

Pour permettre d’avoir des programmes plus clairs, il est possible de donner un nomsymbolique a une constante dont la valeur est precisee dans les declarations en debutde programme.

Ainsi le programme de la figure 2.5 a le meme e!et que le programme donne ala figure 2.1 mais utilise les deux constantes symboliques Valeur_Initiale_x etValeur_Initiale_y qui ont respectivement les valeurs 3 et 12.

int main(){

const int Valeur_Initiale_x = 3,Valeur_Initiale_y = 12;

int x, y, z;

x = Valeur_Initiale_x;y = Valeur_Initiale_y;z = x;x = 2 * x;y = 2 * y;z = x * y;

}

Fig. 2.5 – Exemple d’utilisation de constantes symboliques

2.3. LECTURE ET ECRITURE 7

Comme nous le verrons plus loin dans des exemples, les constantes symboliques sontgeneralement utilisees soit pour donner un nom symbolique a une constante fre-quemment utilisee dans des formules et qui a sens particulier, soit comme parametredonnant la taille maximale d’un probleme. Par exemple, on pourra avoir :

const double Pi = 3.14159, g = 9.81;const int MAX = 100;

double permettant de declarer des constantes ou variables de type reel.

Notons que deux objets di!erents ne peuvent pas avoir le meme identificateur (nom)dans un programme (plus precisement dans un meme bloc, comme nous le verronsplus loin).

Notons enfin que dans les programmes des figures 2.1 et 2.5, l’initialisation desvariables peut se faire lors de leur declaration (voir figure 2.6).

int main(){

const int Valeur_Initiale_x = 3,Valeur_Initiale_y = 12;

int x = Valeur_Initiale_x,y = Valeur_Initiale_y,z = x;

x = 2 * x;y = 2 * y;z = x * y;

}

Fig. 2.6 – Exemple d’initialisation lors de la declaration

2.3 Lecture et ecriture

Etant donne qu’un algorithme doit etre une methode de resolution d’un probleme,il doit exister une maniere de lui fournir des donnees et d’en connaıtre les resultats.Par exemple, l’utilisateur pourra communiquer avec le programme qui s’executegrace a un clavier (pour l’introduction des donnees) et a un ecran (pour l’a"chage

8 CHAPITRE 2. CONCEPTS DE BASE

des resultats). En C++ les dispositifs precis utilises pour e!ectuer ces echangesd’information entre le programme et son utilisateur sont independants de la facondont le programme est ecrit. On dit que les dispositifs d’echanges precis utilisessont transparents pour le programme. Dans le jargon informatique, on dit que leprogramme a des interactions avec le monde exterieur pour chercher et envoyer desvaleurs de et vers celui-ci.

Chercher une valeur du monde exterieur revient a avoir une operation d’assignationun peu particuliere et se fait grace a l’instruction

cin >> v;

ou v est une variable. L’e!et de cette operation est de prendre la valeur introduiteau clavier et d’assigner cette valeur a la variable v1. En C++, cin et cout (voirplus loin) sont des “stream” standard connus apres inclusion du fichier iostream.h,ou iostream dans la norme ANSI (#include <iostream.h>)

Envoyer une valeur vers le monde exterieur se fait grace a l’instruction

cout << w;

ou w est une valeur.

L’e!et de cette operation est d’a"cher la valeur de w sur l’ecran2.

Plusieurs lectures ou ecritures peuvent s’e!ectuer en une instruction. Par exemple

cin >> x >> y >> z;

lit les 3 variables x, y et z.

cout << "x + 3 = " << (x + 3) << " y = " << y << endl;

1De facon generale, on dit que l’on lit une valeur sur input (l’entree) pour l’assigner a la variablev, ou de facon plus concise, on dit que l’on lit la variable v ; selon la configuration de l’ordinateurutilise et la facon d’executer le programme, l’input peut etre par exemple le clavier, un fichier surdisque ou sur une bande magnetique, . . . .

2De facon generale on dit que l’on ecrit une valeur sur output (la sortie) ; selon la configurationde l’ordinateur utilise et la facon d’executer le programme, l’ouput peut se faire par exemple, surl’ecran, une imprimante, un fichier sur disque ou sur une bande magnetique, . . . .

2.4. TYPE DES ENTITES 9

ecrit le texte ”x + 3 = ” suivi de la valeur de l’expression x + 3, du texte ” y = ”,de la valeur de y et enfin ecrit une fin de ligne pour que l’ecriture suivante s’e!ectuesur la ligne d’apres3.

2.4 Type des entites

En C++, di!erentes sortes de valeurs peuvent etre manipulees : on parle du typed’une valeur ou d’une entite.

Un type donne determine– l’ensemble des valeurs possibles pour ce type,– l’ensemble des operations ‘elementaires’ possibles sur des valeurs de ce type et l

’e!et de ces operations.Par exemple, le type bool contient 2 valeurs

false true

qui constituent l’ensemble des valeurs possibles pour ce type. Les valeurs booleennespeuvent etre manipulees, par exemple, par les operateurs logiques ! (not), && (and),|| (or).

Il n’y a que peu de types de base di!erents :– entier (tel que int)– reel (tel que double)– booleen ou logique (bool)– caractere (tel que char)Nous verrons que de nouveaux types peuvent etre definis a partir de ces types debase.

Ainsi -7, 0, 3 sont des constantes entieres4 (de type entier), 3.14159, -4.72,1.0e-5 sont de constantes reelles5 , true et false sont des constantes booleen-nes et ’a’, ’A’, ’z’, ’Z’, ’$’, ’+’,’0’,’9’ sont des constantes caracteres. Uneconstante est egalement appelee litteral.

Remarquons ainsi qu’en C++, contrairement a ce que l’on fait en arithmetique, onfait une distinction (au niveau de leur type) par exemple entre la constante entiere 3

3L’ecriture de resultats en C++ peut etre “formattee” (voir un livre de reference a ce sujet).4En C++, toute constante commencant par un ’0’ (ex : 077) est interprete comme etant en

octal (base 8), et toute constante commencant par ’0x’ (ex : 0xff) est interprete comme etant enhexadecimal (base 16).

5En C++ , la valeur reelle 1.0 10!5 est donnee par la notation scientifique 1.0e-5.

10 CHAPITRE 2. CONCEPTS DE BASE

et le constante reelle 3.0 ; en e!et, leur stockage en memoire et les operations realiseessur ces valeurs sont di!erents.

L’ensemble des constantes entieres et reelles possibles et la precision disponible pourrepresenter les valeurs reelles dependent de la machine et de la version du C++utilise. Ainsi, INT_MIN et INT_MAX d’une part et DBL_MIN et DBL_MAX d’autre partsont des constantes symboliques donnant respectivement les plus petites et plusgrandes valeurs entieres et reelles possibles6.

Le type de chaque variable et de chaque constante symbolique utilisee dans le pro-gramme est precise lors de la declaration de celle-ci. Par exemple, les declarationssuivantes :

const double Pi = 3.14159;const bool vrai = true,

faux = false;const int Valeur_initiale = 12;int Tva, Taux = 10;bool Flag = vrai;char Lettre;double x, y;

declarent une constante symbolique reelle (d’identificateur) Pi ayant la valeur3.14159, deux constantes symboliques booleennes vrai et faux de valeur resp. trueet false, une constante symbolique entiere Valeur_initiale de valeur 12, ainsi queTva et Taux comme etant des variables entieres (Tva non initialisee et Taux initiali-see a 10), Flag comme etant une variable booleenne initialisee a vrai, Lettre unevariable caractere non initialisee et x, y deux variables reelles non initialisees.

Les operations elementaires possibles sur des valeurs d’un type sont definies grace ades operateurs.

Un operateur est une fonction ayant un ou plusieurs arguments et fournissant unresultat.

Ainsi– 3 * 7 est une expression fournie par la multiplication de la valeur 3 entiere par

la valeur 7 ; le resultat est la valeur entiere 21,

6Ces constantes sont definies dans le fichier d’entete limits.h et float.h (faire #include <limits.h > et #include < float.h > )

2.4. TYPE DES ENTITES 11

– 10.0 / 3.0 est l’expression formee par la division de la valeur reelle 10.0 par lavaleur reelle 3.0 ; le resultat est une approximation selon la precision possible dela valeur reelle 3.333 . . . ,

– 3 <= 7 est une expression formee par l’operation relationnelle inferieur ou egal ;la valeur de cette expression est vraie (true).

Notations possibles pour les operateurs

Avant de decrire les di!erents operateurs C++, examinons tout d’abord les sortesd’operateurs et les di!erentes facons de noter les operations.

En arithmetique, on classe les operations selon le nombre de leurs operands.– un operateur ayant un seul operand est dit monodique ou unaire,– un operateur ayant 2 operands est diadique ou binaire.– notons qu’il peut exister des operateurs ternaires par exemple.Une operation peut etre notee de plusieurs facons di!erentes. Ainsi, pour noterl’operation binaire formee de l’operateur ! (qui peut etre +, -, *, /, =, . . . ) etdes 2 operands x et y, on peut utiliser

1. une notation prefixee : !(x, y) ou !xyCette notation est utilisee pour le moins unaire (exemple : -3) et pour lesfonctions (exemple : cos(x)).

2. une notation postfixee, egalement appelee notation polonaise inversee : (x, y)!ou xy!

3. une notation infixee egalement appelee notation algebrique (exemple : x + y,3 * 7, . . . )

En C++ , la notation utilisee pour les operateurs binaires est la notation infixee,la notation utilisee pour les fonctions est la notation prefixee (les arguments desfonctions etant mis entre parentheses et separes par des virgules), les operateursunaires sont soit prefixes soit postfixes.

Exemples :

x = y 3 * 7 -3 cos(x) x++

12 CHAPITRE 2. CONCEPTS DE BASE

L’operateur d’assignation

L’assignation v = w est un operateur ayant l’e!et de mettre dans la variable7 v lavaleur w et ayant comme valeur et type, la valeur et le type de v apres assignation.

De facon plus generale, lors d’une assignation v = w, v doit etre une entite quipeut varier : une variable ou une expression dont le resultat denote une variable.Nous dirons que v doit etre un membre de gauche licite pour l’assignation. Ainsi six est une variable entiere, x = 3 est parfaitement licite tandis que 3 = x qui tented’assigner la valeur de x a la constante 3 n’a pas de sens.

Dans v = w, la valeur de l’expression w doit avoir le meme type que v ou doit pouvoiretre automatiquement transforme dans ce type. Les types arithmetiques (int, bool,char, double) et certains autres peuvent etre transformes automatiquement.

Donc si i est une variable entiere, i = 3.64 assigne 3 a i (3.64 est tronque), et lavaleur de cette expression vaut 3 de type entier. Cette valeur peut etre utilisee parexemple pour d’autres assignations. Ainsi j = i = 3.64 avec i et j des variablesentieres, assigne la valeur 3 a i et j. Attention si j etait reel, etant donne quei = 3.64 a la valeur 3, c’est toujours 3, converti en reel qui serait assigne a j8.

Les operateurs arithmetiques

La table 2.1 donne les operations arithmetiques les plus utilisees.

+ - * Addition (ou + unaire), soustraction (ou - unaire),multiplication

/ division (entiere si les 2 operateurs sont entiers,reelle sinon)

% reste de la division entiere++ incrementation de la valeur de l’operande de 1-- decrementation de la valeur de l’operand de 1

+= -= *= /= %= assignation composee. x != y est equivalent ax = x ! (y)

Tab. 2.1 – Operateurs arithmetiques

7Par definition, une constate ne peut etre modifiee apres sa declaration ou une valeur lui estdonnee.

8De ce fait, pour eviter toute confusion, une bonne pratique est de ne faire d’assignation multiplei = j = . . . = w que si i, j, . . . sont de meme type.

2.4. TYPE DES ENTITES 13

Valeur et type des expressions arithmetiques

1. De facon generale, le type d’une expression arithmetique est le type le plusetendu. Par exemple 3+3.2 donne 6.2 reel.

2. la division entiere donne un resultat entier tronque. Par exemple 8 / 3 a lavaleur 2,

3. ++i (incrementation prefixee) donne la valeur de i apres l’avoir incremente de1.

4. i++ (incrementation postfixee) donne l’ancienne valeur de l’operand i, i ayantete incremente de 1 par ailleurs.9

5. --i et i-- sont similaires a ++i et i++ mais ou l’on decremente de 1 au lieud’incrementer.

L’operateur sizeof

Suivant le compilateur et l’ordinateur utilise, les valeurs d’un type donne peuventetre stockees en memoire sur plus ou moins de bits. Ainsi C++ fournit l’operateurstandard sizeof admettant 2 formats sizeof(type) avec type le nom d’un type ousizeof expression donnant la taille, en octets (bytes) prenant le stockage en memoired’un objet du type ou de l’expression unaire10. Un caractere prend classiquement unoctet, un entier 2 ou 4 octets, un short int 2 octets et un long int 4 octets.

Les operateurs logiques (booleens)

La table 2.2 donne la table de verite des operateurs logiques, c’est-a-dire leur valeurpour chaque valeur d’operand possible : ainsi si x et y sont true, not x est false,x and y est true, . . .

9Plus precisemment, + + i denote i qui a ete incremente auparavant, tandis que i + + denoteune constante dont la valeur a ete initialisee a l’ancienne valeur de i, i ayant ete incremente parailleurs. Ainsi bien que ces instructions soient de pratique souvent douteuses et donc a proscrire,+ + i = 3 est permis en C++ et a comme e!et d’assigner la valeur 3 a i, tandis que i + + = 3 nel’est pas puisque i + + ne denote pas une variable et donc n’est pas un ‘membre de gauche’ ou leftvalue (Lvalue) licite pour l’assignation.

10Ayant une expression binaire (ex : 3 + 4, pour la rendre unaire, il su"t de l’entourer de paren-theses.

14 CHAPITRE 2. CONCEPTS DE BASE

x y not x x and y x or y

true true false true true

true false false false true

false true true false true

false false true false false

Tab. 2.2 – Table de verite des operateurs logiques

Les operateurs relationnels

La table 2.3 donne les operateurs relationnels utilises :

== egalite!= di!erence< inferiorite stricte<= inferiorite ou egalite> superiorite stricte>= superiorite ou egalite

Tab. 2.3 – Operateurs relationnels

L’operateur de sequencement ’,’

L’operateur de sequencement ’,’ donne une expression evaluee de gauche a droitedont la valeur et son type est la valeur et le type du membre de droite. Ainsi

i++, j = i + 3; represente une instruction qui incremente i de 1 et ensuiteassigne i + 3 a j. Cette expression vaut son membre de droite c’est-a-dire la valeurde j apres assignation. Nous verrons que l’operateur de sequencement peut etre utilepar exemple dans les instructions for.

Les operateurs de manipulation de bits

La table 2.4 donne les operations de manipulation de bits.

Le langage C++ incluant en gros le langage C, concu comme une alternative auxlangages d’assemblage, il en a herite ses operateurs.

2.4. TYPE DES ENTITES 15

~ complement bit a bits& et logique bit a bit| ou inclusif bit a bit^ ou exclusif (xor) bit a bit<< glissement gauche>> glissement droit

Tab. 2.4 – Operateurs de manipulation de bits

Expliquons les sur des exemples : les commentaires donnent la representation binairedes variables modifiees apres chaque declaration / instruction. Les variables de typeunsigned short int permettent de stocker des valeurs positives sur 16 bits.

#include <iostream>using namespace std;

int main(){

unsigned short int a = 7, b = 20, c, d, e, f, g, h;

// a == 0000 0000 0000 0111// b == 0000 0000 0001 0100

c = ~a; // c == 1111 1111 1111 1000d = a & b; // d == 0000 0000 0000 0100e = a | b; // e == 0000 0000 0001 0111f = a ^ b; // f == 0000 0000 0001 0011g = b << 5; // g == 0000 0010 1000 0000h = c >> 2; // h == 0011 1111 1111 1110

}

Notons qu’un glissement a droite d’un entier signe negatif donne un resultat nondefini en terme de bits inseres a gauche (des 0 ou des 1).

Remarques :

– Les operateurs >> et << utilises pour faire des lectures (avec cin) et des ecritures(avec cout) renvoient egalement une valeur booleenne vraie si et seulement si

16 CHAPITRE 2. CONCEPTS DE BASE

l’operation s’est deroulee correctement. Ainsi nous verrons plus loin que les ins-tructions if (cin >> i >> j) ... ou while(cin >> i >> j) ... sont sou-vent utilisees. Dans ces instructions, si i et j sont des variables entieres, celavoudra dire que si ce qui est fourni n’est pas une valeur assignable a i et j, le testne sera pas verifie11.

– C++ possede egalement la notion de fonction qui est proche de celle des ope-rateurs. Le langage possede une librairie de fonctions qui peuvent etre utilisees.Ainsi, par exemple, si i et x sont resp. une valeur entiere et reelle, apres inclusiondu fichier d’entete <math.h>,

abs(i) donne la valeur absolue de la valeur entiere i

fabs(x) donne la valeur absolue de la valeur reelle x

cos(x) donne le cosinus de x en radians

– En calcul numerique, pour tester si 2 valeurs reelles v et w sont proches, on doite!ectuer un test relatif, c’est-a-dire proceder comme suit :

a) on utilise une constante de precisionconst double eps = 1.0e-12; // par exemple

b) on teste pour savoir si les valeurs v et w sont prochesfabs(v-w) < eps * (fabs(v) + fabs(w))

Ce test ne fonctionne pas quand les valeurs a tester sont inferieures a 1.0. Dansce cas, il faut faire un test absolu :

abs(v-w) < eps– Les lois de De Morgan sont souvent utilisees pour simplifier des expressions lo-

giques

¬(a " b) = (¬a) # (¬b)

¬(a # b) = (¬a) " (¬b)

avec

¬ pour l’operateur not

" pour l’operateur or

# pour l’operateur and

2.5 Les expressions

Une expression est formee d’operateurs et d’operands ayant une valeur. La syntaxeet la facon dont les expressions sont evaluees en C++ sont regies par des regles

11En windows (resp. Unix) on utilise le caractere ctrl-z (resp. ctrl-d) pour signaler la fin desdonnees au clavier.

2.5. LES EXPRESSIONS 17

strictes.

Tout d’abord, une simple constante ou une variable peut etre vue comme une ex-pression. Ainsi si x et Tva sont des variables entieres valant respectivement 8 et 7 etFlag une variable booleenne valant false,

3 27 x Tva true Flag

sont des expressions C++ ayant respectivement la valeur

3 27 8 7 true false

Nous verrons plus loin que le resultat d’une fonction est egalement une expressionvalide.

Ayant une ou plusieurs expressions, on peut les composer grace a des operateurs etformer des expressions plus complexes. Ainsi

-4 * 6x * Tva+27

x < 4Tva == 7

3 * x * (2 + Tva) - 4 * (x - 7)Flag and x<14

i = 5.4

sont des expressions C++ valides valant respectivement

$24 83 false true 212 false 5

Notons que le type du resultat de ces expressions est ici soit entier soit booleen.

Explicitons ici les regles generales d’evaluation des expressions C++ qui per-mettent de determiner la valeur de chacune de ces expressions.

Parenthesage, priorite et associativite des operateurs

Dans les expressions compliquees, des parentheses peuvent etre utilisees pour iden-tifier proprement les operands non simples associes a chaque operateur. Ainsi

((21+36)*37)+(47+(38+58))

identifie proprement chaque element de cette expression comme le montre la figure2.7.

18 CHAPITRE 2. CONCEPTS DE BASE

Expression Operateur Operand 1 Operand 2

((21+36)*37)+(47+(38+58)) + (21+36)*37 47+(38+58)(21+36)*37 * 21+36 37

21+36 + 21 3647+(38+58) + 47 38+58

38+58 + 38 58

Fig. 2.7 – Decomposition d’une expression

L’utilisation explicite de parentheses pour entourer tous les operands non simplesd’une expression est lourde et rend la lecture de cette expression moins aisee. Ilexiste deux regles permettant de supprimer certaines parentheses d’une expression.Ces regles permettent de determiner l’endroit ou il existe des parentheses implicites.

La premiere regle repose sur la notion de priorite.

A chaque operateur est associe une priorite. En C++ on compte 18 niveaux depriorite di!erents ! Retenons simplement que– les operateurs unaires sont plus prioritaires que les operateurs binaires,– * et / sont plus prioritaires que + et - binaire,– l’assignation est un des operateurs le moins prioritaires,– l’operateur ’,’ est le moins prioritaire de tous,– les operateurs relationnels binaires sont moins prioritaires que les operateurs arith-

metiques et logiques.Regle 1 : Pour parentheser explicitement une expression ou une sous-

expression, on regarde le ou les operateurs les moins prio-ritaires, en considerant qu’une sous-expression entre paren-theses est un operand simple, et si ce n’est pas deja fait onentoure explicitement les operands non simples par des pa-rentheses.

La regle 2 repose sur la notion d’associativite. A chaque operateur est associe unattribut d’associativite gauche ou droite.

En C++ les operateurs unaires ainsi que l’assignation sont associatifs a droite, lesoperateurs binaires sont associatifs a gauche.

Si on considere a nouveau chaque sous-expression mise entre parenthese comme unoperand simple, apres application de la regle 1 il se peut que plusieurs operateursd’une expression ou d’une sous-expression aient la meme priorite (qu’ils soient lesmemes ou non ; par exemple 3 * 7 / 5 ou encore 4 - 5 + 3)

2.5. LES EXPRESSIONS 19

Regle 2 : L’associativite a droite (resp. a gauche) impose que l’on pa-renthese d’abord a droite (resp. a gauche).

Notons qu’il n’existe pas 2 operateurs di!erents ayant la meme priorite et une asso-ciativite di!erente.

Ainsi– x = y = z est equivalent a x = (y = z)– 3 * 7 / 5 est equivalent a (3 * 7) / 5– 4 - 5 + 3 est equivalent a (4 - 5) + 3L’application de ces regles permettent de parentheser explicitement les expressionsC++.

Regles de bonne pratique

– En cas de doute sur la priorite des di!erents operateurs, mettre explicitement desparentheses.

– Eviter de mettre explicitement des parentheses partout (par exemple dans lesassignations multiples ou les additions/multiplications).

Remarque :

Pour tester si x est compris entre les valeurs v1 et v2, le test if (v1 <= x <= v2)ne donnera pas le bon resultat. En fait on testera si v2 est superieur ou egal auresultat v1 <= x (c’est-a-dire 0 ou 1 suivant que le test est faux ou vrai). Le testcorrect sera donc if ( v1 <= x and x <= v2).

Ordre d’evaluation des expressions

L’ordre d’evaluation de la plupart des operateurs C++ est indefini. Par exemplerien ne precise si lors d’une simple addition, c’est d’abord l’operand de gauche quiest evalue avant celui de droite. En pratique cela signifie que suivant le compilateurutilise, le programme de la figure 2.8 peut donner des resultats di!erents.

Seuls les operateurs de sequence ’,’ , and et or logiques sont definis comme etantevalues de gauche a droite.

Ainsi

20 CHAPITRE 2. CONCEPTS DE BASE

#include <iostream>using namespace std;

int main(){

int i=0;

cout << ++i << " " << i++ << " " << i << endl;cout << (i++ - i) << endl;cout << ((++i)++ - i) << endl;

}

Fig. 2.8 – Exemple de programme utilisant des operations ambigues

x = 3 , x += 1;

met finalement x a 4 (et l’expression vaut egalement 4).

De plus, le membre de droite du and logique n’est pas evalue si le membre de gaucheest faux. De meme pour le membre de droite du or logique quand le membre degauche est vrai. C’est ce que l’on appelle l’evaluation avec court-circuit (short circuitevaluation).

Une bonne pratique est de proscrire toute expression dont la valeur est indefinie. Enparticulier, il est fortement recommande de n’utiliser les incrementations et decre-mentations que isolees a toute autre operation, exceptees celles ou l’ordre est specifiecomme avec l’operateur ’,’.

Resultat d’une expression

Si le resultat d’une expression est d’un certain type, on dit que c’est une expressionde ce type. Ainsi par exemple , i < j est une expression booleenne.

En C++, le resultat d’une expression est plus precisement une entite qui denote unevariable ou une constante (d’une certaine valeur et d’un certain type).

Ainsi si i et j sont des variables entieres initialises resp. a 3 et 4, i + j donne uneconstante entiere valant 7.

Le fait que i + j soit une constante l’empeche d’etre utilisee comme membre de

2.5. LES EXPRESSIONS 21

gauche d’une assignation (i + j = 3 n’a pas de sens et est interdit).

Par contre ++i rend comme resultat la variable i elle-meme apres incrementation.Ainsi, malgre le fait que ce genre d’instruction soit a proscrire, ++i = 12 est parfai-tement licite et assigne finalement 12 a i.

Notons que i++ denote une constante valant l’ancienne valeur de i, i ayant eteincremente par ailleurs.

22 CHAPITRE 2. CONCEPTS DE BASE

Chapitre 3

Les instructions

3.1 Structuration

Pour resoudre un probleme algorithmique non trivial, il est en general essentiel dele decomposer en sous-problemes. C++ permet de faire de la programmation struc-turee. Il permet de decouper un algorithme en suivant une decomposition donnee.

Chaque programme C++ sera decompose en modules. Un module devra resoudreun probleme ou un sous-probleme et pourra utiliser d’autres modules. Ceux-ci sontvus comme des outils pour le module qui les utilise ; il ne sait pas comment ils sontconstruits ; il sait seulement comment s’en servir.

Un probleme algorithmique pourra donc etre decompose et resolu par un ensemblede modules.

3.2 L’instruction simple

En C++, toute expression suivie d’un ’ ;’ devient une instruction simple.

Ainsi si r est une variable double,

r = 3.14;++r;r+3;

23

24 CHAPITRE 3. LES INSTRUCTIONS

forme une sequence de 3 instructions simples.

Notons que si les 2 premieres instructions modifient r et ont donc un e!et qui peutetre utile pour la suite du programme, la troisieme est totalement inutile pour leprogramme puisque le resultat du calcul n’est ni assigne a une variable ni sorti, parexemple sur le fichier de sortie.

Notons egalement que l’instruction vide ‘ ;’ existe et n’a aucun e!et. L’instructionvide peut etre utile dans un programme la ou une instruction est requise par lasyntaxe C++ et qu’aucun traitement ne doit etre realise (voir instruction for pourdes exemples).

3.3 L’instruction composee ou bloc

En C++ il est possible de regrouper une sequence d’instructions en une instructioncomposee appelee egalement bloc en entourant la sequence d’accolades ’{’ et ’}’.

Ainsi, dans un programme :

{x = 3;y = 7;z = x * y;

}

est une instruction composee qui regroupe 3 instructions simples d’assignation.

Le format general est

{sequence d’instructions}

Remarquons que la partie traitement d’un programme C++ (plus precisement de lafonction main) n’est rien d’autre qu’une instruction composee.

3.4. LES CONDITIONS 25

3.4 Les conditions

Jusqu’ici nous n’avons vu que la sequence d’instructions ou, lors de l’execution,l’instruction qui suit physiquement l’instruction courante est egalement executeeconsecutivement.

Ce mode d’execution n’est pas su"sant. Si l’on prend des exemples du quotidien, onpeut avoir ainsi :– s’il pleut, je prend mon parapluie ;– tant que le clou n’est pas enfonce, je tape avec le marteau.On remarque que dans chacun de ces exemples on a– une condition (il pleut, le clou n’est pas enfonce) ;– une action a realiser (je prend mon parapluie, je tape avec le marteau) ;– la modalite d’execution de l’action : l’action peut etre executee une fois (si la

condition est satisfaite) ou peut etre executee plusieurs fois (tant que la conditionest satisfaite).

Nous avons donc besoin– de pouvoir e!ectuer certaines actions (instructions) uniquement si une certaine

condition est remplie ;– de pouvoir repeter certaines actions tant qu’une certaine condition est satisfaite.Formellement en C++, une condition n’est rien d’autre qu’une expression booleennedont le resultat est vrai ou faux . (Exemple : si i et j sont des entiers et Flag unbooleen, i < j, Flag or x * j < 37 sont des conditions).

Si la valeur de l’expression est vraie, on dit que la condition est verifiee.

3.5 L’instruction if

L’instruction if permet de faire du traitement conditionnel.

Ainsi– if (i==20)

cout << "bravo" << endl;– if (x <= y)

cout << x << " " << y << endl;else

cout << y << " " << x << endl;permettent de ne realiser un traitement donne que sous une certaine condition(exemples de conditions : i==20, x<=y). Deux formats sont possibles suivant qu’ilfaut ou non realiser une action dans le cas ou la condition n’est pas verifiee.

26 CHAPITRE 3. LES INSTRUCTIONS

Format 1

if(condition)instruction

Format 2

if(condition)instruction1

elseinstruction2

ou instruction, instruction 1 et instruction 2 representent chacune une seule ins-truction.

Remarques :

– Dans le cas ou le traitement conditionnel a realiser est compose d’une sequenced’actions, un bloc { . . . } permet de regrouper cette sequence en une instructioncomme l’illustre l’exemple suivant qui trie 2 nombres entiers lus :

#include <iostream>using namespace std;

int main()// trie 2 nombres{

int x, y , Save;

cout << "introduisez x et y: ";cin >> x >> y;if (x >= y){

Save = x;x = y;y = Save;cout << "x et y ont ete permutes";

}else

cout << "x et y sont non modifies";

3.5. L’INSTRUCTION IF 27

cout << endl << endl << x << " "<< y << " " << endl;

}Notons bien, dans cet exemple, que les blocs ne sont jamais suivis de terminateur‘ ;’ utilise uniquement dans le cas des instructions simples.

– Quelque soit le format utilise, un if C++ forme une seule instruction meme sicelle-ci inclut d’autres instructions plus elementaires.

– Si le programmeur desire realiser une action quand une condition n’est pas verifiee,il doit nier cette condition pour la traduire enif(not (condition))

instructionLes lois de De Morgan lui permettront souvent de simplifier cette negation.

– Verifier qu’une variable booleenne Flag est vraie se traduit simplement en C++par :

if(Flag)La condition if(Flag == true) est redondante et a proscrire.

– Tester si une variable booleenne Flag est fausse se fait comme suit :

if (not Flag)

et est preferre a :

if(Flag == false)

En e!et, la premiere version est plus concise et moins couteuse en temps d’execu-tion que la seconde.

Organigramme de l’instruction if

Plusieurs formalismes, autre que C++, existent pour decrire un algorithme. Chaqueformalisme a ses avantages et ses inconvenients.

Un organigramme permet d’avoir une vue claire sur la sequence d’un petit nombred’instructions mais est totalement inadequat pour decrire clairement desalgorithmes un peu evolues. Nous utiliserons donc ce formalisme pour decrireles instructions C++, mais pas pour decrire des algorithmes. Un organigramme est

28 CHAPITRE 3. LES INSTRUCTIONS

compose de noeuds relies par des fleches. Les noeuds suivant existent :

est un noeud fonction contenantune instruction ou une sequenced’instructions elementaires

C F V

est un noeud de test si C estverifiee, on prend la branche V(vrai) sinon la branche F (faux)

correspond a la lecture de don-nees

correspond a l’ecriture

est le noeud de debut

est un noeud de fin

3.5. L’INSTRUCTION IF 29

Les organigrammes de la figure 3.1 montrent clairement la sequence d’execution pourles deux formats du if C++

Des a present, nous pouvons remarquer qu’il existe en general plusieurs structuresd’algorithme di!erentes pour resoudre un meme probleme.

Supposons par exemple que l’on veuille ecrire le programme C++ qui lit 3 nombresentiers i, j et k et les ecrit par ordre croissant.

Il existe 6 rangements possibles de ces 3 nombres :

cas 1 : i % j % kcas 2 : i % k % jcas 3 : j % i % kcas 4 : j % k % icas 5 : k % i % jcas 6 : k % j % i

Une premiere solution consiste a tester lineairement dans quel cas on se trouve.

Le programme C++ obtenu est donne par la figure 3.2.

Une autre solution consiste a essayer de subdiviser au mieux les cas possibles enclasses.

La figure 3.3 donne le programme correspondant qui ne fait qu’une seule comparaisona la fois et retient le resultat de chacune d’elles pour determiner dans quel cas on setrouve.

Nous pouvons constater que la deuxieme solution est en moyenne plus e"cace quela premiere. En e!et, en supposant, pour simplifier, que chaque comparaison est untest elementaire et que les nombres entiers i, j et k sont tous di!erents, le tableau3.1 donne le nombre de tests elementaires pour chaque cas et chaque solution (etantdonne l’evaluation par court-circuit).

Si l’on suppose que chaque cas est equiprobable, on obtient :– pour la solution 1 une moyenne de 5.17 tests elementaires– pour la solution 2 une moyenne de 2.67 tests elementaires

30 CHAPITRE 3. LES INSTRUCTIONS

nombre de tests solution 1 solution 2

cas 1 : 2 3

cas 2 : 4 3

cas 3 : 5 2

cas 4 : 6 2

cas 5 : 7 3

cas 6 : 7 3

Tab. 3.1 – Nombre de tests elementaires par solution

Remarque :

D’apres la syntaxe du if, un else se rapporte toujours au dernier if en cours.Ainsi dans

if( condition1 )if( condition2 )cout << "cas 1";elsecout << "cas 2";

le else se rapporte au if(condition2).Si l’on veut que le else se rapporte au if le plus global, il faut entourer le ifimbrique (c’est-a-dire celui qui est a l’interieur de l’autre) d’un bloc ’{ . . . }’.On a alors

if( condition1 ){

if( condition2 )cout << "cas 1";

}else

cout << "cas 2";

3.5. L’INSTRUCTION IF 31

Condition F V

F V

Instruction

Instruction 1 Instruction 2

Format 1

Format 2

Condition

Fig. 3.1 – Organigramme des 2 formats du if

32 CHAPITRE 3. LES INSTRUCTIONS

#include <iostream>using namespace std;

int main()// lit 3 nombres et les ecrit sur output// dans l’ordre croissant// version 1{

int i, j, k;

cout << "introduisez 3 nombres:";cin >> i >> j >> k;cout << endl

<< "les nombres dans l’ordre croissant sont:";

if (i <= j && j <= k)cout << i << " " << j << " " << k << endl; // cas 1

else if (i <= k && k <= j)cout << i << " " << k << " " << j << endl; // cas 2

else if (j <= i && i <= k)cout << j << " " << i << " " << k << endl; // cas 3

else if (j <= k && k <= i)cout << j << " " << k << " " << i << endl; // cas 4

else if (k <= i && i <= j)cout << k << " " << i << " " << j << endl; // cas 5

else // (k <= j && j <= i)cout << k << " " << j << " " << i << endl; // cas 6

}

Fig. 3.2 – Programme qui trie 3 nombres : version 1

3.5. L’INSTRUCTION IF 33

#include <iostream>using namespace std;

int main()// lit 3 nombres et les ecrit sur output// dans l’ordre croissant// version plus efficace{

int i, j, k;

cout << "introduisez 3 nombres:";cin >> i >> j >> k;cout << endl

<< "les nombres dans l’ordre croissant sont:";

if (i <= k)if (i <= j)

if (j <= k)cout << i << " " << j

<< " " << k << endl; // cas 1elsecout << i << " " << k

<< " " << j << endl; // cas 2else

cout << j << " " << i<< " " << k << endl; // cas 3

elseif (j <= k)

cout << j << " " << k<< " " << i << endl; // cas 4

elseif (i <= j)cout << k << " " << i

<< " " << j << endl; // cas 5elsecout << k << " " << j

<< " " << i << endl; // cas 6}

Fig. 3.3 – Programme qui trie 3 nombres : version 2

34 CHAPITRE 3. LES INSTRUCTIONS

3.6 L’instruction switch

En C++, l’instruction switch permet de faire du traitement conditionnel suivant lavaleur d’une expression.

Supposons par exemple qu’en fonction de la valeur de l’expression i + j (i,j etantdes variables entieres) plusieurs traitements soient possibles ; on aura par exemple leprogramme de la figure 3.4

switch (i+j){

case 0:case -1:case -2: cout << "i + j in [-2..0]" << endl;

break;case 1: cout << "i + j equals 1" << endl;

break;case 2: cout << "i + j equals 2" << endl;

break;default: cout << "i + j not in [-2..2]" << endl;

}

Fig. 3.4 – Exemple d’instruction switch

Le format general de cette instruction est :

3.7. L’INSTRUCTION WHILE 35

switch ( expression ){

case valeur11 :case valeur12 :. . .case valeur1m : sequence1

break;case valeur21 :case valeur22 :. . .case valeur2n : sequence2

break;. . .case valeurk1 :case valeurk2 :. . .case valeurkp : sequencek

break;default: sequencek+1

}

ou expression (le selecteur) est de type entier, caractere ou enumere (enum)1 et oules valeursi sont des constantes de valeurs du meme type que le selecteur maistoutes di"erentes.

L’e!et de cette instruction est d’evaluer la valeur v de l’expression, et si v est egala une valeur valeurij , la sequencei correspondante est executee ; si par contre v necorrespond a aucune valeur specifiee par les constantes valeurij, la sequencek+1 estexecutee si une partie default a ete precisee.

Notons que si un ou plusieurs break est omis dont celui apres une sequence !,l’execution de la sequence ! sera suivie de celle de la sequence !+1, !+2,. . . jusqu’aumoment ou un break est rencontre.

3.7 L’instruction while

L’instruction while permet de repeter un certain traitement. Son format generalest :

1les types enumeres seront vus ulterieurement

36 CHAPITRE 3. LES INSTRUCTIONS

while(condition)instruction

ou condition est une expression booleenne et instruction une instruction C++.

Intuitivement, l’e!et d’un while est d’executer l’instruction tant que la conditionest verifiee2.

Dans un programme, un while forme une instruction C++ et peut etre place la ouune instruction est permise (dans un if ou un autre while par exemple). De meme,le corps du while, c’est-a-dire l’instruction susceptible d’etre repetee, peut etre parexemple un bloc { . . . }, un if ou un autre while.

L’organigramme donne en figure 3.5 montre comment un while est execute.

FV

Instruction

Condition

Fig. 3.5 – Organigramme du while

L’execution du while se deroule donc comme suit :a) La condition est evaluee ;b) si elle est fausse, le while est termine sinon on execute l’instruction etc) l’execution reprend au point a).Ainsi si i est une variable entiere, le code suivant :

i = 3;

2La condition du while est generalement appelee condition d’arret , meme si, dans le cas duwhile, l’instruction est executee tant que la condition est verifiee

3.7. L’INSTRUCTION WHILE 37

while (i < 10){

i = i + 2;cout << i << " ";

}cout << endl;

imprimera : 5 7 9 11

Un tres bel exemple de l’utilisation du while consiste en le calcul du plus grandcommun diviseur (pgcd) de deux entiers positifs x et y.

Par exemple 6 est le pgcd de 102 et de 30 ; nous noterons ceci par

pgcd(102, 30) = 6.

La methode du mathematicien grec Euclide est basee sur l’observation que le pgcdde x et y est aussi le pgcd de y et du reste de la division entiere de x par y. Ici 12est le reste de la division entiere de 102 par 30 et donc

pgcd(102, 30) = pgcd(30, 12)

De facon similaire on a :

pgcd(30, 12) = pgcd(12, 6)pgcd(12, 6) = pgcd(6, 0)

Et comme pgcd(x, 0) = x puisque 0 est divisible par tout x (pour tout x : 0.x = 0),nous aurons

pgcd(102, 30) = 6

Le corps du programme aura donc la structure suivante :

while (y > 0){ remplacer (x,y) par (y,x%y)} ;le resultat vaut : x

{remplacer (x,y) par (y,x%y) } utilise une variable de travail supplementaire Saveet peut etre realisee par le code C++ suivant :

Save = y;y = x % y;x = Save;

38 CHAPITRE 3. LES INSTRUCTIONS

#include <iostream>using namespace std;

int main()// calcule le pgcd de 2 nombres entiers positifs{

int x, y, Save;

cout << "Introduisez les 2 nombres entiers positifs: " ;cin >> x >> y;while (y > 0){

Save = y;y = x % y;x = Save;

}cout << endl << endl << "Le pgcd vaut: " << x << endl;

}

Fig. 3.6 – Programme qui calcule le pgcd de 2 entiers positifs

Le programme C++ complet calculant le pgcd de 2 nombres entiers positifs est donnea la figure 3.6.

La table d’etat du tableau 3.2 donne la valeur des variables x, y et Save lors del’execution du programme en supposant que l’utilisateur introduit 102 et 30 commevaleur pour respectivement, x et y.

Un autre exemple d’utilisation d’un while est illustre dans le bout de code suivant,ou i et Somme sont des variables entieres, qui somme les nombres entiers positifs lus(termines par une valeur -1 ne faisant pas partie de la liste) :

Somme = 0;cin >> iwhile (i >= 0){Somme += i;cin >> i;

}

Notons que si la condition est fausse au debut de l’execution d’un while, l’instruc-tion ne sera jamais executee. Inversement si la condition du while ne devient jamais

3.8. L’INSTRUCTION DO 39

Instruction Etatx y Save

? ? ?cout << . . . ? ? ?cin >> x >> y; 102 30 ?

1er tourde

boucle

!

"

#

Save = yy = x % yx = Save

10210230

301212

303030

2eme tourde

boucle

!

"

#

Save = yy = x % yx = Save

303012

1266

121212

3eme tourde

boucle

!

"

#

Save = yy = x % yx = Save

12126

600

666

cout <<. . . x . . . 6 0 6

Tab. 3.2 – Table d’etat pour le programme pgcd de la figure 3.6 avec les valeurs 102et 30

fausse, l’instruction while continuera a s’executer indefiniment et ceci jusqu’a ce quele processus qui execute le programme soit ‘tue’ c’est-a-dire arrete de facon brutalepar le programmeur ou le superviseur de l’ordinateur. Tout programmeur novice vatot ou tard avoir a!aire a un tel programme qui cycle. Nous verrons dans un chapitreulterieur comment essayer de s’assurer que chaque boucle du programme se terminetoujours.

Enfin notons qu’il se peut que la condition devienne fausse au milieu de l’executionde l’instruction. L’organigramme de la figure 3.5 montre clairement que ce n’est qu’ala fin de l’execution de l’instruction que la condition est reevaluee.Ainsi dans le programme C++ de la figure 3.7, le corps de la boucle while estexecute 4 fois.

3.8 L’instruction do

L’instruction do permet d’e!ectuer un traitement repetitif en etant certain qu’ils’executera au moins une fois. Son format est le suivant :

40 CHAPITRE 3. LES INSTRUCTIONS

#include <iostream>using namespace std;

int main(){

int i = 0;

cout << " Debut du programme" << endl;while (i < 4){

i += 20;cout << i << endl;i -= 19;

}}

Fig. 3.7 – Exemple de while qui s’execute 4 fois

doinstruction

while (condition)

L’organigramme de la figure 3.8 illustre la sequence du traitement du do.

La condition n’est donc evaluee qu’a la fin du traitement du corps de la boucle do.Si la condition est verifiee lors de son evaluation, on ree!ectue un ‘tour de boucle’,(execution de l’instruction, evaluation de la condition, . . . ) ; sinon, le do se termine.Par exemple, si x est une variable entiere, le code C++ suivant :

x = 2;do{

x += 2;cout << x << " ";

}while (x <= 10)

imprime : 4 6 8 10 12

3.9. LA BOUCLE FOR 41

V F

Instruction

Condition

Fig. 3.8 – Organigramme du do

3.9 La boucle for

L’instruction repetitive for est primordiale en C++.

Le format general du for est le suivant :

for (init; condition; changement)instruction

ou– init et changement sont des expressions C++– condition est une condition C++– instruction est l’instruction constituant le corps de la boucle.L’organigramme de la figure 3.9 decrit la semantique, c’est-a-dire le fonctionnementdu for.

Ainsi, si i et Somme sont des variables entieres,

Somme=0;for (i=1; i <=10; ++i)

42 CHAPITRE 3. LES INSTRUCTIONS

ConditionFV

Instruction

Init

Changement

Fig. 3.9 – Organigramme decrivant le fonctionnement du for

Somme +=i;

calcule, dans Somme, la somme des nombres de 1 a 10 (Somme =$10

i=1 i).

Notons que cette boucle aurait pu s’e!ectuer comme suit :

for (Somme=0,i=0; i <10; Somme+=++i);

le ‘ ;’, marquant l’instruction vide, etant indispensable. Pour eviter les problemesde comprehension du comportement global du code, nous proscrivons cette dernieresolution a la fois parce qu’elle utilise l’incrementation ‘++i’ dans une expression etparce que le traitement, en l’occurrence ici le comptage dans la variable Somme, n’estpas place dans l’instruction correspondant au for mais dans la partie initialisationet changement.

3.9. LA BOUCLE FOR 43

Notons enfin que dans un for, n’importe quel des elements peut-etre vide. Ainsi :

for (; Condition;)Instruction

correspond a une instruction while. dans ce cas, le while est evidemment preferableparce que plus clair.

L’instruction

for (;;)cout << "programme qui cycle" << endl;

est equivalent a

while (true)cout << "programme qui cycle" << endl;

puisque la condition vide dans un for est evaluee a true. Ce code a comme e!etd’ecrire le message sans arret, jusqu’a ce que l’utilisateur ou le systeme ne l’arrete3.

Pour que le programme reste ‘lisible’ c’est-a-dire facilement comprehensible, leprogrammeur doit judicieusement choisir entre l’utilisation d’un while, d’undo ... while ou d’un for.

De facon generale, le choix d’un for est justifie par le fait que– une petite initialisation (compteurs, lecture d’une donnee, . . .) doit etre e!ectuee

avant le traitement repetitif lui meme (Des initialisations plus importantes peuventavoir eu lieu avant le for).

– Apres chaque traitement repetitif, il y a eventuellement une reinitialisation ouune modification, (incrementation ou decrementation des compteurs, lecture de ladonnee suivante, . . .) semblable a ce que l’on trouve dans l’initialisation ; ce codeetant mis dans la partie changement.

– l’instruction contient le traitement proprement dit. Il faut veiller, si possible, ane pas placer le traitement dans la partie initialisation, condition ou changementcomme nous l’avions fait avec for (Somme=0,i=0 ; i <10 ; Somme+=++i) ou lechangement contenait a la fois l’incrementation du compteur i pour l’iterationsuivante et le traitement (comptage dans Somme).

3En general, une execution interactive est arretee en utilisant une sequence de touches du clavier(ex : ’ctrl-c’)

44 CHAPITRE 3. LES INSTRUCTIONS

3.10 Blocs et declaration d’entites

Une liste de declarations de constantes ou de variables est consideree par le compi-lateur C++ comme une instruction et peut donc apparaıtre, entre autres, la ou uneinstruction peut etre placee. Ainsi,

#include <iostream>using namespace std;

int main(){

int i,j=0;cin >> i;int k,l=1;cin >> k;cout << i << " " << j << endl;

}

est un programme C++ correct.

Scope et visibilite

La portee (scope) d’une entite est la partie du programme ou cette entite peut apriori etre utilisee. Le scope d’une entite x est definie comme etant la zone du blocincluant la declaration de x qui commence a la declaration de x et se termine a lafin de ce bloc.

Ainsi, dans la figure 3.10, le scope de i et j commence a leur declaration et setermine a la fin du bloc global ; le scope de x, y et z commence a leur declaration etse termine a la fin du bloc correspondant au corps de la boucle while (i<j).

La notion de visibilite est egalement associee a chaque entite. Dans l’exemple 3.10,la visibilite de chaque variable correspond a son scope. Ainsi i et j peuvent etreutilises dans tous les blocs y compris les blocs imbriques ; par contre x, y et z nesont pas visibles en dehors du bloc correspondant au while (i<j). En particulier,ils sont inconnus et ne peuvent donc pas etre utilises dans la condition correspondanta ce while.

3.10. BLOCS ET DECLARATION D’ENTITES 45

#include <iostream>using namespace std;

int main(){

int i=1, j; //debut du scope de i et jcout << "valeur de j: ";cin >> j;while (i<j){double x,

y=3.14,z=j+1; // debut du scope de x, y, z

cout << "valeur de x: ";cin >> x;while (y<x){++i;y*=2;++z;

}cout << "y= " << y << endl;i=3*i+1;

} // fin du scope de x, y et zcout << "i = " << i;

} // fin du scope de i et j

Fig. 3.10 – Exemple de portee (scope) et de visibilite

Notons que la declaration4 d’une entite x dans un bloc imbrique a un bloc plus globalest permise meme si x est le nom d’une autre entite declaree auparavant dans le blocglobal. Cette declaration ‘supprime’, jusqu’a la fin du bloc imbrique, la visibilite du xplus global. La visibilite d’une entite correspond donc a son scope moins les endroitsou elle est cachee.

Ainsi le programme de la figure 3.11, sachant que l’on introduit par exemple 7,imprime 3.14 7, le x entier n’etant pas visible dans le bloc imbrique car cache parle x de type double.

4Nous verrons plus loin qu’en general, il existe une distinction entre declaration et definitiond’un element. Jusqu’a present et dans cette section, toute declaration correspond egalement a ladefinition d’un nouvel element.

46 CHAPITRE 3. LES INSTRUCTIONS

#include <iostream>using namespace std;

int main(){

int x; // debut du scope du x globalcout << "valeur de x: ";cin >> x;if (x!=0){double x=3.14;// debut du scope du x imbrique cachant le x globalcout << x << " ";

} // fin du scope du x local, le x global redevient visiblecout << x << endl;

} // fin du scope du x global

Fig. 3.11 – Exemple ou la visibilite et le scope d’une entite sont di!erents

Une bonne pratique est, dans la mesure du possible, d’eviter d’utiliser le meme nompour des entites di!erentes.

Une autre bonne pratique est d’essayer de declarer les entites le plus localementpossible, mais de mettre toutes les declarations en debut de bloc, ce qui permet deretrouver facilement les declarations correspondant aux di!erentes entites utilisees.

Declarations dans l’initialisation d’un for

La partie initialisation d’un for peut etre, en outre d’une expression, une listede declarations. Le scope des entites declarees a cet endroit est compris entre ladeclaration et la fin de l’instruction correspondante au for. Ceci est bien utile etrecommandable lorsque la boucle for utilise une variable de controle initialisee audebut et incrementee apres chaque etape. Ainsi, pour l’exemple du for donne pre-cedemment, qui somme dans la variable Somme la somme des entiers de 1 a 10, lefor suivant aurait ete plus adequat

Somme=0;for (int i=0; i<10; ++i)Somme +=i;

3.10. BLOCS ET DECLARATION D’ENTITES 47

Notons que la partie initialisation est soit une expression e!ectuant par exempledes initialisations de variables existentes, soit une liste de declaration, definissant( et eventuellement initialisant) des entites toutes de meme nature (variables ouconstantes d’un type donne). On n’aurait pas pu, dans l’initialisation, a la fois ini-tialiser la variable Somme a 0 et declarer la variable i.

Notons egalement que, etant donne que la valeur de la variable Somme est utiliseeapres le for, cette variable n’aurait pu etre declaree dans la partie initialisation dufor.

Creation et destruction d’entite

Lors de l’execution du programme, chaque fois que l’on passe sur une declaration5,l’entite est creee et, si specifie, initialisee. A la fin de son scope, cette entite estdetruite.

Ainsi, dans le programme de la figure 3.10, i et j sont crees au debut de l’executionet detruites a la fin, tandis que x, y et z sont crees a chaque iteration du while(i<j) et detruites a la fin de chacune de ces iterations, c’est-a-dire a la fin du bloccorrespondant. On voit donc que, dans cet exemple, x, y et z peuvent etre creees etdetruites de nombreuses fois.

Il est important de determiner avec precision ou chaque entite doit etre declaree.

Par exemple dans la bribe de code suivant :

{int i;...for (int j=0; ...; ...){int k;...

}}

les 3 variables i, j et k auront probablement un role di!erent. Par exemple, i peutaccepter un resultat calcule lors du for et qui pourra etre utilise par ailleurs, j est

5sauf celles qui ne correspondent pas a une definition, voir plus loin

48 CHAPITRE 3. LES INSTRUCTIONS

typiquement une ‘variable de controle’ du for qui n’existera plus apres son execution,tandis que k est recree a chaque iteration du for et pourra, par exemple, servir ae!ectuer des calculs completement termines lors de chaque iteration du for.

Ceci sera plus amplement illustre dans les exemples donnes ulterieurement.

3.11 Decomposition d’un probleme

Bien que la bonne connaissance de la syntaxe et de la semantique d’un langage sontdeux elements indispensables pour ecrire un algorithme, la conception proprementdite de ce dernier est une tache souvent bien plus complexe. Pour mener a biencette tache, il faut pouvoir analyser et decomposer proprement le probleme donneen parties, trouver une methode de resolution pour chacune de ces parties, traduireces methodes dans le langage de programmation, et enfin s’assurer que le programmeobtenu est su"sament e!cace et repond correctement au probleme pose : en un motetre “expert en programmation structuree”.

Pour le devenir il faut tout d’abord apprendre a decomposer proprement le probleme.

Prenons un exemple : supposons que l’on veuille ecrire un programme donnant uneapproximation du nombre e.

e peut etre defini comme la limite de la serie :

e = 1 +1

1!+

1

2!+ . . . +

1

n!+ . . .

Etant donne que le programme ne peut pas s’executer pendant une infinite de tempset que la precision de la machine utilisee est finie, il faudra s’arreter a un termedonne.

Il est connu que l’approximation

1 +1

1!+

1

2!+ . . . +

1

n!

di!ere de e d’au plus deux fois le terme suivant dans la serie, c’est-a-dire d’au plus2

(n+1)! .

Si l’on estime qu’une precision eps donnee (exemple : eps = 1.0e$8) est su"sante, ilfaut donc ecrire un programme qui calcule e avec une erreur inferieure a la constanteeps c’est-a-dire de sommer les termes 1

i! jusqu’a ce que 2(i+1)! < eps.

3.11. DECOMPOSITION D’UN PROBLEME 49

L’algorithme devra donc calculer

1 +1

1!+

1

2!+ . . . +

1

n!

avec2

n!& eps et

2

(n + 1)!< eps

ce qui ne peut pas etre fait en une seule instruction puisque le nombre n n’est pasconnu a priori.

Il semble naturel de decomposer l’algorithme en une repetitive qui calcule a chaquetour de boucle un nouveau terme et qui teste si ce nouveau terme doit etre inclusdans l’approximation.

Le canevas general sera alors donne par la figure 3.12.

#include <iostream>using namespace std;

int main()/* Approximation de la valeur de e */{

const double eps=1.0e-8;int n=1;double e=1.0;double Terme=1.0;do{e += Terme;++n;... // calculer le terme 1/n! suivant

}while (2*Terme >= eps);cout << "l’approximation de e = " << e << endl;

}

Fig. 3.12 – Canevas du programme calculant e par approximations successives

L’utilisation d’un do . . . while (2 * Terme >= eps) plutot qu’un while (2 *Terme >= eps) se justifie puisque l’on est certain qu’au premier tour de boucle2*Terme = 2

1! = 2 > eps.

50 CHAPITRE 3. LES INSTRUCTIONS

De plus etant donne quen! = 1.2.3. . . . .n

Calculer n! se fait grace au code

int Fact_n=1;for (int i=2; i<= n; ++i)Fact_n *=i;

qui en theorie reste correct meme pour n valant 0 ou 1 (0 ! = 1), mais en pratiquedonne rapidement une erreur d’overflow a l’execution (nombre n ! non stockable dansla variable entiere Fact n).

Remarquons que

int Fact_n=0;for (int i=1; i<= n; ++i)Fact_n *=i;

n’est evidemment pas correct.

On obtient alors le programme C++ donne en figure 3.13.

Etant donne qu’au nieme tour de boucle on calcule n!, on voit aisement que l’on peutdiminuer le nombre de calculs : il su"t de reprendre la valeur du Terme obtenue autour precedent et de la diviser par n.

La figure 3.14 donne la version 2 ainsi obtenue.

Remarquons, dans la figure 3.14, que le nom des objets (variables, constantes sym-boliques, . . . ) donne une idee de leur usage. De plus, ce programme est commenteet proprement indente. Ces pratiques facilitent la tache de toute personne qui doitcomprendre le fonctionnement du programme.

3.11. DECOMPOSITION D’UN PROBLEME 51

#include <iostream>using namespace std;

int main()/* Approximation de la valeur de e */{

const double eps=1.0e-8;int n=1;double e=1.0;double Terme=1.0;do{int Fact_n = 1;e += Terme;++n;for (int i=2; i<= n; ++i)Fact_n *=i;

Terme = 1/Fact_n // calcul du terme 1/n! suivant}while (2*Terme >= eps);cout << "l’approximation de e = " << e << endl;

}

Fig. 3.13 – Version 1 de l’approximation de e

52 CHAPITRE 3. LES INSTRUCTIONS

#include <iostream>using namespace std;

int main()/* Approximation de la valeur de e */{

const double eps=1.0e-8; // constante de precisionint n=1;long double e=1.0; // approximation jusqu’a n=0double Terme=1.0; // terme pour n=1do // boucle sommant les termes{e += Terme;++n;Terme /= n;

}while (2*Terme >= eps);cout << "l’approximation de e = " << e << endl;

}

Fig. 3.14 – Version amelioree de l’approximation de e

3.12. EXERCICES : INSTRUCTIONS ELEMENTAIRES 53

3.12 Exercices : Instructions elementaires

Ex. 1 Si les variables a, b, c contiennent respectivement les nombres 2, 6 et 1, quellessont leurs valeurs apres l’execution de chacune des suites d’assignations ci-dessous ?

a. a = b ; d. c = a ;c = a ; a = b ;

b = c ;

b. a = a + 1 ; e. b = - a ;b = c + 1 ; b = 2 * b ;c = 2 * c; a = b ;

c. a = b ; f. a = a * a ;b = a ; a = a * a ;

Ex. 2 Ecrire un algorithme qui a"che a17 (a lu sur input) en employant le moinsde multiplications possible.

Ex. 3 Ecrire une suite d’assignations permettant d’echanger les valeurs de 2 va-riables a et b.

Ex. 4 Qu’ecrit sur output le programme suivant quand on lui fournit en input lesvaleurs 2 et 6 ?

#include <iostream>

using namespace std ;

int main(){int a,b;

cin >> a;a *= 2;cin >> b;b += a;cout << a << endl << b << endl;

}

Ex. 5 Qu’ecrit sur output le programme suivant quand on lui fournit en input lesvaleurs 2, 6 et 4 ?

54 CHAPITRE 3. LES INSTRUCTIONS

#include <iostream>

using namespace std ;

int main(){int a,b;

cin >> a >> b;a = b;cin >> b;b += a;cout << a << " " << b << endl;

}

Ex. 6 Qu’ecrit sur output le programme suivant quand on lui fournit en input lesvaleurs 2 et 6 ?

#include <iostream>

using namespace std ;

int main(){int a,b;

cin >> b >> a;a = b+1;cout << a << endl;a = b+1;cout << a << endl;a += 1;cout << a << " " << (a+1) << endl;

}

Ex. 7 Qu’ecrit sur output le programme suivant quand on lui fournit en input :

1. les valeurs 2 et 6 ?

2. les valeurs 8 et 3 ?

3. les valeurs 3 et 3 ?

#include <iostream>

3.12. EXERCICES : INSTRUCTIONS ELEMENTAIRES 55

using namespace std ;

int main(){int a,b;

cin >> a >> b;if (a > b){

cout << a << endl;a = b;

}cout << a << endl;

}

Ex. 8 Qu’ecrit sur output le programme suivant quand on lui fournit en input :

1. les valeurs 2 et 6 ?

2. les valeurs 8 et 3 ?

3. les valeurs 3 et 3 ?

#include <iostream>

using namespace std ;

int main(){int a,b;

cin >> a >> b;if (a > b){

cout << a << endl;a = b;cout << a << endl;

}}

Ex. 9 Ecrire un programme qui lit 3 nombres, et qui, si deux d’entre eux ontla meme valeur, imprime cette valeur (le programme n’imprime rien dans le cascontraire).

56 CHAPITRE 3. LES INSTRUCTIONS

Ex. 10 Pour chacune des 4 instructions d’a"chage (cout), donner l’ensemble desvaleurs de a pour lesquelles celles-ci seront executees.

int a;cin >> a;

if (a > 0){if (a > 1){

if (a > 2){

cout << (a-2) << endl;}else{

cout << (a-1) << endl;}

}else{

cout << a << endl;}

}else{cout << "Erreur" << endl;

}

Ex. 11 Ecrire le morceau de code qui si, a est superieure a 0, teste si a vaut 1,auquel cas il imprime << a vaut 1 >> et qui, si a n’est pas superieure a 0, imprime<< a est inferieure ou egale a 0 >>.

Ex. 12 Indentez correctement le morceau de code suivant :

if (a > 2){if (a > 3){if (a == 4)cout << "message 1" << endl;else

3.12. EXERCICES : INSTRUCTIONS ELEMENTAIRES 57

cout << "message 2" << endl;cout << "message 3" << endl;}}elsecout << "message 4" << endl;

Justifiez l’utilisation des accolades.

Ex. 13 Ecrire le programme qui lit en input trois entiers a, b et c. Si l’entier c estegal a 1 alors le programme a"che sur l’output la valeur de a + b, si c vaut 2 alors leprogramme a"che la valeur de a $ b, si c egal 3 alors l’output sera la valeur de a · b.Enfin si la valeur 4 est assignee a c alors le programme a"che la valeur de a2 + b · a.Si c contient une autre valeur le programme a"che un message d’erreur.

Ex. 14 Ecrire un programme qui imprime la moyenne arithmetique de deuxnombres lus sur input.

Ex. 15 Ecrire un programme qui lit deux nombres sur input et qui soustrait le pluspetit au plus grand et qui a"che le resultat.

Ex. 16 Ecrire un programme qui lit deux nombres a et b sur input et qui calcule eta"che le nombre c tel que b soit la moyenne de a et c.

Ex. 17 Ecrire un programme qui lit trois nombres sur input et qui imprime les deuxplus grands.

Ex. 18 Si a,b,c sont des variables entieres et arret, test1, test2, test3 des va-riables booleennes, on demande la valeur de ces variables apres chacune des instruc-tions ci-dessous :

a=2;b=3;c=4;test1=true;test2=(b>=a) and (c>= b);test3=test1 or test2arret=test3 and (not test2);a+=1;b-=1;c-=2;test1=true;test2=(b>=a) and (c>=b);

58 CHAPITRE 3. LES INSTRUCTIONS

test3=test1 or test2;arret=arret or test2;

Ex. 19 Parmi les douze propositions logiques suivantes, lesquelles sont equiva-lentes ?

1. (a > b) # (N '= 0)

2. (a % b) " (N = 0)

3. (a % b) # (N = 0)

4. ¬(a < b) # ¬(N = 0)

5. (a " a) # (a " ¬a) # (¬b " a) # (¬b " ¬a) # (N = 0)

6. ¬(a > b) # (N '= 0)

7. ¬((a > b) # (N '= 0))

8. ¬((a > b) " (N '= 0))

9. ¬(a > b) " ¬(N '= 0)

10. ¬(a > b) # ¬(N '= 0)

11. (a > b) " (N '= 0)

12. ¬(a ( b) # (N = 0)

Ex. 20 Ecrire le code calculant la valeur de chacune des propositions precedentes.

Ex. 21 Ecrire de maniere optimisee et en n’utilisant que les operateurs logiques et,ou et not, le code correspondant a l’expression logique suivante (ou ) representeun ou exclusif) :

b # ¬(a ) b) # (a " ¬b) # (¬a ( b)

3.13 Exercices : Structures iteratives

3.13.1 Boucles simples

Ex. 22 Que fait cette suite d’instructions :

a=1;

3.13. EXERCICES : STRUCTURES ITERATIVES 59

while (a <= 5){cout << a << " ";a+=1;

}cout << a << endl;

Ex. 23 Que fait cette suite d’instructions :

a=1;do{cout << a << " ";a+=1;

}while (a <= 5);cout << a << endl;

Ex. 24 Que fait cette suite d’instructions :

a=0;while (a < 5){a+=1;cout << a << " ";

}cout << a << endl;

Ex. 25 Que fait cette suite d’instructions :

a=0;do{a+=1;cout << a;

}while(a != 5);cout << a << endl;

Ex. 26 Ecrire un programme qui lit un nombre n sur input et qui a"che tous lesnombres naturels entre 0 et n compris, de maniere croissante.

Variantes :

60 CHAPITRE 3. LES INSTRUCTIONS

1. Tous les nombres entre 0 et n compris, de maniere decroissante ;

2. Tous les nombres pairs entre 0 et n compris, de maniere croissante ou decrois-sante ;

3. Tous les nombres entre 0 et n bornes non-comprises, de maniere coirssante ;

4. etc. Inventez-en d’autres !

Ex. 27 Ecrire un programme qui calcule la moyenne des resultats d’une interroga-tion, ces notes etant donnees en input, la fin des donnees etant signalee par la valeursentinelle -1 (on suppose aussi que la suite des notes contient toujours au moins unelement).

Ex. 28 Ecrire l’equivalent de l’instruction suivante avec une boucle while et puisavec une boucle do :

int i;

for(i=a;i<=b;++i){<instruction;>

}

Ex. 29 Modifier le code ci-dessous en utilisant une boucle for :

int i,sum=0;

cin >> i ;while(i>0){i--;sum+=2*i;

}cout << sum << endl;

Ex. 30 Modifier le code ci-dessous en utilisant une boucle while :

double prod;int i,cpt;

prod=1;cpt=0;for(cin >> i;i<100;++i)

3.13. EXERCICES : STRUCTURES ITERATIVES 61

{cpt++;prod*=i;

}cout << (prod/cpt) << endl;

Ex. 31 Ecrire un programme qui a"che sur output la valeur de n! (ou n! = n *(n $ 1) * (n $ 2) · · · 2 * 1) avec n lu sur input.

Ex. 32 Ecrire un programme qui lit sur input une liste de valeurs entieres se trou-vant dans l’intervalle [$max, max] et terminee par une valeur sentinelle > max (onpeut prendre max = 100 pour fixer les idees). Ecrire un programme qui a"che lenombre de valeurs < 0 de cette liste de valeurs.

Ex. 33 Ecrire un programme qui a"che sur output les nombres entiers strictementpositifs dont le carre est inferieur a un nombre entier n lu sur input.

Ex. 34 Ecrire un programme qui a"che sur output les puissances de 2 a exposantsentiers positifs qui sont inferieures a n lu sur input.

Ex. 35 Ecrire un programme qui a"che sur output les 10 premieres puissances (aexposants entiers > 0) d’un nombre a lu sur input.

Ex. 36 Ecrire le programme qui lit en input un nombre Max > 0 et une suite denombres entiers strictement positifs. On demande d’a"cher sur l’output les premierselements de cette suite, determines par la condition que leur somme ne depasse pasMax. De facon precise, si a1, a2, a3, . . . , an est la suite de nombres, on demanded’a"cher les nombres a1, a2, a3, . . . , ak tels que a1 +a2 + · · ·+ak % Max et si k < n,a1 + a2 + · · ·+ ak+1 > Max.

Aucune hypothese n’est faite quant a l’existence d’une solution. La suite de nombresest suivie d’une valeur sentinelle -1.

Ex. 37 Ecrire le programme qui lit en input deux nombres bi, bs et puis une listenon vide de valeurs entieres triees par ordre croissant. On demande d’a"cher surl’output tous les nombres de cette liste appartenant a l’intervalle [bi, bs]. On supposeque la derniere valeur de la liste est toujours strictement superieure a bs.

Avec :

3 25 -7 -5 1 3 12 18 42 51bi bs liste

62 CHAPITRE 3. LES INSTRUCTIONS

On imprime :

3 12 18

Ex. 38 On suppose que sur l’input se trouve une suite d’entiers positifs, termineepar la valeur sentinelle -1. Comparer le premier (note A) et le dernier element (noteB) de la suite et a"cher un message suivants :

A est inferieur a BA est strictement superieur a BLa suite est vide

Si la suite ne compte qu’un element, on considere que A = B.

Ex. 39 Ecrire un programme qui calcule Fn le ne nombre de Fibonacci dont ladefinition est la suivante : +n & 2 : Fn = Fn!1 + Fn!2 avec F0 = 0 et F1 = 1.

Ex. 40 Le nombre d’or est la limite de la suite :

F2/F1 , F3/F2 , F4/F3 , . . . , Fn+1/Fn , . . .

ou Fn, le nombre de Fibonacci de rang n, est defini par :

Fn = Fn!1 + Fn!2 F0 = 0 F1 = 1

Ecrire un algorithme qui calcule ce nombre d’or avec une precision " (" est lu surinput, 1.0E $ 5 par exemple).

Ex. 41 Que realise l’algorithme suivant ?

#include <iostream>

using namespace std ;

int main(){int d, r;

cin >> r >> d;while ( r >= d ){r -= d;

}

3.13. EXERCICES : STRUCTURES ITERATIVES 63

cout << r << endl;}

Ex. 42 Que realise l’algorithme suivant ? Utilisez des exemples pour vous donnerl’intuition.

#include <iostream>

using namespace std ;

int main(){int a, n, d, r;

cin >> a >> n;r=a;d=n;while ( r >= d ){d *= 3;

}while (d != n){d /= 3;

while ( r >= d ){r -= d;}

}cout << r << endl;}

Ex. 43 Ecrire un programme qui lit en input une suite de nombres strictementpositifs (sentinelle : 0, la suite comporte au moins deux nombres) et indique si elleest croissante, decroissante, strictement croissante, strictement decroissante ou nontriee.

Ex. 44 Ecrire le code qui calcule la valeur approchee de # sur base de la seriesuivante (due a Leibniz). Les calculs s’arretent lorsque la valeur du dernier terme

64 CHAPITRE 3. LES INSTRUCTIONS

ajoute est inferieure a un " donne, ou lorsque le nombre de termes consideres atteintune borne egalement donnee.

#

4= 1 $

1

3+

1

5$

1

7+

1

9$ · · ·

Ex. 45 Ecrire le code qui calcule la valeur approchee de # sur base de la seriesuivante. Les calculs s’arretent lorsque la valeur du dernier terme ajoute est inferieurea un " donne, ou lorsque le nombre de termes consideres atteint une borne egalementdonnee.

#

8=

1

1 · 3+

1

5 · 7+

1

9 · 11+ · · ·

Ex. 46 Ecrire le code qui calcule la valeur approchee de # sur base de la seriesuivante (due a Euler). Les calculs s’arretent lorsque la valeur du dernier termeajoute est inferieure a un " donne, ou lorsque le nombre de termes consideres atteintune borne egalement donnee.

#2

6= 1 +

1

22+

1

32+

1

42+ · · ·

Ex. 47 Le nombre e peut etre defini comme la limite d’une serie :

e = 1 +1

1!+

1

2!+ · · ·+

1

n!+ · · · =

%

i"0

1

i!

Il est connu que l’approximation :

e = 1 +1

1!+

1

2!+ · · · +

1

n!

di!ere de e d’au plus deux fois le terme suivant dans la serie, c’est-a-dire d’au plus2 · 1

(n+1)! .

Ecrire le code calculant e avec une approximation dont l’erreur est inferieure a uneconstante donnee (en ignorant les imprecisions dues aux arrondis sur machine).

3.13. EXERCICES : STRUCTURES ITERATIVES 65

! b: 25 min. !Ex. 48 On peut calculer approximativement le sinus de x en e!ectuant lasommation des n premiers termes de la serie infinie

sin(x) = x $x3

3!+

x5

5!$

x7

7!+ · · ·

ou x est exprime en radians.Reecrire cette somme sous la forme sin(x) =

$

i"0 f(i, x). (On vous demande

donc de trouver f(i, x)). Ecrire ensuite le code calculant de cette manierela valeur de sin(x) ou x est lu sur input.Continuer l’addition des termes successifs dans la serie jusqu’a ce que lavaleur d’un terme devienne inferieure (en amplitude) a une constante "(exemple : " = 10!5)

3.13.2 Boucles imbriquees

Ex. 49 Ecrire une programme qui lit sur input une valeur naturelle n et qui a"chea l’ecran un carre de n caracteres X de cote, comme suit (pour n = 6) :

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Variantes : a"cher uniquement le bord du carre, a"cher un rectangle, a"cher letriangle superieur,. . .

Ex. 50 Ecrire un programme qui lit sur input une valeur naturelle n et qui a"cheensuite a l’ecran toutes les paires (i, j) telles que 0 % i % n et 0 % j % n.

Ces paires seront a"chees sur n lignes de la facon suivante :

(0, 0) (0, 1) · · · (0, n)(1, 0) (1, 1) · · · (1, n)...

......

...(n, 0) (n, 1) · · · (n, n)

66 CHAPITRE 3. LES INSTRUCTIONS

Ex. 51 Ecrire un progamme qui lit sur input une valeur naturelle n et qui a"cheensuite a l’ecran toutes les paires (i, j) telles que 0 % i % n et 0 % j % i. Ces pairesseront a"chees sur n lignes de la facon suivante :

(0, 0)(1, 0) (1, 1)...

......

...(n $ 1, 0) (n $ 1, 1) · · · (n $ 1, n$ 1)(n, 0) (n, 1) · · · · · · (n, n)

Ex. 52 Refaire l’exercice 49 en supposant que n est impaire et en dessinant des Osur les deux diagonales principales a la places des X.

Exemple, pour n = 7 :

OXXXXXOXOXXXOXXXOXOXXXXXOXXXXXOXOXXXOXXXOXOXXXXXO

Chapitre 4

Les fonctions

Nous avons vu au chapitre precedent que la conception d’un programme passe parune phase d’analyse qui decoupe le probleme en parties.

C++ permet de decouper les programmes en modules. Chaque module cree peutetre vu comme un outil resolvant un certain probleme. Les autres modules peuventutiliser cet outil sans se soucier de sa structure. Il leur su"t, pour pouvoir l’utiliser,de savoir comment employer cet outil, c’est-a-dire, comme nous le verrons ci-dessous,de connaıtre le nom du module, les objets (valeurs, variables) qu’il necessite et lafacon dont il renvoie un ou plusieurs resultats.

Cette technique a en particulier deux avantages :– chaque module peut (generalement) etre teste independamment du reste du pro-

gramme ;– si une partie contient une sequence d’instructions qui doit etre realisee a di!erents

endroits du programme, il est possible de n’ecrire cette sequence qu’une seule fois.Cette technique est appelee approche modulaire dans la conception de programme.Un module C++ est une fonction, main() etant la fonction principale par ou debutel’execution du programme.

En gros, une fonction peut etre vue comme une operation dont la valeur est definiepar le programmeur.

Notons que, dans les chapitres precedents, nous avons deja vu qu’en C++, il existedes fonctions predefinies que le programmeur peut utiliser.

Ainsi par exemple cos(x), abs(x) sont des fonctions.

67

68 CHAPITRE 4. LES FONCTIONS

Nous allons voir par la suite comment le programmeur peut en definir de nouvelles.

La conception modulaire des programmes est indispensable pour leur developpement.Cette approche est de plus a la fois robuste et flexible. La suite du chapitre developpeces notions.

4.1 Les fonctions qui retournent une valeur

En C++, une fonction est une operation definie par le programmeur1. Montrons surun exemple comment une fonction est definie et utilisee.

Supposons que l’on desire ecrire un programme qui e!ectue a plusieurs endroits uncalcul de pgcd . Il serait bien utile d’avoir une fonction calculant le pgcd de deuxnombres. Etant donne que les couples de valeurs dont on calcule le pgcd ne sont pastoujours les memes tout au long du programme, il est indispensable que la fonctionde calcul du pgcd soit parametrisee par ces deux valeurs.

Ainsi le programme appellera, c’est-a-dire utilisera, la fonction pgcd tout au long deson code comme illustre par la figure 4.1.

int main(){

int z,i,j,a,b;...z = pgcd(a,b);...if (pgcd(i+2,2*j) != 1){ ...}i = pgcd(j,i);...

}

Fig. 4.1 – Programme utilisant la fonction pgcd

Il est evident que la fonction pgcd devra par ailleurs etre definie. Cette definitionressemblera au programme donne a la figure 3.6. Une di!erence essentielle est le faitque les valeurs initiales dont il faut calculer le pgcd ne doivent pas etre recues de

1exceptees les fonctions predefinies

4.1. LES FONCTIONS QUI RETOURNENT UNE VALEUR 69

l’input mais recues du programme appelant. De meme le resultat ne doit pas etreecrit sur output mais doit retourner au programme appelant.

Ce passage de valeurs se fait par un mecanisme de correspondance appele passagede parametres.

Il su"t de preciser lors de la definition de la fonction pgcd , le nombre et type desvaleurs recues ; ces valeurs sont placees dans des variables au debut de chaque appel.

La fonction pgcd est un objet qui, a la fin de chacune de ses executions, aura unevaleur. Cette valeur est ‘renvoyee’ au module appelant par l’instruction return ex-pression ; ou expression est du type de la fonction ou d’un type compatible avec ellepour l’assignation.

La figure 4.2 donne la definition de cette fonction.

int pgcd (int x, int y)/*

recoit deux valeurs positives ou nulles dans lesvariables x et y et renvoie une valeur entiere:le plus grand commun diviseur (pgcd) de x et de y

*/{

int Save;while (y>0){Save = y;y = x % y;x = Save;

}return x;

}

Fig. 4.2 – Fonction calculant le pgcd de 2 nombres

Cette definition devra normalement se placer avant la fonction main qui l’appelle ;le programme complet aura la structure generale donnee par la figure 4.3.

De facon generale, une definition de fonction est placee avant tout appel a celle-ci.Elle a le format suivant :

type de la fonction nom ([liste de parametres formels])instruction composee

70 CHAPITRE 4. LES FONCTIONS

int pgcd (int x, int y)/*

recoit deux valeurs positives ou nulles dans lesvariables x et y et renvoie une valeur entiere:le plus grand commun diviseur (pgcd) de x et de y

*/{

int Save;while (y>0){Save = y;y = x % y;x = Save;

}return x;

}int main(){

int z,i,j,a,b;...z = pgcd(a,b);...if (pgcd(i+2,2*j) != 1){ ...}i = pgcd(j,i);...

}

Fig. 4.3 – Structure generale du programme utilisant la fonction pgcd

ou– Les crochets “[. . .]## sont des metasymboles qui signifient que ce qui est a l’interieur

est optionnel ; ici il se peut donc que la fonction n’ait pas de parametres formels.– type de la fonction determine le type du resultat ;– nom est le nom de la fonction ;– liste de parametres formels donne la liste des parametres formels avec leur type

et leur type de passage (voir plus loin) ;– instruction composee (bloc) est une instruction composee contenant le traitement

e!ectue par la fonctionL’appel a la fonction se fait en l’invoquant a l’interieur d’une expression :

nom(liste des parametres e"ectifs)

4.2. LES FONCTIONS QUI NE RETOURNENT PAS UNE VALEUR 71

ou– nom est le nom de la fonction appelee ;– liste des parametres e"ectifs donne les parametres e"ectifs . Nous en reparlerons

un peu plus tard dans ce chapitre.Le resultat (a chaque appel) de la fonction donne une valeur du type de la fonction.

A la section 4.3, nous expliquerons comment s’e!ectue de facon generale le passagedes parametres ainsi que le deroulement de l’execution lors d’un appel a une fonction.

4.2 Les fonctions qui ne retournent pas une valeur

Il se peut qu’on ait besoin de definir un module dont l’e!et ne soit pas uniquementde produire un seul resultat. Par exemple le module pourrait e!ectuer des ecrituressur output ou des lectures sur input, ou produire plusieurs resultats. Dans ce cas, ilvaut mieux definir une fonction C++ qui n’a pas de resultat.

Par exemple la fonction Print_Star a pour e!et d’ecrire sur output une ligned’etoiles

void Print_Star(){

cout << "*********************************" << endl;}

le mot-cle void signifie que la fonction ne renvoie pas de resultat (elle est de type’vide’). Un objet de type void ne peut bien evidemment pas etre assigne a unevariable ou utilise dans une expression.

Dans d’autres langages de programmation, la notion de procedure existe a cote dela notion de fonction. Une fonction C++ void peut etre vue comme une procedure(et sera appelee procedure dans la suite) c’est-a-dire un module auxiliaire n’ayantpas une valeur resultat. Par contre, nous verrons que le passage des parametres parreference va permettre a une procedure de renvoyer des resultats au programmeappelant via l’utilisation de ses parametres.

72 CHAPITRE 4. LES FONCTIONS

4.3 Execution du module appele

Dans la suite, nous parlerons du module appelant pour identifier le module qui ef-fectue l’appel a la fonction invoquee ; cette derniere sera identifiee comme etant lemodule appele.

Lorsqu’un module est appele par un autre module, la sequence du traitement est lasuivante :

1. Les variables locales au module appele, correspondantes aux parametres for-mels, sont creees.

2. Les parametres sont transmis (passes) entre le module appelant et le moduleappele. Ceci sera decrit en details ci-dessous.

3. L’execution du module appelant est suspendue pour laisser la place a l’execu-tion du module appele.

4. Lorsque le module appele termine son execution, les variables locales sont“detruites”.

5. Le module appelant reprend son execution. Si le module precedement appeleetait une fonction retournant une valeur, la valeur de cette fonction a eteconservee pour permettre l’evaluation de l’expression utilisant le resultat decette fonction.

Une fonction ou une procedure etant, en substance, un module de traitement para-metrise, il faut clairement preciser a quoi correspond chaque parametre formel audebut de chacune de ses executions.

Tout d’abord, rappelons qu’une fonction est vue comme un operateur : chaque appela cette fonction produit un resultat (sauf pour les fonctions de type void) qui est lavaleur de cette fonction pour cet appel ; cette valeur resultat est donc transmise aumodule appelant .

Le passage des parametres constitue l’autre facon de transmettre des valeurs ou desresultats entre le module appelant et le module appele.

Pour preciser comment se font les transferts d’informations entre modules, il fautremarquer tout d’abord que le module appele declare des parametres formels. Deuxmodes de transmission des parametres sont possibles en C++ : par valeur ou parreference. Ces deux modes sont expliques plus bas. Pour specifier qu’un parametreest transmis par reference, le caractere ‘&’ est ajoute a la declaration du parametreformel correspondant, juste avant son nom. Pour les parametres sans le caractere‘&’, le passage se fait par valeur.

Par exemple l’entete de la procedure Ex_de_Param suivant :

4.3. EXECUTION DU MODULE APPELE 73

void Ex_de_Param(int x, int y, double Brol, bool &s, bool &t,double q)

declare que la procedure Ex_de_Param a 6 parametres formels

x et y de type int transmis par valeurBrol de type double transmis par valeurs et t de type bool transmis par referenceet q de type double transmis par valeur

Dans la partie traitement de cette procedure, les 6 parametres formels sont vuscomme des variables locales a cette procedure, c’est-a-dire connues seulement a l’inte-rieur de cette procedure2 et peuvent etre utilises comme toute autre variable.

Lors d’un appel a une fonction definie auparavant, des parametres e"ectifs doiventetre precises. Le nombre de parametres e!ectifs doit etre le meme que le nombre deparametres formels3.

Il doit y avoir une correspondance entre les parametres formels et les parametrese!ectifs selon la position. Cela signifie que le ieme parametre e!ectif en ordre d’ap-parition dans la liste doit correspondre au ieme parametre formel.

La correspondance doit se faire comme suit :– si le parametre formel correspondant est passe par reference (le caractere ‘&’ est

specifie devant son nom), le parametre e!ectif doit etre une variable du memetype que le parametre formel ;

– si le parametre formel correspondant est passe par valeur (le caractere ‘&’ ne setrouve pas devant son nom dans la declaration), le parametre e!ectif doit etreune expression dont la valeur est de type compatible pour l’assigna-tion avec le type du parametre formel. En gros une variable v d’un type A estcompatible pour l’assignation avec une valeur w d’un type B si v := w est permis.

Par exemple, avec l’entete de definition telle que donnee plus haut et en supposantque Flag et Drapeau sont des variables booleennes, Val une variable reelle et i unevariable entiere, les appels suivant sont valides :a) Ex_de_Param(37,i+2,1.0,Flag,Drapeau,Val)b) Ex_de_Param(i,i,3.14* 2/17,Drapeau,Flag,2*Val)mais les appels suivants sont incorrectsc) Ex_de_Param(37,i,1.0,Flag,Drapeau,6.28,i)

2nous verrons qu’en realite, dans le cas des parametres transmis par reference, seul le nom desvariables est local ; la variable elle-meme est en fait, globale au module appele

3sauf si certaines valeurs par defaut sont possibles ; voir plus loin

74 CHAPITRE 4. LES FONCTIONS

d) Ex_de_Param(i,i,1.0,true,Drapeau,1.0)c) est incorrect parce que le nombre des parametres e"ectifs est di!erent (su-perieur ici) au nombre des parametres formels.

d) est incorrect parce que le quatrieme parametre e!ectif doit etre une variablepuisque c’est un parametre passe par reference

Il nous reste a expliquer les mecanismes de passage des parametres.

Passage par valeur

Si un parametre est passe par valeur, la variable manipulee dans le moduleappele, c’est-a-dire le parametre formel, est une variable locale dont la valeurest initialisee au debut de l’execution du module a la valeur donnee dans leparametre e!ectif correspondant.

Donc lorsqu’un module est appele, le passage de chaque parametre passe par valeurs’e!ectue comme suit :

1. L’expression donnee comme parametre e!ectif est evaluee.

2. Cette valeur est assignee dans la variable locale donnee par le parametre formel.

Regardons cela sur l’exemple donne en figure 4.4.

La table d’etat donnee au tableau 4.1 decrit la sequence des instructions executeesainsi que le contenu des variables en supposant que les valeurs lues soient 3 et 7.Dans cette table, quand rien n’est mis sur une ligne en regard d’une variable, celasignifie que cette variable n’existe pas a cet endroit (ainsi, i,j et res n’existent paspartout).

Remarquons que les variables locales a la fonction n’existent que durant son execu-tion. Leur scope est l’instruction composee associee a la fonction. Elles sont creees audebut de son execution et sont “detruites” a la fin de l’execution de cette fonction, lavaleur de la fonction donnee lors du return etant transmis au programme appelant.

Nous remarquons donc qu’au moment de l’appel a la fonction max, les variableslocales i et j sont creees et on assigne a i et a j respectivement la valeur desparametres e!ectifs correspondants (respectivement les valeurs de x et de y) Lavariable res est cree lors de sa declaration, c’est-a-dire au debut du traitement demax.

4.3. EXECUTION DU MODULE APPELE 75

#include <iostream>using namespace std;int max(int i, int j)/* calcule la valeur maximale de i et j */{

int res;if (i>j)res=i;

elseres=j;

return res;}int main(){

int x,y;cout << "inserer 2 nombres entiers: ";cin >> x >> y;cout << endl << "La valeur maximale est: " << max(x,y)

<< endl;}

Fig. 4.4 – Exemple de transmission par valeur

Instruction Etatx y i j res

int x,y ? ?cout << ... ? ?cin >> x >> y 3 7max(x,y) 3 7transferts de parametres 3 7 3 7int res 3 7 3 7 ?res=j 3 7 3 7 7return res 3 7 3 7 7cout << ... 3 7

Tab. 4.1 – Table d’etat pour l’execution du programme de la figure 4.4

Passage par reference

Si un parametre est passe par reference, la variable manipulee par le mo-dule appele est celle donnee en parametre e!ectif : seul son nom (sa refe-rence) change localement.

76 CHAPITRE 4. LES FONCTIONS

Donc lorsqu’une fonction est appelee, le passage de chaque parametre passe parreference s’e!ectue comme suit :– Une correspondance est etablie entre le nom local donne par le parametre formel

et la variable donnee en parametre e!ectif.Regardons le fonctionnement de ce mecanisme sur l’exemple donne par la figure 4.5.

void swap(int &i, int &j)/* permute les valeurs de i et de j */{

int Save = i;i = j;j = Save;

}

int main(){

int a, b, x, y;x=3;a=12;y=7;b=24;swap(x,y);cout << x << " " << y << endl;swap(a,b);cout << a << " " << b << endl;

}

Fig. 4.5 – Exemple de transmission par reference

La table d’etat donnee au tableau 4.2 decrit la sequence des instructions executeesainsi que le contenu des variables.

Remarquons que, contrairement aux parametres formels transmis par valeurs, les pa-rametres formels transmis par reference ne correspondent pas a des variables locales :il n’y a que le nom des variables donnees en parametres e!ectifs qui est localementdi!erent. On dit que le parametre formel est un alias du parametre e!ectif.

Lorsqu’une variable est transmise par reference, elle peut etre modifiee par le mo-dule appele. Cette variable peut donc contenir un resultat calcule dans le moduleappele. Il est donc possible de creer des procedures “renvoyant” plusieurs resultatsau programme appelant. Notons que ceci n’est pas possible avec un parametre passepar valeur puisque le parametre e!ectif n’est jamais modifie par le module appele.

4.3. EXECUTION DU MODULE APPELE 77

Instruction Etatx y a b Save

int a,b,x,y ? ? ? ?x = 3 3 ? ? ?a = 12 3 ? 12 ?y = 7 3 7 12 ?b = 24 3 7 12 24[swap(x,y)] x , i y , j

int Save = i{, x} 3 7 12 24 3{x ,}i = j{, y} 7 7 12 24 3{y ,}j = Save 7 3 12 24 3cout << x << " " << y 7 3 12 24[swap(a,b)] a , i b , j

int Save= i{, a} 7 3 12 24 12{a ,}i = j{, b} 7 3 24 24 12{b ,}j = Save 7 3 24 12 12cout << a << " " << b << endl 7 3 24 12

Tab. 4.2 – Table d’etat pour l’execution du programme de la figure 4.5

Ainsi si l’on veut creer un module qui calcule le pgcd et le plus petit commun multiple(ppcm) de deux entiers positifs, on peut creer une procedure dont l’entete peut etrela suivante :

void pgcd_et_ppcm (int x, int y, int &pgcd, int &ppcm)

L’appel a cette procedure pourra donc se faire comme suit :

pgcd_et_ppcm (i,j,val_pgcd,val_ppcm)

ou i et j donnent les valeurs des entiers dont il faut calculer le pgcd et le ppcm etval_pgcd et val_ppcm sont des variables qui contiendront, a la fin de l’execution dela procedure, les resultats.

Notons que theoriquement, on aurait pu definir une fonction calculant le pgcd quiaurait en plus, un parametre transmis par reference pour donner la valeur du ppcm.Ceci est une pratique a bannir puisque une fonction non void doit conceptuellementetre un module qui produit un et un seul resultat.

De ce fait une regle de bonne pratique est la suivante :

78 CHAPITRE 4. LES FONCTIONS

Regle de bonne pratique :

Une fonction qui calcule une valeur donnee par un return ne peut contenir quedes parametres passes par valeur4.

Notons encore que si une variable du module appelant contient une valeur a trans-mettre au module appele et si ce parametre n’est pas modifie tout au long de l’exe-cution de ce module appele, le parametre peut tout aussi bien etre transmis parreference que par valeur, en specifiant qu’il est constant au sein de cette fonction.Pour cela, le mot cle const doit etre rajoute au debut de la declaration du para-metre formel correspondant (exemple : const int &i), economisant ainsi un empla-cement memoire et une copie de valeur. En pratique ceci n’est reellement economiquequ’avec des entites volumineuses. Le programmeur peut se definir des variables de’type’ classe par exemple. Dans ce cas le fait de construire une copie d’une tellevariable peut ne pas etre anodin tant au niveau de la place memoire prise que dutemps d’execution utilise pour e!ectuer cette copie.

Pour ecrire un programme de facon structuree, deux autres regles de bonne pratiquesont :

Regles de bonne pratique :

Une fonction de type void ne doit pas contenir de return.Une fonction d’un type di!erent de void ne doit contenir qu’un seul return ex-pression, place comme derniere instruction de cette fonction.

Ces regles obligent le programmeur a executer ses fonctions jusqu’a la derniere ins-truction c’est-a-dire sans sortir en plein milieu du code par un return. Cette pratiqueoblige parfois a structurer vos algorithmes di!erement, mais s’avere souvent salutairepour comprendre le fonctionnement precis du programme.

4En C++ et plus particulierement en programmation systeme, lorsqu’une fonction Trait e!ec-tue un traitement qui peut echouer (par exemple lorsque le programme fait une requete pour avoirplus de memoire et le systeme ne la lui accorde pas), une bonne pratique est que la fonction plutotque d’etre du type void, soit de type bool et qu’elle renvoie true si le traitement s’est deroule sansechec et false si le traitement a echoue. Ainsi, la fonction appelante plutot que de faire un simpleappel Trait(arguments e!ectifs) fera if(not Trait(arguments e!ectifs)) traitement de l’erreur

4.3. EXECUTION DU MODULE APPELE 79

Surcharge (overloading) de fonctions

Jusqu’a present nous avons dit que deux identificateurs ayant le meme scope (declaresdans le meme bloc par exemple), ne pouvaient avoir le meme nom.

Par contre plusieurs fonctions di!erentes peuvent avoir le meme nom. Nous dironsque ces fonctions sont surchargees (overloaded). Cela n’est permis que s’il existe unedi!erence sur le nombre et/ou au niveau du type des parametres formels. En e!et,le compilateur5 doit pouvoir comparer les arguments e!ectifs par rapport aux para-metres formels et determiner pour chaque appel, quelle fonction doit etre executee.

Ainsi nous pouvons ecrire une fonction swap pour les entiers et une pour les reelsavec :

void swap (int &i, int &j){

int Save = i;i = j;j = Save;

}

void swap (double &i, double &j){

double Save = i;i = j;j = Save;

}

Lors d’un appel a une fonction swap, suivant que les arguments e!ectifs sont entiersou reels, c’est la premiere ou la deuxieme fonction qui est appelee6.

De meme, nous pourrions imaginer une fonction evaluant l’expression ab ou eb suivantque l’on donne 2 ou 1 argument (supposons pour simplifier que b est naturel). Onaurait :

double exp(double a, int b)

5Le compilateur est le programme qui traduit un programme en langage de haut niveau (C++par exemple), en code directement comprehensible par l’ordinateur

6Le compilateur pourra generer le code correspondant a l’appel de la bonne fonction swap enayant analyse le type des parametres e!ectifs

80 CHAPITRE 4. LES FONCTIONS

{double res=1.0;for (int i=1; i <=b; ++i)res*=a;

return res;}

double exp(int b){

const double e=2.71828;double res=1.0;for ( ; b>0; --b)res *=e;

return res;}

Notons que le compilateur refuse toute ambiguite. Par exemple, le programme ci-dessous ne compile pas puisqu’il ne peut determiner s’il faut tronquer 6.28 et appelerla fonction somme entiere ou etendre 3 et appeler la fonction somme reelle.

#include <iostream>using namespace std;

int somme(int i, int j){

return i+j;}

double somme(double r, double s){

return r+s;}

int main(){

cout << "Somme 3 + 6.28 = " << somme(3,6.28) << endl;}

4.3. EXECUTION DU MODULE APPELE 81

Valeurs par defaut

En C++, il est possible d’omettre les derniers arguments d’une fonction lors d’unappel, si ceux-ci ont une valeur par defaut. Pour cela, lors de la definition de lafonction, on precise pour les parametres concernes, la valeur par defaut en ajoutant’=valeur’ a la fin. Ainsi, si l’on a une procedure Print_Car qui ecrit une ligne d’unecertaine taille d’un caractere donne en parametre et que l’on veut que par defaut, laligne ait 70 caracteres, on aura la definition suivante :

void Print_Car(char c, int nb=70){

for(int i=1; i<=nb; ++i)cout << c;

cout << endl;}

Print_Car(’x’,80) imprimera une ligne de 80 caracteres ’x’, tandis quePrint_Car(’%’) imprimera une ligne de 70 caracteres ’%’.

Si de plus on veut que ce soit le caractere ’*’ qui soit imprime par defaut, la definitiondeviendra :

void Print_Car(char c=’*’, int nb=70){

for(int i=1; i<=nb; ++i)cout << c;

cout << endl;}

L’appel par Print_Car() aura alors comme e!et d’imprimer une ligne de 70 aste-risques.

Notons qu’ici aussi, des ambiguites peuvent apparaıtre. Dans ce cas, le programmene compilera pas ; comme avec le programme suivant ou le compilateur ne peutdeterminer quelle fonction doit etre appelee :

#include <iostream>using name space std;

82 CHAPITRE 4. LES FONCTIONS

int somme(double i){

return i+10;}

double somme(double r, double s=7.0){

return r+s;}

int main(){

cout << "Somme(6.28) = " << somme(6.28) << endl;}

Remarque importante :

Lors d’un appel de fonction, aucun argument ne doit etre donne, si cette fonctionn’a pas de parametres ou qu’elle a des valeurs par defaut pour ceux-ci. Dans cecas, le programmeur ne doit pas oublier d’ecrire les parentheses ’()’, comme dansPrint_Car() plus haut. S’il omet les parentheses, cela ne donnera vraissemblable-ment pas d’erreur a la compilation, mais sera vu comme une expression donnantla valeur de l’adresse en memoire de la fonction7 Print_Car (ce qui ne provoqueraaucun appel a la fonction).

4.4 Exercices : Les Fonctions

Ex. 53 Ecrire une fonction a valeur entiere qui calcule x exposant i.

Ex. 54 Ecrire une fonction a valeur booleenne qui teste la parite d’un nombre.

Ex. 55 La distance euclidienne entre 2 points de coordonnees (x1, y1) et (x2, y2) estdonnee par la formule :

s =&

(x1 $ x2)2 + (y1 $ y2)2

Ecrire une fonction qui recoit les coordonnees de 2 points et calcule la distance entreces 2 points (vous pouvez utiliser la fonction sqrt).

7plus precisement, un pointeur vers la fonction ; la notion de pointeur est developpee plus loin

4.4. EXERCICES : LES FONCTIONS 83

Ex. 56 Ecrire une fonction a valeur booleenne qui teste la primalite d’un nombre.

Ex. 57 Ecrire une fonction a valeur entiere qui retourne le nombre de nombrespremiers strictement inferieurs a une valeur fournie.

Ex. 58 Ecrire la fonction swap qui e!ectue l’echange de ses deux parametres entiresa et b.

Ex. 59 Ecrire la fonction Fibonacci qui recoit en parametre 2 nombres de Fibo-nacci consecutifs, disons Fn et Fn+1 pour fixer les idees. Apres l’appel, on souhaitedisposer des 2 nombres Fibonacci suivants, c’est-a-dire Fn+2 et Fn+3.

Ex. 60 Ecrire une fonction qui trie trois variables de maniere croissante. Parexemple, considerons les trois variables sont a, b et c contenant respectivement 5,3 et 7. Apres l’appel a la fonction (trie(a,b,c), par exemple), nous aurons a = 3,b = 5 et c = 7.

Ex. 61 Chercher les erreurs dans les algorithmes suivants :

(a) // Cette fonction retourne la factorielle de nint factorielle(int n);{int i:

for(i=1;i<=N;++i){

n*=i;}return(n);

}

(b) // Cette fonction multiplie par 2 la valeur contenue dans a.void fois_2(int a){a*=2;

}

Ex. 62 Un algorithme pour trouver le plus grand commun diviseur de deux nombresentiers a ete decouvert par Silver et Terzian en 1962. Dans beaucoup de cas, il estplus rapide que l’algorithme d’Euclide.

Etant donne deux nombres entiers a et b, l’algorithme procede en trois etapes :

1. Determiner la plus grande puissance k de 2 qui divise a la fois a et b (ou k estun nombre naturel) ; remplacer a par a/2k et b par b/2k.

84 CHAPITRE 4. LES FONCTIONS

2. A present, a ou b est impair. Si a '= b, faire ce qui suit :

– t -| a $ b |– Si t est pair, remplacer t par t/2. Repeter ceci tant que t est pair.– a - t si a > b, b - t sinon.– Si a '= b, repeter l’etape 2.

3. a present, a = b. Le plus grand commun diviseur des deux nombres donnesvaut 2k · a.

En appliquant cet algorithme a 504 et 420, nous obtenons

a b

504 420252 210126 105

21 10521 21

Reponse : 22 * 21 = 84

Ecrire une fonction Silver_Terzian qui realise cet algorithme.

4.4. EXERCICES : LES FONCTIONS 85

! b: 25 min. !

Ex. 63 Ecrire une fonction qui calcule la valeur approchee de # sur basede la serie suivante. Les calculs s’arretent lorsque la valeur du dernier termeajoute est inferieure a un " donne, ou lorsque le nombre de termes consideresatteint une borne egalement donnee.

#

4= 4 · arctan

'1

5

(

$ arctan

'1

239

(

avec la serie :

arctan(x) =x

1 + x2

'

1+2

x2

1 + x2+

2

3·4

5·(

x2

1 + x2)2+· · ·

(

=x

1 + x2

$%

i=0

ci(x2

1 + x2)i

ou c0 = 1et pour i > 0, ci = 2·i

2·i+1ci!1

Ex. 64 Deux suites (xi) et (yi) sont definies par

y0 = 2

x0 = 1

xi+1 =2

yi+1

yi+1 =yi + xi

2

Nous admettons que les suites convergent toutes les deux vers.

2, avec de plusxi <

.2 < yi. Ecrire une fonction rac2 qui calcule deux approximations superieure

et inferieure de.

2, de di!erence inferieure a " (prevoyez aussi d’arreter les calculssi le nombre d’etapes depasse une borne fixee).

Ex. 65 Soit r un nombre reel positif. Deux suites de nombres reels xn et yn sontspecifiees par :

x1 = 1,y1 = 1,

et pour n & 1 :

86 CHAPITRE 4. LES FONCTIONS

)

xn+1 = xn + r.yn

yn+1 = xn + yn

Il n’est pas di"cile de prouver yn '= 0 pour n & 1, et aussi que le quotient xn

yn

converge vers.

r.

Ecrire une fonction qui calcule la valeur approchee xn

ynde

.r, ou n est la plus petite

valeur pour laquelle la condition suivante est vraie :

(xn)2 < (r + ")(yn)2 et (xn)2 > (r $ ")(yn)2.

(" etant comme d’habitude une valeur donnee). L’execution sera interrompue si lenombre d’etapes devient trop grand.

Ex. 66 On souhaite disposer d’un module tools contenant les fonctions maximum,minimum, abs pour des nombres entiers. On vous demande d’ecrire ce module, lefichier d’en-tete correspondant et un exemple de module principal qui l’utilise.

Ex. 67 Dans certaines conditions, on peut calculer une abscisse ou une fonction fs’annule par la methode de Newton.

Partant d’une approximation initiale x0 (valeur quelconque qu’il vaut mieux prendrepres de l’abscisse cherchee) on calcule les approximations suivantes x1, x2, ..., xi, xi+1

par la formule suivante :

xi+1 = xi $ f(xi)f !(xi)

On s’arrete lorsque l’approximation xi est su"samment precise.

De cette facon on peut calculer la racine nme d’un nombre a. Il faut calculer lavaleur de x pour laquelle la fonction f(x) = xn $ a s’annule.

Ayant f #(x) = n · xn!1 on obtient l’evaluation suivante de x :

xi+1 = xi $xn

i!a

n·xn"1

i

4.4. EXERCICES : LES FONCTIONS 87

On calcule ainsi les approximations successives pour n.

a jusqu’a ce que :

|xin $ a| % " (" = 10!5 par exemple).

Ecrire une fonction racine a valeur reelle qui calcule la racine nme d’un nombre a (areel et n entier donnes en parametre). Cette fonction prend x0 = 1.

! b: 40 min. !

Ex. 68 Ecrire une fonction pyramide qui cree une pyramide de hauteur n(n passe comme parametre) avec les chi!res suivants (a l’aide de bouclesimbriquees) :

123234543456765456789876567890109876

7890123210987890123454321098901234567654321090123456789876543210123456789010987654321

(Ne pas ecrire 11 chaınes de caracteres a plusieurs chi!res).

88 CHAPITRE 4. LES FONCTIONS

Chapitre 5

Definition de types

Jusqu’a present les variables manipulees appartenaient toujours a un des types pre-definis int, double, bool ou char. Dans ce chapitre nous expliquons comment leprogrammeur peut se definir de nouveaux types, et par consequent de nouvellesvariables de ces nouveaux types.

Rappelons qu’un type donne determine l’ensemble des valeurs possibles pour ce typeainsi que les operations possibles sur ces valeurs.

Nous verrons dans ce chapitre et les chapitres suivants que plusieurs “canevas” detype construit sont possibles :

1. Les types simples ou chaque variable peut contenir une valeur simple : les typesint, double, bool ou char sont des types simples. Dans ce chapitre nous allonsen definir d’autres : les types enumeres et pointeurs.

2. Les types non simples homogenes ou chaque variable est une collection devaleurs de meme type. Les tableaux sont des types simples homogenes. Nousverrons par exemple que les tableaux a une dimension sont des vecteurs, lestableaux a deux dimensions sont des matrices.

3. Les classes qui permettent non seulement de se creer des variables (les ’objets’)non simples non homogenes, c’est-a-dire qui peuvent contenir une collection devaleurs de types di!erents mais qui permettent egalement de definir la faconde manipuler ces objets. Les classes ne seront pas vues dans ce chapitre.

4. Les references qui permettent de donner un autre nom (c’est-a-dire un alias)a une entite (constante ou variable, simple ou non).

89

90 CHAPITRE 5. DEFINITION DE TYPES

5.1 Les types enumeres

Cette section est consacre aux types enumeres.

Il est frequent qu’un programme doive “manipuler” des valeurs non numeriques. Parexemple, il se peut que le programme manipule des couleurs.

Une solution est de codifier chaque couleur sous forme d’un nombre. Le programmeainsi construit sera peu lisible puisqu’il faudra en permanence se rappeler a quellecouleur correspond quel code.

Les types enumeres permettent de pallier cet inconvenient. Un type enumere est unecollection de valeurs definies explicitement par le programmeur.

Le programmeur definira, par exemple, le type enumere couleur en donnant ladeclaration suivante :

enum couleur {bleu,blanc,rouge,vert,jaune,noir};

Il ne pourra ensuite se declarer des variables de ce type

couleur c;

Les valeurs (constantes) pour ce type sont : bleu, blanc, rouge, vert, jaune,noir. (non entourees de quotes).

Une variable enumeree ne peut accepter que des valeurs de son type.

Avec la definition donnee plus haut, un programme pourra faire par exemple :

c = bleu ;if (c != rouge)...

De facon generale, un type enumere est defini comme suit :

enum nom {cst1, cst2, . . . , cstn};

ou

5.1. LES TYPES ENUMERES 91

– nom identifie le nouveau type– cst1, cst2 . . . cstn est une liste d’identificateurs donnant les valeurs (les constantes)

pour ce typeet une liste de variables v1, v2, . . . de ce type est declaree par :

nom v1, v2, . . . ;

Notons que si une variable enumeree ne peut recevoir une valeur entiere, l’inverseest possible. Ainsi,

enum couleur {bleu,blanc,rouge,vert,jaune,noir};int i;couleur c;

c=bleu;i=c;

est permis.

Dans ce cas, par defaut, la premiere valeur (bleu) vaudra 0, la suivante (blanc) 1,etc.

De meme, il n’est pas possible de lire ni d’ecrire une valeur de type enumeree ;l’ecriture :

cout << bleu << endl;

ecrira, apres conversion automatique, 0. De facon generale, dans toute expressioncontenant un type enumere, mais ou une valeur entiere convient, la conversion au-tomatique a lieu.

Il est possible de donner explicitement la valeur entiere correspondant a uneconstante enumeree. Par exemple,

enum val {a=100, b=50, c=100, d, e=-10};

declare un type val avec 5 constantes (d vaudra 101) et le test a==c sera verifie.

92 CHAPITRE 5. DEFINITION DE TYPES

Bonne pratique

Lorsque l’on e!ectue dans un programme, des declarations, il vaut mieux declarerd’abord les constantes, ensuite les types et enfin les variables. Nous verrons queles declarations typedef vont permettre de donner des nouveaux noms a des typescompliques (defini par le programmeur).

5.2 Les pointeurs

Chaque variable C++ est stockee en memoire a une certaine adresse memoire. Unprogramme C++ peut travailler avec des variables manipulant des adresses d’autresvariables ou constantes d’un certain type. Ces variables sont appelees des pointeurs.

Ainsi dans l’exemple ci-dessous, p est un pointeur vers un entier qui contient succes-sivement l’adresse de la variable entiere i et l’adresse de la variable entiere Somme.

int i=0,Somme=1;

int *p;

p = &i;...p = &Somme;

Nous dirons que p pointe d’abord vers i, graphiquement :

p 0 i p 0 iou

et ensuite pointe vers Somme.

p 1 Somme p 1 Sommeou

Un pointeur p vers une variable d’un certain type Typ est declare :

Typ *p;

5.2. LES POINTEURS 93

ou l’asterisque prefixe p.

Par ailleurs, l’operateur unaire prefixe ’&’ permet d’obtenir l’adresse d’une variable.Par exemple, &i donne l’adresse de i. Notons que l’exemple precedant aurait puinitialiser p lors de sa declaration (int *p = &i;).

Ayant 2 variables p et q de type pointeur vers un type donne, la valeur de q peutetre assignee a p (p=q). Ainsi, pour l’exemple de la figure 5.1, le resultat au niveaudes correspondances entre variables est donne par la figure 5.2.

const double pi=3.14;int i=0,

j=1;double r=2.71,

s=15.15;int *p;int *q;double *pr;double *ps;const double *pp;

//variable pointeur vers une constante double

p = &i;q = p; //p et q pointent vers ipr = &r;pp = &pi; // OK, pp peut pointer vers une constante double// p = &r est interdit car r est double et p ne peut// pointer que vers un entier// pr = &pi est interdit car pi est une constante et pr// ne peut pointer// que vers une variable double

Fig. 5.1 – Exemple de code manipulant des pointeurs

Notons que dans une declaration de pointeurs, l’asterisque doit prefixer chaque va-riable de type pointeur. Ainsi, int *p, q ; declare deux variables : p pointeur versun entier et q entier ! Pour eviter les erreurs, une bonne pratique est, comme nousl’avons fait dans l’exemple precedant, de declarer chaque variable de type pointeurisolement.

Notons egalement que const double *pp ; donne une variable pointeur vers uneconstante double. Pour avoir une constante pointeur vers une variable double, onecrit double * const pp ;.

94 CHAPITRE 5. DEFINITION DE TYPES

p

q

pr

ps

pp pi

s

r

i0

j1

2.71

15.15

3.14

?

Fig. 5.2 – Correspondances des variables apres l’execution de l’exemple 5.1

Il peut etre interessant de tester si un pointeur pointe vers quelque chose. Pour cela,la valeur NULL peut etre assignee a tout pointeur et specifie que le pointeur ne pointevers rien. Dans le jargon informatique, ayant int *p = NULL ; nous dirons que ppointe vers Nil ou est a Nil ; ou encore, nous parlerons de pointeur NULL. Notons queNULL == 0 et NULL == false. Ainsi, if (p) teste si le pointeur p n’est pas NULL.

Ayant un pointeur p vers une entite i, nous pouvons manipuler cette entite enutilisant l’operateur unaire prefixe * qui renvoit l’objet (variable, constante, . . .)pointe. Cette operation est appelee dereferencing. Ainsi, le code de la figure 5.3imprime 7 -3 7, c’est-a-dire la valeur de i, de j (valant -3 grace a *q = -3 et k(ayant la meme valeur que i grace a *r = *p ;)

int i=7, j, k=9;int *p = &i;int *q = &j;int *r = &k;

*r = *p; // idem k=i*q = -3;cout << *p << " " << j << " " << k << endl;

Fig. 5.3 – Exemple d’utilisation du dereferencing

*p est errone avec p indefini ou p valant NULL. Cette erreur ne sera pas detectee a

5.3. LES REFERENCES 95

la compilation mais produira une erreur a l’execution.

Dans l’exemple de la figure 5.4, la variable ppp de type pointeur vers pointeur verspointeur d’entier est declare et utilise ; le code imprime la valeur 7.

#include <iostream>using namespace std;

int main(){

int x=7;int *p = &x;int **pp = &p;int ***ppp = &pp;

cout << ***ppp << endl;}

Fig. 5.4 – Exemple a plusieurs niveaux de pointeurs

5.3 Les references

La notion de reference a deja ete vue lors de notre etude sur les fonctions avec passagede parametres par reference. Il est possible de se declarer une reference c’est-a-direun nom alternatif pour une variable ou une constante donnee. Ainsi,

int i=7;int &ri = i;

declare une variable entiere i qui peut etre identifiee soit avec i soit avec ri. Gra-phiquement :

i ri 7

Notons que comme la definition d’une reference ne definit pas reellement de nouvellesvariables, cette definition est constante et doit donc etre directement initialisee1.

1Il existe des exceptions ; ainsi, dans le cas du passage de parametres par reference, le lien entrela variable et la reference se fait dynamiquement a chaque appel.

96 CHAPITRE 5. DEFINITION DE TYPES

Note sur C :

Dans le langage C, le passage par reference n’existe pas. La fonction swap vue plushaut peut etre realisee de facon plus compliquee en utilisant le passage par valeurd’adresses comme suit :

void swap(int *pi, int *pj){

int Save = *pi;*pi = *pj;*pj = Save;

}

et pour permuter deux variables entieres x et y, l’appel correspondant est le suivant :

swap(&x, &y);

5.4 Les tableaux

Dans cette section nous decrivons la maniere de definir et de manipuler des variablesde type tableau permettant de traiter des tables d’informations homogenes, c’est-a-dire ou chaque element a la meme structure.

Nous parlerons principalement des tableaux a une dimension, appeles vecteur, eta deux dimensions, appeles matrices, mais donnerons les elements necessaires pourpouvoir utiliser des tableaux a plus de deux dimensions.

Nous avons introduit la notion de variable en comparant celle-ci a un tiroir qui pou-vait contenir une valeur. Si nous etendons cette comparaison, nous pouvons imaginerune variable de type vecteur comme etant une sorte de commode avec une colonnede tiroirs ; chaque tiroir est une variable et peut donc contenir une valeur. Un indicepermet d’identifier proprement chaque tiroir de la commode.

Le type vecteur n’est pas un type C++ predefini. Par contre C++ possede uneconstruction ([]) permettant de se definir un tel type vecteur.

Ainsi la declaration de variable suivante definit un vecteur V ayant 10 composantesentieres, indicees de 0 a 9,

5.4. LES TABLEAUX 97

int V[10];

Schematiquement on pourra representer V comme une variable composee de 10variables de type entier indicees de 1 a 10 et denotees V [0] jusqu’a V [9], commeillustre par la figure 5.5

V? V [0]? V [1]? V [2]? V [3]? V [4]? V [5]? V [6]? V [7]? V [8]? V [9]

Fig. 5.5 – Schema de la variable V du type vecteur

V peut etre indice par n’importe quelle valeur entre 0 et 9. Ainsi si i est une variableentiere valant 3, V[i] denote V[3], V[i+2] denote V[5], V[2*i] denote V[6], . . . .

Le programme ne peut en aucun cas manipuler une composante dont l’indice esten dehors de l’intervalle [0..9]. Une telle composante n’existe en e!et pas.

Par exemple si V a 10 composantes d’indice allant de 0 a 9, le code suivant :

cin >> i;V[i] = 2;

sera erronne lors de son execution si la valeur lue est en dehors de l’intervalle [0..9].Notons que le programme pourrait tres bien ne pas s’arreter mais avoir un compor-tement aleatoire !

Chaque composante de V peut, par ailleurs, etre vue comme une variable, dans cecas ci, de type entier. On peut donc y assigner une valeur entiere et ensuite consulterla valeur de cette composante.

Pour illustrer ces concepts, ecrivons un programme C++ qui, recevant des notesd’etudiants entre 0 et 10 termine par une valeur -1 qui, par convention determine lafin de la liste de notes, compte le nombre d’etudiants ayant chacune des notes.

98 CHAPITRE 5. DEFINITION DE TYPES

Tout d’abord il est interessant de se definir une variable V de type vecteur a 11composantes entieres d’indice variant entre 0 et 10 ; chaque composante V [i] (0 %i % 10) servira de compteur du nombre d’etudiants ayant obtenu la note i. Cescompteurs seront au depart initialises a 0.

On aura donc par exemple :

int V[11];

De plus le programme devra lire des valeurs entre 0 et 10.

La figure 5.6 donne le programme resultant.

A la fin de l’execution du programme le contenu de V pourra, par exemple, etre lesuivant :

V 8 5 7 12 17 25 36 24 8 3 2V [0] V [1] V [2] V [3] V [4] V [5] V [6] V [7] V [8] V [9] V [10]

Remarque

– Le programme de la figure 5.6 a ete parametrise en definissant : une constantesymbolique n donnant la note maximale possible. En definissant n = 20 le pro-gramme pourra compter des notes sur 20 points et cela sans devoir changer des10 en 20 dans d’autres endroits du programme.Parametriser un programme en utilisant des constantes symboliques est une bonnepratique qui permet de modifier certains elements tels que la ‘taille’ du problemea resoudre sans devoir modifier le programme en de nombreux endroits.

– Pour des raisons de lisibilite, il est preferrable de definir chaque vecteur isolement.

Initialisation de vecteurs

Il est possible d’initialiser un vecteur lors de sa declaration grace a une liste devaleurs. Par exemple,

int V[] = {7,7,7,7};int V2[8] = {6,6,6,6};

5.4. LES TABLEAUX 99

#include <iostream>using namespace std;int main()/*

lit des valeurs entre 0 et n etcompte le nombre d’occurrences de chaque valeur

*/{

const int n = 10; // note maximaleint V[n+1];int k;

// Initialisation des compteurs a 0for (int i=0; i<=n; ++i)V[i]=0;

//comptagecin >> k;while (k >= 0){

++V[k];cin >> k;

}

// ecriture des resultatsfor (int i=0; i<=n; ++i)cout << "nombre de " << i << " = " << V[i] << endl;}

Fig. 5.6 – Programme de comptage de notes

declare un vecteur V d’entiers, d’indice variant de 0 a 3 dont chaque composante estinitialisee a 7 et un vecteur V2 a 8 composantes entieres dont les composantes 0 a 3sont initialisees a 6.

Par contre, une telle liste de valeurs ne peut etre utilisee pour initialiser un vecteurdeja declare auparavant. De meme, si V1 et V2 sont deux vecteurs, on ne peut lesassigner ou les comparer en bloc ; les instructions V1 = V2 ou if(V1==V2) ne sontdonc pas permises.

100 CHAPITRE 5. DEFINITION DE TYPES

Representation interne d’un array en C++

En C++, le nom d’un tableau peut etre vu et utilise comme un pointeur vers sonpremier element. Par exemple dans :

int V[]={1,2,3,4};int *p1 = V; // p1 pointe vers V[0]int *p2 = &V[0]; // p2 (comme p1 et V) pointe vers V[0]int *p3 = &V[3]; // p3 pointe vers V[3]

*V = 7; // Equivalent a V[0]=7*p3 = 8; // Equivalent a V[3]=8

Graphiquement, apres l’execution du code on aura :

0 1 2 3

V 7 2 3 8

p1 p2 p3

Notons que l’utilisation de pointeurs pour designer des elements de vecteurs ne rendpas la lecture du programme fort aisee et est a deconseiller formellement.

Une chose importante a remarquer est que, ayant la fonction IncrementeArray dela figure 5.7 qui a 2 parametres, un vecteur V (ou la taille ne doit pas etre specifiee)et un entier specifiant le nombre d’elements du vecteur, le code suivant, modifie levecteur et pas une copie. En e!et, la fonction a 2 variables locales, V qui peut etrevu comme un pointeur ayant la meme valeur que l’argument e!ectif A donne lors del’appel, c’est-a-dire l’adresse de A[0], et la taille de A. A et V pointant vers la memezone, la fonction modifie bien le vecteur.

Utilisation de l’operateur sizeof avec les tableaux

Si V est un tableau de 10 elements de type double et que le type double est stockesur 8 bytes, l’expression sizeof V aura la valeur 80. Ainsi,

sizeof V / sizeof V[0]

5.4. LES TABLEAUX 101

#include <iostream>using namespace std;

void IncrementeArray(int V[],int Taille){

for (int i=0; i<Taille; ++i)++V[i];

}

int main(){

const int n=10;int A[n];

for (int i=0; i<n; ++i)cin >> A[i];

IncrementeArray(A,n);cout << "Resulat" << endl;for (int i=0; i<n; ++i)cout << A[i] << " ";

cout << endl;}

Fig. 5.7 – Exemple simple de transfert d’un vecteur en parametre

donnera le nombre d’elements dans le tableau V.

Les chaınes de caracteres

En C et donc egalement en C++2 une chaıne de caracteres (string en anglais) per-mettant de faire de la manipulation de textes, est en fait represente sous forme d’unvecteur de caracteres. Ainsi la declaration char nom[] = "Jean" ; est stocke en me-moire sous forme d’un vecteur de 5 caracteres, comme represente graphiquement parla figure ci-dessous ; le code ’\0’ (zero) etant ajoute a la fin du string pour marquersa fin.

De nombreuses fonctions predefinies existent en C et C++ pour manipuler des

2En C++, la classe string existe et permet de manipuler des chaınes de caracteres plus propre-ment qu’en C

102 CHAPITRE 5. DEFINITION DE TYPES

0 1 2 3 4

Nom J e a n \0

chaınes de caracteres

5.5 typedef

La declarative typedef permet de simplifier les declarations d’entites en C++. Unedeclaration typedef ressemble a une declaration d’une variable, la seule di!erenceest que le mot-cle typedef precede cette declaration. L’e!et est egalement di!erent :une declaration typedef declare un nouveau nom de type et non pas une nouvellevariable. Ainsi :– typedef double VecteurReel[10] ;

declare que VecteurReel est un nouveau nom pour le type vecteur de 10 variablesde type double,

– typedef char texte[] ;declare que texte est un nouveau nom pour le type vecteur de caracteres.

Une fois ces nouveaux noms de type definis, on peut declarer des variables de cestypes :

VecteurReel V,W;texte T = "coucou";

L’utilisation de typedef pour donner des noms aux types complexes permet desimplifier la lecture des programmes et sera pratiquee systematiquement dans lasuite.

5.6 Definition generale des tableaux

La definition generale de tableau a une dimension en C++ est la suivante :

typedef type des composantes nom[dimension]

ou

5.6. DEFINITION GENERALE DES TABLEAUX 103

– type des composantes est le type de chaque composante. Ce type ne peut pas etreun nom de type non encore defini (Nous supposons que type des composantes estun identificateur donnant le type des composantes).

– nom est le nom du nouveau type tableau– dimension est un entier positif qui donne le nombre d’elements dans tout tableau

de type nom ; ces elements etant indices a partir de 0 jusqu’a dimension -1On peut alors se declarer des variables tableau du type nom comme suit :

nom v1, v2, . . . ;

en donnant eventuellement des initialisations aux variables.

Le programme peut manipuler une composante du tableau a la fois avec V [indice]ou V est le vecteur de type nom et indice est une valeur dans l’intervalle permis.

La declaration de tableaux a plusieurs dimensions a le format suivant :

typedef type des composantes nom[dimension1][dimension2]

ou nom, dimensioni et type des composantes sont definis comme plus haut.

Le chapitre 6 illustre l’utilisation de tels tableaux.

104 CHAPITRE 5. DEFINITION DE TYPES

Chapitre 6

Exemples d’utilisation de tableaux

Pour illustrer la notion de tableau en C++, nous donnons ici quelques exemplessimples de manipulations de vecteurs, afin de se familiariser avec la conception d’al-gorithmes utilisant des vecteurs.

6.1 La lecture et l’ecriture d’un vecteur

Les operations cin >> et cout << C++ n’autorisent pas de respectivement, lire etecrire des valeurs non simples1. En particulier, il n’est pas possible en une seuleinstruction cin de lire un vecteur a n composantes (n, strictement superieur a 1,etant une constante symbolique), de meme qu’il n’est pas possible en une seuleinstruction cout d’ecrire les n composantes d’un vecteur.

La lecture et l’ecriture d’un vecteur devra donc s’e!ectuer composante par compo-sante ; on aura donc :

Lecture Ecriture

cin >> V[i] cout << V[i]

et cela, si l’on suppose que V a des composantes de 0 jusque n $ 1, pour i variantde 0 a n $ 1.

1cout / V avec V un vecteur a pour e!et d’imprimer la valeur de V c’est-a-dire l’adresse deV[0]

105

106 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

Ceci peut etre aisement traduit par une boucle for :

Lecture Ecriture

for (int i=0 ; i<n ; ++i) for (int i=0 ; i<n ; ++i)cin >> V[i] cout << V[i] << " ";

Pour concevoir proprement un tel programme et parce que la lecture et l’ecritu-re de tels vecteurs sont des operations frequemment e!ectuees par toute une seriede programmes, on a interet a definir chacun de ces deux traitements a l’interieurd’une procedure ayant le vecteur comme parametre. Notons que la procedure delecture du vecteur peut passer le vecteur en parametre par valeur etant donne quec’est l’adresse du premier element qui sera copie et non le vecteur lui meme. Il estpreferable, pour informer l’utilisateur et pour eviter les changements errones dansla procedure Ecriture, que cette derniere passe le vecteur en specifiant qu’il estconstant en son sein.

Le programme C++ complet comprenant :– la procedure de lecture d’un vecteur V aux composantes d’indice variant entre 0

et n $ 1,– la procedure d’ecriture d’un tel vecteur,est donnee par la figure 6.1. Notons que le parametre formel Taille est declareen tant que contante dans les fonctions Lecture et Ecriture ; ce qui interdit demodifier sa valeur au sein de ces fonctions.

6.1. LA LECTURE ET L’ECRITURE D’UN VECTEUR 107

#include <iostream>using namespace std;

void Lecture(int V[],const int Taille){

for (int i=0; i<Taille; ++i)cin >> V[i];

}

void Ecriture(const int V[],const int Taille){

for (int i=0; i<Taille; ++i)cout << V[i] << " ";

cout << endl;}

int main(){

const int n=10;int A[n];

cout << "Introduisez les valeurs pour A :";Lecture (A,n);cout << "A= ";Ecriture(A,n);

}

Fig. 6.1 – Lecture et ecriture d’un vecteur

108 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

6.2 La recherche du minimum

Ayant un vecteur contenant des valeurs, un traitement possible consiste a rechercherl’indice de la composante de valeur minimale.

Pour cela, il faut parcourir entierement le vecteur. Pour chaque element, il fauttester si cet element est inferieur a tous les elements deja rencontres. Ceci peutse faire aisement si l’on retient (dans une variable) le minimum rencontre jusqu’apresent, ou plutot sa position c’est-a-dire son indice dans le vecteur.

Au depart le premier element est trivialement le premier minimum des nombres dejainspectes, puisqu’il est le seul inspecte jusqu’a present.

Ensuite pour chaque composante inspectee, on regarde si elle est plus petite quele minimum retenu (via son indice) ; si c’est le cas, on retient l’indice du nouveauminimum provisoire. A la fin des traitements, etant donne que l’on a inspecte toutesles composantes, l’indice retenu est celui du minimum.

Cette fonction IndiceDuMinin est donnee par la figure 6.2. Notons que la dimensiondu parametre formel V n’est pas donnee.

int IndiceDuMin(int V[], const int n)/*

recherche l’indice de l’element de valeur minimaledans le vecteur V

Hypothese: n > 0*/{

int IndiceMinProvisoire=0;for (int i=1; i<n; ++i)if (V[i]<V[IndiceMinProvisoire])IndiceMinProvisoire = i; // retient le nouveau minimum

return IndiceMinProvisoire;}

Fig. 6.2 – Recherche de l’indice de l’element de valeur minimale dans un vecteur

6.3. L’IMAGE MIROIR D’UN VECTEUR 109

6.3 L’image miroir d’un vecteur

L’image miroir d’un vecteur V est un vecteur W tel que

. V [0] = W [n $ 1]

. V [1] = W [n $ 2]...

. V [n $ 1] = W [0]

comme l’illustre la figure 6.3

… … 10 2 n-1 10 2 n-1

W V

Fig. 6.3 – Construction de l’image miroir de V dans W

ce qui peut s’ecrire de facon concise par :

+i : 0 % i % n $ 1 : W [n $ 1 $ i] = V [i]

Ayant declare les deux vecteurs et initialise V , le code C++ construisant l’imagemiroir de V dans W est donne par :

for (int i=0; i<n; ++i)W[n-1-i]=V[i];

Si l’on veut un programme qui calcule l’image miroir de V en place, c’est-a-dire enplacant le resultat dans V et sans utiliser de valeur de travail supplementaire, le codedoit etre modifie. En e!et,

for (int i=0; i<n; ++i)V[n-1-i]=V[i];

ne donne absolument pas le bon resultat !

110 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

… 0 1 n-2 n-1

V

Fig. 6.4 – Construction de l’image miroir de V en place

La construction de l’image miroir de V peut etre vue comme n/2 permutationsV [i] ! V [n $ 1 $ i] (pour 0 % i % n/2 $ 1), comme l’illustre la figure 6.4.

De ce fait, avant d’e!ectuer l’instruction V [n $ 1 $ i] = V [i] il faut sauver la valeurde V [n $ 1 $ i] pour pouvoir l’y mettre dans V [i]. Cela peut s’ecrire :

Save = V[n-1-i];V[n-1-i] = V[i];V[i] = Save;

et comme cette permutation doit se faire pour les valeurs de i entre 0 et n/2 $ 1, lafonction image miroir, ainsi qu’un programme C++ l’utilisant est obtenu et donnepar la figure 6.5.

Notons que si n est pair tous les elements de V sont deplaces, si n est impair,l’element du milieu c’est-a-dire d’indice n/2 n’est pas modifie.

6.3. L’IMAGE MIROIR D’UN VECTEUR 111

#include <iostream>using namespace std;

void Lecture(int V[],int Taille){

for (int i=0; i<Taille; ++i)cin >> V[i];

}

void Ecriture(const int V[],int Taille){

for (int i=0; i<Taille; ++i)cout << V[i] << " ";

cout << endl;}

void Miroir(int V[], int n)/* Construit l’image miroir de V en place */{

for (int i=0; i<n/2; ++i){int Save = V[n-1-i];V[n-1-i] = V[i];V[i] = Save;

}}

int main(){

const int n=10;int A[n];

Lecture (A,n);Miroir(A,n);cout << "A= ";Ecriture(A,n);

}

Fig. 6.5 – Construction en place de l’image Miroir d’un vecteur

112 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

6.4 Le glissement circulaire gauche

Un vecteur V glisse circulairement d’une position vers la gauche, est un vecteur Wtel que

. W [0] = V [1]

. W [1] = V [2]

...

. W [n $ 2] = V [n $ 1]

. W [n $ 1] = V [0]

comme l’illustre la figure 6.6

… 1 20

V

… 10 2 n-1n-2

n-1

W

3

Fig. 6.6 – Construction de W par glissement circulaire de V d’une position

ce qui de facon concise peut s’ecrire :

+i : 0 % i % n $ 1 : W [i] = V [(i + 1)%n]

Si l’on veut un programme qui e!ectue un glissement circulaire gauche de une posi-tion en place, comme dans l’exemple de l’image miroir, il faut faire attention de nepas ecraser des valeurs dont on aura besoin par la suite. Ici, il su"t de sauver V [0]et d’e!ectuer successivement le glissement des elements V [1] jusque V [n $ 1] etenfin de mettre la valeur sauvee dans V [n $ 1], comme l’illustre la figure 6.7.

… 10 2 n-1n-2

V

Fig. 6.7 – Glissement circulaire de V d’une position

La procedure Gliss_Circ_Gauche_de_1 est donnee dans la figure 6.8.

6.5. LA RECHERCHE D’UN ELEMENT 113

void Gliss_Cir_Gauche_de_1(int V[], int n)/*

Glissement circulaire de V d’une position vers la gaucheHypothese: n > 0

*/{

int Save=V[0];for (int i=1; i<n; ++i){V[i-1] = V[i];

}V[n-1] = Save;

}

Fig. 6.8 – Glissement circulaire d’un vecteur d’une position vers la gauche

Notons que le code pour un glissement circulaire droit d’une position necessite unparcours descendant du vecteur, et aurait donc ete :

int Save=V[n-1];for (int i=n-1; i>0; --i)V[i]=V[i-1];

V[0]=Save;

6.5 La recherche d’un element

En general, chaque composante d’un tableau contient plusieurs informations. Unexemple simple est celui du vecteur ou chaque entree contient des informations surune personne tels que son nom, adresse, numero de telephone, etc. Si l’on analysechaque composante, on voit :– qu’une partie sert a l’identifier ; c’est ce que nous appellerons la cle, et– que le reste contient des informations supplementaires ; c’est ce que nous appelle-

rons l’information satellite.Ainsi le nom de la personne pourra etre la cle et son adresse, numero de telephone,. . . l’information satellite, comme l’illustre la figure 6.9.

Nous verrons, quand nous etudierons la notion de classe, la maniere de se definir desvariables contenant plusieurs parties ou chaque partie peut avoir un type di!erentdes autres.

114 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

V [0] Dupont Jean, rue du Moulin 7, 1515 Bruxelles, 7865765, . . .V [1] Durant Jules, rue de la Gare 21, 2718 Namur, 7657859, . . .

...

...

...V [n $ 1]

nom prenom, adresse, numero de telephone, . . .

* +, -

cles* +, -

informations satellites

Fig. 6.9 – Exemple de vecteur ou chaque composante contient une cle et une infor-mation satellite

Nous allons nous interesser ici a la recherche de l’indice d’une composante d’unvecteur dont la cle est donnee. L’algorithme devra, par exemple, trouver l’indicede la composante du vecteur contenant les informations sur Monsieur ou MadameDurant. Notons qu’il se peut que plusieurs Durant soient repertories dans la table ;il faut alors preciser si l’on desire obtenir :– n’importe lequel,– le premier rencontre dans la table (en supposant que l’on fait la recherche sequen-

tiellement en parcourant les composantes dans l’ordre croissant),– le dernier rencontre dans la table,– celui dont le prenom est Jules,– . . . .Nous supposons ici que l’algorithme devra donner l’indice du premier element ren-contre ayant la bonne valeur de cle.

Pour l’instant, nous simplifions le probleme et allons supposer que les composantesdes vecteurs manipules contiennent des cles de type double et n’ont pas d’infor-mation satellite associee. Cette simplification ne modifie fondamentalement pas leprobleme.

Premier essai

Une premiere idee consiste a parcourir entierement le vecteur ; si la composanterencontree est egale a la cle donnee, l’indice de l’element teste dans le vecteur estalors retenu.

La fonction Recherche ainsi obtenue est donnee en figure 6.10 ou V est le vecteur

6.5. LA RECHERCHE D’UN ELEMENT 115

de taille n et x est la cle donnee.

int Recherche(int V[]; int n; double x)/* Renvoie l’indice de la composante de V ayant la valeur x */{

int res;for (int i=0; i<n; ++i)if (x==V[i])res=i;

return res;}

Fig. 6.10 – premier essai de la fonction Recherche

Cet algorithme n’est pas satisfaisant et de facon generale peut meme etre considerecomme faux pour les raisons suivantes :

1. On ne peut pas a priori supposer que x existe dans V. Il se peut donc, si x n’estpas dans V, que res reste indefini.

2. L’algorithme est ine"cace puisque meme quand V[i]==x, il continue la re-cherche ;

3. Si x se trouve plusieurs fois dans V, Recherche renvoie l’indice de la dernierecomposante de V ayant la valeur x et non la premiere.

Solution

– Tout d’abord, il faut se definir une convention pour denoter le resultat d’unerecherche infructueuse. On peut convenir que, si c’est le cas Recherche devrarenvoyer la valeur -1.

– Il su"t alors d’arreter la recherche des que l’on a trouve un element V[i]==x.Il faut donc un test qui verifie- si x==V[i]- si i < n

La fonction Recherche obtenue est donnee en figure 6.11.

Analysons l’algorithme de la Fonction Recherche d’un petit peu plus pres pouren preciser les endroits ou, pour des problemes similaires, le programmeur doit fairespecialement attention et ou un programmeur debutant fera tres souvent des erreurs.– Tout d’abord il ne faut pas oublier le test (i<n). En e!et

for (int i=0; x!=V[i]; ++i) ...

116 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

int Recherche(int V[]; int n; double x)/* Renvoie l’indice de la composante de V ayant la valeur x

Hypothese n > 0*/{

int i;for (i=0; i<n and x!=V[i]; ++i);

if (i==n)i=-1;

return i;}

Fig. 6.11 – Recherche dans V de l’indice de l’element de cle x

est faux si x n’est pas dans V puisque quand i voudra n, le programme testera six != V[n] ce qui ne peut se faire puisque V [n] n’existe pas.

– Ensuite, notons que le code suivant est errone :for (i=0; x!=V[i] and i<n; ++i) ...

ou le test (i<n) est e!ectue apres x != V[i]. En e!et si x n’est pas dans V, i seraincremente jusqu’a valoir n et le test suivant sera alors e!ectue : x !=V[n] andn<n) qui est errone puisque V [n] n’existe pas.

– Enfin notons que pour determiner si x n’est pas dans V, le test e!ectue est :

if (V[i] !=x)

ou encoreif (i==n)

Solution avec un element V [n] disponible

Bien souvent dans une table, le nombre de composantes manipulees varie tout aulong de l’execution du programme : le programme peut creer des entrees, en detruireet manipuler les entrees initialisees.

Malheureusement, il est impossible de se declarer un tableau dont la taille variependant l’execution du programme.

En general, le programme doit donc se reserver un tableau V de max entrees et a uncertain moment de son execution, seules les entrees d’indice variant entre 0 et n $ 1sont initialisees comme illustre par la figure 6.12.

6.6. LE PRODUIT DE MATRICES 117

… 0 1 n

V n-1 max-1

… 3 7 8 ? ?

Fig. 6.12 – Etat du vecteur V au milieu de l’execution du programme

La valeur n n’est donc plus ici une constante, mais varie tout au long de l’execution,ayant une valeur entre 0 (vecteur vide) et max (vecteur rempli).

Le programme principal contiendra, par exemple, les declarations suivantes :

const int max = 100; // taille maximaleint n=0; // n est l’indice de remplissage (du 1er libre)int V[max]; // Initialement V est vide (n==0)

En particulier, on peut s’arranger pour toujours laisser V [n] libre pour pouvoir l’uti-liser comme variable de travail.

Ainsi l’algorithme de recherche precedant e!ectue deux tests pour chaque compo-sante i examinee :– un test pour determiner si x n’est pas egal a V[i]– un test pour verifier que l’on n’est pas en bout de vecteur.Si, au debut de l’algorithme, on place la cle x dans V[n], on est certain de trouverx lors de la recherche, meme si x est di!erent de V[i] pour toute valeur i entre 0 etn $ 1.

Le test i<n peut donc etre supprime dans le for. L’algorithme ainsi obtenu, quisuppose que le sous-vecteur V [0..n $ 1], avec n < max, contient les elements signifi-catifs, est donne en figure 6.13. Il n’e!ectue plus qu’un seul test par element examineet est donc plus e"cace que l’algorithme de la figure 6.11.

6.6 Le produit de matrices

Le produit d’une matrice A(m*!) par une matrice B(!*n) est une matrice C(m*n)ou +i, j 1 % i % m, 1 % j % n, Cij =

$!k=1 Aik.Bkj.

Ceci est aisement traduit par le programme de la figure 6.14 qui lit A et B avantd’appeler Produit de Matrice qui, a partir de A et B, calcule C.

118 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

int Recherche(int V[]; int n; double x)/* Renvoie l’indice de la composante de V ayant la valeur x

Hypothese n > 0*/{

int i;V[n]=x;for (i=0; x!=V[i]; ++i);

if (i==n)i =-1;

return i;}

Fig. 6.13 – Recherche de l’indice dans V de l’element de cle x : version amelioree quiutilise V [n] comme composante de travail

6.6. LE PRODUIT DE MATRICES 119

#include <iostream>using namespace std;

const int m = 2;const int l = 3;const int n = 2;typedef double MatML[m][l];typedef double MatLN[l][n];typedef double MatMN[m][n];

void prod_mat (MatML A, MatLN B, MatMN C,const int m, const int l, const int n)

{for (int i = 0; i < m ; ++i)

for (int j = 0; j < n; ++j){

C[i][j] = 0;for (int k = 0; k < l ; ++k)

C[i][j] += A[i][k] * B[k][j];}

}

int main(){

MatML A;MatLN B;MatMN C;

for (int i = 0; i < m; ++i)for (int k = 0; k < l; ++k)

cin >> A[i][k];for (int k = 0; k < l; ++k)

for (int j = 0; j < n; ++j)cin >> B[k][j];

prod_mat (A, B, C, m, l, n);cout << "A * B = " << endl << endl;for (int i = 0; i < m; ++i){

for (int j = 0; j < n; ++j)cout << C[i][j] << " ";

cout << endl;}

}

Fig. 6.14 – Produit de matrices

120 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

6.7 Exercices : Les tableaux

6.7.1 Tableaux Simples

Ex. 69 Ecrire un programme qui lit une serie de valeurs lues au clavier et qui ensuiteles a"che dans l’ordre inverse. La fin de la serie est marquee par la lecture de lavaleur $1 et on supposera qu’il n’y aura jamais plus de 100 valeurs lues.

Ex. 70 Ecrire la fonction int find_max(int t[],int n) qui renvoie l’indice duplus grand des elements du tableau t, qui est de taille n. On supposera que le tableaun’est pas vide.

Ex. 71 Ecrire une fonction symetrie_vecteur qui opere une symetrie sur les ele-ments d’un vecteur V de dimension n, c’est-a-dire echange la premiere et la dernierecomposante du vecteur, la deuxieme et l’avant-derniere,... (v1, v2, . . . , vn) devient(vn, vn!1, . . . , v1). Vous ne pouvez pas utiliser de vecteur de travail.

Ex. 72 Ecrire une fonction produit_scalaire qui renvoie le produit scalaire dedeux vecteurs de longueur n donnes en parametre (u, v) :

n%

i=1

ui · vi

Ex. 73 Ecrire une fonction qui tasse un vecteur d’entiers : tous les zeros se re-trouvent a la fin, l’ordre des autres nombres ne doit pas etre conserve.

Exemple : (0,1,0,2,0,0,3,4) devient (4,1,3,2,0,0,0,0).

Ex. 74 Ecrire une fonction qui tasse un vecteur d’entiers : tous les zeros se re-trouvent a la fin et l’ordre des autres nombres n’est pas modifie.Exemple : (1,9,0,0,7,8,0,4) devient (1,9,7,8,4,0,0,0).

Ex. 75 Soient deux vecteurs POS et DON de longueur n. Ecrire une fonctionimprime_ind qui a"che les elements de DON dans l’ordre indique par POS (quicontient les entiers de 1 a n).

Exemple (n = 4)

DON 9 6 8 2 POS 2 4 3 1

donne 6 2 8 9.

6.7. EXERCICES : LES TABLEAUX 121

Ex. 76 (Question de l’examen d’Aout 2006) Un nombre premier est un en-tier naturel strictement superieur a 1, n’admettant que deux entiers naturels diviseursdistincts : 1 et lui-meme.

On vous demande d’ecrire un crible, c’est-a-dire une fonction qui trouve tous lesnombres premiers inferieurs a un entier n donne.

La fonction devra avoir la signature void crible(int t[],int n) et remplira letableau t de tous les nombres premiers inferieurs a n. La fin de la liste de nombrespremiers sera marquee par la valeur sentinelle $1. Le tableau t sera suppose su"-samment grand pour stocker n+1 entiers.

Par exemple, apres l’appel crible(t,20), le tableau t aura le contenu suivant :

2 3 5 7 11 13 17 19 $1 · · ·

L’evaluation de votre solution sera principalement basee sur son e"cacite.

Ex. 77 Le coe"cient binomial.

np

/

est la valeur n!p!(n!p)! . Les coe"cients binomiaux

peuvent etre calcule iterativement, sans e!ectuer ni multiplication, ni division, aumoyen des formules suivantes :.00

/

= 1.10

/

= 1.

np

/

= 0 si p > n ou si p < 0.

np

/

=.

n!1p!1

/

+.

n!1p

/

sinon.

La representation classique se fait alors sous la forme d’un triangle, appele Trianglede Pascal.

ligne 0 1ligne 1 1 1ligne 2 1 2 1ligne 3 1 3 3 1ligne 4 1 4 6 4 1ligne 5 1 5 10 10 5 1ligne 6 1 6 15 20 15 6 1ligne 7 1 7 21 35 35 21 7 1

Les coe"cients.

nk

/

, 0 % k % n figurent a la ne ligne. Le triangle est construit enplacant des 1 aux extremites de chaque ligne et en completant la ligne en reportant

122 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

la somme de deux nombres adjacents de la ligne superieure : celui juste au dessus,et celui a sa gauche.

Ecrire une fonction void pascal(vecteur v, int n) qui renvoie dans le vecteurv la ne ligne du triangle de Pascal. Le tableau sera suppose su"samment grand.

Ex. 78 Ecrire une fonction gliss_1_g e!ectuant un glissement circulaire gauched’une position sur un vecteur.

Ecrire une fonction gliss_1_d e!ectuant un glissement circulaire droit d’une posi-tion sur un vecteur.

! b: 40 min. !

Ex. 79 Ecrire une fonction qui e!ectue un glissement circulaire gauche deK positions sur un vecteur. Chaque element ne doit etre deplace qu’uneseule fois et votre fonction ne doit pas utiliser de vecteur de travail.

6.7.2 Tableaux a Deux Dimensions : les Matrices

Ex. 80 Ecrire :

1. une fonction init_mat_m_n qui initialise une matrice m * n a composantesentieres (m et n sont des constantes donnees).

2. une fonction imprime_mat_m_n qui imprime une telle matrice.

Ex. 81 Ecrire une fonction trace a valeur reelle qui calcule la trace d’une matriceA de dimension n * n donnee en parametre :

trace(A) =n

%

i=1

Aii

Ex. 82 Ecrire une fonction qui e!ectue le produit de la matrice A(l * m) par lamatrice B(m * n).

Ex. 83 Rappel : une matrice Aij (i de 1 a n, j de 1 a n) est dite symetrique si elleest egale a sa transposee, c’est-a-dire si :

6.8. EXERCICES : MANIPULATIONS DE CARACTERES 123

+i, j : 1 % i, j % n : Aij = Aji

et antisymetrique si :

+i, j : 1 % i, j % n : Aij = $Aji.

Ecrire les fonctions symetrie et antisymetrie qui testent respectivement la syme-trie et l’antisymetrie d’une matrice carree.

! b: 40 min. !

Ex. 84 Ecrire une fonction rotation qui e!ectue une rotation, en place, de+90% (dans le sens trigonometrique) dans une matrice carree n * n.

0

1

1 2 34 5 67 8 9

2

3 devient :

0

1

3 6 92 5 81 4 7

2

3

6.8 Exercices : Manipulations de caracteres

Ex. 85 Ecrire une fonction length qui renvoie la longueur d’une chaıne.

Ex. 86 Ecrire une fonction qui remplace par le contenu de la variable c, de typecaractere, le k-ieme caractere de s.

Ex. 87 Ecrire une fonction qui renvoie une chaıne formee des n premiers caracteresde s (ou s si n est trop grand).

Variantes :– Une fonction qui renvoie une chaıne formee des n derniers caracteres.– Une fonction qui renvoie une chaıne formee des n caracteres du string s a partir

de la position p (comptee a partir de 0).

Ex. 88 Ecrire une fonction qui renvoie la valeur vrai si et seulement si tous lescaracteres de A se trouvent dans le meme ordre dans B, mais pas necessairement defacon contigue.

124 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

Par exemple :

inclus(’mai’, ’machin’) = vrai ;inclus(’mai’, ’miserable’) = faux.

Ex. 89 Ecrire une fonction MAT_premiers qui met en majuscule la premiere lettrede chaque mot d’une chaıne. On suppose que le string est compose de caracteresalphabetiques et de blancs, ces derniers separant les mots.

Ex. 90 Ecrire un fonction Supprime_double ayant comme parametre un vecteur Vde n composantes de type caracteres, qui supprime, en place, les caracteres identiquesqui se suivent.

Exemple :

hassaaard1!!!!!!!n

devient

hasard1!!!!!!!n

! b: 40 min. !Ex. 91 Soit une chaıne V (|V | % MAXV ) et deux chaınes C et R.On demande d’ecrire la fonction FANDR avec 4 parametres : V, C, R etDROP , qui cherche dans V la chaıne C et en remplace chaque occurrencepar la chaıne R (en decalant si necessaire les autres caracteres de V ). Sides caracteres doivent etre decales apres MAXV , ils sont perdus (il fautneanmoins poursuivre le traitement). L’entier DROP contiendra le nombrede ces caracteres perdus.Variantes :– Apres un remplacement la recherche se poursuit par le caractere suivant le

dernier caractere de remplacement ; seuls les caracteres initialement dansV font l’objet de la recherche.

– Apres un remplacement la recherche se poursuit par le premier caracterede remplacement.

6.8. EXERCICES : MANIPULATIONS DE CARACTERES 125

Ex. 92 Ecrire une fonction qui donne la valeur numerique d’une chaıne de chi!reen base 10.

Variantes : signe - eventuel, point decimal, notation scientifique (e.g. 3.9E+5).

Ex. 93 Ecrire une fonction qui transforme un nombre entier en un string.

! b: 35 min. !Ex. 94 Dans un string, on cherche le maximum des longueurs des suites decaracteres tous di!erents.Exemple 1 :

A B C C D C A B E* +, -

E D F

ABC, DCA, ABE, EDF , . . . sont des suites de caracteres distincts delongueur 3.DCABE est une suite de caracteres distincts de longueur 5, il n’y a pas desuite de longueur superieure a 5 dont les caracteres soient tous distincts. Lareponse est donc 5.Exemple 2 :

, -* +

ABCDE CABF* +, -

A

DECABF est une suite admissible maximale de longueur 6.

126 CHAPITRE 6. EXEMPLES D’UTILISATION DE TABLEAUX

Chapitre 7

Decoupe d’un programme C++

7.1 Definitions et declarations

En C++, pour qu’une entite soit utilisable, elle doit avoir ete definie ou declareeavant.

La di!erence entre une declaration et une definition est que la premiere specifie lenom de l’entite et ses caracteristiques, tandis que la definition implique egalementdes choses telles que de l’allocation memoire, c’est-a-dire le fait de se reserver de laplace memoire pour stocker l’entite, des initialisations, . . ..

Chaque entite doit etre definie une et une seule fois et peut dans certains cas etredeclaree plusieurs fois.

Jusqu’a present, pour qu’une fonction A soit utilisable (appelable), il fallait qu’ellesoit definie avant la fonction B qui l’appelait pour etre connue de cette derniere. Ilest possible de ne pas definir A avant B mais de simplement declarer A en donnantson prototype avant B et de definir A ailleurs, soit plus loin dans le fichier soit dansun autre fichier.

Par exemple, si la fonction main utilise une fonction Print_Car, on pourra ecrire :

#include <iostream>using namespace std;

void Print_Car(char c, int nb); // prototype

127

128 CHAPITRE 7. DECOUPE D’UN PROGRAMME C++

int main(){

...Print_Car(’*’, 70);...

}

void Print_Car(char c, int nb) //definition de Print_Car{

for (int i=1; i<=nb; ++i)cout << c;

cout << endl;}

Notons dans cet exemple que, si la fonction main desire que des valeurs par defautexistent pour Print_Car, c’est dans la declaration du prototype que ces valeurs pardefaut doivent etre donnees et pas (plus) lors de la definition, comme montre dansl’exemple :

#include <iostream>using namespace std;

void Print_Car(char c=’*’, int nb=70); // prototype de Print_Car

int main(){

...Print_Car(’*’);...

}

void Print_Car(char c, int nb) //definition de Print_Car{

for (int i=1; i<=nb; ++i)cout << c;

cout << endl;}

7.2. DECOUPE D’UN PROGRAMME 129

7.2 Decoupe d’un programme

Quand le programme C++ comprend plusieurs fonctions et definitions, generale-ment, on decoupe celui-ci en plusieurs fichiers chacun contenant des parties du pro-gramme et chacun etant compile separement.

Classiquement, le programme est forme de pairs de fichiers de 2 types :

– des fichiers de definitions de fonctions1 qui portent, par convention, le su"xe’.cpp’2 Ces fichiers peuvent egalement contenir des definitions de variables globales,c’est-a-dire definies en dehors d’une fonction.

– des fichiers de definitions de types, constantes et declaration de variables et fonc-tions definies dans le fichier ’.cpp’ correspondant. Ces fichiers portent, par conven-tion, le su"xe ’.h’.

Si, par exemple dans le fichier main.cpp, la fonction main utilise une fonctionprod_mat definie dans un fichier prodmat.cpp, le programmeur aura egalement ecritun fichier prodmat.h contenant la declaration de la fonction prodmat ainsi que lesdefinitions des constantes et types globaux utilises. Ce fichier prodmat.h sera inclu(avec #include "prodmat.h") au debut du fichier main.cpp pour que ce derniersache le type des fonctions definies par ailleurs. Le fichier prodmat.h sera egalementinclu au debut du fichier prodmat.cpp ce qui permettra au compilateur, lors de lacompilation de prodmat.cpp, de verifier si aucune erreur de frappe n’a ete faite entrela declaration et la definition de la fonction prod_mat. Cet exemple est donne enfigures 7.1 et 7.2.

La decoupe en fichiers se fait, si possible, de facon logique en regroupant les defini-tions et declarations par type de variables et fonctions definies.

1, de methodes, constructeurs et destructeurs de classes2En C ces fichiers portent le su"xe ’.c’

130 CHAPITRE 7. DECOUPE D’UN PROGRAMME C++

// fichier prodmat.h

const int m = 2;const int l = 3;const int n = 2;

typedef double MatML[m][l];typedef double MatLN[l][n];typedef double MatMN[m][n];

void prod_mat (MatML A, MatLN B, MatMN C,const int m, const int l, const int n);

// fichier prodmat.cpp

#include "prodmat.h"

void prod_mat (MatML A, MatLN B, MatMN C,const int m, const int l, const int n)

{for (int i = 0; i < m ; ++i)

for (int j = 0; j < n; ++j){C[i][j] = 0;for (int k = 0; k < l ; ++k)C[i][j] += A[i][k] * B[k][j];

}}

Fig. 7.1 – Exemple de decoupe d’un programme en fichiers

7.2. DECOUPE D’UN PROGRAMME 131

// fichier main.cpp

#include <iostream>#include "prodmat.h"using namespace std;

int main(){

MatML A;MatLN B;MatMN C;int arret;

for (int i = 0; i < m; ++i)for (int k = 0; k < l; ++k)

cin >> A[i][k];

for (int k = 0; k < l; ++k)for (int j = 0; j < n; ++j)

cin >> B[k][j];

prod_mat (A, B, C, m, l, n);cout << "A * B = " << endl << endl;for (int i = 0; i < m; ++i){for (int j = 0; j < n; ++j)

cout << C[i][j] << " ";cout << endl;

}}

Fig. 7.2 – Exemple de decoupe en fichiers (suite)

132 CHAPITRE 7. DECOUPE D’UN PROGRAMME C++

Chapitre 8

E!cacite d’un algorithme

Nous avions vu deux versions d’un programme qui trie 3 nombres. Pour de telsprogrammes, il est assez simple de montrer qu’une version est plus e!cace qu’uneautre. Pour des algorithmes plus complexes, le travail est bien souvent moins aise. Lasuccession ou l’imbrication des tests et des boucles et la multitude des possibilitesfait qu’il n’est generalement plus possible ou raisonnable d’evaluer l’e"cacite del’algorithme pour chaque cas possible.

Dans ce chapitre, nous donnerons un apercu des techniques et methodes d’esti-mation de l’e"cacite d’un algorithme. En particulier nous definirons la notation‘grand O’ qui permettra de classer les algorithmes selon leur type d’e"cacite sansdevoir detailler le nombre exact d’instructions executees par l’algorithme.

8.1 Criteres de choix d’un algorithme

Generalement, il existe plusieurs methodes pour resoudre un meme probleme. Il fautalors en choisir une et concevoir un algorithme pour cette methode de telle sorte quecet algorithme satisfasse le mieux possible aux exigences.

Parmi les criteres de selection d’une methode et en consequence d’un algorithme,deux criteres predominent : la simplicite et l’e!cacite de cet algorithme.

Si parfois ces criteres vont de paire, bien souvent ils sont contradictoires ; un algo-rithme e"cace est, en e!et, bien souvent complique et fait appel a des methodes fortelaborees. Le concepteur doit alors choisir entre la simplicite et l’e"cacite.

133

134 CHAPITRE 8. EFFICACITE D’UN ALGORITHME

Si l’algorithme devra etre mis en oeuvre sur un nombre limite de donnees qui nedemanderont qu’un nombre limite de calculs, cet algorithme doit de preference etresimple. En e!et un algorithme simple est plus facile a concevoir et a moins de chanced’etre erronne que ne le sera un algorithme complexe.

Si, par contre, un algorithme est frequemment execute pour une masse importantede donnees, il doit etre le plus e"cace possible.

Au lieu de parler de l’e"cacite d’un algorithme, on parlera generalement de la notionopposee, a savoir, de la complexite d’un algorithme.

La complexite d’un algorithme ou d’un programme peut etre mesuree de diversesfacons. Generalement, le temps d’execution est la mesure principale de la complexited’un algorithme.

D’autres mesures sont possibles dont :

1. la quantite d’espace memoire occupe par le programme et en particulier parses variables ;

2. la quantite d’espace disque (ou bande) necessaire pour que le programme s’exe-cute ;

3. la quantite d’information qui doit etre transferee (par lecture ou ecriture) entrele programme et les disques (ou les bandes) ;

4. ...

Nous n’allons pas considerer de programme qui echange un grand nombre d’infor-mations avec des disques ou des bandes.

En general nous analyserons le temps d’execution d’un programme pour evaluer sacomplexite. La mesure de l’espace memoire occupe par les variables sera un autrecritere qui pourra etre utilise ici.

De facon generale, la complexite d’un algorithme pour un ensemble de ressourcesdonne est une mesure de la quantite de ces ressources utilisees par cet algorithme.

Le critere que nous utiliserons dans la suite est un nombre d’actions elementairesexecutees. Nous prendrons soin, avant toute chose, de preciser le type d’actions prisesen compte et si nous voulons une complexite minimale, moyenne ou maximale.

Nous verrons que, generalement, la complexite d’un algorithme est exprimee par unefonction qui depend de la taille du probleme a resoudre.

8.2. EXEMPLE : LA RECHERCHE DU MINIMUM 135

8.2 Exemple : la recherche du minimum

Le travail necessaire pour qu’un programme s’execute depend de la ‘taille’ du jeu dedonnees fourni.

Par exemple, si nous analysons la fonction Indice du min, , qui recherche l’indice del’element de valeur minimale dans un vecteur V , il est intuitivement normal que cetalgorithme prenne un temps proportionnel a n, la taille du vecteur V , et soit note,par exemple, T (n).

Redonnons ici la partie traitement de cet algorithme en ayant eu soin de transformerle for en while pour mieux detailler chaque etape d’execution de l’algorithme :

i_min_provisoire = 0;i = 1;while (i<n){if (V[i] < V[i_min_provisoire])

i_min_provisoire = i;++i;

}return i_min_provisoire;

//1//2//3

//4//5//6

//7

Pour obtenir des resultats qui restent valables quelque soit l’ordinateur utilise, ilfaut realiser des calculs simples donnant une approximation dans une unite qui soitindependante du processeur utilise. On peut, par exemple, faire l’approximationque chaque assignation et chaque test elementaire representent une unite de tempsd’execution.

D’autres hypotheses auraient pu etre faites. Par exemple, on aurait pu ne considererque les assignations et rechercher la complexite en nombre d’assignations, ou neconsiderer que les tests et rechercher la complexite en nombres de tests e!ectues parl’algorithme.

La complexite depend donc fortement de l’unite prise qui doit etre clairement pre-cisee.

Avec nos hypotheses, les instructions des lignes 1, 2 et 7 prennent chacune une unitede temps d’execution.

La boucle while de la ligne 3 a 6, s’execute n $ 1 fois, mais le test s’e!ectue n fois.La ligne 3 prend n unites et la ligne 6, n $ 1 unites.

136 CHAPITRE 8. EFFICACITE D’UN ALGORITHME

Le test de la ligne 4 prend une unite ; il est e!ectue n $ 1 fois et donc la ligne 4utilisera au total n $ 1 unites.

L’assignation de la ligne 5 prend une unite. Elle n’est e!ectuee que lorsque le testdu if est verifie. Si l’on ne desire pas uniquement faire une seule mesure sur unjeu de donnees bien precis, il faut donc expliciter si l’on desire connaıtre le tempsd’execution dans le meilleur des cas (Tmin(n)), le pire des cas (TMAX(n)), ou enmoyenne (Tµ(n)). Ici :

Tmin(n) est donne dans le cas ou le test du if n’est jamais verifie, ce qui signifie quele minimum se trouve a la premiere composante. On a alors :

Tmin(n) = 1 + 1 + n + (n $ 1) + (n $ 1) + 1 = 3n + 1

TMAX(n) est donne dans le cas ou le test du if est a chaque fois verifie, ce qui corres-pond au cas ou toutes les valeurs de V sont distinctes et triees en ordre decroissant.On a alors :

TMAX(n) = 1 + 1 + n + (n $ 1) + (n $ 1) + (n $ 1) + 1 = 4n

Le calcul de Tµ(n) est generalement beaucoup plus di"cile a e!ectuer. Pour cela,on peut faire des hypotheses sur les jeux de donnees possibles et leur distributionstatistique ; ces hypotheses doivent etre le plus realiste possible. Pour que le calculde Tµ(n) ne soit pas trop complique, on tolere generalement certaines hypothesessimplificatrices. Nous pouvons, par exemple, supposer que toutes les valeurs dans Vsont distinctes et faire l’hypotheses que la distribution des valeurs est uniforme, levecteur etant non trie a priori.

Pour faire ce calcul, separons les actions dues au while, au if et a l’assignationfinale, des actions d’assignation de la variable i min provisoire et posons :

C(n) =le nombre moyen d’assignations a i min provisoirepour un vecteur de taille n

(0)

Si nous posons R(n) = le nombre d’actions dues au while, au if et a l’assignationfinale, ce nombre est connu et vaut :

R(n) = 3n

Nous avons : Tµ(n) = C(n) + R(n)

Pour exprimer la valeur de C(n), regardons d’abord sa valeur pour n valant 1, 2, . . .

Pour n valant 1, C(1) = 1 puisqu’il y a une assignation en debut, et que le corps dela boucle while ne s’execute jamais.

8.2. EXEMPLE : LA RECHERCHE DU MINIMUM 137

Pour n valant 2, le vecteur contient une valeur maximale Max et une valeur minimalemin. Deux possibilites equiprobables peuvent se presenter :

1. V = min Max

2. V = Max min

Le cas 1. donne une assignation et le cas 2. deux assignations, soit en moyenne :

C(2) =3

2

Essayons d’avoir une expression generale pour C(n).

V [0] a une chance sur n d’etre le minimum, une chance sur n d’etre le deuxiememinimum, . . .Donc V [0] a 1

nchance d’etre le ieme minimum et cela pour tout i entre 1 et n.

Si nous posons C(0) = 0 et si dans le sous-vecteur qu’il reste a traiter il y a j ele-ments plus petits que le minimum provisoire, le nombre d’assignations qu’il faudraencore e!ectuer vaudra, d’apres la definition donnee en (0), C(j), car les nombresplus grands que le minimum provisoire n’impliqueront aucune assignation supple-mentaire. Ce raisonnement est valable pour tout i & 1.

En consequence :

C(i) = 1 +1

i

i!1%

j=0

C(j) (+i & 1) (8.1)

le 1 etant du a la premiere assignation (c’est-a-dire a l’instruction //1).

Pour avoir une idee precise de la valeur de C(n), il faut eliminer les C(j) du membrede droite.

Pour cela exprimons (8.1) d’une autre facon en le multipliant par i

i.C(i) = i +i!1%

j=1

C(j) (8.2)

et de meme si on multiplie C(i + 1) par i + 1 :

(i + 1).C(i + 1) = (i + 1) +i

%

j=1

C(j) (8.3)

138 CHAPITRE 8. EFFICACITE D’UN ALGORITHME

En soustrayant l’equation 8.2 de l’equation 8.3 nous obtenons :

(i + 1).C(i + 1) $ i.C(i) = 1 + C(i) (8.4)

Ce qui peut s’ecrire :

C(i + 1) =1

i + 1+ C(i) (+i & 1) (8.5)

Pour C(1) la formule reste vrai (C(1) = 1)

Et donc :

C(n) =1

n+

1

n $ 1+ . . . + 1 =

n%

i=1

1

i(8.6)

Notons que nous aurions pu deduire ce resultat plus rapidement en exprimant queC(i + 1) vaut le nombre moyen d’assignations du a la recherche du minimum pourles i premiers nombres, c’est-a-dire C(i), plus la probabilite que le nombre V [i] soitle minimum, c’est-a-dire 1

i+1 , ce qui nous redonne 8.5.

Pour e!ectuer une approximation de C(n), evaluons les aires S1 et S2 donnes engrise dans la figure 8.1 ou la fonction f(x) = 1

x.

= S 1

= S 2

1

2

f(x) = 1/x

1 2 3 … n n+1 x

Fig. 8.1 – Approximation de C(n)

Ayant4 n+11

1xdx = !n(n + 1), on voit que

S2 < !n(n + 1) < S1

Comme

S1 = 1 +1

2+ . . . +

1

n=

n%

i=1

1

i= C(n)

S2 =1

2+

1

3+ . . . +

1

n + 1=

n+1%

i=2

1

i= C(n + 1) $ 1

8.3. AUTRE EXEMPLE : LA RECHERCHE D’UN ELEMENT 139

et ceci pour tout n & 1.

On a donc :C(n + 1) $ 1 < !n(n + 1) < C(n) (+n & 1)

De ce fait :

C(n) < !n(n) + 1 (+n > 1)

Et finalement, comme !n(n) < !n(n + 1), on obtient :

!n(n) < C(n) < !n(n) + 1 (8.7)

8.7 permet de borner Tµ(n) (rappelons nous que Tµ(n) = C(n) + R(n) avec R(n) =3n)

Nous obtenons donc :

3n + !n(n) < Tµ(n) < 3n + !n(n) + 1 (8.8)

8.3 Autre exemple : la recherche d’un element

Calculons les Tmin(n), TMax(n) et Tµ(n) de l’algorithme de recherche d’un elementdans un vecteur V .

Redonnons ici la partie traitement de cet algorithme :

i = 0;while (i<n && x!=V[i])++i;

if (i==n)i = NOT_FOUND;

return i;

//1//2//3//4//5//6

Tmin(n) est donne lorsque l’on trouve directement l’element.

140 CHAPITRE 8. EFFICACITE D’UN ALGORITHME

Si l’on suppose ici encore que toute assignation et tout test elementaire prend uneunite de temps et que le test du while est compose de deux tests elementaires (letemps pour le && etant omis), on obtient :

Tmin(n) = 5 (c’est-a-dire 1 en //1 + 2 en //2 + 1 en //4 + 1 en //6)

TMAX(n) est donne dans le cas ou l’element x n’est pas dans V .

TMAX(n) = 3n + 5

Tµ(n) depend de la probabilite p que x soit dans V .

En supposant que si x est dans V , il a la meme probabilite d’etre dans n’importequelle composante V [i] (pour 0 % i < n), en enumerant les cas possibles on obtient :

Cas possibles Nombre d’unites

1 : on trouve x en V [0] 3 + 2

2 : on trouve x en V [1] 6 + 2...

n : on trouve x en V [n $ 1] 3n + 2

Si x est dans V , µ = 3n(1 + 2 + . . . + n) + 2

= 3(n+1)2 + 2 (puisque

$ni=1 i = (1+n)n

2 )= 3

2n + 72

Si x n’est pas dans V :min = µ = MAX = 3n + 5

Finalement Tµ(n) = (1 $ p)(3n + 5) + p(32n + 7

2) = (3 $ 32p)n + ($3

2p + 5)

8.4 Le grand O

Les exemples precedants montrent qu’il est souvent tres di"cile de calculer le tempsmoyen d’execution d’un algorithme. Ces calculs precis sont, de plus, inutiles puis-qu’ils se basent sur des approximations grossieres du temps d’execution des actionselementaires ; evaluations qui ne tiennent pas non plus compte, par exemple, del’ordinateur et du compilateur utilises.

8.4. LE GRAND O 141

Une evaluation du temps d’execution d’un algorithme ne devient generalement inte-ressante que lorsque la taille du jeu de donnees, c’est-a-dire de n dans nos exemplesprecedants, est grande. En e!et pour n petit, les algorithmes s’executent generale-ment tres rapidement.

Supposons que la complexite moyenne d’un algorithme A = 100.n, que la complexited’un algorithme B = 15.n2, celle de C = n4 et celle de D = 2n.

La table 8.2 donne les valeurs de complexite pour n = 1, 10, 100, 1000, 106 et 109 ;les figures 8.3 et 8.4 donnent les valeurs des fonction respectivement pour n variantentre 0 et 12, et 0 et 18.

n = A B C D1 100 15 1 210 1000 1500 10000 1 103

100 10000 150000 108 1 1030

1000 100000 15.106 1012 1 10300

10000 106 15.108 1016 1 103000

106 108 15.1012 1024 1 10300000

109 1011 15.1018 1036 1 103.108

Fig. 8.2 – Valeur de 100.n, 15.n2, n4 et 2n pour di!erentes valeurs de n

Ces valeurs doivent etre divisees par le nombre d’instructions elementaires par se-conde pour obtenir un temps exprime en seconde.

Les micro ou mini ordinateurs actuels permettent d’executer de l’ordre de 109 ins-tructions par seconde soit environ 1014 instructions par jour.

Cela signifie qu’en un jour, si aucune autre limitation ne perturbe l’execution desalgorithmes (telle que l’espace memoire disponible par exemple), on peut resoudreavec l’algorithme– A , un probleme pour n 1 1012

– B , un probleme pour n 1 107

– C , un probleme pour n 1 3000– D , un probleme pour n 1 50et donc A est meilleur que B, B est meilleur que C et C est meilleur que D (saufpour de petites valeurs de n).

En vertu des remarques precedantes et de ces chi!res, on prefere donner un ordrede grandeur permettant d’avoir une idee du type d’algorithme. Pour cela, on utilisela notation grand O definie ci-dessous, qui permet :– de determiner si un algorithme a une chance de s’executer pour un n donne,

142 CHAPITRE 8. EFFICACITE D’UN ALGORITHME

0

500

1000

1500

2000

2500

3000

3500

4000

4500

5000

0 2 4 6 8 10 12

Fig. 8.3 – Valeur des fonctions A = ’–’, B = ’- -’, C ’-.-’ et D = ’. . . ’ pour n allantde 0 a 12

– connaissant le temps d’execution pour un n donne, de faire une approximation dece temps pour une autre valeur de n.

La notation grand O est definie comme suit :

Une complexite T (n) est dite en grand O de f(n) (en O(f(n)) s’il existeun entier n0 et une constante c > 0 tels que pour tout entier n > n0 nousavons T (n) % c.f(n)

Cette definition permet d’e!ectuer de nombreuses simplifications dans les calculs.En e!et de cette definition il resulte que :

1. Les facteurs constants ne sont pas importants.En e!et, si par exemple T (n) = n ou si T (n) = 100n, T (n) est toujours enO(n)

8.4. LE GRAND O 143

0

2

4

6

8

10

12

x104

0 2 4 6 8 10 12 14 16 18

Fig. 8.4 – Valeur des fonctions A = ’–’, B = ’- -’, C ’-.-’ et D = ’. . . ’ pour n allantde 0 a 18

2. Les termes d’ordres inferieurs sont negligeables.Ainsi si T (n) = n3 + 4n2 + 20n + 100, on peut voir que pour n > n0 = 5

– n3 > 4n2

– n3 > 20n– n3 > 100

et donc pour n > 5, T (n) = n3 + 4n2 + 20.n + 100 < 4n3.De ce fait T (n) est en O(n3).

144 CHAPITRE 8. EFFICACITE D’UN ALGORITHME

8.5 Calcul du grand O

8.5.1 Classes de complexite

Si on peut calculer qu’un algorithme est un O(n2), il n’est pas interessant, meme sicela est trivialement vrai, d’exprimer le fait que T (n) est en O(n3), O(n4), . . .

Lorsque l’on evalue le grand O d’une complexite, c’est la meilleure estimation “sim-ple” que l’on cherche a obtenir.

Ainsi on classe generalement les complexites d’algorithmes selon leur grand O commedonne par la table 8.1.

Grand O Classe d’algorithmesO(1) constant

O(log n) logarithmiqueO(n) lineaire

O(n logn) n log nO(n2) quadratiqueO(n3) cubiqueO(2n) exponentiel

Tab. 8.1 – Classes de complexite

Dans la suite, nous essayerons toujours de donner une approximation de la com-plexite des algorithmes vus.

Remarquons qu’en vertu du fait que +a, b positifs

loga n = logb n. loga b

et comme loga b est une constante pour a et b fixes, si T (n) est en O(loga n) il estegalement en O(logb n). (la base du logarithme n’a donc pas d’importance pour expri-mer un grand O). Notons encore que ceci n’est pas vrai pour la base des complexitesexponentielles.

8.5.2 Regles de calcul

Donnons ici un petit nombre de regles permettant d’evaluer la complexite d’un algo-rithme. Ces regles ne donnent en general qu’une surapproximation parfois grossiere ;

8.5. CALCUL DU GRAND O 145

une estimation plus precise devant tenir compte du fonctionnement de l’algorithme.

Regle 1 : Il faut clairement definir l’unite utilisee et les elements pris en compte.Ainsi si l’on tient compte des tests elementaires, des assignations, des read etdes write, une assignation a une variable simple, une instruction read ou write,ou l’evaluation d’une expression (ou d’une condition) simple ne donnant paslieu a l’execution de fonctions, prend un temps constant fixe et est donc enO(1). L’assignation a des objets plus complexes (par exemple d’objets d’uneclasse au sens C++) peut ne plus etre en O(1).

Regle 2 : Si un traitement 1 prend un temps T1(n) qui est en O(f1(n)) et untraitement 2 prend un temps T2(n) qui est en O(f2(n)) alors le traitement 1suivit du traitement 2 prend T1(n) + T2(n) et est en

O(f1(n) + f2(n)) = O(max(f1(n), f2(n)))

ou max prend la fonction qui croıt le plus vite c’est-a-dire de plus grand“ordre”.

Par exemple si T1(n) est en O(n2) et T2(n) est en O(n)

T1(n) + T2(n) est en O(n2 + n) = O(n2)

En particulier une sequence d’instructions simples (dont on tient compte) nefaisant aucun appel a une procedure ou a une fonction ne depend pas de n etest donc en O(1).

Regle 3 : Pour unif (condition)

instruction1

elseinstruction2

ou instruction1 est en O(f1(n))instruction2 est en O(f2(n))l’evaluation de la condition est en O(g(n))

(generalement g(n) est en O(1) sauf si l’evaluation de la condition n’impliquel’execution de fonctions)

suivant le test, le if sera en O(max(f1(n), g(n))) ou en O(max(f2(n), g(n)))et peut etre borne par :

O(max(f1(n), f2(n), g(n)))

Cette regle peut etre generalisee pour l’instruction switch.

Regle 4 : Pour un while ou un do ... while, sachant que le corps de la boucleest en O(f1(n)) et que l’evaluation de la condition est en O(f2(n)), si on a unefonction en O(g(n)) qui donne une borne superieure du nombre de fois quele corps sera execute, alors le while ou le do ... while est en O(f(n).g(n))avec f(n) = max(f1(n), f2(n)).

146 CHAPITRE 8. EFFICACITE D’UN ALGORITHME

Regle 5 : Pour une boucle for,for (init; cond; change)

ins

il su"t de “traduire” le for en while :init;while (cond){

inschange;

}Ce qui signifie que, ayant des complexites respectivement O(finit(n)),O(fcond(n)), O(fchange(n)) et O(fins(n)) et ayant le nombre d’iterations bornepar une fonction en O(g(n)), alors le for est en

O(max(finit(n), g(n).max(fcond(n), fchange(n), fins(n))))

Exemples :– for (i = 0; i < 10; + + i}

traitementest en O(10.f(n)) c’est-a-dire en O(f(n))

– for (i = 0; i < n; + + i}traitement

est en O(f(n).n)Ainsi le produit de matrice (M.L par L.N) est en O(M.L.N).

Regle 6 : L’appel a une fonction est en O(f(n)) correspondant a la complexite dutraitement de cette fonction pour les parametres e!ectifs donnes.

On peut ra"ner ces calculs, selon que l’on essaye de trouver une complexite minimale,moyenne ou maximale. Ce ra"nement se situera dans le nombre de fois qu’uneinstruction sera repetee, ayant par exemple certaines hypotheses sur la probabiliteque certaines conditions de if ou de boucles sont verifiees.

8.5.3 Application des regles de calcul

Ayant ces regles d’evaluation du grand O pour chaque type d’instruction, le calculde la complexite d’un algorithme se fait en partant des complexites des instructionssimples, et en calculant, de proche en proche, les complexites des instructions nonsimples a partir des resultats deja calcules. Ces calculs procedent en quelque sortepar des regroupements en suivant la structure de l’algorithme.

Prenons l’exemple de la recherche du minimum c’est-a-dire du traitement suivant(apres traduction en utilisant un while :

8.5. CALCUL DU GRAND O 147

i_min_provisoire = 0;i = 1;while (i<n){if (V[i] < V[i_min_provisoire])

i_min_provisoire = i;++i;

}return i_min_provisoire;

//1//2//3

//4//5//6

//7

Nous supposons que chaque instruction simple est prise en compte pour le calcul dela complexite.

Le traitement, comme tout algorithme, peut etre decompose sous forme d’arbre(informatique) comme donne par la figure 8.5 ou chaque noeud ‘represente’ uneinstruction C++ simple ou composee.

1-7

1 2 3-6 7

4-5

5

6

Fig. 8.5 – Decomposition d’un traitement en suivant la structure

Les noeuds sans fils, appeles les feuilles de l’arbre, correspondant aux instructions 1,2, 6, et 7 representent des instructions d’assignations simples ou return qui, d’apresla regle 1, sont en O(1).

De ce fait, d’apres la regle 3, le noeud 4-5 correspondant a l’instruction if est enO(1).

On peut maintenant evaluer la complexite du while (noeud 3-6), il s’execute n$ 1

148 CHAPITRE 8. EFFICACITE D’UN ALGORITHME

fois et la complexite du corps de la boucle est, comme on vient de le voir, en O(1).Donc d’apres la regle 4, la complexite du while est en O(n).

Finalement, d’apres la regle 2, la complexite de tout le traitement est en O(1 + 1 +n + 1) qui peut etre simplifie par O(n).

Remarques :

1. Si, par exemple, un algorithme A est en O(n) et un algorithme B est en O(n2),A est ‘meilleur’, en terme, de complexite que B.

Par contre si A et B sont tous les deux en O(f(n)), il faut detailler le calculde la complexite pour determiner lequel est plus complexe que l’autre.

Ainsi, on peut voir que les 2 algorithmes de recherche de la position d’unelement x dans un vecteur V , version initiale et amelioree ou on met l’elementrecherche x dans V [n], ont tous deux une complexite en O(n) mais le seconde!ectue moins de tests que le premier et est donc plus e"cace.

2. D’apres la definition du O(), l’algorithme de recherche du minimum est enO(n) et donc trivialement egalement en O(n2), O(n3), . . ., O(nn). Lorsque l’oncherche la complexite d’un algorithme, on essaye bien evidement de donner lavaleur la plus precise (Exemple : O(n) pour la recherche du minimum)

3. Rappelons que l’on peut evaluer plusieurs complexites di!erentes. Par exemple,pour les algorithmes de tris de vecteur, on peut regarder la complexite en termede nombre d’instructions ou seulement regarder les instructions qui e!ectuentdes deplacements d’elements (le deplacement d’un element peut prendre beau-coup plus de temps que les autres instructions). Certains tris sont en O(n2)si l’on regarde toutes les instructions, mais en O(n) en terme de deplacementd’informations.

Chapitre 9

Exemples de complexite

La table 8.1 au chapitre precedant nous a donne quelques complexites courammentutilisees. Nous avons deja trouve des algorithmes en O(n). Nous verrons au chapitresuivant des algorithmes de tri en O(n2).

Les algorithmes de complexite exponentielle constituent une classe importante d’al-gorithmes. L’exemple classique est celui du voyageur de commerce qui doit visiterles capitales des 50 etats des Etats-Unis d’Amerique. Il dispose d’un plan detailledes routes, voies maritimes ou aeriennes entre les capitales, avec le temps mis pourchaque trajet. On lui demande d’e!ectuer le voyage le plus bref possible. Ce pro-bleme, qui se resume a la recherche du plus court chemin passant par les n sommetsd’un graphe, n’admet pas d’algorithme (connu) di!erent d’un algorithme exponen-tiel. (ici en O(nn))

Un algorithme en O(1) est un algorithme independant d’une taille de donnees.

Dans la section suivante nous decrirons un algorithme en O(!n) : il s’agit de l’algori-thme de recherche dichotomique, baptise egalement algorithme de recherche binaire.

9.1 La recherche dichotomique

De facon similaire a la recherche lineaire vue a la section 6.5, cet algorithme e!ectuela recherche de la position d’un element, de cle x donnee, dans un vecteur a nelements indice 0..n $ 1 ; mais ici le vecteur est obligatoirement trie en ordre nondecroissant, ce qui veut dire que pour tout indice i et j du vecteur avec i % j on asurement V [i] % V [j].

149

150 CHAPITRE 9. EXEMPLES DE COMPLEXITE

Definissons-nous une fonction Recherche Dichotomique qui recoit le vecteur V et lacle x et dont le resultat est l’indice, dans V , d’un element quelconque valant x (ou$1 si x n’est pas dans V ).

Sachant que x est suppose etre dans le vecteur V , la recherche s’e!ectue dans la partiedu vecteur dont les indices vont d’une borne inferieure bi a une borne superieure Bsincluses. Initialement bi = 0 et Bs = n $ 1.

La recherche de x dans V entre bi et Bs consiste a regarder l’element milieu d’indicem = (bi + Bs)/2.

1. Soit V [m] < x et il faut recommencer la recherche avec une nouvelle borneinferieure (bi = m + 1).

2. Soit V [m] > x et il faut recommencer la recherche avec une nouvelle bornesuperieure (Bs = m $ 1).

3. Soit V [m] = x et m est l’indice recherche.

Cette recherche se termine soit lorsque V [m] = x soit lorsque l’intervalle de recherche[bi..Bs] est devenu vide. Dans le premier cas m est l’indice recherche sinon, parconvention, le resultat de la recherche vaudra $1.

L’algorithme de la figure 9.1 est une version un peu amelioree du canevas decrit plushaut. Si V [m] n’est pas inferieur a x, il modifie systematiquement Bs. Si V [m] = x,cette modification est inutile mais n’influence pas le resultat. Par contre le nombremoyen de tests de cet algorithme est inferieur a celui propose par le canevas donneplus haut.

En utilisant les regles vues precedement, on peut assez rapidement observer quela complexite moyenne ou maximale en nombre d’actions elementaires de cet al-gorithme est en O(f(n)) ou f(n) est le nombre respectivement moyen (fµ(n)) oumaximum (fmax(n)) de fois que la boucle do est executee.

Pour evaluer fmax(n), commencons par analyser le comportement de l’algorithme.Nous pouvons tout d’abord remarquer qu’a chaque tour de boucle, au pire des cas,l’intervalle de recherche est divise par deux, quelle que soit la valeur de x.

La figure 9.2 donne une evolution possible de l’intervalle de recherche dans V pourun vecteur a 100 elements en supposant que x n’est pas dans V mais que V [40] <x < V [41]

Le do s’execute donc au maximum 7 fois. De facon generale on peut voir que :

2fmax(n)!1 % n < 2fmax(n)

et donc que fmax(n) % log2(n) + 1

9.1. LA RECHERCHE DICHOTOMIQUE 151

const int NOT_FOUND = -1;

typedef int elem;typedef elem Vect[MAX];

int RechercheDichotomique(const Vect &V, int n, elem x)/*

Renvoie l’indice dans le vecteur Vdont les n premieres composantes sont trieespar ordre non decroissant,d’un element de valeur x, ou 0 si x n’est pas dans V

*/{

int bi = 0;int Bs = n-1;int m;

do{

m = (bi+Bs)/2;if (V[m] < x)

bi = m+1;elseBs = m-1;

}while(x != V[m] && bi <= Bs);if (x != V[m])m = NOT_FOUND;

return m;}

Fig. 9.1 – Recherche dichotomique dans le vecteur V trie de l’indice de l’element decle x

La recherche dichotomique a donc une complexite maximale en O(!n n). On peutcalculer que la complexite moyenne (en supposant ou non que x est dans V ) estegalement en O(!n n).

152 CHAPITRE 9. EXEMPLES DE COMPLEXITE

0 10 20 30 40 50 60 70 80 90 99

0 10 20 30 40 48

25 30 40 48

37 48

3 7 41

40 41

41

100

49

24

12

5

2

1

intervalle de recherche nombre d'éléments

Fig. 9.2 – Evolution de l’intervalle de recherche lors d’une recherche dichotomique

9.2 La recherche dans un texte

Comme nous l’avons deja vu au chapite 5, un texte ou, plus precisement, une chaınede caracteres, (appelee string en anglais), peut etre memorisee dans un vecteur decaracteres. Comme pour n’importe quel autre vecteur, on peut manipuler la ieme

composante d’un string s par s[i].

strlen(s) est une fonction qui renvoie la longueur e"ective du string s. Par exemple,si s est declare du type string (par exemple typedef char string[256]) conte-nant ”bonjour”, strlen(s) renverra 7.

La recherche de la position i d’un string s dans un texte (c’est-a-dire un autre string

9.2. LA RECHERCHE DANS UN TEXTE 153

ici) t demande que l’on trouve un indice i % strlen(t) tel que

+j : 0 % j % strlen(s) $ 1 s[j] = t[i + j]

et est donnee, par exemple, par l’algorithme de la figure 9.3

Notons que dans cet algorithme, le test du do ...while provient du fait que n $ irepresente la longueur de ce qui reste dans le texte t a partir de i ; m $ j representela longueur de ce qui reste a comparer dans s, a partir de j ; et si n $ i & m $ j, ilreste encore su"samment de texte pour que la comparaison puisse aboutir.

La complexite de cet algorithme en nombre d’actions, d’assignations et de tests,correspond au nombre de fois que le corps de la boucle do ...while est execute.

En supposant que la longueur de s est plus petite que la longueur de t, la complexitemaximale est en O(n.m).

154 CHAPITRE 9. EXEMPLES DE COMPLEXITE

#include <string.h>const int MAX = 256;const int NOT_FOUND = -1;typedef char string[MAX];

int Inclusion(string s, string t)/*

Renvoie l’indice dans le string t,de l’endroit ou se trouve le string s,ou -1 si s n’est pas dans t

*/{

int m = strlen(s);int n = strlen(t);int i;

if (m != 0 && m <= n){

int j = 0;i = 0;do{

if (s[j] == t[i]){

++i;++j;

}else{

i = i-j+1;j = 0;

}}while (j < m && n-i >= m-j);if (j >= m)

i = i - m;else

i = NOT_FOUND;}else

i = NOT_FOUND;return i;

}

Fig. 9.3 – Recherche dans le texte t de l’indice ou se trouve le string s

9.3. EXERCICES : COMPLEXITE 155

9.3 Exercices : Complexite

Ex. 95 Trouvez la complexite au pire cas du code suivant :

for (int i = 0; i < n; i=i+3)t[i]=0;

Ex. 96 Trouvez la complexite au pire cas de la fonction suivante :

typedef int matrice[dim][dim];void somme(matrice c, matrice a, matrice b, int n){

for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {c[i][j] = a[i][j] + b[i][j];

}}

}

Ex. 97 Trouvez la complexite au pire cas du code suivant :

for (int j=0;j<4;j++)for (int i = 0; i < n; i++)

t[i]*=t[i];

Ex. 98 Trouvez la complexite au pire cas de la fonction suivante :

typedef int matrice[dim][dim];void produit(matrice c, matrice a, matrice b, int n){

for (int i = 0; i < n; ++i) {for (int j = 0; j < n; ++j) {

c[i][j] = 0;for (int k = 0; k < n; ++k)

c[i][j] += a[i][k] * b[k][j];}

}}

Ex. 99 Trouvez la complexite au pire cas de la fonction suivante :

156 CHAPITRE 9. EXEMPLES DE COMPLEXITE

void tri_insertion(int V[]){

int tmp, j;

for (int i = 1; i < n; i++) {tmp = V[i];j = i - 1;while (j >= 0 and V[j] > tmp) {

V[j + 1] = V[j];j--;

}V[j + 1] = tmp;

}}

Ex. 100 Trouvez la complexite au pire cas et minimale de la fonction suivante :

typedef int matrice[dim][dim];bool symetrie(matrice a,int n){

bool sym = true;

for (int i = 0; i < n and sym; i++)for (int j = 0; j < i and sym; j++)

if (a[i][j] != a[j][i])sym = false;

return (sym);}

Ex. 101 Trouvez la complexite au pire cas du code suivant :

int i=0,j=0;

while(j<n){

t[j*n+i]=i;if (i==n-1){j=j+1;i=0;}else

9.3. EXERCICES : COMPLEXITE 157

i++;}

Ex. 102 Trouvez la complexite au pire cas du code suivant :

i = 1;while (i <= n){

j = 1;while (j <= m){cout << j;++j;++i;

}++i;

}

L’hypothese est faite que m<=n.

Ex. 103 Trouvez la complexite au pire cas du code suivant :

for (int i=1; i <= n; ++i){

j = 1;while (j <= m){r = 2*(i+j)+r;++j;

}k = 1;while (k <= l){r = r*k;++k;

}cout << r;

}

Ex. 104 Trouvez la complexite au pire cas du code suivant :

158 CHAPITRE 9. EXEMPLES DE COMPLEXITE

for (int i=1; i<n; ++i){

for (int j=1;j<= n; j = j*2){cout <<j;

}}

Ex. 105 En faisant l’hypothese que n est positif, donner, en termes de grand O etsimplifiee, la complexite au pire cas du code suivant :

int c = 0;

for (int i = n * n; i > 0; i = i / 2)for (int j = n; j > 0; j = j / 4)c++;

cout << c << endl;

Ex. 106 Trouvez les complexites au pire cas et minimale du code suivant, en sup-posant que la fonction boolrand renvoie true ou false avec la meme probabilite etqu’elle est de complexite constante.

for (int i=0; i<n; ++i)v[i] = true;

for(int i=0; i<n-1; ++i)w[i] = boolrand();

w[n-1] = false;flag = true;j = 1;while(flag){

if (v[j]==w[j])for(int i=j; i<n-1; ++i)w[i] = boolrand();

elseflag = false;

++j;}

! b: 10 min. !

9.3. EXERCICES : COMPLEXITE 159

Ex. 107 On se donne la fonction suivante :

int incseq (int V[], int N) {int i = 0, k = 0, maxseq = 0;

while (i < N - 1) {int seq = 1;

for (int j = i; j < N - 1 && V[j] <= V[j + 1]; ++j) ++seq;

if (seq > maxseq) {maxseq = seq;k = i;

}

i += seq;}return k;

}

Donner la complexite au pire cas de l’algorithme en fonction de N.

Ex. 108 (Question de l’examen d’Aout 2006) On se donne la fonction C++suivante :

void binaire (int r[], int n) {for (int i = 1; i < n; ++i) {

int tempr = r[i];int bi = -1;int bs = i;while (bs - bi > 1) {

int j = (bs + bi) / 2;if (tempr < r[j]) bs = j; else bi = j;

}for (int j = i; j > bs; --j) r[j] = r[j - 1];r[bs] = tempr;

}}

1. Que fait cette fonction ? Detailler les di!erentes etapes de l’algorithme.

160 CHAPITRE 9. EXEMPLES DE COMPLEXITE

2. Donner sa complexite au pire cas, sous forme d’un grand O et en fonction dela taille n du vecteur traite.

Chapitre 10

Les tris

Un ordre pour un ensemble S d’elements est une relation R reflexive, transitive etantisymetrique1. L’ordre R est total si pour toute paire (a, b) d’elements de S, aRbou bRa. Sinon, on dit que l’ordre est partiel, ce qui signifie qu’il peut exister deselements a, b tels que ni aRb ni bRa ne sont verifiees.

Trier une collection d’objets est l’operation qui consiste a les ranger selon l’ordredonne. Un tel rangement revient a e!ectuer une permutation des elements pour lesordonner. Generalement la relation % est utilisee comme relation d’ordre.

Le tri peut etre e!ectue– soit par ordre croissant (ou plus precisement, non decroissant), ce qui signifie qu’on

aura pour tout couple d’indice i, j avec i < j : V [i] % V [j]– soit par ordre decroissant (ou plus precisement, non croissant), ce qui signifie qu’on

aura pour tout couple d’indice i, j avec i < j : V [i] & V [j].Generalement un algorithme de tri manipule des elements contenant :– une cle,– une information satellite.Par exemple, on pourrait avoir besoin de trier un tableau contenant des informationssur des personnes :– les noms et prenoms peuvent constituer la cle sur laquelle on e!ectue les compa-

raisons pour trier les valeurs,– les autres informations constituent l’information satellite associee.Ainsi, apres le tri, le tableau contiendra les informations sur les personnes, classeesdans l’ordre alphabetique de leur nom. La figure 10.1 illustre un tel tableau avant etapres le tri.

1Dans certains ouvrages, un ordre est une relation irreflexive, transitive et antisymetrique. Dansce cas nous parlerons d’ordre strict.

161

162 CHAPITRE 10. LES TRIS

Vanbegin Marc Av. Louise Alexandre Eric . . .Dupont Jean rue du Moulin Dupont Jean rue du MoulinPascal Paul . . . ( Dupont Jean rue du ChateauVan Aa Michel . . . Milcamps Rene rue de LiegeDupont Jean rue du Chateau Pascal Paul . . .Milcamps Rene rue de Liege Van Aa Michel . . .Alexandre Eric . . . Vanbegin Marc Av. Louise

Nom Adresse Nom Adresse

Tableau avant le tri Tableau apres le tri

Fig. 10.1 – Tri d’un tableau

Plusieurs remarques peuvent etre formulees a partir de l’exemple de la figure 10.1.

Tout d’abord, la relation d’ordre doit etre precisement definie.Ici nous avons ignore les espaces eventuels et considere que les lettres majusculessont equivalentes aux minuscules.

Ainsi : Van Aa = vanaa < vanbegin = Vanbegin.

Nous pouvons egalement constater que 2 Dupont Jean se trouvent dans la table ;l’algorithme de tri peut, a priori, les classer dans n’importe quel ordre. Dans le casou la table contient 2 entrees de meme cle, il est parfois important que l’algorithmede tri conserve l’ancien ordre relatif : on dira alors que la methode de tri est stable.On avait– en indice 2 <Dupont Jean, rue du Moulin>– en indice 5 <Dupont Jean, rue du Chateau>et donc, si l’algorithme est stable, a la fin, <Dupont Jean, rue du Moulin> doitrester avant <Dupont Jean, rue du Chateau>

Il existe de nombreuses methodes de tri. Dans les sections suivantes nous donnerons4 methodes de tri ; chacune d’entre elles sera un representant simple d’une techniquede tri classiquement utilisee.

D’autres methodes de tri ne seront pas presentees ici car elles font appel a destechniques de programmation non encore etudiees jusqu’a present.

Pour simplifier notre etude et etant donne que, jusqu’a present, nous n’avons pas ex-plique comment declarer un tableau de type vecteur ou chaque composante contientplusieurs informations de type di!erents, nous allons presenter nos algorithmes detri sur des vecteurs V d’entiers (les valeurs de V representant les cles). Les modifi-cations a apporter pour trier des tableaux semblables a celui de la figure 10.1 sontminimes.

10.1. LE TRI PAR SELECTION 163

Notons egalement que les tris presentes ci-dessous rangent les elements par ordrenon decroissant.

10.1 Le tri par selection

Le tri par selection presente ici est l’exemple le plus simple des tris qui utilisent latechnique consistant a selectionner les elements a trier dans un certain ordre, afinde les mettre, un par un, a leur place definitive.

A chaque etape, la selection consiste a reperer un element minimum parmi les ele-ments qui n’ont pas encore ete correctement places, et a le placer a sa positiondefinitive.

Si parmi les elements non encore places correctement, il existe plusieurs valeursminimales, la selection choisit le premier, c’est-a-dire celui qui a un indice minimum,afin que le tri soit stable.

Ainsi, a l’etape 02, la selection choisit un minimum dans tout le vecteur (entre l’indice0 et l’indice n $ 1 inclus), et l’echange avec V [0].

A la deuxieme etape le minimum selectionne dans l’intervalle entre 1 et n $ 1 de Vest echange avec V [1].

Ainsi, au debut de l’etape i, les elements V [0] jusque V [i $ 1] ont deja pris leurplace definitive, et les elements V [i] jusque V [n $ 1] sont non encore tries mais onest assure que toutes les valeurs de V [i] jusque V [n$ 1] sont superieures ou egales atoutes les valeurs V [0] jusque V [i $ 1] incluses et en particulier a V [i $ 1]. Ceci estillustre par la figure 10.2.

elements tries contenant les elements non tries contenant des valeursi $ 1 premiers minima superieures ou egales aux valeurs triees.

0 i $ 1 i n $ 1

Fig. 10.2 – Situation du vecteur au debut de l’etape i du tri par selection

Ainsi apres l’etape n $ 2, le vecteur V est entierement trie puisque V [n $ 1] estsuperieur ou egal aux elements V [0] jusque V [n $ 2] tries.

La figure 10.3 donne la procedure C++ de tri par selection ou V est le vecteur a

2c’est-a-dire la premiere etape ou l’on traite V [0]

164 CHAPITRE 10. LES TRIS

trier et n est une valeur naturelle inferieure ou egale a max donnant les elements atrier (V [0] jusque V [n $ 1])

const int MAX = 10;

typedef int elem;typedef elem Vect[MAX];

void TriSelection(Vect V, int n) // 1/*Trie les n premieres composantes du vecteur Vpar selection*/{

for (int i = 0; i<n-1; ++i) // 2{

elem Save; //utilise pour l’echange

// recherche du min ds V[i..n]int min = i; // min = indice du min provisoire // 3for (int j = i+1; j<n; ++j) // 4

if (V[j]<V[min]) // 5min = j; // 6

//placement du ieme element: echange V[i]<->V[min]Save = V[min]; // 7V[min] = V[i]; // 8V[i] = Save; // 9

}}

Fig. 10.3 – Procedure de tri par selection

Notons que ce tri n’est pas stable.

Essayons de nous assurer que l’algorithme fonctionne correctement meme dans lescas limites.– Si n = 0 ou 1, l’algorithme ne fait rien.– Sinon, si a l’etape i, le i + 1eme minimum est, au depart, en indice i, l’echange

entre V [i] et V [min] ne modifie rien.

10.2. LE TRI PAR INSERTION 165

10.1.1 Complexite du tri par selection

Pour calculer les complexites moyenne et maximale du tri par selection, decomposonsl’algorithme sous forme d’arbre en suivant sa structure ; ceci est donne en figure 10.4.

1-9

1 2-9

4-63 7 8 9

5-6

6

Fig. 10.4 – Decomposition du tri par selection

En supposant que toutes les instructions et les traitements de passages de parametresinterviennent dans ce calcul, en suivant les regles pour calculer cette complexite, nousobtenons– 1, 3, 6, 7, 8 et 9 , sont en O(1)– le if (5-6) est en O(1)– le for (4-6) est en O(n $ i) et peut etre borne par O(n)– le for global (2-9) est en O(n2)– la procedure TriSelection (1-9) est en O(n2)

10.2 Le tri par insertion

La technique consiste a considerer chaque element a trier, un par un et a l’inserer enbonne place relative dans la partie des elements deja tries aux etapes precedentes.

166 CHAPITRE 10. LES TRIS

A chaque etape, l’insertion d’un element est e!ectuee.

Au depart, V [0] est l’element de reference.

A l’etape 1, l’element V [1] est place correctement par rapport a la partie deja triee,c’est-a-dire uniquement V [0].

A la ieme etape, V [i] est donc insere.

La figure 10.5 donne la situation de V au debut de cette ieme etape.

elements tries entre eux partie non encore modifiee0 i $ 1 i n $ 1

Fig. 10.5 – Situation du vecteur au debut de la ieme etape du tri par insertion

La ieme etape peut donc etre decomposee en 3 parties :

1. La recherche de la place j ou doit s’inserer V [i],

2. Le deplacement vers la droite d’une position des elements qui doivent se mettreapres l’element a inserer (car leur valeur est superieure a celle de V [i]),

3. L’insertion de V [i] a sa nouvelle place.

Notons que la nouvelle place de la valeur inseree x n’est pas a priori, sa place defi-nitive puisqu’il se peut que d’autres elements soient inseres, par apres, avant x.

La procedure presentee a la figure 10.6 e!ectue le tri par insertion en fusionnantla partie 2 et 3. En e!et, la recherche de la nouvelle position de l’element a inserer(place temporairement dans Save) se fait dans ce cas-ci lineairement en partant del’indice i $ 1 et en descendant. Tant que V [j] est strictement superieur a l’elementa inserer, on le deplace en V [j + 1] et on decremente j.

L’algorithme fonctionne meme si n = 0 ou 1 (dans ces cas il ne fait rien) et eststable.

De nouveau, on peut voir que le tri par insertion presente ici a une complexitemoyenne et maximale en O(n2). Remarquons qu’ici la complexite minimale corres-pond au cas ou le vecteur est deja trie et est en O(n).

10.2.1 Amelioration du tri par insertion

Dans l’algorithme de tri par insertion presente plus haut, il fallait, a chaque iterationde la recherche de la position du nouvel element, tester pour savoir si l’on n’avait

10.3. LE TRI PAR ECHANGE (BULLE) 167

const int MAX = 10;

typedef int elem;typedef elem Vect[MAX];

void TriInsertion(Vect V, int n)/*Trie les n premieres composantes du vecteur Vpar insertion*/{

for (int i = 1; i<n; ++i) // Insertion de V[i]{

int j;elem Save = V[i]; //utilise pour l’echange// insertion de V[i]for (j = i-1; j>=0 && V[j] > Save; --j)

V[j+1] = V[j];V[j+1] = Save;

}}

Fig. 10.6 – Procedure de tri par insertion

pas atteint le debut du vecteur. Ce test peut etre supprime si V [0] est non utilise,c’est-a-dire si le premier element significatif de V est l’element V [1].

On peut alors mettre l’element a inserer dans V [0] au debut du tri et etre assurede ne jamais vouloir manipuler de composante avant V [0]. De plus, l’insertion seratoujours en V [j + 1] (a la limite V [1]).

L’algorithme du tri par insertion simplifiee est donne en figure 10.7.

Notons que cet algorithme est plus e"cace que le premier presente, mais a toujoursune complexite moyenne et maximale en 0(n2).

10.3 Le tri par echange (Bulle)

La technique d’echange consiste a comparer les elements du vecteur, 2 par 2, et a lespermuter (ou echanger) s’il ne sont pas dans le bon ordre, jusqu’a ce que le vecteur

168 CHAPITRE 10. LES TRIS

const int MAX = 10;

typedef int elem;typedef elem Vect[MAX];

void TriInsertion_2(Vect V, int n)/*Trie les n premieres composantes du vecteur Vpar insertion (V[0] libre) V[1..n] est utilise*/{

for (int i = 2; i<=n; ++i){

int j;V[0] = V[i]; //utilise pour l’echange// insertion de V[i]for (j = i-1; V[j] > V[0]; --j)

V[j+1] = V[j];V[j+1] = V[0];

}}

Fig. 10.7 – Procedure de tri par insertion amelioree

soit completement trie.

Le tri Bulle e!ectue des comparaisons entre voisins.

A la premiere etape, V [0] et V [1] sont compares et eventuellement permutes. Ensuite,l’algorithme compare V [1] et V [2], et ainsi de suite jusque V [n$2] et V [n$1]. Etantparti de V [0] jusqu’a V [n $ 2], on peut voir qu’apres cette premiere etape, V [n $ 1]contiendra le maximum des valeurs de V , le sous-vecteur de V d’indices 0..(n $ 2)contenant les autres valeurs deja legerement reorganisees.

A la 2eme etape, on recommence donc les comparaisons/echanges pour les valeursentre V [0] et V [n $ 2], ce qui aura pour e!et de mettre en place dans V [n $ 2], lesecond maximum et de reorganiser a nouveau legerement les autres valeurs dans lesous-vecteur de V d’indices 0..(n $ 3).

A la ieme etape, on devra donc reorganiser le sous-vecteur de V d’indices 0..(n $ i).

L’algorithme e!ectuant le tri Bulle est donne en figure 10.8.

10.4. LE TRI PAR ENUMERATION (COMPTAGE) 169

const int MAX = 10;

typedef int elem;typedef elem Vect[MAX];

void TriBulle(Vect V, int n)/*Trie les n premieres composantes du vecteur Vpar methode Bulle*/{

for (int i=n-1; i>0; --i) // rearrange V[0..i]for (int j=1; j<=i; ++j)

if (V[j-1] > V[j]){

elem Save = V[j];V[j] = V[j-1];V[j-1] = Save;

}}

Fig. 10.8 – Procedure de tri Bulle

Ce tri est en O(n2) mais est en moyenne moins e"cace que les tris presentes jus-qu’ici. Il existe de nombreuses variantes utilisant cette technique et ameliorant lesperformances de cet algorithme tout en restant en O(n2).

Notons qu’avec l’algorithme donne en figure 10.8, le tri est stable.

10.4 Le tri par enumeration (comptage)

La technique de tri par enumeration consiste, grosso modo, a– compter le nombre ni de valeurs inferieures ou egales a toute valeur V [i] du vecteur

a trier,– sachant que ni valeurs devront etre placees avant la valeur V [i], placer cette der-

niere a sa position definitive.Pour que cette technique soit praticable, il faut :– que l’intervalle des valeurs possibles dans V soit petit,– que l’on puisse travailler avec un vecteur W de meme type que V .

170 CHAPITRE 10. LES TRIS

Si le nombre de valeurs di!erentes possibles est grand (fort superieur a n), l’algo-rithme devient non e"cace et prend trop de place memoire.

Nous supposons ici que les valeurs possibles sont dans l’intervalle [0..m $ 1]. Deplus, pour simplifier, nous supposerons que les composantes significatives de V sontdans l’intervalle [1..n]. Dans ces conditions, l’algorithme doit se reserver un vecteurCount de m composantes entieres (d’indice 0..m $ 1), qui est utilise pour e!ectuerle comptage des valeurs. m ne peut donc pas etre trop grand.

L’algorithme e!ectue plusieurs taches successives :

1. le comptage dans le vecteur Count du nombre de chacune des valeurs possibles,

2. le calcul dans les di!erentes entrees Count[i] du nombre de valeurs inferieu-res ou egales a i : cette etape revient a sommer dans Count[i], les valeurs deCount[0] a Count[i] calculees a l’etape 1.

3. le placement dans W des elements de V de facon triee. Cette etape se basesur le fait qu’apres l’etape 2, si Count[i] = p, un element de valeur i pourra semettre en position p dans W , le suivant en position p $ 1, . . . .

4. le copiage de W dans V pour remettre le resultat dans le vecteur initial.

L’algorithme est donne a la figure 10.9

10.4. LE TRI PAR ENUMERATION (COMPTAGE) 171

const int MAX = 10;const int m = 5;

typedef int elem;typedef elem Vect[MAX];

void TriEnumeration(Vect V, int n)/*Trie les n premieres composantes du vecteur Vpar enumeration*/{

typedef int Compteur[m];

Vect W;Compteur Count;

// 1 Comptage// 1.1 Initialisationfor (int i=0; i < m; ++i)

Count[i]=0;// 1.2 Comptage proprement ditfor (int j=0; j<n; ++j)

++Count[V[j]];

// 2 Cumul des compteursfor (int i=1; i<m; ++i)Count[i] += Count[i-1];

// 3 Placement des elements dans Wfor (int j=n-1; j>=0; --j){W[Count[V[j]]] = V[j];--Count[V[j]];

}

// 4 Recopiage de W dans Vfor (int j=0; j<n; ++j)

V[j] = W[j];}

Fig. 10.9 – Procedure de tri par enumeration

172 CHAPITRE 10. LES TRIS

Notons que :– tel quel, l’algorithme semble curieux puisqu’il n’y a pas d’information satellite

associee aux cles. En e!et dans ce cas il aurait su"t, apres l’etape 1., de remplirV successivement avec Count[i] valeurs i pour i allant de 0 a m $ 1 avec le codesuivant :rempl=1;for(int i=0; i<m;++i)for(int j=0; j<=Count[i];++j){

V[rempl]=i;++rempl;

}Cet algorithme ne fonctionne evidement pas dans le cas general ou les elementsont des informations satellites qui doivent rester associees a leur cle.

– l’etape 3. procede par un for dont la variable de controle a sa valeur qui va endecroisant, ceci pour que le tri soit stable.

Si m < n, la complexite moyenne et maximale de ce tri est en O(n) ce qui estremarquable.

10.5 Autres methodes de tri

Il existe evidemment bien d’autres methodes de tri. Certaines appartiennent a unedes 4 classes presentees dans les sections precedentes (selection, insertion, echangeou comptage) ou sont un melange de plusieurs de ces techniques.

D’autres utilisent des techniques totalement di!erentes : parmi celles-ci on peut citerle tri rapide (Quicksort), le tri par tas (Heapsort) et le tri par fusion (Mergesort).

Ces tris utilisent des techniques de programmation (telles que la recursivite) ou desstructures de donnees (telles que les arbres) non encore etudiees ici.

10.6 Exercices : Les tris de tableaux

10.6.1 Tris simples

Ex. 109 Ecrire une fonction triant un vecteur par selection.

Ex. 110 Ecrire une fonction triant un vecteur par insertion.

10.6. EXERCICES : LES TRIS DE TABLEAUX 173

Ex. 111 Ecrire une fonction triant un vecteur par insertion avec recherche prealabledu minimum.

10.6.2 Tri par enumeration

Ex. 112 Ecrire une fonction triant un vecteur par enumeration (les valeurs possiblespour les composantes : 0 . . . 10).

Pour rappel, le tri par enumeration fonctionne comme suit, en supposant qu’on desiretrier le tableau V :

1. On commence par compter le nombre d’occurrences de chaque valeur du ta-bleau V, dans un tableau count. A la fin de cette etape, count[i] contiendra lenombre d’occurrences de la valeur i dans V.

2. On somme ensuite les valeurs contenues dans count de maniere a ce quecount[i] contienne le nombre de valeurs % i presentes dans V.

3. count permet alors de recopier les valeurs de V dans un nouveau vecteur W,et ce, de maniere triee. Pour ce faire, on parcourt V element par element.Chaque valeur v presente dans V, doit etre placee dans la count[v]e case de W,et count[v] doit etre decrementee pour eviter les collisions.

Commencez par executer a la main le tri par enumeration sur le vecteur V suivant :

V = 4 8 4 2 8 1 4

10.6.3 Tri Shell

Ex. 113 Methode Shell (diminishing increment sort)

Les performances de l’algorithme de l’insertion lineaire sont essentiellement fonctiondu nombre de fois que l’on devra << tourner >> dans le while. Si au depart levecteur A est presque trie, le nombre d’iterations du while est petit et l’algorithmepeut alors etre tres performant. Par contre, si le vecteur de depart est dans unordre tout a fait quelconque, on peut montrer que le nombre de deplacements estproportionnel a n2 pour l’ensemble des n elements. Cela est du au fait que l’on nedeplace les elements que d’une position a la fois dans le vecteur. Donald Shell s’estdit que l’on devrait obtenir de meilleures performances si au lieu de faire des <<sauts >> d’une seule position dans le vecteur, on faisait des sauts plus longs d’unseul coup, la longueur de ses sauts diminuant a chaque grande etape du tri. A laderniere de ces grandes etapes on sera quand meme oblige de comparer des elementssuccessifs (<< saut >> de longueur 1), ce qui revient a la methode precedente,

174 CHAPITRE 10. LES TRIS

mais comme a ce moment le vecteur sera deja presque trie, cette derniere etape seratres rapide. Le fait que l’on compare des elements de plus en plus proches justifie lenom de << methode de l’increment decroissant >> que l’on donne parfois a cettemethode.

Description de la methode :

Soit un vecteur A de dimension n. La premiere etape consiste a le considerer commeetant forme d’un nombre ht de sous-vecteurs imbriques les uns dans les autresqu’on trie independamment, c’est-a-dire qu’on est amene a trier le sous-vecteur(A1, A1+ht

, A1+2ht, ...) puis le sous-vecteur (A2, A2+ht

, A2+2ht, ...) et ainsi de suite jus-

qu’au sous-vecteur (Aht, A2ht

, A3ht, ...). On considere donc bien ht sous-vecteurs dont

les elements sont distants de ht positions :

10.6. EXERCICES : LES TRIS DE TABLEAUX 175

1 2 ht 2ht n

x $ · · · + x $ · · · + x $ · · · · · ·

La maniere de trier ces sous-vecteurs peut etre tout a fait quelconque, nous utilise-rons ici l’insertion lineaire deja etudiee.A l’etape suivante on subdivise (en pensee) A en un nombre ht!1 de sous-vecteurs(avec ht!1 < ht) de la meme maniere et on trie ces ht!1 sous-vecteurs a leur tour.Apres quoi on recommence avec ht!2 < ht!1 sous-vecteurs et ainsi de suite jusqu’aun nombre h1 de sous-vecteurs qui vaudra obligatoirement 1, c’est-a-dire qu’a cettederniere etape on trie d’un seul coup tout le vecteur A par insertion lineaire (etaperapide, voir ci-dessus).Le probleme qui reste a resoudre est le choix des nombres ht, ht!1, ..., h1(= 1). Enprincipe n’importe quelle suite decroissante se terminant par 1 peut convenir maisle choix de la suite peut influencer les performances de l’algorithme. Il semble, em-piriquement, qu’une suite o!rant de bonnes performances soit :

h1 = 1hs+1 = 3 hs + 1 + s & 1

On prendra comme premier element ht de la suite (le plus grand) : ht tel que ht+2 & n.

En resume, pour appliquer cette methode il faut d’abord calculer la sequence desincrements hs(s = 1, 2, ..., t) (ou du moins le ht qui est le premier increment utilise).Ensuite il faut faire une boucle sur les di!erents hs a l’interieur de laquelle il fautfaire une boucle sur les di!erents sous-vecteurs a trier selon l’insertion lineaire.

Remarques :

– La complexite de cette methode ne se justifie que pour n tres grand.– Ce tri n’est pas stable.

10.6.4 Tri Cocktail Shaker

Ex. 114 Le tri bulle fait avancer les plus grands elements vers la fin du vecteur au furet a mesure de l’execution. On obtient donc ainsi une tendance au regroupement desplus petits elements en debut de vecteur. D’autre part, beaucoup de comparaisonsportant sur ces elements sont redondantes d’une iteration a l’autre. Il serait doncinteressant de placer ces petits elements en position definitive et de ne plus lesconsiderer lors d’etapes ulterieures. Pour cela il faut connaıtre une borne inferieureen deca de laquelle le vecteur est definitivement trie, tout comme dans le tri bulle

176 CHAPITRE 10. LES TRIS

on avait une borne superieure au-dela de laquelle le vecteur etait definitivement trie.Pour realiser cela, on e!ectue une etape du tri bulle une fois dans un sens (selon lesindices croissants des composantes), ce qui fournit une borne superieure, puis unefois dans l’autre sens (selon les indices decroissants des composantes), ce qui fournitune borne inferieure.

On continue de la sorte en alternant le sens de parcours du vecteur jusqu’a ce qu’ilsoit completement trie, c’est-a-dire jusqu’a ce que les bornes se rejoignent. C’esta cette alternance de sens de parcours du vecteur que cette methode de tri doitson nom de << cocktail shaker >>. Nous vous demandons de programmer cettemethode.

Remarque :

Les tris << bulle >> et << cocktail shaker >> sont tous deux stables et ont tousdeux un temps d’execution proportionnel a n2.

10.6.5 Tri par ventilation

Ex. 115 Tri par ventilation

Soit une table A composee de n elements du type

cle information satellite

On veut trier A, en ordre croissant, sur cle.

La methode du tri par ventilation s’applique dans le cas ou chaque cle s’exprimecomme une suite de composantes a1, a2, · · · , ak. (Les ai peuvent etre des sous-cles).Ainsi, par exemple, un nombre s’ecrit comme une suite de chi!res compris entre 0et 9, un mot est forme de lettres, etc.

La methode consiste a e!ectuer des tris successifs sur chaque composante de la cle enallant de gauche a droite. Par exemple, en supposant que les cles sont des nombres aquatre chi!res, on e!ectue d’abord un tri sur le chi!re des milliers, puis pour chaqueintervalle ayant le meme chi!re pour les milliers on trie sur le chi!re des centaines,ensuite sur celui des dizaines, et enfin sur celui des unites.

Chapitre 11

Verification d’un algorithme

11.1 Introduction

Jusqu’a present nous avons explique nos algorithmes en decrivant, de facon infor-melle, les di!erents traitements e!ectues par ceux-ci. Nous nous sommes ”assures”de leur bon fonctionnement en les testant sur certains exemples et, en particulier, surdes cas limites. Pour verifier completement, en utilisant des tests, qu’un algorithmefonctionne correctement dans tous les cas, il faudrait, en theorie, le tester avec toutesles donnees possibles1, ce qui, en pratique, est generalement impossible vu le nombreenorme de donnees possibles.

Ce chapitre presente, sur des exemples simples, le probleme de la verification d’unalgorithme. Le probleme general de verification complete d’algorithme est complexeet ne possede pas de methode systematique automatisable. Il est pourtant essentielque le concepteur d’algorithmes soit conscient des problemes et qu’il ait des no-tions de base en matiere de verification d’algorithmes. En e!et, il est evident quela conception et la verification d’algorithmes vont de pair. En particulier, la notiond’invariant de boucle, presentee ci-dessous, est essentielle pour comprendre commentfonctionne un programme.

Dans la suite de ce chapitre, apres avoir fait un bref rappel de logique, nous allonsenoncer le probleme a resoudre en parlant de la notion de preuve partielle et totale,et ensuite, presenter la notion d’invariant de boucle qui, associee a la notion de

1Ceci est vrai si nous nous limitons aux algorithmes sequentiels (une seule action a la fois),deterministes (2 executions de l’algorithme avec les memes donnees, donnent lieu exactement aumeme traitement). Si ce n’est pas le cas, le probleme de test ou de verification d’un algorithme estbeaucoup plus complexe.

177

178 CHAPITRE 11. VERIFICATION D’UN ALGORITHME

terminaison de boucle, permet de verifier un algorithme.

11.2 Rappels de logique

11.2.1 Le calcul des propositions

Syntaxe

Soit un ensemble P de propositions p, q, r, ....

Nous definissons l’ensemble des formules f possibles en logique des propositions parles definitions en Backus Naur Form2 (BNF) suivante :

f ::= p (+p 2 P)f ::= ¬ff ::= f op ff ::= (f)

avec

op ::= "| # | 3 | - | 4

(" le ou inclusif, # le et, 3, - et 4 les implications).

Par exemple ; p # q est une formule valide puisque la definition BNF permet lasequence de reecriture suivante :

– f– f op f– f # f– p # f– p # q

2Ce formalisme permet de definir un langage ; par exemple l’ensemble des syntaxes possibles pourune formule logique ou pour un programme C++. Les definitions donne un systeme de reecritureou la notation f ::= p signifie f peut etre reecrit en p. Quand f peut etre reecrit en p ou q on ecritf ::= p , f :: q ou de facon plus compacte f ::= p|q.

11.2. RAPPELS DE LOGIQUE 179

De meme les fomules suivantes peuvent etre obtenues :

p # (q " r)(p 3 q) 4 ¬r

Semantique

Une formule peut avoir la valeur vraie ou fausse suivant la valeur des di!erentespropositions. Nous dirons que le domaine semantique d’une formule est l’ensemble{T, F} (Vrai ou Faux). Nous parlons d’interpretation d’une formule, c’est-a-direson evaluation, en ayant donne une valeur T ou F a chaque proposition.

La semantique de chaque operateur logique est definie grace a sa table de veritecomme explique dans un chapitre precedant.

Par exemple : pour l’interpretation v ou v(p) = F et v(q) = T

v(p # q) = Fv(p 4 q) = Fv(p 3 q) = T

Pour eviter de parentheser completement les formules nous posons :

¬ < ",# < -,3,4

ou < signifie est plus prioritaire que.

Une formule logique est consistante si elle est vraie pour au moins une interpretation,et est valide ou tautologique si elle elle vraie pour toutes les interpretations.

Deux formules f1 et f2 sont (logiquement) equivalentes (denote f1 5 f2 ou 5 est unmeta-operateur) si pour toute interpretation v, v(f1) = v(f2) c’est-a-dire si f1 4 f2

est toujours vrai.

Par exemple, p " q 5 q " p

5 est moins prioritaire que les autres operateurs logiques.

180 CHAPITRE 11. VERIFICATION D’UN ALGORITHME

Notons qu’une equivalence logique reste valable si l’on substitue partout une propo-sition par n’importe quelle formule (regle de substitution uniforme).

La liste suivante donne quelques equivalences logiques representatives :

1. ¬¬f 5 f

2. f # f 5 f 5 f " f

3. f1 # f2 5 f2 # f1

4. f1 " f2 5 f2 " f1

5. f1 4 f2 5 f2 4 f1

6. f1 # (f2 # f3) 5 (f1 # f2) # f3

7. f1 " (f2 " f3) 5 (f1 " f2) " f3

8. T # f 5 f

9. T " f 5 T

10. F # f 5 F

11. F " f 5 f

12. f1 3 f2 5 ¬f1 " f2

13. f1 3 T 5 T

14. f1 3 F 5 ¬f1

15. T 3 f2 5 f2

16. F 3 f2 5 T

17. ¬(p " q) 5 (¬p) # (¬q)

18. ¬(p # q) 5 (¬p) " (¬q)

Enfin, si f1 3 f2 est valide (toujours vraie) nous disons que f1 implique logiquementf2 ou que f1 est une condition su"sante pour f2 et que f2 est une condition necessairepour f1 (notation f1 ( f2 ou ( est un meta-operateur).

( a la meme priorite que 5.

11.2.2 La logique des predicats

Pour pouvoir exprimer des contraintes sur des variables de programmes, il faut ajou-ter aux formules logiques, la possibilite d’utiliser des variables simples ou indicees(array), d’e!ectuer des manipulations d’expressions arithmetiques et relationnelles.

11.2. RAPPELS DE LOGIQUE 181

De plus il faut ajouter les quantificateurs + (pour tout) et 6 (il existe). Le formatgeneral d’une formule resp. + et 6 est :

+x f(x)6x f(x)

ou f(x) est une formule qui utilise la variable x, variable qui est liee par le quantifi-cateur.

Par opposition, les variables simples ou indicees qui ne sont pas liees par un quan-tificateur sont nommees variables libres.

Par exemple dans :

x < y + 1 : x et y sont libresx = 7 4 y = 3 : x et y sont libres+x (x · y = 0 3 x = 0) : x est lie par +x et y est libre+x (x · y = 0 3 x = 0 " y = 0) : x est lie par +x et y est libre

+ et 6 sont moins prioritaires que ¬ mais sont plus prioritaires que ",#,-,3 et 4et les meta-operateurs ( et 5. Notons que l’avant derniere formule est consistante(peut etre vraie) tandis que la derniere est valide (est vraie pour toute interpreta-tion).

De meme, la formule +x (0 % x < 0 3 x = 7) est valide (toujours vraie) puisque0 % x < 0 est toujours fausse.

Cette logique est la logique des predicats ou logique du premier ordre.

Les notions d’equivalence et d’implication logiques sont etendues. La liste suivantedonne quelques equivalences logiques representatives ou f(x), f1(x) et f2(x) sont desformules utilisant la variable libre x :

1. +x f(x) 5 ¬6x ¬f(x)

2. 6x f(x) 5 ¬+x ¬f(x)

3. Si le domaine des valeurs possibles pour x n’est pas vide :+x f(x) ( 6x f(x) (sinon ce n’est pas vrai)

4. 6x (f1(x) " f2(x)) 5 (6x f1(x) " 6x f2(x))

5. +x (f1(x) # f2(x)) 5 (+x f1(x) # +x f2(x))

182 CHAPITRE 11. VERIFICATION D’UN ALGORITHME

11.3 Preuve partielle et totale

Etant donne qu’il est, en pratique generalement impossible de tester completement lebon fonctionnement d’un algorithme, il faut se rabattre sur des methodes analytiquespour le verifier.

Essayons de prouver que le calcul du pgcd par la methode d’Euclide, donne a lafigure 11.1 donne le bon resultat en supposant qu’au depart x & 0 et y > 0.

int pgcd(int x, int y)// calcule le pgcd de 2 nombres entiers positifs{

while (y > 0){

int Save = y;y = x % y;x = Save;

}return x;

}

Fig. 11.1 – Calcul du pgcd de 2 entiers positifs

Deux problemes doivent etre resolus. Il faut s’assurer :

1. que pour tout couple de donnees (x, y) possible la fonction pgcd se termine,

2. que si cette fonction se termine, elle donne le bon resultat.

Nous dirons que pour faire la preuve totale d’un algorithme, il faut verifier le point2, c’est-a-dire en faire sa preuve partielle et prouver le point 1, c’est-a-dire verifierla terminaison.

Pour un algorithme sans boucle ni appel de fonction, la preuve totale est relative-ment simple puisque l’on peut suivre, instruction par instruction, son evolution endecoupant les di!erents cas possibles suivant les tests e!ectues tout au long de sonexecution.

Le probleme provient des boucles ou le meme code peut etre utilise plusieurs fois, lenombre de repetitions variant suivant les donnees.

Pour e!ectuer la preuve partielle d’une boucle, il faut tout d’abord trouver uninvariant de boucle c’est-a-dire une a!rmation ou assertion vraie a chaque fois quel’execution du programme atteint le debut de chaque iteration de la boucle3 En par-

3L’invariant d’un while est vrai avant chaque fois que la condition est evaluee.

11.3. PREUVE PARTIELLE ET TOTALE 183

ticulier, l’invariant est vrai au debut de l’execution de la boucle si l’execution duprogramme atteint cet endroit.

De plus pour e!ectuer la preuve partielle, cet invariant, compose avec le fait que lacondition C de continuation du while devienne fausse, doit permettre de verifier quela boucle a e!ectivement “calcule” le resultat R qui lui etait demande (I #¬C ( R)

Essayons de degager une interpretation intuitive de la notion d’invariant. Si l’onregarde l’ensemble des situations dans lesquelles se trouve un algorithme au debutde chaque iteration d’une boucle, on remarque que les situations, evoluent tout enayant des points communs. E!ectivement la boucle est une facon de realiser de faconiterative un certain traitement qui ne peut etre realise en une fois. L’invariant seraune facon d’exprimer que le resultat escompte (a la terminaison de la boucle) estegal a ce qui a deja ete realise lors des precedantes iterations de la boucle plus cequi reste encore a realiser. Au debut de la boucle, ce qui est realise est vide, on auraalors Resultat = Ce qui reste a realiser. A la terminaison de la boucle, la conditiond’arret de la boucle permet de verifier que ce qui reste a realiser est vide. On auraalors Resultat = Ce qui est realise et la preuve partielle pour la boucle sera realisee.

Invariant d’une boucle : a"rmation ou assertion I, vraie a chaque foisque l’execution du programme atteint le debutde la boucle (au debut et apres chaque iteration).Cette a"rmation doit permettre de verifier (s’ily a lieu) qu’a la sortie de la boucle, le resultatescompte apres la boucle a e!ectivement ete at-teint

Par exemple, pour un while(C) il faut que I # ¬C ( R (l’assertion formalisant leresultat). Pour exprimer l’invariant de la boucle while de la fonction pgcd, il faututiliser des notations permettant de distinguer les valeurs initiales de x et de y deleur valeur courante.

Nous denoterons xi, yi les valeurs respectivement de x et y apres la ieme iteration,(initialement x = x0, y = y0) et x, y leur valeur courante.

L’invariant vaut alors :

Invariant : (pgcd(x0, y0) = pgcd(x, y)) # (x & 0) # (y & 0)

Cette formule exprime le fait que pour calculer pgcd(x0, y0) il reste a calculer pgcd(x, y)(ici on peut oublier ce qui a deja ete realise). Le progres provient du fait qu’il estplus simple de calculer pgcd(x, y) que pgcd(x0, y0).

Verifier un invariant se fait par recurrence. Pour le pgcd,

184 CHAPITRE 11. VERIFICATION D’UN ALGORITHME

Base :

Initialement, l’invariant est trivialement vrai puisque x = x0 et y = y0 avec x0 ety0 & 0.

Induction :

Si la formule est vraie au debut de la ieme iteration et que la boucle e"ectueune iteration supplementaire alors l’invariant est vrai au debut de la i+1eme

iteration.En e!et, on peut montrer que

x > 0 # y > 0 ( pgcd(x, y) = pgcd(y, x%y)

De plusx > 0 # y > 0 ( x%y & 0

Et donc, etant donne qu’apres chaque iteration, les nouvelles valeurs de x et yvalent respectivement l’ancienne valeur de y et l’ancienne valeur de x modulol’ancienne valeur de y (ce que l’on peut noter xi = yi!1 , yi = xi!1%yi!1) on voitque l’invariant est vrai au debut de la i + 1eme iteration.

Pour completer la preuve partielle il faut montrer que l’invariant et la terminaisonimplique le resultat.

En e!et :

(pgcd(x0, y0) = pgcd(x, y)) # (x & 0) # (y & 0) # (y % 0) ( pgcd(x0, y0) = x

Enfin, pour completer la preuve totale il faut encore montrer que la boucle se terminee!ectivement.

Ayant x0 > 0 et y0 & 0 on peut voir qu’a chaque iteration, la valeur (entiere) ydiminue d’au moins une unite. De ce fait le test (y > 0) sera assurement faux unmoment donne.

En general, on verifie qu’une boucle se termine toujours en mettant en evidence unefonction de terminaison, c’est-a-dire une fonction f entiere qui depend de la valeurdes variables et telle que :– la valeur de la fonction decroıt strictement d’au moins une unite a chaque itera-

tion ;– si l’invariant de la boucle est vrai, f est positif ou nul.

11.4. EXEMPLES DE PREUVES D’ALGORITHMES 185

L’invariant etant par definition vrai au debut de chaque iteration de la boucle, on estalors assure de la terminaison de celle-ci. Comme pour la recherche d’un invariantcorrect, la recherche d’une telle fonction de terminaison ne possede pas de methodesystematique.

11.4 Exemples de preuves d’algorithmes

Cette section analyse brievement di!erents algorithmes vus precedemment et donneles di!erents invariants des boucles.– Dans la fonction de recherche de l’indice du minimum, pour trouver l’invariant de

la boucle for, il faut traduire ce for en un while. On obtient le code suivant :i_min_provisoire = 0;i = 1;while(i < n){

if (V[i] < V[i_min_provisoire])i_min_provisoire = i;

++i;}

et l’invariant du while permettant d’e!ectuer la preuve totale de l’algorithme estdonnee ci-dessous :

I , 0 < i % n # +j (1 % j % i $ 1 3 V [i min provisoire] % V [j])

Notons bien que I reste vrai meme quand la condition du while (i<n) devientfausse. Une fonction de terminaison qui, associee a l’invariant permet de faire lapreuve totale, vaut :

n $ i

– Dans la procedure Miroir, apres avoir traduit le code en :i = 0;while (i < n/2){int Save = V[n-1-i];V[n-1-i] = V[i];V[i] = Save;++i;

}

186 CHAPITRE 11. VERIFICATION D’UN ALGORITHME

l’invariant permettant de faire la preuve totale vaut :

I , 0 % i % n/2# +j (0 % j < i 3 (V1[j] = V0[n $ 1 $ j] # V1[n $ 1 $ j] = V0[j]))# +j (i % j % n/2 3 (V1[j] = V0[j] # V1[n $ 1 $ j] = V0[n $ 1 $ j]))

ou V0[k] et V1[k] denotent respectivement la valeur initiale et la valeur courantede V [k]. La fonction de terminaison vaut :

n/2 $ i

– Le code suivant donne le tri par selection. Les commentaires donnent les invariantsdes boucles.

11.4. EXEMPLES DE PREUVES D’ALGORITHMES 187

void TriSelection(Vect V, int n)/*Trie les n premieres composantes du vecteur Vpar selection*/{

/* invariant 2:0 % i < n #+k (0 % k < i $ 1 3 V [k] % V [k + 1]) #+m +k (0 % m % i $ 1 # i % k < n 3 V [m] % V [k])

*//* fonction de terminaison 2: n-1-i */for (int i = 0; i<n-1; ++i){

elem Save; //utilise pour l’echange

// recherche du min ds V[i..n]int min = i; // min = indice du min provisoire/* invariant 1:

(i < j % n) # +k (i % k < j 3 V [min] % V [k])*//* fonction de terminaison 1: n-j */for (int j = i+1; j<n; ++j)

if (V[j]<V[min])min = j;

//placement du ieme element: echange V[i]<->V[min]Save = V[min];V[min] = V[i];V[i] = Save;

}}

– Le code suivant donne le tri par insertion. Les commentaires donnent les invariantsdes boucles

188 CHAPITRE 11. VERIFICATION D’UN ALGORITHME

void TriInsertion(Vect V, int n)/*Trie les n premieres composantes du vecteur Vpar insertion*/{

/* invariant 2:0 < i % n #+k (0 % k < i $ 1 3 V [k] % V [k + 1])

*//* fonction de terminaison 2: n-i */for (int i = 1; i<n; ++i){

int j;elem Save = V[i]; //utilise pour l’echange// insertion de V[i]/* invariant 1:

$1 % j % i $ 1 #+k (j + 2 % k % i 3 V [k] > Save)

*//* fonction de terminaison 1: j */for (j = i-1; j>=0 && V[j] > Save; --j)

V[j+1] = V[j];V[j+1] = Save;

}}

11.5 Exercices : Les invariants

Ex. 115bis Exprimez en logique du premier ordre les afirmations suivantes (voussupposerez que tous le tableaux sont de taille N) :

1. La variable v est positive ou nulle.

2. La variable v contient une valeur comprise entre 5 et 8 (bornes incluses).

3. Si la variable v est positive, alors la variable w est negative ou nulle.

4. Le tableau V ne contient que des valeurs positives ou nulles.

5. Au moins une case du tableau V contient une valeur superieure ou egale a 5.

6. La variable v contient la somme des elements du tableau W.

7. La variable v contient la somme des i premiers elements du tableau W.

8. Les i premiers elements du tableau V sont inferieurs a 6.

11.5. EXERCICES : LES INVARIANTS 189

9. Tous les elements du tableau V sont inferieurs ou egaux a max.

10. max est la valeur maximale contenue dans le tableau.

11. Le tableau V contient toutes des valeurs di!erentes.

12. Le tableau V est trie (de facon croissante).

13. Les i premieres cases du tableau V sont triees (de facon croissante).

14. Les cases comprises entre les indices k1 et k2 du tableau V sont triees.

15. Si la variable i est positive, les i premieres cases du tableau V sont triees defacon croissantes ; sinon, le tableau V est initialise a 0.

16. La partie du tableau V comprise entre les indices k1 et k2 est triee, et il n’existepas de plus grande partie (en terme de nombre de cases) du tableau qui soittriee.

Ex. 116 Donnez formellement l’invariant de la boucle suivante (on suppose que met n sont positifs) :

i = 0;j = 0;

while (i != n){

j += m;++i;

}

Ex. 117 Soit le morceau de code suivant (on suppose que n est > 0) :

int v[n];int w[n];int i=0, s=0;

while(i<n){

s += v[i]*w[i];++i;

}

– Expliquez en francais ce que fait cette boucle.– Exprimez a l’aide d’une formule logique la precondition et la postcondition de la

boucle.

190 CHAPITRE 11. VERIFICATION D’UN ALGORITHME

– Donnez-en l’invariant.– Servez-vous de ces informations pour demontrer que la boucle fait bien ce que

vous aviez prevu (en supposant qu’elle se termine).

Ex. 118 Meme question pour la boucle suivante (On suppose cette fois que max > 0et v [0] = 0) :

sum = v[0];i = 0;

while (sum < max and i < n){

sum += v[i];++i;

}

Ex. 119 Soit le morceau de code suivant : Donnez formellement l’invariant de laboucle suivante et montrez que cette boucle se termine.

// n > 0i = 1;a = 0;b = 1;

while (i < n){

++i;b += a;a = b-a;

}

– Que calcule ce morceau de code ?– Quels sont la post-condition et l’invariant de la boucle ?– Supposons que cette boucle se termine. Demontrez qu’elle calcule bien ce que vous

aviez prevu.– Enfin, prouvez que cette boucle se termine.

Ex. 120 A quoi sert la boucle suivante ?

q = 0;r = j1;

11.5. EXERCICES : LES INVARIANTS 191

while (r >= j2){

++q;r -= j2;

}

Demontrez que cette boucle est correcte (qu’elle fait bien ce que vous venez depredire). Pour ce faire, calculez d’abord l’invariant et la postcondition du while.

Ex. 121 Voici la fonction plus :

int plus (unsigned int x, unsigned int y){

while(y>0){++x;--y;

}return x;

}

Demontrez que cette boucle calcule bien la somme de x et y dans x. Pour ce faire,aidez-vous de l’invariant du while, et de la fonction de terminaison.

Ex. 122 La boucle suivante est censee imprimer la valeur maximale de v. Demontrezque c’est e!ectivement correct en utilisant l’invariant de la boucle.

i = 0;

while (i < n-1){

if (v[i] > v[i+1]){save = v[i];v[i] = v[i+1];v[i+1] = save;

}++i;

}

cout << v[n-1];

192 CHAPITRE 11. VERIFICATION D’UN ALGORITHME

! b: 40 min. !

Ex. 123 (Question de janvier 2003) On se donne la fonction suivante,qui fait l’hypothese que le vecteur V passe en parametre est trie.

int RD (int V[], int n, int x) {int bi = 0, bs = n - 1, m = (n - 1) / 2;

while (bs >= bi && V[m] != x) {if (V[m] < x) bi = m + 1;else bs = m - 1;m = (bi + bs) / 2;

}

if (x != V[m]) m = -1;

return m;}

– Quelle est l’utilite de cette fonction ?– Donner l’invariant de la boucle.– Demontrer la terminaison de la boucle.

11.5. EXERCICES : LES INVARIANTS 193

Ex. 124 (Question d’aout 2000) Soit l’algorithme suivant :

const N = 200;const Max = 1000;int max_val, max_nb;int T[N];/* le vecteur est initialise avec des valeurs* aleatoires entre 0 et MAX*/for (int i=0; i<N; i++) T[i] = my_random(0, Max) ;max_val = -1 ;max_nb = 0 ;for (int i=0; i<N; i++) {

if (T[i] > max_val) {max_val = T[i];max_nb = 1;

}else if (T[i] == max_val) max_nb++;

}

– Exprimez par une formule logique du premier ordre le resultat de l’algorithme(c’est-a-dire l’e!et qu’il a sur max_val et max_nb).

– Donnez l’invariant de cet algorithme. Justifiez votre reponse en montrant quel’invariant est preserve au fil des iterations, qu’il est verifie en debut d’iterationsi la pre-condition est verifiee (Pre ( I) et qu’il implique la post-condition si lacondition de sortie de boucle est verifiee (i = N # I ( Post)

Ex. 125 Voici la fonction incseq :

int incseq (int V[], int N) {int i = 0, k = 0, maxseq = 0 ;

while (i < N - 1) {int seq = 1 ;

for (int j = i; j < N - 1 && V[j] <= V[j+1]; ++j) ++seq ;

if (seq > maxseq) {maxseq = seq ;k = i ;

}

194 CHAPITRE 11. VERIFICATION D’UN ALGORITHME

i += seq ;}

return k ;}

– Decrivez l’e!et de cette fonction. Exprimez sa valeur de retour en fonction desvaleurs de ses parametres.

– Donnez formellement la postcondition de cette fonction.– Donnez l’invariant de la boucle principale (Question de l’examen de juin

2003).– Concluez-en que la fonction a bien l’e!et que vous aviez prevu (en supposant

qu’elle se termine)– Prouvez-en la terminaison.

Chapitre 12

Allocation dynamique de memoire

12.1 Introduction

Jusqu’a present, nous n’avons defini que des structures de donnees, (c’est-a-dire desensemble de donnees ayant une certaine structure manipulable par des operationsdeterminees), de taille fixe. Par exemple, lors de la declaration d’un tableau, lataille de ce tableau est constant. Cela nous a oblige par exemple, a declarer desvecteurs de taille maximale et d’avoir un indice de remplissage indiquant le nombred’elements significatifs dans le vecteur. Ainsi, dans l’exemple 12.1, le vecteur aura100000 elements mais en pratique le nombre reel d’elements significatifs peut etreegal a 10 (nombre n lu) ; 99990 elements etant reserves inutilement.

Les instructions new et delete permettent de faire de l’allocation dynamique de la

#define MAX 100000typedef Vecteur int[MAX];...Vecteur V;int n;

cin >> n;for (int i=0; i<n; ++i)

cin >> V[i];...

Fig. 12.1 – Utilisation d’un vecteur statique

195

196 CHAPITRE 12. ALLOCATION DYNAMIQUE DE MEMOIRE

memoire, et ainsi d’eviter cet inconvenient. De facon plus generale, ces 2 instructionspermettent de programmer des structures de donnees dynamiques.

Les structures de donnees dynamiques c’est-a-dire dont la structure et/ou le nombred’elements evoluent au cours de l’execution, occupent une part importante de l’al-gorithmique. Le chapitre consacre aux listes presentera en details, un premier typede structures de donnees dynamiques.

Avant cela, nous etudions les instructions new et delete et la facon dont la memoirevive de l’ordinateur est allouee a un programme C++ qui s’execute.

12.2 New et delete

L’utilisation de pointeurs permet d’utiliser l’instruction new dont le but est de sereserver de la memoire pour le programme. L’instruction new a deux syntaxes pos-sibles :

new type datanew type data [size]

ou type data est un type de donnees defini et size une valeur entiere positive. L’e!etde l’instruction est de creer une nouvelle variable ou un vecteur de size variablesde type type data. Cette variable ou ce vecteur est anonyme mais son adresse estrenvoyee comme valeur de l’instruction new. Cette adresse peut etre assignee a unevariable de type pointeur vers une variable type data, ce qui permet par la suited’y acceder. Les variables creees lors de l’execution du new sont appelees variablesdynamiques par opposition aux variables statiques etudiees jusqu’a present.

Ainsi les instructions de la figure 12.2 declarent deux variables p et V de type pointeurvers un entier et creent ensuite une variable entiere et un vecteur de n composantesentieres, tous deux anonymes, avec p (resp. V) qui pointe vers cette variable entiere(resp. ce vecteur). V peut ensuite etre manipule comme un simple vecteur ; la di!e-rence entre ce vecteur V et celui de l’exemple 12.1, est qu’ici la dimension de V dependde la valeur qu’a la variable n au moment du new. L’instruction new permet donc icide se reserver un vecteur dont la taille est calculee lors de l’execution, contrairementaux declarations de vecteurs statiques.

Notons que dans l’exemple precedant, n, p et V sont des variables statiques tandisque les variables pointees par p et V sont des variables dynamiques. La notationcrochet V[i] est synonyme de *(V+i) c’est-a-dire la variable dont l’adresse est i

12.2. NEW ET DELETE 197

...int n;int *p, *V;...cin >> n;

p = new int;V = new int[n];

*p = 7;

for (int i=0; i<n; ++i)cin >> V[i];

...

Fig. 12.2 – Exemple d’utilisation simple de l’instruction new

positions plus loin que l’adresse V. Notons que cette correspondance est immediateetant donne que les vecteurs sont indices en C++ a partir de 0.

Contrairement aux variables statiques que nous avons utilisees jusqu’a present et quisont declarees par exemple en debut d’un bloc, les variables dynamiques, creees lorsd’un new, n’ont pas une duree de vie liee au bloc dans lequel elles sont declarees. Unevariable dynamique existe jusqu’a la terminaison du programme ou jusqu’a ce quele programme la desalloue (libere la place qu’elle occupe en memoire) explicitementgrace a l’instruction delete qui, suivant qu’il s’agisse d’une variable simple ou d’unvecteur, a deux syntaxes possibles :

delete var namedelete[] var name

ou var name est le nom de la variable de type pointeur contenant l’adresse de lavariable dynamique qu’il faut desallouer.

L’instruction delete var name indique aux routines de run time1 que la memoiren’est plus utilisee par le programme et pourra donc etre reallouee lors d’un newulterieur.

Notons que apres un delete var name, le pointeur var name n’est pas modifie etdoit eventuellement etre explicitement mis a NULL pour indiquer qu’il ne pointe plus

1gestionnaire d’execution en francais, c’est-a-dire l’ensemble des fonctions appelees lors desdi!erentes taches elaborees du programme, comme un appel a une fonction, un new, un delete ...

198 CHAPITRE 12. ALLOCATION DYNAMIQUE DE MEMOIRE

int n;int **M;...cin >> n;...

// allocation et initialisationM = new int*[n];

for(int i=0;i<n;i++){

M[i]= new int[n];for (int j=0; j<n; ++j)M[i][j]=0;

}...//desallocationfor(int i=0;i<n;i++)

delete[] M[i];delete[] M;

Fig. 12.3 – Allocation dynamique et initialisation d’une matrice a n * n

vers une variable.

Allocation de tableaux a plusieurs dimensions

L’instruction new ne permet pas directement de se reserver une variable tableau aplusieurs dimensions. Dans le cas d’un tableau a 2 dimensions, comme l’illustre lafigure 12.3 pour un matrice n * n, la technique classique consiste a se reserver unvecteur de pointeurs et pour chacun d’eux de se reserver un vecteur d’elements.Cette technique peut etre generalisee pour n’importe quel nombre fini constant dedimensions.

12.3. NOTIONS DE PILE ET TAS 199

12.3 Notions de pile et tas

Avant de parler de gestion de la memoire lors de l’execution d’un programme C++,il faut introduire les structures de donnees pile (stack en anglais) et tas (heap). Ene!et, la gestion memoire utilise ces structures de donnees.

12.3.1 Pile (Stack)

Pour comprendre comment fonctionne une pile (en informatique) il su"t de s’imagi-ner une pile d’assiettes ou l’on ne peut prendre que l’assiette du dessus et mettre uneassiette au dessus ; prendre une assiette au milieu ou en bas de la pile est interdit.Lors de sa creation, la pile S ne contient aucune donnee. Nous disons que la pile estvide. Ensuite, la pile peut etre manipulee par 4 operations :

push(elem) : qui met un nouvel element elem au sommet de la pile au dessus deselements qui etaient deja presents

pop() : qui enleve l’element au sommet de la piletop() : qui donne la valeur de l’element au sommet de la pile (supposee non vide)empty() : qui teste si la pile est vide.

Normalement aucune autre operation n’est possible sur une pile. Si l’on prend unepile dans laquelle on a mis 4 elements dans l’ordre A, B, C et D, nous avons– D au sommet de la pile ; c’est le premier element qu’on peut enlever et c’est

l’element dont on peut actuellement consulter la valeur,– C juste en dessous de D (il faudra enlever D pour pouvoir le consulter et l’enlever),– B sous C,– et enfin A au bas de la pile (c’est donc le dernier element qui devra etre enleve

pour que la pile soit vide).Dans le jargon informatique, nous disons qu’une pile gere les elements dans une poli-tique du dernier entre - premier sorti. L’abreviation LIFO est generalement utilisee.LIFO est l’acronyme de Last In First Out2.

12.3.2 Tas (Heap)

Contrairement a la pile, tous les elements d’un tas sont directement accessibles ;aucun ordre n’est defini entre eux et on peut a tout moment rajouter un element

2L’autre politique classique de gestion d’elements est la gestion FIFO (First In First Out) oules elements sortent dans l’ordre ou ils sont entres

200 CHAPITRE 12. ALLOCATION DYNAMIQUE DE MEMOIRE

et enlever n’importe lequel present sur le tas3. Un tas est donc ici un ensembled’elements sans structure.

12.4 Gestion de la memoire lors de l’executiond’un programme

La memoire allouee a un programme C++ ,et la plupart des langages imperatifsmodernes comme Pascal, C, Java, Ada, ...., peut etre subdivisee en :

– une partie statique contenant le code des fonctions du programme ainsi que leslitteraux (constantes globales) et variables globales statiques dont la duree de vieest celle du programme,

– la pile (stack) run-time qui, en particulier, va contenir toutes les variables statiqueslocales aux di!erents blocs du programme,

– le tas (heap) run-time qui contient toutes les variables allouees dynamiquementpar un new.

Au debut de l’execution d’un programme C++, la partie statique contenant le codeest chargee et de la place est reservee pour la pile et le tas run-time ; cette placepouvant etre adaptee suivant les besoins durant l’execution du programme.

Ensuite, a chaque fois que l’execution entre dans un nouveau bloc d’instructions oua chaque nouvel appel de fonction, un bloc de donnees (frame en anglais) est missur la pile run time. Cette frame contiendra entre autre :

– de la place pour l’ensemble des variables statiques declarees localement dans cebloc

– de la place pour l’ensemble des variables temporaires utilisees par le code pourfaire les calculs intermediaires dans les evaluations d’expressions

Pour les fonctions, les informations suivantes sont egalement mises dans la frame :

– une image du contexte appelant (valeurs des registres au moment de l’appel) avecen particulier l’adresse de retour au module appelant,

– les parametres e!ectifs, sous forme de variables locales supplementaires pour lesparametres transmis par valeur, ou d’adresses pour les parametres transmis parreference.

3En algorithmique, on rajoute parfois des priorites ou un ordre determine aux elements d’untas. (voir par exemple de tri par tas (heapsort) dans le cours d’algorithmique generale)

12.4. GESTION DE LA MEMOIRE LORS DE L’EXECUTION D’UN PROGRAMME201

int main() //1{ //2

int i,j; //3int *p, *q; //4cin >>i; //5p = new int[i]; //6if (i==3) //7{ //8int *r; //9int t=4; //10r = q = new int[i]; //11

} //12delete[] p; //13...

} //14

Fig. 12.4 – Exemple illustrant la gestion de la memoire lors de l’execution d’unprogramme

A chaque sortie d’un bloc ou d’une fonction, la frame correspondante, qui se trouveobligatoirement au sommet de la pile run-time, est enlevee. Le nouveau sommet de lapile correspond a la frame du bloc qui avait “appele” le bloc qui vient de se terminer(c’est-a-dire soit le bloc englobant soit le module appelant).

Remarque : les instructions push et pop de la pile run-time correspondent donca l’ajout et au retrait d’une frame au sommet de la pile. Par contre la consultationdes elements de la pile run-time ne se limite pas a un acces a l’element en sommetde pile. En e!et, le code C++ ne manipule pas uniquement des variables locales aubloc, mais peut egalement manipuler des variables plus globales4.

Enfin, a chaque new, de la memoire est allouee sur le tas run-time a une placedisponible. A chaque delete, l’emplacement est libere5.

12.4.1 Exemple d’execution d’un programme

L’exemple de la figure 12.4 illustre ce qui vient d’etre presente.

4la maniere dont les di!erentes variables et constantes locales ou non sont accedees en memoireest expliquee dans le cadre du cours de compilateur

5la gestion du tas run-time (avec freelist et freeblock) est expliquee dans le cadre du coursd’architecture 2.

202 CHAPITRE 12. ALLOCATION DYNAMIQUE DE MEMOIRE

Fig. 12.5 – Evolution de la memoire lors de l’execution du programme de la figure12.4

L’evolution de la memoire tout au long de l’execution du programme est illustre dansla figure 12.5, en supposant que la valeur lue sur input et stockee dans la variablei est egale a 3. Dans cet exemple, pour les lignes de programmes qui correspondenta l’entree dans un nouveau bloc, c’est-a-dire les instructions 2 et 8, une nouvelleframe est rajoutee sur la pile run-time prevoyant la place pour les variables qui yseront declarees. A la sortie d’un bloc c’est-a-dire aux lignes, 12 (et 14), la framecorrespondante qui se trouve en sommet de pile est retiree. Chaque new, situes auxlignes 6 et 11 reserve une zone memoire pour un vecteur de 3 entiers sur le tas.L’instruction 13 libere sur le tas la zone memoire correspondante au vecteur pointepar p.

12.5. ERREURS FREQUEMMENT COMMISES 203

int main(){

int *p,*q;int i;cin >> i;p = new int[i];q = new int[i];p[3] = 7;q[3] = 8;cout << p[3] << " " << q[3] << endl;

}

Fig. 12.6 – Programme referencant une zone eventuellement non allouee

12.5 Erreurs frequemment commises

Nous avons vu qu’une variable dynamique “vit” entre l’instruction new qui la cree etle delete qui la detruit. Cela cree de frequentes erreurs dans les programmes commele montrent les quelques exemples suivants.

Probleme de reservation memoire non su!sante

Dans le programme de la figure 12.6, deux vecteurs sont alloues dynamiquement etpointes respectivement par p et q. Si l’utilisateur introduit une valeur inferieure a 4 (0par exemple), le comportement du programme devient aleatoire ou peut provoquerune erreur, puisqu’aucun emplacement n’est reserve pour p[3] et q[3].

Notons que le meme probleme existe avec les vecteurs statiques, mais dans ce cas lenombre d’elements dans le vecteur est connu avant le debut de l’execution.

La solution pour eviter ce type d’erreurs est de tester la valeur des indices avant deles utiliser.

Probleme de non desallocation

Les programmes des figures 12.7 et 12.8 vont provoquer des erreurs du type run timeoverflow qui indiquent que le programme n’a pas assez de memoire dans l’ordinateurpour fonctionner. Ces programmes allouent dynamiquement a chaque tour de la

204 CHAPITRE 12. ALLOCATION DYNAMIQUE DE MEMOIRE

int main(){

while(true){int *p = new int;

}}

Fig. 12.7 – Programme allouant sans arret une nouvelle variable sans la desallouer

int main(){

int i;

cin >>i;while (i >0){int *p1;p1 = new int[i]; //allocation d’un vecteur a i elements entiers

} //oubli de la desallocation de la memoire pointee par p1}

Fig. 12.8 – Programme allouant eventuellement de nombreuses fois un nouveauvecteur sans le desallouer

boucle while une variable entiere (resp. un vecteur d’entiers) mais “oublient” de fairela desallocation correspondante. Notons que p (resp. p1) est une variable statiquedetruite et recreee a chaque tour de boucle. Pour eviter l’overflow, un delete p(resp. delete[] p1) aurait donc du etre ajoute avant la sortie de la boucle.

Probleme de pointeurs pendants (dangling pointers)

Dans le programme de la figure 12.9, un vecteur est alloue dynamiquement et pointepar p et q. Ensuite ce vecteur est desalloue, p mis a NULL mais q continue a pointervers une zone qui n’est plus allouee (ou poura eventuellement etre reallouee parailleurs). L’utilisation de plusieurs pointeurs vers la meme zone memoire est donctres dangereuse puisqu’on oublie facilement de les reinitialiser tous lors d’un delete.

Remarque : le langage Java ne demande pas de faire explicitement la desalloca-tion de memoire. En cas de besoin, une routine run-time appelee garbage collector

12.5. ERREURS FREQUEMMENT COMMISES 205

int main(){

int i;int *p, *q;

cin >>i;p = new int[i];p[0] = 77;q = p;delete[] p; // q pointe vers une zone desalloueep = NULL;...cout << q[0]; // erreur ou effet aleatoire...

}

Fig. 12.9 – Programme referencant une zone desallouee

(litteralement ramasse-miettes) recupere les zones memoire du tas qui ne sont plusreferencees par le programme pour pouvoir les reallouer6. Pour permettre au garbagecollector de recuperer une zone sur le heap, il su"t donc de supprimer la reference acette zone, par exemple en mettant les pointeurs (dans l’example precedant p et q)a null.

6Le fonctionnement precis d’un garbage collector est explique dans le cadre du cours Compilateur2

206 CHAPITRE 12. ALLOCATION DYNAMIQUE DE MEMOIRE

Chapitre 13

Les classes

13.1 Introduction

Le langage C++ est un langage oriente-objet. Un objet peut etre vu comme uneentite qui contient simultanement plusieurs donnees de “type” eventuellement di!e-rents appeles des attributs de donnees1 et qui peut etre manipulee par des operations,appelees methodes2. Ces methodes sont des fonctions associees a l’objet permettant,entre autre, la manipulation des attributs de ces objets.

Dans la“philosophie” generale de la programmation oriente-objet, on concoit un pro-gramme comme un ensemble d’ objets e!ectuant certains traitements eventuellementen faisant des requetes a d’autres objets pour collaborer dans le travail a e!ectuer.

De facon semblable aux variables ou constantes qui sont associees a un certain type,chaque objet est d’une certaine classe. Avant de declarer un objet, sa classe doit doncetre definie3, pour preciser l’ensemble des attributs des futurs objets de cette classe,ainsi que les methodes associees. Comme nous le verrons ci-dessous, la definitiond’une classe ne se fait pas comme pour les types grace au mot-cle typedef, maisavec le mot-cle class.

Remarque : Le but du cours n’est pas de faire une etude complete des aspectsorientes-objets du langage C++ et en particulier de la notion d’heritage specifique ace type de langages. Nous n’allons pas non plus etudier comment faire une conception

1nous parlerons egalement de champ (ou de membres de donnees dans le jargon C++)2egalement appelee fonction membre dans le jargon C++3certaines classes et objets sont predefinis dans des librairies. Par exemple cin et cout sont deux

objets predefinis dans la classe iostream

207

208 CHAPITRE 13. LES CLASSES

d’un systeme oriente-objet, par exemple en utilisant le langage UML. Nous allonsici introduire un petit nombres de concepts, en particulier pour nous permettre depresenter, au chapitre suivant, les structures de donnees telles que les listes chaıneeset les algorithmes associes. Ainsi, les algorithmes presentees seront plus prochesde solutions developpees dans un langage de programmation imperatif non oriente-objet4 que de solutions oriente-objets “propres”.

13.2 Un exemple de classe et d’objets sans me-thodes

Reprenons le probleme du tri d’un vecteur d’elements mais ou chacun des elementscontient une cle par exemple entiere et une information satellite par exemple de typecaractere. L’algorithme de la figure 13.1 declare la classe elem, le type Vect et lesfonctions de lecture, d’ecriture et de tri du vecteur en utilisant la methode de tri parenumeration.

Dans ce programme, chaque V[i] est un objet (instance de la classe elem a 2 champs :Cle et Info.

La notation generale pour identifier le champ d’un objet est :

Objet.Champ

Remarquons le mot cle public qui exprime que les champs d’un objet sont manipu-lables partout ou celui-ci est visible. Nous verrons un peu plus loin l’utilisation dumot cle private.

13.3 Un exemple de classe avec methodes

Dans l’exemple precedant, la classe elem ne contenait que des champs, c’est-a-direde l’information. Nous considerons maintenant un exemple plus elabore qui montrecomment associer du traitement a ces informations sous la forme de methodes.

Le petit programme de la figure 13.2 montre la definition d’une classe Clock et lamanipulation d’un objet k de cette classe (declare par Clock k).

4comme le langage C utilisant des struct

13.3. UN EXEMPLE DE CLASSE AVEC METHODES 209

#include <iostream>using namespace std;const int MAX=10, m = 5; // val possibles 1..4class elem{

public:int Cle;char Info;

};typedef elem Vect[MAX];

void LectureVecteur(Vect V,int n){for (int i=0; i<n; ++i)cin >> V[i].Cle >> V[i].Info;

}void EcritureVecteur(Vect V,int n){

for (int i=0; i<n; ++i)cout << V[i].Cle << " " << V[i].Info << endl;

}void TriEnumeration(Vect V, int n){

typedef int Compteur[m];Vect W;Compteur Count;for (int i=0; i < m; ++i) Count[i]=0;for (int j=0; j<n; ++j) ++Count[V[j].Cle];for (int i=2; i<m; ++i) Count[i] += Count[i-1];for (int j=0; j<n; ++j){W[Count[V[j].Cle-1]] = V[j];++Count[V[j].Cle-1];

}for (int j=0; j<n; ++j) V[j] = W[j];

}int main(){

Vect A;cout << "Introduisez les" << MAX << " valeurs pour le vecteur (cle (1.."

<< m-1 <<") + info)" << endl;LectureVecteur(A,MAX);TriEnumeration(A,10);EcritureVecteur(A,MAX);

}

Fig. 13.1 – Programme lisant un vecteur d’elements a 2 champs, e!ectuant un trides elements par la methode enumerative et ecrivant les resultats

210 CHAPITRE 13. LES CLASSES

#include <iostream>using namespace std;class Clock{

public:void set(int hour, int min);int read_hour() { return h; } //definition de read_hourint read_min() { return m; } // definition de read_minvoid write();void tick();

private:int h, m;

};// definition des autres methodes de Clock

void Clock::set(int hour, int min){ h=hour; m=min;}void Clock::write(){

cout.fill(’0’);cout.width(2);cout << h;cout << ":" ;cout.fill(’0’);cout.width(2);cout << m;

}void Clock::tick() {

m=(m+1) % 60;if (m==0) h=(h+1)%24;

}

int main() {Clock k;int heure, min;cout << "Donner heure et min : " ;cin >> heure >> min;k.set(heure, min);cout << "De combien de minutes avancer ? " ;cin >> min;for (int i=0; i<min; i++) k.tick();cout << "Voici l’heure : " ; k.write(); cout << endl;

}

Fig. 13.2 – Programme definissant et utilisant une classe Clock et un objet k decette classe

13.4. ALLOCATION DYNAMIQUE D’OBJETS 211

On voit dans cet exemple que le programme commence par donner le prototype dela classe Clock, c’est-a-dire la liste de ses attributs de donnees et methodes. Chaqueobjet de cette classe aura deux attributs de donnees h et m et pourra etre manipulevia les 5 methodes set, read_hour, read_min, write et tick. L’implementationdes methodes set, write et tick n’est donnee que plus tard ; par exemple avec :

void Clock::set(int hour, int min){ h=hour; m=min; }

qui exprime que pour la classe Clock, la methode set qui ne renvoie pas de resultat(type void), et qui recoit, lors de son appel, 2 parametres entiers, modifie les champsh et m internes a l’objet invoque. Ainsi pour l’objet k le code k.set(23,59), met ka 23 heures et 59 minutes (k.h est a 23 et k.m a 59).

Comme pour les champs, la notation generale pour demander l’execution d’une Me-thode d’un Objet est :

Objet.Methode(parametres e"ectifs)

Une methode se comporte comme une fonction qui connaıt les champs de l’objetdont il depend et ne doit pas utiliser la notation “.” pour les manipuler.

L’implementation d’une methode en C++ peut etre donnee directement comme pourles methodes read_hour et read_min ou ailleurs, comme pour les 3 autres methodesde la classe Clock.

Notons que les champs h et m sont declares private ce qui signifie qu’ils ne peuventpas etre manipules en dehors des 5 methodes de la classe Clock. Par exemple dansla fonction main, on ne pourra pas avoir l’instruction k.h=23 puisque h n’est pasvisible (public).

13.4 Allocation dynamique d’objets

Comme pour toute autre variables, il est possible de se creer dynamiquement desobjets. Ainsi, avec la definition de la classe Clock de la section precedante, lesinstructions suivantes :

Clock *p;

212 CHAPITRE 13. LES CLASSES

...p = new Clock; // p pointe vers une nouvelle Clockp->set(11, 40);p->tick(); // l’operateur "fleche" plus lisible

// mais equivalent a (*p).tick();...delete p;

manipulent un objet de cette classe a travers le pointeur p.

De facon generale, ayant un pointeur p vers un objet, les notations

p $ > c et p $ > m()

permettent d’acceder au champ c et d’appeler la methode m() de l’objet pointe parp.

13.5 Canevas de declaration d’une classe

Une declaration de classe a la structure suivante :

class Name {

public:... // declaration des attributs de donnees publics... // declaration des methodes publiques

private:... // declaration des attributs de donnees prives... // declaration des methodes privees

};

Lors des declarations de methodes, soit les methodes sont egalement definies (commec’est le cas pour read_hour et read_min dans l’exemple de la figure 13.2), soit ellesdoivent etre definies par ailleurs. Dans ce cas, la syntaxe consiste dans le nom de lamethode definie, a rappeler le nom de la classe concernee (comme par exemple voidClock : :write() de l’exemple de la figure 13.2).

13.6. LE POINTEUR THIS 213

De plus, lorsqu’une classe est definie, di!erentes methodes specifiques peuvent etreincluses dans cette definition :

– des constructeurs qui sont executes automatiquement lors de la creation d’un objet(plus precisement un constructeur est execute a chaque creation d’objet),

– un constructeur de copie qui est execute lors d’une transmission par valeur d’unobjet comme parametre d’une fonction appelee,

– un destructeur execute lors de la destruction d’un objet (sortie de bloc ou delete),– di!erents operateurs (par exemple d’assignation ou definissant d’autres operations)– ....

Comme nous l’avons deja dit, nous n’expliquerons pas ici l’ensemble des possibilitesd’une classe ; nous nous limiterons aux constructeurs executes lors de la creationd’un objet.

Pour cela, completons la classe clock comme a la figure 13.3, ou nous omettonsl’implementation des autres methodes. On remarque l’apparition de deux methodesClock qui ont donc le meme nom que la classe, toutes deux sans type de retour. Cesfonctions sont les constructeurs de la classe Clock. Clock() est le constructeur pardefaut et Clock(int hour, int min) est le constructeur avec parametres.

Dans cet exemple, c1 et c2 sont crees et initialises respectivement a 0h00’ grace auconstructeur par defaut et a 23h59’ grace au constructeur a 2 parametres. Ensuitec2 est avance d’une minute.

Notons la declaration du constructeur avec parametre qui utilise une liste d’initiali-sation dont la syntaxe est “ : attribut1 (valeur1), attribut2(valeur2)...” et qui commeson nom l’indique initialise les attributs de donnees aux valeurs specifiees.

Notons egalement que, pour une classe donnee, la definition d’un constructeur n’estpas obligatoire ; un constructeur par defaut qui ne fait aucune initialisation est alorsappele lors de la creation d’un objet de cette classe. Par contre, si un ou plusieursconstructeurs sont donnes, le constructeur sans parametre doit obligatoirement etredefini egalement.

13.6 Le pointeur this

Lors de toute execution d’une methode d’un objet de classe C, la constante this detype pointeur vers C existe par defaut et peut etre utilisee. this a comme valeur,l’adresse de l’objet lui-meme. *this est donc l’objet lui meme.

214 CHAPITRE 13. LES CLASSES

class Clock{

public:Clock() {h=m=0;}Clock(int hour, int min); //implementation donnee ailleursvoid set(int hour, int min);int read_hour(){ return h; } //definition de read_hour

int read_min(){ return m; } // definition de read_min

void write();void tick();

private:int h, m;

};

// definition des autres methodes de Clock

Clock::Clock(int hour, int min): h(hour),m(min){}

...int main(){

Clock c1;Clock c2(23,59);...c2.tick();...

}

Fig. 13.3 – Definition d’une classe Clock avec 2 constructeurs

Notons que le plus souvent, l’utilisation de this est implicite. En e!et tout champc manipule au sein d’un objet peut etre ecrit this->c. Sauf s’il faut preciser quel’on manipule bien le champ (ou la methode) en question, on prefere generalementla notation succinte.

Par contre, une utilisation standard de this est donne dans l’exemple suivant (dansles methode set() et tick() :

13.6. LE POINTEUR THIS 215

class Clock{

public:Clock& set(int hour, int min);int read_hour() { return h; } //definition de read_hourint read_min() { return m; } // definition de read_min...Clock& tick();

private:int h, m;

};// definition des autres methodes de Clock

Clock& Clock::set(int hour, int min){

h=hour;m=min;return *this;

}

Clock& Clock::tick(){

m=(m+1) % 60;if (m==0) h=(h+1)%24;return *this;

}

int main(){

Clock k;int heure, min;cout << "Donner heure et min : " ;cin >> heure >> min;k.set(heure, min);cout << "On avance de 3 minutes" ;k.tick().tick().tick();...

}

Dans cet exemple, les methodes set() et tick() ont comme type une reference versun objet de meme type (Clock) et le resultat de ces methodes est l’objet lui-mememodifie (return *this). Ainsi k.tick() est l’objet et peut etre utilise a la suitepour une manipulation ulterieure. Ici, par exemple, on a fait 3 tick() successifs.

216 CHAPITRE 13. LES CLASSES

Nous avons maintenant su"samment introduit les classes pour pouvoir passer auchapitre suivant qui etudie la notion de structure chaınee (liste). Rappelons que,pour simplifier la presentation, les algorithmes presentes n’utiliserons pas un stylede programmation oriente-objet pur. Par exemple, le this ne sera pas utilise commeexplique dans l’exemple precedent. Une etude plus complete des aspects orientesobjet se fera dans le cadre de cours de programmation et d’algorithmique ulterieurs.

13.7 Exercices : Les pointeurs et les classes

13.7.1 Les pointeurs

Ex. 126 Que contiennent i et j apres les instructions suivantes :

int i = 10;int *ip = &i;int j = *ip;*ip += 1;ip += 1 ;if (i == j)

i = 5 ;else if (j+1 == i)

i = 6 ;else if (j+2 == i)

i = 7 ;

Ex. 127 Decrivez l’e!et des instructions suivantes. Indiquez celles qui provoquentune erreur de compilation et/ou une erreur a l’execution.

1. int i = 10;int *ip= &i;int *ip = i;double *ipp = &i;

2. int j = 5;int *ip= &j;int *ip2 = ip;int *ip3 = &ip;int *ip4 = *ip;int **ip5 = &ip;int k = ip;int l = *ip;

13.7. EXERCICES : LES POINTEURS ET LES CLASSES 217

*ip = 2;ip + = 1;*ip = new int;ip = new int;

3. int j = 5;int *ip= &j;int *ip2 = new int;delete j;delete ip2;delete *ip2;delete ip;

4. int V[5]={1,2,3,4,5} ;int i ;int * ip ;int ** ipp ;i = V[1] ;ip = V ;i = *ip ;++ip ;i = *ip ;i = ip[1] ;V[2] = 4 ;ipp = &ip ;i = **ipp ;

5. int * V ;int * p ;int i ;V = new int[5] ;V[0] = 1 ;V[1] = 2 ;p = &(V[0]) ;delete[] V ;i = *p ;

13.7.2 Les classes

Ex. 128 Ecrire une classe Etudiant qui possede comme champs : un entier matriculeet un tableau d’entiers de taille 3 nomme cotes (chaque case de cotes corresponda un cours)– Ecrire une fonction qui recoit un objet de type Etudiant et retourne la moyenne

de ses cotes.

218 CHAPITRE 13. LES CLASSES

– Ecrire un constructeur qui a partir d’un matricule et de trois cotes, cree unEtudiant.

– Ecrire un programme dans lequel on cree un tableau de 5 elements de typeEtudiant (representant un groupe de 5 etudiants) et qui calcule les moyennesdes notes obtenues par les etudiants du groupe pour chacun des cours.

Ex. 129 Ecrire une classe Point qui permet de representer un point dans un systemea deux coordonnees x et y. Ecrire une fonction qui recoit deux objets de type Pointet retourne la distance euclidienne entre ces deux points.

Ex. 130 Quelle est l’e!et de ces instructions ?

class MyClass {public:int i ;double d ;int V[3] ;int * W ;

} ;

MyClass x ;x.i = 9 ;x.W = new int[4] ;x.W[0] = x.i ;MyClass * p = new MyClass ;p->i = x.i ;p->i = x.W[0] ;double * pp ;pp = &(x.d) ;*pp = 9 ;p->V[0] = x.V[2] = 4 ;delete p ;x.d = *pp ;

Ex. 131 Ecrire une classe Trait qui represente un trait forme de maximum 10segments de droite dans un espace a deux dimensions. Un trait sera donc representepar au plus 11 objets de type Point. Comme on veut eviter de creer des Pointinutiles, votre classe Trait contiendra un tableau de 11 pointeurs vers des objets detype Point, qui seront crees au moment voulu.

Vous ecrirez :

1. Un constructeur qui initialise la classe Trait a l’aide de deux points. Les casesinutilisees du vecteur de points seront mises a NULL ;

13.7. EXERCICES : LES POINTEURS ET LES CLASSES 219

2. Une fonction qui recoit un objet de type Trait et un pointeur vers un objetde type Point et qui ajoute le Point dans la premiere case libre de l’objet detype Trait (de maniere a alonger le trait).

3. Une fonction recevant un pointeur vers un objet de type Trait et calculant salongueur.

Ex. 132 On peut encoder un polynome dans un vecteur dont les composantes ont 2champs : un champ donnant le degre du monome et un champ donnant le coefficientcorrespondant. On suppose les monomes tries par ordre decroissant sur leur degre.

Ayant :

const int n=20;

class monome{public:

int degre;double coeff;

};

Le polynome 27x11 + 5x4 $ 3

est code par :

p 11 4 0 -1 . . . . . . degre27 5 -3 ? . . . . . . coe"cient

On utilise la valeur $1 dans le champ degre comme sentinelle.

Ecrivez le prototype d’une classe polynome qui stockera des polynomes sous la formed’un tableau de monome, de taille n. Vous prevoirez :– un constructeur qui initialisera le polynome a 0.– un constructeur qui recevra un tableau de monome en parametres et qui s’en servira

pour initialiser le polynome. On supposera que les monomes sont classes dans letableau par ordre decroissant.

– une fonction d’initialisation init, qui lira sur l’input une serie de couples d’entiers7d, v8 qui representent chacun un monome v · xd du polynome (on supposera queles monomes sont entres en ordre decroissant). La liste de couples d’entiers seraterminee par une valeur sentinelle $1. Le polynome sera initialise avec ces valeurs.

220 CHAPITRE 13. LES CLASSES

– une fonction affiche qui a"che le polynome.

Ex. 133 On vous demande d’ecrire les fonctions suivantes, qui manipulent des po-lynomes.

1. Une fonction int eval(double x) qui evalue le polynome en x.

Amelioration : utiliser la methode de Horner. Cette methode repose sur laconstatation suivante :

Ayant p(x) =$n

i=0 ci · xi on a

p(x) = (. . . ((cn · x + cn!1) · x + cn!2) . . .) · x + c0

2. Ecrire une fonction polynome somme(polynome p, polynome q)qui retournela somme des polynomes p et q.

Exemple :

Si p = 3x4 + 7, q = x7 $ x4 + 2x alors r = x7 + 2x4 + 2x + 7.

3. Ecrire une fonction polynome produit(polynome p, polynome q) qui re-tourne le polynome produit de p et q.

Exemple :

Si p = 3x4 + 7, q = x7 $ x4 + 2x alors r = 3x11 $ 3x8 + 7x7 + 6x5 $ 7x4 = 14x.

4. Ecrire une fonction polynome derivee(polynome p, int n) qui retourne laderivee nieme du polynome.

Exemple :

Si p = 3x4 + x2 + 7, etn = 1 alors r = 12x3 + 2xn = 2 alors r = 36x2 + 2.

5. Ecrire void division(polynome p, polynome d, polynome &q, polynome &r),une fonction qui calcule dans q le polynome quotient et dans r le polynomereste de la division de p par d.

Chapitre 14

Les listes chaınees

14.1 Notion de liste chaınee

Une liste d’elements est une structure de donnees ou les elements sont ranges dansun ordre lineaire. Une liste est soit vide, soit contient des elements dont le premierest immediatement accessible et ou chaque element donne acces au suivant de laliste. Par exemple,

<> est la liste vide ne contenant aucun element< 3, 7, 21 > est une liste contenant 3 elements : le premier est 3, suivi

des elements 7 et 21

Ayant une liste L, les actions possibles sont :

– parcourir et traiter tous les elements de la liste ; le traitement peut consister aimprimer l’elements, a le modifier,..., selon ce que doit faire le programme,

– tester la presence d’un element dans la liste,– localiser un element,– rajouter un element

. en debut de liste,

. en fin de liste,

. avant un element donne,

. en preservant un certain ordre– supprimer un element,– tester si la liste est vide,– supprimer la liste,– ...

221

222 CHAPITRE 14. LES LISTES CHAINEES

Fig. 14.1 – Liste chainee < 3, 7, 21 >

En algorithmique, la notion de liste est frequemment utilisee. Etant donne l’aspectdynamique de cette structure de donnees, elle est souvent implementee avec deselements alloues dynamiquement. Les sections qui suivent donnent deux implemen-tations d’une liste. Leur utilisation pratique sera illustree dans le cadre des travauxpratiques ou de cours d’algorithmiques ulterieurs.

14.2 Liste simplement chaınee

Dans sa version la plus simple, chaque element d’une liste comprend un pointeurvers l’element suivant.

Ainsi la figure 14.1 represente la liste simplement chaınee < 3, 7, 21 > avec le pointeurp donnant acces a la tete de la liste. Chaque element (de type elem) est constitued’un champ info contenant les donnees et d’un champ next de type pointeur versun elem contenant l’adresse de l’element suivant, ou null s’il est le dernier de laliste. Le symbole # est une convention graphique pour representer le pointeur null.Dans l’implementation qui suit, nous definissons le type liste equivalent a pointeurvers un elem ; p et les champs next sont donc de type liste.

Le programme suivant implemente les listes simplement chaınees. Nous le commen-tons plus bas.

#include <iostream>using namespace std;

typedef int TypeOfInfo; // type de l’information manipulee

class elem;typedef elem* liste;

class elem {public:

14.2. LISTE SIMPLEMENT CHAINEE 223

TypeOfInfo info;liste next;elem():info(0),next(NULL){}elem(TypeOfInfo i, liste n):info(i),next(n) {}

};

void Parcours(liste tete){// parcours de la liste

// I: p qui pointe vers l’element a traiterfor(liste p = tete; p != NULL; p = p->next)cout << p->info << endl;

}

bool Contient(liste tete, TypeOfInfo x) {// recherche de l’element dont l’info est x

liste p;// I: p pointe vers l’element a examiner dans la listefor(p = tete; p != NULL && p->info != x; p = p-> next);return p != NULL;

}

liste Element(liste tete, TypeOfInfo x) {// localisation dans la liste tete de l’element dont l’info est x// renvoie NULL si x n’est pas dans la liste

liste p;// I: p pointe vers l’element a examinerfor(p = tete; p != NULL && p->info != x; p = p-> next);return p;

}

void InsertionEnTete(liste &tete, TypeOfInfo x) {// insertion d’un nouvel element en tete de liste

liste p = new elem;p->info = x;p->next = tete;tete = p;

}void InsertionEnTeteV2(liste & tete, TypeOfInfo x) {// insertion d’un nouvel element en tete de liste (version 2)

tete = new elem(x,tete);}

224 CHAPITRE 14. LES LISTES CHAINEES

void InsertionEnFin(liste &tete, TypeOfInfo x){// insertion d’un element en fin de liste

if (tete == NULL) // dans une liste videtete = new elem(x,NULL);

else { // dans une liste non videliste q = tete;liste p = tete->next;// I: q pointe vers l’element deja examine, p vers celui a examinerwhile (p != NULL) {q = p;p = p->next;

}q->next = new elem(x,NULL);

}}

void InsertionEnFinV2(liste &tete, TypeOfInfo x) {// insertion d’un element en fin de liste (version 2)

liste q = NULL;liste p = tete;

// I: q pointe vers l’element deja examine (initialement NULL),// p vers celui a examiner (initialement celui pointe par tetewhile (p != NULL) {q = p;p = p->next;

}p = new elem(x,NULL);if (q == NULL) // liste vide?tete = p; // insertion en tete

elseq->next = p; // insertion en fin

}

void InsertionTriee(liste & tete, TypeOfInfo x) {// insertion d’un element dans une liste triee

if (tete == NULL || tete->info > x) // insertion en tetetete = new elem(x,tete);

else { // recherche de l’emplacementliste q = tete;liste p = tete->next;// I: q pointe vers l’element deja examine (et plus petit)// p pointe vers l’element a examinerwhile (p != NULL && p->info <= x) {

14.2. LISTE SIMPLEMENT CHAINEE 225

q = p;p = p->next;

}q->next = new elem(x,p); //insertion entre l’elem pointe par q et par p

}}

void InsertionTrieeV2(liste &tete, TypeOfInfo x) {// insertion d’un element dans une liste triee (version 2)

liste q = NULL;liste p = tete;// I: q pointe vers l’element deja examine (initialement NULL),// p vers celui a examiner (initialement celui pointe par tetewhile (p != NULL && p->info <= x) {q = p;p = p->next;

}if (q == NULL) { // insertion en debut de listetete = new elem;q = tete;

}else {q->next = new elem;q = q->next;

}q->info = x;q->next = p;

}

void Suppression(liste & tete, TypeOfInfo x) {// suppression de l’elem x de la liste (s’il est present)

// recherche de l’elem a supprimerliste q = NULL;liste p = tete;// I: q pointe vers l’element deja examine (initialement NULL),// p vers celui a examiner (initialement celui pointe par tetewhile (p != NULL && p->info != x) {q = p;p = p->next;

}if (p != NULL) { // element trouve?if (q == NULL) // suppression en tetetete = p->next;

226 CHAPITRE 14. LES LISTES CHAINEES

else // suppression au milieu de la listeq->next = p->next;

delete p;}

}

void SuppressionListe(liste & tete) {// Suppression de tous les elements d’une liste

while (tete != NULL) {liste p = tete;tete = tete->next ;delete p;

}}

int main() {// exemple de main manipulant des listes simplement chaınees

int i,j;bool encore;liste triee = NULL;liste first = new elem(5,NULL);first = new elem(3,first);first = new elem(4,first);Parcours(first);cout << "nombre cherche : " ;cin >> i;cout << "remplacant : ";cin >>j;if (Contient(first,i))Element(first,i)->info = j;

cout << "nombre a inserer en tete: " ;cin >> i;InsertionEnTete(first,i);cout << "nombre a inserer en tete: " ;cin >> i;InsertionEnTeteV2(first,i);cout << "nombre a inserer en fin: " ;cin >> i;InsertionEnFin(first,i);cout << "nombre a inserer en fin: " ;cin >> i;InsertionEnFinV2(first,i);

14.2. LISTE SIMPLEMENT CHAINEE 227

Parcours(first);cout << "nombre a supprimer: " ;cin >> i;Suppression(first,i);cout << " liste: ";Parcours(first);cout << "suppression de la liste" << endl;SuppressionListe(first);cout << " liste: ";Parcours(first);cout << " liste triee ajouter un elem? (0/1):";cin >> encore;while(encore){cout << "valeur : ";cin >> i;InsertionTrieeV2(triee,i);cout << "liste triee ajouter un elem? (0/1):";cin >> encore;

}cout << "liste resultante :" << endl;Parcours(triee);

}

Detaillons ce programme :

Declaration du type liste simplement chaınee

TypeOfInfo (ici entier) est le type des informations de la liste. Pour avoir une listedont les informations ont un autre type, il su"t de redefinir TypeOfInfo de faconadequate.

Le type liste est pointeur vers un elem.

La classe elem contient deux champs :

– info contenant l’information et– next de type pointeur vers un elem (normalement le suivant de la liste).

Deux constructeurs sont definis pour la classe elem :

228 CHAPITRE 14. LES LISTES CHAINEES

– le constructeur par defaut qui initialise les champs info et next respectivementa 0 et NULL,

– le constructeur a 2 parametres qui initialise l’element avec les valeurs des para-metres recus.

Parcours d’une liste

Le parcours d’une liste (fonction Parcours) utilise un pointeur p qui pointe succes-sivement vers chaque element depuis l’element de tete jusqu’au dernier. La liste estentierement parcourue quand p a la valeur NULL.

Recherche d’un element dans une liste

Le programme definit la fonction Contient qui renvoie une valeur vraie si et seule-ment si l’element est dans la liste.

La fonction est assez similaire au parcours, mais si p est di!erent de NULL, elle testesi cet element a son champ info egal au parametre x, c’est-a-dire ce que l’on cherche.

Localisation d’un element dans une liste

Cette fonction (Element) est similaire a la fonction Contient mais renvoie l’adressede l’element cherche ou NULL si la liste ne contient pas d’element dont l’informationvaut ce qui est recherche.

Insertion d’un element en tete de liste

Deux versions sont proposees. InsertionEnTete cree un element, initialise les champsinfo au parametre x et son champ next a l’adresse de l’element qui etait en tetede liste (ou NULL si la liste etait vide). L’adresse du nouvel element est ensuite misedans la variable tete et devient ainsi la nouvelle tete de liste.

La fonction InsertionEnTeteV2 est similaire mais utilise le constructeur qui initia-lise les champs ; ce qui permet de faire toute l’operation en une seule instruction.

14.2. LISTE SIMPLEMENT CHAINEE 229

Insertion d’un element en fin de liste

Deux versions sont proposees. La fonction InsertionEnFin fait un premier test poursavoir si la liste est vide, auquel cas, il s’agit d’une insertion en debut de liste (vide).Sinon, 2 pointeurs de travail sont utilises :

– q qui pointe vers le dernier element teste,– p qui pointe vers le suivant ou vaut NULL si cet element n’existe plus.

Le while avance dans la liste en maintenant p et q. A la fin du parcours, q pointedonc vers le dernier element, et on y raccroche le nouvel element a inserer.

La fonction InsertionEnFinV2 utilise egalement 2 pointeurs de travail p et q, mais

– q pointe vers le dernier element teste et initialement est mis a NULL,– p pointe vers le suivant (initialement le pointeur tete) ou vaut NULL si cet element

n’existe plus.

Le test pour savoir si la liste est vide, et s’il faut mettre a jour le pointeur tete oule champ next du dernier de la liste pointe par q se fait a posteriori (en testant si qvaut toujours NULL).

Insertion d’un element dans une liste triee

Deux versions sont proposees. Dans la fonction InsertionTriee un premier testpermet de determiner s’il s’agit d’une insertion en debut de liste (liste vide ou elementa inserer de valeur inferieure au premier de la liste), ou non. Dans le second, cas, unerecherche permet d’avoir deux pointeurs : q qui pointe vers l’element avant l’elementa inserer, et p qui pointe vers l’element apres (ou NULL si l’on est en fin de liste).Ensuite l’instruction q->next = new elem(x,p) cree un nouvel element suivi del’element pointe par p (ou NULL si on a atteind la fin de la liste) et accroche cenouvel element apres l’element pointe par q.

La version InsertionTrieeV2 part de q valant NULL et p l’element de tete, fait larecherche et ensuite teste s’il s’agit d’une insertion en debut ou milieu de liste. Lorsde cette insertion, q pointe vers le nouvel element.

230 CHAPITRE 14. LES LISTES CHAINEES

Suppression d’un element d’une liste

La suppression d’un element d’une liste consiste en une recherche suivie, si l’elementest trouve, d’une suppression,

– soit en tete de liste (pointeur q est a NULL) : on met alors a jour le pointeur tete,– soit en milieu (ou fin) de liste : on fait la mise a jour q->next = p->next.

Enfin, il ne faut oublier de faire le delete de l’element supprime de la liste.

Suppression d’une liste

Cela revient a un parcours ou tout element rencontre est detruit, en veillant a nepas perdre l’adresse du suivant. A la fin, le pointeur tete sera a NULL.

14.3 Liste doublement chaınee circulaire

La manipulation de listes simplement chaınees a 2 inconvenients :

– lors d’insertions ou de suppressions, il faut souvent traiter le cas en debut de listecomme un cas particulier ; contrairement aux autres cas ou c’est le pointeur nextqu’on adapte, il faut adapter le pointeur tete de liste,

– ayant un pointeur vers un element, on ne peut pas retrouver facilement l’elementqui le precede, ce qui est necessaire, par exemple, pour supprimer cet element. Ene!et, pour cela, il faut repartir du debut de la liste et faire un parcours jusqu’al’element. Par ailleurs, cela demande la connaissance de la tete de liste concernee,ce qui n’est pas forcement possible.

Pour pallier ces inconvenients, 3 modifications sont faites par rapport aux listessimplement chaınees.

– Un premier element non significatif (qui ne correspond reellement a aucune infor-mation) est ajoute lors de la creation de la liste. Cet element est appele pre-tetepuisqu’il precede l’element de tete. Dans notre jargon, nous l’appellerons frequem-ment element bidon. Une liste (meme vide) contient donc toujours un element. Sila liste n’est pas vide, son champ next pointe vers le premier element significatifde la liste.

– Un champ previous est rajoute aux elements, qui, pour chaque element, pointe

14.3. LISTE DOUBLEMENT CHAINEE CIRCULAIRE 231

Fig. 14.2 – Liste circulaire doublement liee avec pre-tete vide

Fig. 14.3 – Liste < 3, 7, 21 > circulaire doublement liee avec pre-tete

vers l’element qui precede dans la liste.

Enfin, notons que pour normaliser completement le traitement, l’element de pre-tetesera considere comme l’element qui suit le dernier de la liste et l’element qui precedele premier de la liste. De meme, l’element qui precede l’element de pre-tete est ledernier de la liste. C’est pour cette raison que l’on nomme ce type de liste listecirculaire doublement liee (avec pre-tete).

Les figures 14.2 et 14.3 montrent deux listes circulaires doublement liees avec pre-tete : une vide et l’autre contenant les elements < 3, 7, 21 >.

Le programme suivant implemente les listes circulaires doublement liees avec pre-tete. Ce programme est commente plus bas.

#include <iostream.h>

typedef int TypeOfInfo;

class elem;

232 CHAPITRE 14. LES LISTES CHAINEES

typedef elem* liste;

class elem {public:

TypeOfInfo info; // type de l’information manipuleeliste next;liste previous;elem(): info(0), previous(this), next(this){}elem(TypeOfInfo i, liste p, liste n)

: info(i),previous(p), next(n) {}};

void CreationListe(liste &tete){// Cree une liste vide cad avec un element de pre-tete// raccroche au pointeur tete

tete = new elem;}

void InsAvant(liste e, TypeOfInfo x) {// Insertion entre l’elemement pointe par e et celui pointe par e->previous

liste r = new elem(x, e->previous, e);e->previous->next = r; // modifie champ next du precedante->previous = r; // modifie le champ previous du suivant

// (pointe par e)}

void InsertionEnTete(liste e, TypeOfInfo x) {// Insertion en tete cad apres la pre-tete

InsAvant(tete->next, x); //insertion avant l’element suivant la pre-tete}

void InsFin(liste tete, TypeOfInfo x) {// Insertion en fin (cad avant la pre-tete puisque liste circulaire)

InsAvant(tete, x);}

void InsertionTriee(liste tete, TypeOfInfo x) {// Insertion dans une liste triee

// recherche de l’emplacementliste p = tete->next;tete->info = x+1; // met x+1 dans l’info de la pre-tete pour s’arreter

// si x est le plus grand de la listewhile (p->info <= x) // Recherche le premier dont l’info est

14.3. LISTE DOUBLEMENT CHAINEE CIRCULAIRE 233

// plus grand que xp = p -> next;

InsAvant(p,x); // insertion avant l’element trouve}

void Parcours(liste tete){// Parcours d’une liste en passant la pre-tete

for(liste p = tete->next; p != tete; p = p-> next)cout << p->info << endl;

}

bool Contient(liste tete, TypeOfInfo x) {// Recherche d’un element dans une liste// Renvoie vrai ssi l’element est dans la liste

liste p;tete->info = x;for(p = tete->next; p->info != x; p = p-> next);return p != tete;

}

liste Recherche(liste tete, TypeOfInfo x) {// Recherche d’un element dans une liste// Renvoie sa position (NULL s’il n’est pas present)

liste p;tete->info = x;for(p = tete->next; p->info != x; p = p-> next);if(p == tete) p = NULL;return p;

}

void Suppression(liste tete, TypeOfInfo x) {// Suppression d’un element dont l’info est x de la liste

// Recherche de l’elementliste p = tete->next;tete -> info = x; // place x dans l’info de la pre-tete pour arreter

// la recherche si l’element n’est pas dans la listewhile (p->info != x) //recherche propremet ditep = p -> next;

if (p != tete) // element trouve: on va l’enlever de la liste{p->previous->next = p->next; // mise a jour de l’elem avantp->next->previous = p->previous; // mise a jour de l’elem apresdelete p;

234 CHAPITRE 14. LES LISTES CHAINEES

}}

void SuppressionListe(liste &tete) {// Suppression d’une liste d’elem

liste p,q;p = tete->next;while (p != tete){q = p;p = p->next;delete q;

}delete p;tete = NULL; // liste detruite (different de liste vide)

}

int main() {// utilisation de la liste ...

return 0;}

Declaration du type liste doublement chaınee circulaire

Chaque element comporte les 3 champs : info, next et previous. Le constructeurpar defaut initialise info a 0 et next et previous a l’adresse de l’element lui-meme.

La creation d’une liste consiste a creer un element et a faire pointer la tete de la listevers cet element.

Parcours d’une liste

Le parcours debute apres l’element de pre-tete et se termine quand le pointeur p deparcours pointe a nouveau vers la pre-tete (dont l’adresse est dans la variable tete).

14.3. LISTE DOUBLEMENT CHAINEE CIRCULAIRE 235

Recherche d’un element dans une liste

L’algorithme est similaire a la recherche dans une liste simple excepte que l’on partde l’element apres la pre-tete apres avoir insere la valeur recherchee dans le champinfo de la pre-tete. Remarquons que ceci ne pose pas de probleme puisque l’infode la pre-tete n’est pas significative. Ceci permet, de supprimer le test pour savoirsi on n’est pas en fin de liste ; si x n’est pas dans la liste, on s’arrete sur la pre-teteet renvoie la valeur NULL.

Insertion d’un element en tete de liste

Il s’agit d’une insertion avant le premier element (dont l’adresse est tete->next). Lafonction InsAvant fait l’insertion d’un nouvel element avant l’element d’adresse e.4 pointeurs sont assignes, Les champs previous et next du nouvel element respec-tivement mis a e->previous (element qui etait avant e) et e et les champs next del’ancien precedant de e et previous de e, qui recoivent l’adresse du nouvel element.Notons que la tete de la liste ne doit pas etre transmise par reference puisqu’elle nesera jamais modifiee.

Insertion d’un element en fin de liste

Il s’agit simplement d’une insertion avant la pre-tete.

Insertion d’un element dans une liste triee

Apres avoir mis x + 1 dans le champ info de la pre-tete, il s’agit d’une insertionavant le premier element dont l’information est superieure a x.

Suppression d’un element d’une liste

Apres avoir trouve l’adresse de l’element a supprimer, il faut modifier le champ nextdu predecesseur, y mettre (instruction p->previous->next = p->next) l’adressedu successeur, modifier le champ previous du successeur et mettre l’adresse dupredecesseur (p->next->previous = p->previous). Il ne faut pas oublier de faireun delete de l’element.

236 CHAPITRE 14. LES LISTES CHAINEES

class elem;

typedef elem *liste;

class elem{

public:int info; // Informationliste next; // Pointeur vers le suivantelem(){}elem(int i,liste p);

};

elem::elem(int i,liste p){

info=i;next=p;

}

Fig. 14.4 – declarations de la classe elem et du type liste

Suppression d’une liste

L’algorithme est similaire a la destruction d’une liste simple, mais doit tenir comptedu fait que la liste est circulaire (le dernier element pointe vers la pre-tete).

14.4 Exercices : Les listes

14.4.1 Les listes lineaires simples : fonctions elementaires

Ayant les declarations de la classe elem et du type liste donne a la figure 14.4,

Ex. 134 Ecrire une fonction int longueur(liste L) qui retourne la longueur dela liste L (c’est-a-dire le nombre d’elements chaınes entre-eux pour former cette liste).

Ex. 135 Ecrire une fonction bool existe(liste L, int k) qui retourne un boo-leen indiquant si la valeur k apparaıt dans la liste L.

Ex. 136 Ecrire une fonction void insered (liste & L, int k) qui insere un ele-ment portant l’information k en debut de la liste L.

14.4. EXERCICES : LES LISTES 237

Ex. 137 Ecrire une fonction void inseref (liste & L, int k). Meme chose queinsered, mais en fin de la liste.

Ex. 138 Ecrire une fonction void inserem (liste & L, int k, int pos) quiinsere un element portant l’information k dans la liste L, en position pos, c’est-a-direjuste apres le (pos $ 1)e element. Si la liste contient moins de pos $ 1 elements, unmessage d’erreur sera imprime et la liste ne sera pas modifiee. On suppose que posest un entier > 0.

Ex. 139 Ecrire une fonction void inserap (liste L, int k, int l) qui insereun element portant l’information k derriere l’information l dans la liste L. Si l’infor-mation l n’existe pas dans la liste, un message d’erreur sera imprime et l’insertionne sera pas e!ectuee.

Ex. 140 Ecrire une fonction void inserav (liste & L, int k, int l). Memechose que inserap, mais inserer k devant l’information l.

Ex. 141 Ecrire une fonction void retired (liste & L) qui retire le premier ele-ment de la liste L. Si la liste est vide, un message d’erreur sera imprime.

Ex. 142 Ecrire une fonction void retiref (liste & L). Meme chose que retired,mais avec le dernier element.

Ex. 143 Ecrire une fonction void retirem (liste & L, int pos). Meme choseque retired mais c’est le pose element qui doit etre retire. Si la liste contient moinsde pos elements, imprimer un message d’erreur. On suppose que pos est un entierstrictement positif.

! b: 25 min. !

Ex. 144 Ecrire une fonction void inverse (liste & L). Cette fonctiondoit << inverser >> la liste L, c’est-a-dire la modifier de telle sorte quece qui etait le premier element devienne le dernier, le deuxieme deviennel’avant dernier, ..., jusqu’au dernier qui devient le premier.

Ex. 145 Ecrire une fonction void attache (liste & L1, liste & L2). Cette me-thode doit attacher la liste L2 derriere la liste L1. La liste L2 devra etre vide apresl’appel.

238 CHAPITRE 14. LES LISTES CHAINEES

14.4.2 Listes circulaires avec element bidon au debut

Ayant les declarations de la classe elem et du type liste donne a la figure 14.4,

Ex. 146 Ecrire un fonction retournant une liste vide.

Ex. 147 Idem que l’exercice 135 pour une liste circulaire avec element bidon audebut.

Ex. 148 Idem que l’exercice 137 pour une liste circulaire avec element bidon audebut.

Ex. 149 Idem que l’exercice 140 pour une liste circulaire avec element bidon audebut.

14.4.3 Listes lineaires simples triees

Ex. 150 Ayant les declarations de la classe elem et du type liste donne a lafigure 14.4, realiser une fonction void inserTrie(liste & L, elem *e) qui inserel’element pointe par e dans la liste dont la tete est donnee par L. Cette liste est trieeen ordre decroissant et doit le rester apres l’insertion.

14.4.4 Listes implementees dans des vecteurs

Ex. 151 Dans toutes les methodes de tri etudiees precedemment, on deplacait phy-siquement en memoire les elements qui ne se trouvaient pas a leur place. Si ceselements comprennent des informations satellites, celles-ci doivent bien entendu<< suivre >> leur cle lors des deplacements. Si les informations satellites sontlongues, leur deplacement peut prendre beaucoup de temps ; dans ce cas il peutetre interessant d’ajouter a chacun des elements un pointeur de facon a chaıner leselements sous forme de liste. Le deplacement physique est alors remplace par unemanipulation de pointeurs. En fin de tri l’ordre des elements ne sera plus donne parla position physique de ces elements mais en suivant les pointeurs de chaınage. Laplupart des methodes de tri que nous avons vues peuvent etre exploitees en se ser-vant d’une liste, il y a cependant des cas ou cela ne serait pas possible ou du moinspeu performant. Par exemple, pour la methode Shell, il serait tres di"cile de scinderle vecteur donne en sous-vecteurs imbriques si les elements n’etaient pas consecutifsen memoire.

14.4. EXERCICES : LES LISTES 239

Tri par insertion avec liste Au vecteur info contenant les elements a trier,on ajoute un vecteur next de meme longueur et destine a contenir les pointeursde chaınage. Appelons tete la tete de liste, c’est-a-dire le pointeur vers le premierelement de la liste. Supposons que l’on doive e!ectuer un tri de facon a obtenir leselements en ordre decroissant (il est evident que l’on procederait de maniere analoguepour un ordre croissant).

Initialisation : tete = 0next[0] = $1 ;

ce qui revient a dire qu’au depart la liste contient le seul element info[0] ; a toutmoment la liste contiendra les elements deja tries et rien qu’eux, l’ordre etant bienentendu donne par les pointeurs. On ajoute un a un les autres elements dans cetteliste ; pour cela, on parcourt les composantes de info de la 2e a la ne (n = nombred’elements a trier) et on insere l’element considere en position correcte dans la listede la facon suivante : en partant de tete on parcourt la liste jusqu’a rencontrer unelement inferieur a celui a inserer. A ce moment on place l’element a inserer devantl’element trouve (rappelons-nous que l’on veut un ordre decroissant). Quand on dit<< placer devant >> il s’agit evidemment d’un placement logique c’est-a-dire parmanipulation de pointeurs.

Exemple :info 3 5 4

element a inserer

LINK

devient

info 3 5 4

LINK

Si en suivant la liste on arrive au bout de celle-ci cela signifie que l’element a inserer

240 CHAPITRE 14. LES LISTES CHAINEES

est le plus petit rencontre jusqu’a present, il vient donc en queue de liste.

! b: 30 min. !

Exercice : Ecrire la fonction qui realise le tri decrit ci-dessus.

14.4.5 Listes doublement liees

Ex. 152 Chaque element d’une telle liste comprend deux champs de type pointeur :l’un contenant l’adresse de l’element qui le suit, l’autre l’adresse de l’element qui leprecede. Il est donc possible de parcourir une telle liste dans les deux sens.

On definit :

#include <iostream>#include <climits>

using namespace std ;

/* Minimum and maximum values : a ‘signed int’ can hold INT_MIN and INT_MAX */

class elem;

typedef elem *liste;

class elem{public:

int info; // Informationliste next, previous; // Pointeur vers le suivant et le precedentelem(int i, liste p, liste q);

};

elem::elem(int i, liste p, liste q) // Initialisation{

info = i;next = p;

14.4. EXERCICES : LES LISTES 241

previous = q;}

void initliste(liste &L) // Initialisation{

L = new elem (INT_MIN, NULL, NULL);L->next = new elem (INT_MAX, NULL, L);

}

Pour simplifier les tests, on placera en debut et en fin de liste des sentinelles. La listeest donc initialisee a :

NULL

NULL

info

svt

prec

tete

On demande d’ecrire une fonction void insere(liste L, int k) qui fait une in-sertion de l’entier k dans la liste L en en conservant l’orde croissant.

242 CHAPITRE 14. LES LISTES CHAINEES

14.4.6 Intersection et di"erence symetrique

! b: 25 min. !Ex. 153 On considere 2 listes triees par ordre croissant sans repetition sur lechamp info des elements. On desire qu’apres traitement la 1re liste contienneles elements qui etaient communs aux 2 listes et que la 2e contienne leselements qui n’apparaissaient que dans l’une de ces listes.Autrement dit, si les listes de tetes a et b representent respectivement lesensembles A et B, apres traitement, a representera l’intersection :

A 9 B = {x 2 A etx 2 B}

et b representera la di!erence symetrique :

A ! B = {x 2 A\B ou x 2 B \A}

Ces listes seront evidemment encore triees.Exemples :A = {2,4,5,7,8,12,17} B = {3,4,9,12,15,17,20}.

deviendra, apres traitement

A = {4, 12, 17} B = {2, 3, 5, 7, 8, 9, 15, 20}

Nous donnons deux solutions. La premiere consiste a traiter les elements dechaque liste un par un. La seconde consiste a traiter les elements par blocs.Dans l’exemple ci-dessus, les elements 5,7,8 peuvent etre << deplaces >> enune fois. Cette derniere solution permet quelques economies d’assignations(comparer les traitements de chacun des 3 cas).Afin d’eviter les tests d’insertion en debut de liste, on place en tete de chaqueliste un element bidon que l’on supprime a la fin du traitement.

14.4.7 Liste triee scindee en plusieurs sous-listes

Soit une liste triee scindee en n sous-listes, la tete de chaque sous-liste se trouvantdans une composante du vecteur V . On dispose donc d’un vecteur V de pointeursdont chacune des composantes (numerotees de 0 a N $ 1) represente une tete deliste triee (listes simples, sans en-tete) telles que

14.4. EXERCICES : LES LISTES 243

ki % ki+1 +i|1 % i < mN (voir dessins)

En particulier, on a kmi% kmi+1+ i|0 % i < N $ 1 c’est-a-dire que l’element de fin

d’une liste est % au premier element de la liste suivante.

0 $3 k1| $3 k2| $3 k3| $3 · · · $3 km1|#

1 $3 km1+1| $3 · · · $3 km2|#

2...

...

N-1 $3 | $3 · · · $3 kmN|#

! b: 20 min. !

Ex. 154 Ecrire une fonction liste recherche (liste L, int k) quirenvoie un pointeur vers la 1e occurrence de l’element d’information k.Hypothese : on suppose qu’aucune sous-liste n’est vide.

Ex. 155 Ecrire une fonction suppression(liste & L, int i, int j) avec (1 %i % j) qui supprimera dans cette structure tous les elements compris entre le ie etle je (inclus).

L’algorithme sera decompose en deux phases :

1. Passer les i $ 1 premiers elements.

2. Supprimer les elements i a j.

Pour la phase 1, on s’arretera des que les i$ 1 premiers elements auront ete passes.Dans ces conditions, deux cas peuvent se presenter : on s’arrete au milieu d’une listeet on devra detruire un morceau en << milieu de liste >> ou on s’arrete a la find’une liste et on detruira a partir du debut de la liste suivante.

Si on ne doit pas executer la phase 1 (i = 1) on s’arrangera pour initialiser lesvariables de maniere a simuler l’arret en bout d’une pseudo liste no. -1.

La phase 2 est decoupee en deux parties :(a) La destruction a partir du milieu d’une liste.

244 CHAPITRE 14. LES LISTES CHAINEES

(b) La destruction a partir du debut d’une liste.L’etape (b) peut etre repetee si necessaire.

14.4.8 Listes cycliques

Ex. 156 Considerons les definitions suivantes :

class element;

typedef element *liste;

class element{public:

int info;bool marque;liste svt;

};

ou le champ marque est suppose faux par defaut.

Cette structure est utilisee pour representer des listes << cycliques >> formeesd’une partie transitoire et d’une partie periodique selon le schema suivant :

1 2 3 4 5

67

On demande d’ecrire une fonction qui, recevant l’adresse du 1er element, imprimeles informations des elements de la liste en separant la partie transitoire de la partieperiodique par un asterisque. Dans l’exemple, on obtiendra :

1 2 * 3 4 5 6 7

14.4. EXERCICES : LES LISTES 245

14.4.9 Parcours de 3 listes triees

Ex. 157 Soit :

class element;

typedef element *liste

class element{public:

int info;liste svt;

};

On dispose de trois listes quelconques (elles peuvent etre vides) et triees en ordrecroissant. Leurs tetes correspondantes sont Liste1, Liste2, Liste3 de type liste.Dans une meme liste toutes les infos sont di!erentes.

On voudrait savoir s’il existe un triplet (p1,p2,p3) tel que p1 pointe vers un elementde Liste1, p2 vers un element de Liste2 et p3 vers un element de Liste3, et telque :

info de p1 = info de p2 = info de p3

Remarquez qu’il peut en exister plusieurs.

On demande d’ecrire une fonction void triplet (liste Liste1,liste Liste2,liste Liste3, liste &p1, liste &p2, liste &p3) qui recherche le premier teltriplet et qui le retourne via p1, p2 et p3. S’il n’y en a pas, p1 prendra la valeur #.

Exemple :

1 8 20 30

20 21 22 30 40

10 20 31

p1

p2

p3

Liste1

Liste2

Liste3

246 CHAPITRE 14. LES LISTES CHAINEES

Remarque : vous ne pouvez parcourir les listes qu’une seule fois.

14.4.10 Listes a di"erents niveaux

Ex. 158 On donne les declarations :

class element ;

typedef element * liste ;

class element{

public:int info ;liste svt ;liste tete ;

} ;

On considere des structures du type de celle donnee en exemple ci-dessous ou chaqueelement peut, a la fois, faire partie d’une liste et etre la tete d’une autre.

info tete svt...

Exemple :rac

1 2 3 4

5 6 7 8 9

10 11 12

13 14 15

NULLNULL NULL

NULL NULL NULL NULL NULL

NULL

NULL NULL NULL NULL NULL

NULLNULL

Ceci est une structure a 4 niveaux

14.4. EXERCICES : LES LISTES 247

– La sous-structure contenant les infos 5, 6 et 7 est une liste de niveau 2 de troiselements. Sa tete est contenue dans l’element qui supporte l’info 2.

– La sous-structure contenant les infos 8 et 9 est aussi une liste de niveau 2. Sa teteest contenue dans l’element qui supporte l’info 4.

– La sous-structure contenant l’info 13 est une liste de niveau 4 a un seul element.Sa tete est contenue dans l’element qui supporte l’info 10.

! b: 40 min. !

Ecrivez la procedure lineariser(liste rac, liste fins[n] liste debuts[n])qui doit :– Relier ensemble les listes de meme niveau.– Installer dans debuts[i] et fins[i] respectivement, un pointeur vers le

premier et un pointeur vers le dernier element de la liste de niveau i ainsiobtenue pour tout i de 1 a n.

Par exemple, si rac donne acces a la structure representee ci-dessus, apresexecution de la procedure, on aura :

1 2 3 4

5 6 7 8 9

10 11 12

13 14 15

NULLNULL

NULL NULL NULL

NULL

NULL NULL NULL

finsdebuts

NULL

NULL

NULL

NULL

NULL

Attention : on ne peut parcourir la structure qu’une seule fois.