You are on page 1of 85

07/12/2010

Algorithmique et structures
de données
Par Ghislain Akinocho
Ingénieur Services et Solutions

Agenda
 Objectif
 Programme
◦ Les composants élémentaires des algorithmes
◦ Rappel « Langage C »
◦ Les listes linéaires chaînées
◦ Structures linéaires particulières
 Les piles
 Les files
◦ La récursivité
◦ Les Arbres binaires
◦ Les fichiers séquentiels
◦ Algorithmes de Tri
Applications : TP Langage C

mardi 7 décembre 2010 2

1
07/12/2010

Objectif
 Bonne base algorithme
 Maîtrise des différentes structures de
données manipulées et utiles en
programmation

mardi 7 décembre 2010 3

Les composants élémentaires


des algorithmes

2
07/12/2010

Introduction
 Définition
 L’algorithme est un ensemble de règles ou
une suite d’instructions qui doivent être
exécutées dans un ordre bien défini en vue de
résoudre un problème donné.

 L’algorithme doit contenir des instructions


compréhensibles par le développeur qui doit
écrire le programme.

mardi 7 décembre 2010 5

Introduction
 Pré-requis
Pour écrire un bon algorithme, il faut :
 Avoir une maîtrise des règles de base
d’écriture d’un algorithme.
 Etre méthodique et rigoureux et
 Avoir une bonne intuition

mardi 7 décembre 2010 6

3
07/12/2010

Introduction
 En algorithme on distingue 4 grandes
familles d’instructions
◦ L’affectation des variables
◦ La lecture et l’écriture
◦ Les tests de contrôle
◦ Les boucles

mardi 7 décembre 2010 7

L’algorithme et le programme
 L’algorithme et le programme
informatique sont différents sur deux
principaux points :
1er point :
◦ Un algorithme est indépendant de tout
langage de programmation. Il peut se
présenter sous plusieurs formes :
 Diagrammes
 Textes
…
mardi 7 décembre 2010 8

4
07/12/2010

L’algorithme et le programme
◦ Un programme informatique lui dépend
du langage dans lequel il doit être écrit. Il
correspond donc à une interprétation/
traduction de l’algorithme en un langage
donné.
 Pascal
 Cobol
 Php
 Java
 C, …

mardi 7 décembre 2010 9

L’algorithme et le programme
2nd point :
 Un algorithme ne peut être exécuté
directement sur une machine tandis que
le programme peut être exécuté sur une
machine donnée. Pour cela il suffit de
disposer de logiciels nécessaires à
l’exécution du programme.

mardi 7 décembre 2010 10

5
07/12/2010

Notions de base
 Les variables : Généralités
 En programmation tout comme en
algorithmique une variable est un conteneur
d’informations qui peut être référencé par le
nom qu’il porte.

 En informatique, ce conteneur est une zone


mémoire allouée au niveau de la mémoire
vive du système lors du lancement du
programme pour stocker des informations
bien définies et manipulées par l’application.
mardi 7 décembre 2010 11

Notions de base
 Les variables : Généralités
○ Pour accéder à ces informations, on utilise le
nom de la variable qui permet de connaitre
l’adresse correspondant à l’espace allouée à la
variable.

○ Avant d’utiliser une variable il faut au


préalable la déclarer.

mardi 7 décembre 2010 12

6
07/12/2010

Notions de base
 Les variables : Déclaration
○ La déclaration d’une variable s’effectue
toujours au début de l’algorithme et permet de
savoir le type d’informations qu’elle doit
contenir tout le long du programme.

○ Il existe plusieurs types de variables prédéfinis


qui se divisent en deux grands groupes :
 Les types numériques
 Les types alphanumériques

mardi 7 décembre 2010 13

Notions de base
 Les variables : Les types numériques
○ En algorithme, les types numériques que l’on
distingue sont :
 Les bytes (valeurs entre 0 et 255)
 Les entiers simples
 Les entiers longs
 Les réels simples

mardi 7 décembre 2010 14

7
07/12/2010

Notions de base
 Les variables : Les types alphanumériques
○ Les types alphanumériques correspondent aux
chaînes de caractères et aux caractères eux-
mêmes.

mardi 7 décembre 2010 15

Notions de base
 Les variables : Formalisme de la déclaration
 variable nom_variable : type
○ Le nom d’une variable doit toujours commencer
par un caractère alphanumérique

○ Exemple :
variable age : byte

mardi 7 décembre 2010 16

8
07/12/2010

Notions de base
 Les variables : Affectation d’une variable
 Affecter une valeur à une variable c’est écrire dans la
zone mémoire référencée par le nom de la variable.

 On ne doit affecter à une variable qu’une valeur


correspondant au type défini pour la variable lors de sa
déclaration.

mardi 7 décembre 2010 17

Notions de base
 Les variables : Formalisme de l’affectation
nom_variable valeur
○ Le nom de la variable est suivi d’une flèche dirigée vers la
variable elle-même suivie de la valeur à affecter à la
variable.
○ Exemple :
age 27

mardi 7 décembre 2010 18

9
07/12/2010

Notions de base
 Les opérateurs : opérateurs arithmétiques
○ L’affectation d’une valeur à une variable peut se faire
également par le biais d’une opération arithmétique.
○ Il s’agira dans ce cas d’affecter le résultat de l’opération à
la variable.
○ Les différents opérateurs arithmétiques existant en
algorithmique sont :
+ : l’addition
- : la soustraction
/ : la division
% ou mod: le modulo

mardi 7 décembre 2010 19

Notions de base
 Les opérateurs : opérateurs arithmétiques
 Exemple :
a 15
b 10
c a+b

mardi 7 décembre 2010 20

10
07/12/2010

Notions de base
 Les fonctions d’entrée / sortie
 En algorithme, pour effectuer certaines tâches on a
besoin d’informations qui doivent être fournies par
l’utilisateur.

 Pour récupérer ces données on dispose de fonctions


particulières et prédéfinies dites d’entrées/sorties qui
permettent au système de lire ces valeurs externes.

 La fonction lire permet de lire les informations


venant de l’extérieur et de les stocker dans une variable
passée en paramètre.
 Exemple :
lire (age)

mardi 7 décembre 2010 21

Notions de base
 Les fonctions d’entrée / sortie
 De même il existe des fonctions d’entrées/sorties
permettant de restituer un résultat à l’écran ou
d’afficher une simple information.

 La fonction afficher permet d’afficher aussi bien le


contenu d’une variable qu’une simple information ou les
deux à la fois.
 Exemple :
afficher (age)
afficher(" Salut la compagnie ")
afficher ("Votre age : ", age)
mardi 7 décembre 2010 22

11
07/12/2010

Notions de base
 Exemple d’algorithme :

Problème :
 Un commerçant de la place désire mettre en place un
algorithme permettant de calculer le prix d’achat d’un
produit x en connaissant le nombre d’articles et le prix
unitaire.

mardi 7 décembre 2010 23

Notions de base
 Exemple d’algorithme :
 Résolution :
Algorithme prix_d_achat
{Cet algorithme permet de calculer le prix
d’achat d’un produit}
variables pUnit : réel
Qte : entier
Debut
afficher("Donner le nombre d’articles : ")
lire(Qte)
afficher("Donner le prix unitaire : ")
lire(pUnit)
afficher("Le prix d’achat est : ", pUnit*Qte)
Fin

mardi 7 décembre 2010 24

12
07/12/2010

Notions de base
 Les structures conditionnelles
 Les structures conditionnelles permettent, dans
l’algorithme de poser des conditions sur l’exécution de
certaines instructions.
 On distingue :
 Les structures conditionnelles simples
 Les structures conditionnelles en boucle

mardi 7 décembre 2010 25

Notions de base
 Les structures conditionnelles simples
 L’exécution des instructions conditionnelles ne se
déroule qu’une seule fois lorsque la condition est
remplie.
 La structure utilisée pour cela est :

SI ALORS SINON

mardi 7 décembre 2010 26

13
07/12/2010

Notions de base
 Les structures conditionnelles
SI ALORS SINON
Formalisme :
SI (condition)
ALORS {la condition est vraie}
<instructions>
SINON {la condition est fausse}
<instructions>
FINSI

mardi 7 décembre 2010 27

Notions de base
 Les structures conditionnelles
Rappel sur les opérateurs logiques:
>: supérieur à
<: inférieur à
>=: supérieur ou égal à
<=: inférieur ou égal à
!=: différent de
ou: ou logique
et: et logique
!: non
=: égal à

mardi 7 décembre 2010 28

14
07/12/2010

Notions de base
 Les structures conditionnelles
 Exemple :
Variables x, y, max : réels
lire(x)
Lire(y)
SI (x>y)
ALORS
max x
SINON
max y
FINSI

mardi 7 décembre 2010 29

Notions de base
 Exercices d’application
 Ecrire un algorithme permettant de résoudre l’équation
suivante : aX + b = 0, a != 0 et b != 0.

 Ecrire un algorithme qui demande deux nombres


entiers à un utilisateur et l’informe ensuite si leur
produit est négatif ou positif sans effectuer de calcul
préalable (le cas nul sera ignoré).

 Un magasin de reprographie facture 25Fcfa les 10


premières photocopies, 20Fcfa les vingt suivantes et
15Fcfa au-delà. Ecrire un algorithme qui demande à
l’utilisateur le nombre de photocopies effectuées et qui
affiche la facture correspondante.
mardi 7 décembre 2010 30

15
07/12/2010

Notions de base
 Les structures conditionnelles en boucle
 Dans le cas des structures conditionnelles en boucle,
lorsque la condition est vérifiée, les instructions
concernées par cette condition seront exécutées
autant de fois que la condition restera vraie.
 Les structures en boucle utilisées sont :
 tant que … faire
 faire … tant que
 pour …

mardi 7 décembre 2010 31

Notions de base
tant que … faire
Syntaxe :
 tant que (<condition>) faire
<instructions>
 fintq
Le bloc d’instructions <instructions> sera
exécuté tant que la condition <condition> sera
vraie.

mardi 7 décembre 2010 32

16
07/12/2010

Notions de base
tant que … faire
Exemple :
i 0
tant que (i<10) faire
i i + 1
fintq

mardi 7 décembre 2010 33

Notions de base
faire … tant que
Syntaxe :
faire
<instructions>
tant que (<condition>)
Le bloc d’instructions <instructions> sera
exécuté tant que <condition> restera vraie.

mardi 7 décembre 2010 34

17
07/12/2010

Notions de base
faire … tant que
Exemple :
i 0
faire
i i + 1
tant que (i<10)

mardi 7 décembre 2010 35

Notions de base
pour …
Syntaxe :
pour variable <debut> jusqu’à <fin>
<instructions>
finpr
NB: <fin> et <debut> sont des valeurs entières.
Le bloc d’instructions <instructions> sera exécuté
(<fin> - <debut> + 1) fois.

mardi 7 décembre 2010 36

18
07/12/2010

Notions de base
pour …
Exemple :
pour i 0 jusqu’à 9 faire
n n + 1
afficher ("Bonjour ", i, "fois")
finpour

mardi 7 décembre 2010 37

Notions de base
Quand utiliser la structure :
- tant que … faire ou faire … tant que
- Lorsque le nombre de fois qu’on veut exécuter le
bloc d’instructions n’est pas connu.
NB :
- En ce qui concerne la structure tant que … faire, le
bloc d’instructions peut ne jamais être exécuté car le
test se fait avant l’entrée dans la boucle.
- En ce qui concerne la structure faire … tant que, le
bloc d’instructions sera toujours exécuté (au moins
une fois) car le test se fait après la première
exécution du bloc.
mardi 7 décembre 2010 38

19
07/12/2010

Notions de base
Quand utiliser la structure :
- pour
- Lorsque le nombre de fois qu’on veut exécuter le
bloc d’instructions est bien connu.

mardi 7 décembre 2010 39

Notions de base
 Exercices d’application
 Ecrire un algorithme qui demande à l’utilisateur un
nombre compris entre 1 et 3(inclus) tant que la
réponse est incorrecte.
 Ecrire un algorithme qui demande à l’utilisateur un
nombre de départ et qui ensuite affiche les dix nombres
suivants. Par exemple, si l’utilisateur entre le nombre
12, le programme affichera les nombres de 13 à
22(inclus).
 Ecrire un algorithme permettant de calculer le factoriel
d’un nombre
 Ecrire un algorithme qui permet de résoudre une
équation du second degré :
Rappel : aX² + bX + c = 0.
mardi 7 décembre 2010 40

20
07/12/2010

Notions de base
 Exercices d’application
 Ecrire un algorithme qui demande un nombre compris
entre 10 et 20 tant que la réponse est incorrecte. En cas
de réponse supérieure à 20, on fera apparaître un
message « Plus petit !» et inversement, « Plus grand ! »
si le nombre est inférieur à 10.
 Ecrire un algorithme qui demande un nombre de
départ et qui ensuite affiche la table de multiplication
de ce nombre.
Exemple : cas où l’utilisateur saisit « 7 »
Table de 7 :
7x1=7
7 x 2 = 14

7 x 10 = 70
mardi 7 décembre 2010 41

Notions de base
Les Tableaux et Pointeurs
Les tableaux
Une variable de type tableau est une variable pouvant contenir
plusieurs données de même type à un instant t donné.
On en distingue deux types :
• Les tableaux à 1 dimension
• Les tableaux à plusieurs dimensions

mardi 7 décembre 2010 42

21
07/12/2010

Notions de base
Les Tableaux à 1 dimension
Ces tableaux disposent d’une seule ligne et de
plusieurs colonnes.
Les tableaux à une dimension sont encore appelés
des vecteurs.
Déclaration d’un tableau 1D:
Variable nom_variable [taille] : type
Exemple :
variable tab[8] : entier

1 2 3 4 5 6 7 8

mardi 7 décembre 2010 43

Notions de base
Les Tableaux à 1 dimension
Affectation dans un tableau 1D:
nom_variable [indice]  valeur
Exemple :
tab[5]  19
19
1 2 3 4 5 6 7 8

mardi 7 décembre 2010 44

22
07/12/2010

Notions de base
Les Tableaux à plusieurs dimensions
Ces tableaux disposent de plusieurs lignes et de
plusieurs colonnes.
Les tableaux à 2 dimensions sont encore appelés des
matrices.
Déclaration d’un tableau 2D:
Variable nom_variable [nbLignes] [nbColonnes] : type
Exemple :
variable tab[3][4] : entier

mardi 7 décembre 2010 45

Notions de base
Les Tableaux à plusieurs dimensions
Déclaration d’un tableau 2D:
Variable nom_variable [nbLignes] [nbColonnes] : type
Exemple :
variable tab[3][4] : entier

1 2 3 4
1
2
3

mardi 7 décembre 2010 46

23
07/12/2010

Notions de base
Les Tableaux à plusieurs dimensions
Affectation dans un tableau 2D:
nom_variable [numLign] [numCol]  valeur
Exemple :
tab[2][3]  5

1 2 3 4
1
2 5
3

mardi 7 décembre 2010 47

Notions de base
 Exercices d’application
 Ecrire un algorithme qui initialise les éléments d’un
tableau 1D et d’un tableau 2D d’entiers par des « 1 ».
 Ecrire un algorithme qui demande à l’utilisateur une
série de 20 valeurs et affiche :
 La somme des éléments
 La moyenne des éléments
 Le maximum
 Le minimum
 Et le produit
 Ecrire un algorithme qui inverse les éléments d’un
tableau 1D.
 Ecrire un algorithme qui permet de déterminer le
maximum et le minimum dans un tableau 2D d’entiers

mardi 7 décembre 2010 48

24
07/12/2010

EXERCICES
 Matrice
Ecrire un algorithme qui calcule le produit et la somme de matrices.
Enchainement de l’exécution :
1- On demandera à l’utilisateur les dimensions respectives des
matrices A[n, m] et B[o,p].
2- On demandera ensuite l’opération à effectuer
3- On vérifiera suivant les valeurs de n, m, o et p que l’opération est
possible.
4-Si oui on fait le calcul et affiche le résultat sinon on affiche un
message expliquant l’impossibilité d’effectuer l’opération.

mardi 7 décembre 2010 49

Les pointeurs
 Un pointeur est une variable spéciale qui peut contenir
l’adresse d’une autre variable.

 Il faut noter que le pointeur ne contient que l’adresse


d’une variable dont le type correspond au type spécifié
lors de sa déclaration.

 Remarque
 Les pointeurs et les noms de variables ont le même rôle : Ils donnent
l’accès à un emplacement mémoire interne de l’ordinateur. Par
ailleurs il faut bien faire la différence :
 Un pointeur est une variable qui peut 'pointer' sur différentes
adresses.
 Le nom d’une variable reste toujours lié à la même adresse.

mardi 7 décembre 2010 50

25
07/12/2010

Les pointeurs
 Il existe deux modes d’adressage principaux
 L’adressage direct
 L’adressage indirect

 L’adressage direct
 Dans la programmation les variables sont utilisées pour
stocker des informations. La valeur d’une variable se trouve à
un endroit spécifique dans la mémoire de l’ordinateur.
 Le nom de la variable nous permet d’accéder directement à
cette valeur.

mardi 7 décembre 2010 51

Les pointeurs
 L’adressage indirect
 Si nous ne voulons ou ne pouvons pas utiliser le nom d’une
variable A, nous pouvons copier l’adresse de cette variable
dans une variable spéciale P appelée pointeur.

 Ensuite, nous pouvons retrouver l’information de la variable


en passant par le pointeur P.

Exemple :
 Soit A une variable contenant la valeur 19 et P un pointeur qui
contient l’adresse de A. En mémoire, A et P peuvent se
présenter comme suit :

mardi 7 décembre 2010 52

26
07/12/2010

Les pointeurs
 Formalisme de déclaration d’un pointeur
variable *<nom_pointeur> : <type>
Exemple : variable *ptr : entier
 Déclaration d’une variable de type pointeur sur un entier.
 La variable ptr (de type pointeur) contiendra l’adresse d’une variable
de type entier.

 Exemple :
Algorithme pointeur
variable *ptr : entier
variable n : entier
DEBUT
n 10
ptr  &n {On dit que ptr pointe sur n / n est pointé par ptr}
afficher(*ptr) { 10 sera affiché }
FIN
mardi 7 décembre 2010 53

Les pointeurs
 Vocabulaire
Soit la déclaration suivante :
variable *ptr, n : entier

Dans l’algorithme :
-> *ptr désignera « le contenu de la variable pointée par
ptr »
-> &n désignera « l’adresse de n »
-> ptr désignera « la variable contenant l’adresse d’une
autre variable de type entier »

mardi 7 décembre 2010 54

27
07/12/2010

Les pointeurs
 Opérations élémentaires sur les pointeurs
 Priorités de * et &
Les opérateurs * et & ont la même priorité que les autres opérateurs unaires
(!, ++, --).
Dans une même expression ces opérateurs sont évalués de droite à gauche.
Si un pointeur P pointe sur une variable X, alors *P peut être utilisé partout
où on peut écrire X.

Exemple :
Après l’instruction P = &X;
les expressions suivantes sont équivalentes :
Y  *P+1  Y  X+1
*P  *P+10  X  X+10
*P +=2  X +=2
++*P  ++X
(*P)++  X++

mardi 7 décembre 2010 55

Exercices
Exercice :
A1
B 2
C 3
variable *P1, *P2 : entiers
P1  &A
P2 &C
P1  P2
P2  &B
*P1 -= *P2
A++*P2**P1
P1&A
*P2*P1/=*P2
Résumez dans un tableau le résultat de chaque instruction du
programme ci-dessus

mardi 7 décembre 2010 56

28
07/12/2010

Exercices
Exercice :
Soit P un pointeur qui 'pointe' sur un tableau A d’entiers contenant
les valeurs suivantes : {12, 23, 34, 45, 56, 67, 78, 89, 90};
variable *P : entier
P = A;
Quelles valeurs ou adresses fournissent ces expressions:
a) *P+2
b) *(P+2)
c) &P+1
d) &A[4]-3
e) A+3
f) &A[7]-P
g) P+(*P-10)
h) *(P+*(P+8)-A[7])

mardi 7 décembre 2010 57

Les fonctions et les procédures


 Généralités

Les fonctions et les procédures sont des sous-algorithmes


permettant de simplifier l’élaboration de l’algorithme
principal et de mieux le structurer.

Un sous-algorithme contient le code permettant


d’exécuter un traitement particulier qui pourrait
apparaître plusieurs fois dans l’algorithme principal.

mardi 7 décembre 2010 58

29
07/12/2010

Les fonctions et les procédures


 Généralités

La plupart des langages de programmation nous


permettent de subdiviser nos programmes en sous-
programmes, fonctions ou procédures plus simples et plus
compacts.

A l'aide de ces structures nous pouvons


modulariser nos programmes pour obtenir des
solutions plus élégantes et plus efficientes.

mardi 7 décembre 2010 59

Les fonctions et les procédures


 Généralités

Modules (en C : les fonctions)


 Dans ce contexte, un module désigne une entité de données et
d'instructions qui fournissent une solution à une (petite) partie bien
définie d'un problème plus complexe.

 Un module peut faire appel à d'autres modules, leur transmettre


des données et recevoir des données en retour. L'ensemble des
modules ainsi reliés doit alors être capable de résoudre le problème
global.

mardi 7 décembre 2010 60

30
07/12/2010

Les fonctions et les procédures


 Avantages
 Voici quelques avantages d'un programme
modulaire:
 Meilleure lisibilité
 Diminution du risque d'erreurs
 Dissimulation des méthodes
Lors de l'utilisation d'un module il faut seulement connaître son
effet, sans devoir s'occuper des détails de sa réalisation.
 Réutilisation de modules déjà existants
Il est facile d'utiliser des modules qu'on a écrits soi-même ou qui
ont été développés par d'autres personnes.

mardi 7 décembre 2010 61

Les fonctions et les procédures


 Avantages
 Voici quelques avantages d'un programme
modulaire:
 Simplicité de l'entretien
 Un module peut être changé ou remplacé sans devoir toucher aux
autres modules du programme.

 Favorisation du travail en équipe


Un programme peut être développé en équipe par délégation de la
programmation des modules à différentes personnes ou groupes de
personnes. Une fois développés, les modules peuvent constituer une
base de travail commune.

mardi 7 décembre 2010 62

31
07/12/2010

Les fonctions et les procédures


Contrairement aux procédures, les fonctions retournent
toujours une valeur à l’issue de leur exécution.
Syntaxe de déclaration d’une fonction:
<type_de_retour> fonction <nom_fonction> (<type_param_i> param_i,…)
{Zone de déclaration des variables}
DEBUT
{Contenu du sous-algorithme}
retourner valeur_de_retour {obligatoire}
FIN

mardi 7 décembre 2010 63

Les fonctions et les procédures


Syntaxe de déclaration d’une procédure :
procédure <nom_procédure>(<type_param_i> param_i, …)
{Zone de déclaration des variables}
DEBUT
{Contenu du sous-algorithme}
FIN

mardi 7 décembre 2010 64

32
07/12/2010

Les fonctions et les procédures


Syntaxe d’ un algorithme principal contenant des sous-
algorithmes
Algorithme exemple
procédure <nom_procédure> (<type_param_i> param_i, …)
{Zone de déclaration des variables de la
procédure}
DEBUT
{Contenu du sous-algorithme}
FIN
(…) {Mettre les autres sous algorithmes ici}

{Zone de déclaration des variables de l’algorithme


principal}
DEBUT
{Contenu de l’algorithme principal}
FIN

mardi 7 décembre 2010 65

EXERCICES
 La pendule 1
Ecrivez un algorithme qui demande un nombre de secondes et affiche
l’heure correspondante sous la forme suivante -> Hh:Mmn:Ss
Exemple : 78 -> 0h1mn18s avec H =0, M = 1 et S = 18
 La pendule 2
Ecrivez un algorithme qui demande sous forme de nombres l'heure
qu'il est (un nombre pour les heures, un pour les minutes et un pour les
secondes). Cet algorithme indiquera ensuite s'il s'agit d'une heure
valide ou non.
 Boule de cristal
Cet algorithme est destiné à prédire l'avenir, et il doit être infaillible ! Il
lira au clavier l’heure et les minutes, et il affichera l’heure qu’il sera une
minute plus tard. Par exemple, si l'utilisateur tape 21 puis 32,
l'algorithme doit répondre : "Dans une minute, il sera 21 heure(s) 33".
NB : on suppose que l'utilisateur entre une heure valide. Pas besoin
donc de la vérifier.

mardi 7 décembre 2010 66

33
07/12/2010

STRUCTURES
 Déclaration d’une structure
 Une structure est un type qui permet de stocker plusieurs données,
de même type ou de types différents, dans une même variable de
type structure.
 Exemple : Déclaration d’une structure Point qui contient deux
champs x et y de type réel (float en C)
 struct Point{
float x;
float y;
};
 La déclaration d’une variable de type struct Point se fait ensuite
comme pour toute autre variable :
 struct Point P; {Déclaration d’une variable P}

mardi 7 décembre 2010 67

STRUCTURES
 Utilisation d’une structure

 Une fois la variable déclarée, on accède aux données x et y du point


P par un point. Ces données sont désignées dans le programme par
P.x, P.y. Ces données, ici de type float, sont traitées comme
n’importe qu’elle autre donnée de type float dans le programme.

 L’on pourrait rajouter d’autres données des types que l’on souhaite à
la suite des données x, y dans la structure.

 Exemple de programme avec une structure Point.

mardi 7 décembre 2010 68

34
07/12/2010

STRUCTURES
 Utilisation d’une structure
int main(){
struct Point{
float x;
float y;
}; {ne pas oublier le point-virgule}
struct Point P;
puts("Coordonnées d’un Point 2D :");
scanf("%f %f ", &P.x, &P.y);
tmp = (P.x * P.x) + (P.y * P.y);
printf("Distance OP = %f", sqrt(tmp));
return 0;
}

mardi 7 décembre 2010 69

STRUCTURES
 Utilisation d’une structure
 Pour éviter la répétition du mot struct, lors de la déclaration des
variables de type struct Point, on peut définir un raccourci par
un typedef lors de la définition de la structure pour donner un
nouveau nom à ce type :
typedef struct Point{
float x;
float y;
}Point2D;
La déclaration d’une variable de type struct Point est alors
simplifiée comme suit :
Point2D P;

NB : Les données x et y du Point2D sont toujours désignées par P.x


et P.y

mardi 7 décembre 2010 70

35
07/12/2010

STRUCTURES
 Utilisation d’une structure
 Reprenons l’exemple suivant considérant la nouvelle forme de
déclaration :
int main(){
struct Point{
float x;
float y;
}Point2D; {ne pas oublier le point-virgule}
Point2D P;
puts("Coordonnées d’un Point 2D :");
scanf("%f %f ", &P.x, &P.y);
tmp = (P.x * P.x) + (P.y * P.y);
printf("Distance OP = %f", sqrt(tmp));
return 0;
}

mardi 7 décembre 2010 71

STRUCTURES
 Utilisation d’une structure
typedef struct Point{
float x;
float y;
}Point2D; {ne pas oublier le point-virgule}
{La procédure suivante prend un Point2D en paramètre}
void AfficherPoint2D (Point2D P){
printf(" abs = %f , ord = %f " , P.x, P.y);
}
{La fonction suivante rend un Point2D}
Point2D SaisiePoint2D(){
Point2D P;
afficher("Coordonnées d’un Point 2D :")
scanf ("%f %f" , &P.x, &P.y);
return P;
}

mardi 7 décembre 2010 72

36
07/12/2010

STRUCTURES
 Utilisation d’une structure
{suite … }
{La fonction suivante calcule la distance OP}
float CalculeDistanceOP (Point2D P){
return sqrt((P.x * P.x) + (P.y * P.y));
}

int main(){
Point2D P;
float distanceOP;
P = SaisiePoint2D()
AfficherPoint2D (P);
distanceOP = CalculeDistanceOP (P)
printf("Distance OP = %.2f", distanceOP);
return 0;
}

mardi 7 décembre 2010 73

EXERCICES
 Exercice 1
Définir une structure NombreRationnel permettant de coder un nombre
rationnel, avec numérateur et dénominateur. On écrira des fonctions de
saisie, d’affichage, de multiplication et d’addition de deux rationnels. Pour
l’addition, pour simplifier, on ne cherchera pas nécessairement le plus petit
dénominateur commun.

 Exercice 2
Une menuiserie industrielle gère un stock de panneaux de bois. Chaque
panneau possède une largeur, une longueur et une épaisseur en millimètre,
ainsi que le type de bois qui peut être : pin (code 0), chêne (code 1) ou hêtre
(code 2).

a) Définir une structure Panneau contenant toutes les informations


relatives à un panneau de bois.
b) Ecrire les fonctions de saisie et d’affichage d’un panneau de bois.
c) Ecrire une fonction qui calcule le volume en mètres cube d’un panneau.

mardi 7 décembre 2010 74

37
07/12/2010

LISTES CHAINEES
 Qu’est ce qu’une liste chaînée ?
Une liste chaînée est un ensemble de cellules liées entre elles par des
pointeurs. Chaque cellule est une structure contenant :
- Une ou plusieurs données comme dans n’importe qu’elle structure
- Un pointeur suivant sur la cellule suivante.
On accède à la liste par un pointeur L sur la première cellule, puis en
parcourant la liste d’une cellule à l’autre en suivant les pointeurs suivant.
Le dernier pointeur suivant vaut NULL (c’est-à-dire pointe sur RIEN), ce
qui indique la fin de la liste .

Cellules

L Donnée 1 Donnée 2 Donnée 3 Donnée 4

Pointeurs « suivant » Pointeur « NULL »


Figure 1 – Exemple de liste chaînée avec 4 cellules

mardi 7 décembre 2010 75

LISTES CHAINEES
 Déclarer une liste chaînée
Pour déclarer une liste chaînée, il faut déclarer une nouvelle structure de
données : la structure qui représentera une cellule.
{Les données dans les cellules sont des réels (float)}
typedef float TypeDonnee;
{Définition du type cellule}
typedef struct Cell{
/* Définition des données */

TypeDonnee donnee ;
/* Pointeur sur la structure suivante de même type que celle qu’on est
entrain de définir */
struct Cell *suivant;
}TypeCellule;

NB: La structure TypeCellule contient un pointeur sur TypeCellule. On ne peut


pas déclarer directement un pointeur sur le type TypeCellule qui est en cours
de définition.

mardi 7 décembre 2010 76

38
07/12/2010

LISTES CHAINEES
 Déclarer une liste chaînée
On déclare ensuite le pointeur qui donne l’adresse de la première cellule
(NULL si la liste est vide)
TypeCellule *L; /* Déclaration d’une liste */

void AfficheDonnee (TypeCellule *cellule){


printf(" %f " , cellule->donnee);
}

TypeDonnee SaisieDonnee (){


TypeDonnee donnee;
puts ("Saisissez la donnee :" );
scanf("%f", &donnee);
return donnee;
}

mardi 7 décembre 2010 77

LISTES CHAINEES
 Insertion en tête de liste
La fonction suivante prend en paramètres une liste et une donnée, et ajoute
en tête de liste. La fonction renvoie la nouvelle adresse de la tête de liste. (Cf.
Figure 2).
TypeCellule * InsererEnTete (TypeCellule *OldListe, TypeDonnee
donnee){
TypeCellule *NewListe ; /* Nouvelle tête de liste */
/*Allocation de mémoire pour la nouvelle cellule*/
NewListe = (typeCellule*) malloc (sizeof(TypeCellule)):
/*On met la donnée à ajouter dans la cellule*/
NewListe->donnee = donnee;
/*Le pointeur suivant de la nouvelle cellule pointe
maintenant sur l’ancienne liste */
NewListe->suivant = OldListe;
/*On retourne la nouvelle liste */
return NewListe;
}

mardi 7 décembre 2010 78

39
07/12/2010

LISTES CHAINEES
 Insertion en tête de liste
Schéma récapitulatif

OldListe Donnée 1 Etc …. Donnée n

NewListe newData

Figure 2 – Insertion en tête de liste

NB : Faut pas confondre l’utilisation du point (.) et l’utilisation de la flèche pour


accéder aux champs d’une structure. On utilise le point pour une variable de
type structure et la flèche pour une variable de type « pointeur » sur
structure.

mardi 7 décembre 2010 79

LISTES CHAINEES
 Construction d’une liste chaînée
Les listes chaînées se construisent par des insertions successives. La fonction suivante
réalise la saisie d’une liste chaînée au clavier.
TypeCellule * SaisieListe (){
char choix;
TypeDonnee donnee;
/*Déclaration d’une liste vide, initialisation à NULL obligatoire */
TypeCellule *L = NULL;
do{
donnee = SaisieDonnee();
L = InsererEnTete (L, donnee); /*Insertion en tête */

puts("Voulez-vous insérer un élément ?");


getchar (choix);
} while(choix == ‘o’ || choix == ‘O’);
return L;
}

mardi 7 décembre 2010 80

40
07/12/2010

LISTES CHAINEES
 Parcours de liste
L’idée de parcours de liste chaînée est de prendre un pointeur auxilliaire p.
On fait pointer p sur la première cellule, puis le pointeur p passe à la cellule
suivante (par une affectation p = p->suivant) , etc …. Le parcours s’arrête
lorsque p vaut le suivant de la dernière cellule, c’est-à-dire lorsque p vaut
NULL.

L Donnée 1 Donnée 2 Donnée 3 Donnée 4

P Pointeur p->suivant

Figure 3 – Parcours de la liste chaînée

mardi 7 décembre 2010 81

LISTES CHAINEES
 Parcours de liste
En guise d’exemple, considérons la procédure suivante qui réalise l’affichage
d’une liste chaînée.
void AfficheListe (TypeCellule *L){
TypeCellule *P;
P = L /* On pointe sur la première cellule */
while (P != NULL){
AfficheDonnee (P);
P = P->suivant
}
}
Lors du parcours, on s’arrête lorsque p vaut NULL et non pas quand
p->suivant vaut NULL. En effet, p->suivant vaut NULL lorsque p pointe
sur la dernière cellule. Il faut bien évidemment traiter également cette
dernière.

mardi 7 décembre 2010 82

41
07/12/2010

LISTES CHAINEES
 Insertion en fin de liste
Avec des insertions en tête de liste, la liste obtenue est classée à l’envers, le
dernier élément saisi est le premier élément de la liste. Pour obtenir les
éléments à l’endroit, il faut faire les insertions en fin de liste.
L’ajout d’une cellule en fin de liste est un peu plus compliqué que l’insertion
en tête de liste. Notamment, elle nécessite un parcours de la liste pour
rechercher l’adresse du dernier élément. (Cf. figure 4)

TypeCellule * InsererEnFin (TypeCellule *L, TypeDonnee donnee){


TypeCellule *NewCell ; /* Nouvelle cellule */
/*Allocation de mémoire pour la nouvelle cellule*/
NewCell = (typeCellule *) malloc (sizeof(TypeCellule));
NewCell->donnee = donnee ;
NewCell->suivant = NULL;
if (L == NULL){
/* Cas particulier : liste vide */

mardi 7 décembre 2010 83

LISTES CHAINEES
 Insertion en fin de liste
(Suite …)
L = NewCell;
} else {
/*On recherche la dernière cellule */
p = L;
while (p->suivant != NULL){
p = p->suivant;
} /* fin while */
p->suivant = NewCell; /*chaînage*/
} /* fin if */
return L;
} /* fin main */

mardi 7 décembre 2010 84

42
07/12/2010

LISTES CHAINEES
 Insertion en fin de liste
Schéma récapitulatif :

NewCell Donnée 5

Chaînage

L Donnée 1 Donnée 2 Donnée 3 Donnée 4

P
Figure 4 – Insertion en fin de liste

mardi 7 décembre 2010 85

LISTES CHAINEES
 Insertion en fin de liste
L’insertion en fin de liste permet de saisie une liste chaînée à l’endroit :
TypeCellule * SaisieListe (){
char choix;
TypeDonnee donnee;
/*Déclaration d’une liste vide, initialisation à
NULL obligatoire */
TypeCellule *L = NULL;
do{
donnee = SaisieDonnee();
L = InsererEnFin (L, donnee); /* Insertion en
tête */
puts("Voulez-vous insérer un élément ?");
getchar (choix);
} while(choix == ‘o’ || choix == ‘O’);
return L;
}
mardi 7 décembre 2010 86

43
07/12/2010

LISTES CHAINEES
 Libération de mémoire
Pour libérer la mémoire occupée par une liste chaînée, il faut détruire
chacune des cellules avec la fonction free. Pour éviter les éventuels
bugs, il vaut mieux que la fonction réinitialise la tête de la liste à NULL (liste
vide). Pour cela la fonction doit modifier le pointeur de tête de liste, et il faut
donc passer ce pointeur par adresse.
void Liberation (TypeCellule **pL){
/*Passage d’un pointeur par adresse*/
/*Pointeur sur Pointeur*/
typeCellule *p;
while (*pL != NULL) {
p = *pL;
*pL = (*pL)->suivant
free(p)
}
*pL = NULL;
}

mardi 7 décembre 2010 87

LISTES CHAINEES
 Libération de mémoire
Exemple global :

Programme FreeMemory
int main(){
typeCellule *L;
L = SaisieListeEndroit ();
AfficheListe (L);
Liberation (&L); /*passage de l’adresse du
pointeur*/
return 0;
}

NB: Après l’appel de la procédure Liberation, la liste est vide (pointeur


NULL)

mardi 7 décembre 2010 88

44
07/12/2010

EXERCICES
Exercice 1 :
Ecrire un programme qui :
 Initialise une liste chainée de nombres entiers par insertion successive en fin
avec l’aide de l’utilisateur.
 Affiche toute la liste ainsi créée.
 Insère la valeur "0" après tout nombre pair rencontrée dans la liste.
 Affiche ensuite la chaîne résultante.
 On pourra créer une fonction « AfficheListe » qui affichera le contenu de la
liste.
Par exemple :
Chaine initialisée : 1 – 5 – 8 – 9 – 0 – 2 – 8 – 7
Chaîne résultante : 1 – 5 – 8 – 0 – 9 – 0 – 0 – 2 – 0 – 8 – 0 - 7

mardi 7 décembre 2010 89

PILES
 Qu’est ce qu’une pile ?
Une pile est une structure de données dans laquelle on peut ajouter et
supprimer des éléments suivant la règle du dernier arrivé premier sorti ou
encore LIFO (Last In First Out).

Le nom de pile vient d’une analogie avec une pile d’assiettes (par exemple)
où l’on poserait toujours les assiettes sur le dessus de la pile, et où l’on
prendrait toujours les assiettes sur le dessus de la pile. Ainsi la dernière
assiette posée sera utilisée avant toutes les autres.

Une pile peut être implémentée par un tableau ou par une liste chaînée.
Dans les deux cas, il est commode de réaliser sur les piles des opérations de
base, appelées primitives de gestion des piles.

mardi 7 décembre 2010 90

45
07/12/2010

PILES
 Les primitives de gestion des piles

- Initialiser
Crée une pile vide
- EstVide
Renvoie 1 si la pile est vide, 0 sinon
- Empiler
Permet d’ajouter un élément au sommet de la pile. La fonction renvoie un
code d’erreur si besoin au cas de manque de mémoire.
- Depiler
Supprime le sommet de la pile. L’élément supprimé est retourné par la
fonction Depiler pour pouvoir être utilisé.

mardi 7 décembre 2010 91

PILES
 Les primitives de gestion des piles
Le principe de gestion des piles est que, lorsqu’on utilise une pile, on ne se
préoccupe pas de la manière dont elle a été implémentée, mais on utilise
uniquement les primitives qui sont toujours les mêmes.

Dans les prochains slides, nous étudierons l’implémentation des primitives


de gestion des piles sous forme de liste chaînée.

mardi 7 décembre 2010 92

46
07/12/2010

PILES
 Types
Pour implémenter une pile sous forme de liste chaînée, on crée la structure
de données suivante.

typedef float TypeDonnee;

typedef struct Cell{


TypeDonnee donnee;
/* pointeur sur la cellule précédente */
struct Cell *suivant;
}TypeCellule;

typedef TypeCellule* Pile;


/* La Pile est un pointeur sur la tête de liste */

mardi 7 décembre 2010 93

PILES
 Créer une pile vide
La fonction permettant de créer une pile vide est la suivante :

Pile Initialiser(){
return NULL; /*On retourne une liste vide*/

mardi 7 décembre 2010 94

47
07/12/2010

PILES
 Pile vide
La fonction permettant de savoir si la pile est vide est la suivante. Elle
renvoie 1 si la pile est vide, 0 sinon.

int EstVide(Pile P){


P == NULL ? return 1 : return 0 ;
}

mardi 7 décembre 2010 95

PILES
 Ajouter un élément au sommet
La fonction d’ajout d’un élément est une fonction d’insertion en tête de liste.

void Empiler(Pile* pP, TypeDonnee elt){


Pile NewCell;
NewCell = (TypeCellule*)malloc(sizeof(TypeCellule));
/*ajout de l’élément à empiler*/
NewCell ->donnee = elt;
/*Insertion en tête de liste*/
NewCell ->suivant = *pP;
/*Mise à jour de la tête de liste*/
*pP  NewCell;
}

mardi 7 décembre 2010 96

48
07/12/2010

PILES
 Supprimer un élément de la pile
La fonction Depiler supprime la tête de liste en cas de pile non vide. La
fonction renvoie 1 en cas d’erreur, et 0 en cas de succès. La pile est passée par
adresse, on a donc un double pointeur.

int Depiler(Pile* pP , TypeDonnee *pElt){


Pile q;
if (EstVide(*pP) != 1){
return 1; /*On ne peut pas supprimer d’élément*/

}
q = *pP /*Mémorisation d’adresse de la première cellule*/

*pElt = (*pP)->donnee; /*recupération de la valeur*/

*pP = (*pP)->suivant /*passage au suivant*/

free(q) /*Destruction de la cellule mémorisée*/

return 0;
}

mardi 7 décembre 2010 97

EXERCICES
Exercice 1
Ecrire une primitive C qui libère toute la mémoire occupée par
une pile.

Exercice 2
Ecrire un programme utilisant une pile (implémentée sous forme de
liste chaînée) qui affiche une liste chaînée à l’envers.

mardi 7 décembre 2010 98

49
07/12/2010

FILES
 Qu’est ce qu’une file ?
Une file est une structure de données dans laquelle on peut ajouter et
supprimer des éléments suivant la règle du premier arrivé premier sorti, ou
encore FIFO (First In First Out).

Le nom de file vient de l’analogie avec une file d’attente à un guichet, dans
laquelle le premier arrivé sera le premier servi. Les usagers arrivent en fin de
file et sortent de la file à sa tête.

Une file peut être implémentée par une liste chaînée, ou par un tableau avec
une gestion circulaire. Comme dans le cas des piles, la gestion par tableaux
présente l’inconvénient que la file a une capacité limitée, contrairement à la
gestion par listes chaînées.

Comme dans le cas des piles, on gère les files à l’aide des primitives.

mardi 7 décembre 2010 99

FILES
 Les primitives de gestion des files

- Initialiser
Crée une file vide
- EstVide
Renvoie 1 si la file est vide, 0 sinon
- Enfiler
Permet d’ajouter un élément en fin de la file. La fonction renvoie un
code d’erreur si besoin au cas de manque de mémoire.
- Defiler
Supprime la tête de la file. L’élément supprimé est retourné par la
fonction Defiler pour pouvoir être utilisé.

mardi 7 décembre 2010 100

50
07/12/2010

FILES
 Types
Pour implémenter une file sous forme de liste chaînée, on introduit un
pointeur sur la tête de liste et un pointeur sur la queue de liste (Cf. figure 5).
Ceci permet de faire les insertion en queue de liste sans avoir à parcourir la
liste pour trouver l’adresse de la dernière cellule.

typedef float TypeDonnee;

typedef struct Cell{


TypeDonnee donnee;
struct Cell *suivant; /* pointeur sur la cellule suivante */

}TypeCellule;

typedef struct {
TypeCellule *tete, *queue; /* pointeur sur la première et
dernière cellule */

}File;
mardi 7 décembre 2010 101

FILES
 Pointeur en tête et en queue de file
Schéma illustratif :

Donnée 1 Donnée 2 Donnée 3 Donnée 4

tête queue
Figure 4 – Gestion d’une file

mardi 7 décembre 2010 102

51
07/12/2010

PILES
 Créer une file vide
La fonction permettant de créer une file vide est la suivante :

Pile Initialiser(){
File filevide;
filevide.tete = NULL; /* liste vide : NULL */

mardi 7 décembre 2010 103

FILES
 File vide
La fonction permettant de savoir si la file est vide est la suivante. Elle
renvoie 1 si la file est vide, 0 sinon.

int EstVide(File F){


F.tete == NULL ? return 1 : return 0 ;
}

mardi 7 décembre 2010 104

52
07/12/2010

FILES
 Accéder en tête de file
Le tête de la file est le premier élément entré dans la liste. La fonction renvoie
1 en cas de liste vide, 0 sinon.

int AccederTete(File F, TypeDonnee *pelt){


if (estVide(F) == 1){
return 1; /*code d’erreur, la file est vide*/
}
/* On renvoie la donnée de la tête*/
*pel = F.tete->donnee
return 0;
}

mardi 7 décembre 2010 105

FILES
 Ajouter un élément à la fin de la file
La fonction d’ajout d’un élément est une fonction d’insertion en fin de liste.
void Enfiler(File* pF, TypeDonnee elt){
TypeCellule *NewCell;
NewCell = (TypeCellule*)malloc(sizeof(TypeCellule));
NewCell->donnee = elt; /*ajout de l’élément à enfiler*/

NewCell->suivant = NULL;
if (pF->tete == NULL){ /* si file vide */
pF->queue = pF->tete = NewCell;
} else {
pF->queue->suivant = NewCell; /* insertion en fin
de file */
pF->queue = NewCell;
}
}

mardi 7 décembre 2010 106

53
07/12/2010

FILES
 Supprimer un élément de la file
La fonction Defiler supprime la tête de liste en cas de file non vide. La fonction
renvoie 1 en cas d’erreur, et 0 en cas de succès. La file est passée par adresse, on
a donc un double pointeur.

int Defiler(File* pF, TypeDonnee *pelt){


TypeCellule *p;
if (EstVide(*pF) == 1)
return 1; /*On ne peut pas supprimer d’élément*/

*pelt = pF->tete->donnee; /*On renvoie l’élément*/

p = pF->tete; /*Mémorisation de la tete de file*/

pF->tete = pF->tete->suivant; /*passage au suivant*/


free(p); /*Destruction de l’ancienne tete de file*/

return 0;
}

mardi 7 décembre 2010 107

FILES
 Détruire
La destruction de la liste doit libérer toute la mémoire de la liste chaînée
(destruction individuelle des cellules).

void Detruire(File *pF){


TypeCellule *p, *q;
p = pF->tete /*Initialisation pour parcourir la liste*/

while (p!= NULL){ /* Parcours de la liste */

q = p; /* Mémorisation de l’adresse */

p = p->suivant; /* Passage au suivant */

free (q);
}
*pF = p;
}

mardi 7 décembre 2010 108

54
07/12/2010

EXERCICES
Exercice 1
Ecrire une fonction capable de comparer le contenu d’une file et le contenu d’une
pile. Dans le cas où la pile contient de son sommet vers sa base, les mêmes
éléments que la file de son début vers sa fin, la fonction doit retourner Vrai. Dans
le cas contraire elle retourne Faux.

mardi 7 décembre 2010 109

LA RECURSIVITE
 Introduction
« Une procédure récursive est une procédure récursive »
Une procédure récursive est une procédure qui s’appelle elle-même. La
récursivité utilise toujours la pile du programme en cours.

Une "pile" est une zone mémoire réservée à chaque programme; sa taille
peut être fixée manuellement par l'utilisateur. Son rôle est de stocker les
variables locales et les paramètres d'une procédure de sorte à pouvoir y
revenir au cas où un appel modulaire aurait interrompu l’exécution en
cours.

Dans une procédure récursive, toutes les variables locales sont stockées dans
la pile, et empilées autant de fois qu'il y a d'appels récursifs. Donc la pile se
remplit progressivement, et si on ne fait pas attention on arrive à un
"débordement de pile". Ensuite, les variables sont désempilées.

Une procédure récursive comporte un appel à elle-même, alors qu'une


procédure non récursive ne comporte que des appels à d'autres procédures.

mardi 7 décembre 2010 110

55
07/12/2010

LA RECURSIVITE
 Construction d’un algorithme récursif

Un peu comme pour définir une suite par récurrence en


maths, il faut :
1. Un (ou plusieurs) cas de base, dans lequel l’algorithme ne
s’appelle pas lui-même.
Sinon l’algorithme ne peut pas terminer.
2. Si on ne se situe pas dans un cas de base, l’algorithme fait
appel à lui-même (appel récursif).
Chaque appel récursif doit en principe se « rapprocher » d’un
cas de base, de façon à permettre la terminaison du
programme.

mardi 7 décembre 2010 111

LA RECURSIVITE
 Introduction

Exemple :

void recursive (/*paramètres*/) {


/*déclarations des variables locales*/
if( /*test d’arrêt*/){
/*instructions du point d’arrêt*/
} else {
/* instructions*/
recursive(/*paramètres changés*/);
/*instructions*/
}
}

mardi 7 décembre 2010 112

56
07/12/2010

LA RECURSIVITE
 Exemple avec factoriel
int factorielle (int n) {
if(n==0)
return 1 ; {cas de base}
{point terminal}
else
return n * factorielle (n-1);
}
Cette fonction retourne n! si n est positif ou nul, et ne termine pas si n
est négatif (appels récursifs infinis).

Note : Concrètement, si n est négatif le programme provoque (le plus


souvent) une erreur à l’exécution pour cause de dépassement de
mémoire autorisé dans les appels de fonctions (pile d’appel).

mardi 7 décembre 2010 113

LA RECURSIVITE
 Commentaires
On constate, et il le faut, que les paramètres de l'appel récursif
changent.

En effet, à chaque appel, l'ordinateur stocke dans la pile les


variables locales; le fait de ne rien changer dans les paramètres
ferait que l'ordinateur effectuerait un appel infini à cette procédure,
ce qui se traduirait en réalité par un débordement de pile, et d'arrêt
de l'exécution de la procédure en cours.

Grâce à ces changements, tôt ou tard l'ordinateur rencontrera un


ensemble de paramètres vérifiant le test d'arrêt, et donc à ce
moment la procédure récursive aura atteint le "fond" (point
terminal).

Ensuite les paramètres ainsi que les variables locales sont


désempilées au fur et à mesure qu'on remonte les niveaux.
mardi 7 décembre 2010 114

57
07/12/2010

LA RECURSIVITE
 Commentaires
On constate, et il le faut, que les paramètres de l'appel récursif
changent.
En effet, à chaque appel, l'ordinateur stocke dans la pile les
variables locales; le fait de ne rien changer dans les paramètres
ferait que l'ordinateur effectuerait un appel infini à cette procédure,
ce qui se traduirait en réalité par un débordement de pile, et d'arrêt
de l'exécution de la procédure en cours.
Grâce à ces changements, tôt ou tard l'ordinateur rencontrera un
ensemble de paramètres vérifiant le test d'arrêt, et donc à ce
moment la procédure récursive aura atteint le "fond" (point
terminal).
Ensuite les paramètres ainsi que les variables locales sont
désempilées au fur et à mesure qu'on remonte les niveaux.
Note (Point essentiel) : chaque appel récursif dispose de ses
propres variables locales.

mardi 7 décembre 2010 115

LA RECURSIVITE
 Itérations contre récursion
void fonctionRecur (){
while (C){
if (C){
/* … bloc … */
/*… bloc … */
fonctionRecur();
}
}
}

Réciproque :
Tout algorithme itératif peut être transformé en algorithme récursif sans
boucle (en gérant convenablement les variables). Mais :
– ça peut être moins lisible ;
– chaque itération prend un peu de place en mémoire (occupe la pile) ;
– la gestion des variables peut être plus compliquée.

mardi 7 décembre 2010 116

58
07/12/2010

LA RECURSIVITE
 Intérêt des algorithmes récursifs

Essentiellement, deux intérêts (pas si différents) :

1. décomposer une action répétitive en sous-actions «


identiques » de petites tailles :
pour rechercher un élément dans un tableau trié, ou pour
trier un tableau (tri fusion), on parle de « diviser pour
régner ».

2. pour explorer un ensemble de possibilités (par exemple


des coups dans un jeu), on peut faire un appel récursif sur
chaque possibilité.

mardi 7 décembre 2010 117

EXERCICES
Exercice 1
Reécrire les solutions des exercices du slide 93 en
utilisant une méthode itérative.
Exercice 2
Soient les suite un et vn suivantes :

u 0 = 2
 , pour n > 0
u n + 1 = 4 − 7 u n

v0 = v1 = 1
 , pour n > 1
vn+2 = vn + vn+1

Ecrire des modules itératifs et récursifs permettant de


calculer l’élément d’indice n pour chaque série.

mardi 7 décembre 2010 118

59
07/12/2010

ARBRES BINAIRES
 Représentations arborescentes
 Les arborescences sont utilisées :
◦ Dans la vie de tous les jours pour représenter des hiérarchies,
des classifications.

◦ Dans le domaine de l’informatique : pour représenter les


informations ci-dessus et aussi :

 L’organisation interne des fichiers en mémoire


 Le mode de calcul d’une expression
 L’organisation de données triées

mardi 7 décembre 2010 119

ARBRES BINAIRES
 Exemple

Signification du lien :
-Du plus générique au plus spécifique

Autres significations possibles :


-Du plus ancien au plus récent
-De la plus haute priorité à la moindre
-Du plus complexe au plus simple

mardi 7 décembre 2010 120

60
07/12/2010

ARBRES BINAIRES
Comment caractériser un arbre :
 Par sa racine
 Par ses sommets
 Par les arcs reliant les sommets entre eux
 Par ses feuilles

mardi 7 décembre 2010 121

ARBRES BINAIRES
 Définition récursive d’un arbre binaire

 Un arbre binaire est :


◦ Soit un arbre binaire vide
◦ Soit un arbre binaire avec deux sous arbres binaires (appelés fils gauche
et fils droit )

 Définition récursive car un arbre binaire est défini


par un arbre binaire.

 La règle "soit un arbre binaire vide" assure l’arrêt


et donc la cohérence de la définition.

mardi 7 décembre 2010 122

61
07/12/2010

ARBRES BINAIRES
 Primitives de gestion des arbres binaires
 Soit ArbreBinaire une structure définissant un élément
d’un arbre binaire et une référence gauche et droit à ses deux
fils.
typedef float TypeDonnee;
typedef struct arbre {
TypeDonnee info;
struct arbre *gauche;
struct arbre *droit;
} ArbreBinaire;

 int estVide (ArbreBinaire *cible);


◦ /*Retourne 1 (VRAI) si l’arbre est vide et 0 (FAUX)
sinon*/

mardi 7 décembre 2010 123

ARBRES BINAIRES
 Primitives de gestion des arbres binaires
 int info (ArbreBinaire *cible, TypeDonnee *eltA);
◦ /*Ecrit dans la zone pointée par eltA la valeur
enrégistrée dans la racine, retourne un code d’erreur
si l’arbre est vide*/
 ArbreBinaire* filsG(ArbreBinaire *cible);
◦ /*Retourne l’arbre binaire formé par le sous arbre
gauche (fils gauche)*/
 ArbreBinaire* filsD(ArbreBinaire *cible);
◦ /*Retourne l’arbre binaire formé par le sous arbre
droit (fils droit)*/
 ArbreBinaire* newArbreBinaire (ArbreBinaire
*Gauche, typeDonnee info, ArbreBinaire *Droit);
◦ /*Retourne l’arbre binaire créé*/

mardi 7 décembre 2010 124

62
07/12/2010

ARBRES BINAIRES
 Primitive : estVide

int estVide (ArbreBinaire *cible){


/*Retourne 1 (VRAI) si l’arbre est vide et 0 (FAUX)
sinon*/
if(cible == NULL) return 1;
else return 0;
}

mardi 7 décembre 2010 125

ARBRES BINAIRES
 Primitive : info

int info (ArbreBinaire *cible, TypeDonnee *eltA){


/*Ecrit dans la zone pointée par eltA la valeur
enrégistrée dans la racine, retourne un code d’erreur si
l’arbre est vide*/
if(estVide(cible)) return -1; /*code d’erreur*/
else *eltA = cible->info;
return 0;
}

mardi 7 décembre 2010 126

63
07/12/2010

ARBRES BINAIRES
 Primitive : filsG

ArbreBinaire* filsG (ArbreBinaire *cible){


/*Retourne l’arbre binaire formé par le sous arbre gauche
(fils gauche)*/
if(!estVide(cible)){
return cible->gauche;
}
}

mardi 7 décembre 2010 127

ARBRES BINAIRES
 Primitive : filsD

ArbreBinaire* filsD (ArbreBinaire *cible){


/*Retourne l’arbre binaire formé par le sous arbre droit
(fils droit)*/
if(!estVide(cible)){
return cible->droit;
}
}

mardi 7 décembre 2010 128

64
07/12/2010

ARBRES BINAIRES
 Création d’un arbre binaire

ArbreBinaire* newArbreBinaire (ArbreBinaire *Gauche,


typeDonnee info, ArbreBinaire *Droit){
ArbreBinaire *newArbre;
newArbre = (ArbreBinaire *) malloc (sizeof(ArbreBinaire));
newArbre->info = info;
newArbre->droit = Droit;
newArbre->gauche = Gauche;
return newArbre;
}

mardi 7 décembre 2010 129

ARBRES BINAIRES
 Affichages : ordres possibles
 Soit l’arbre suivant :

Affichage
- ordre préfixe : 3 3 7 4 8 0 1 5 2 6 7 9 (racine, gauche, droite)
- ordre postfixe : 4 8 7 3 1 6 7 2 9 5 0 3 (gauche, droite, racine)
- ordre infixe : 4 8 7 3 3 1 0 6 7 2 5 9 (gauche, racine, droite)

mardi 7 décembre 2010 130

65
07/12/2010

ARBRES BINAIRES
 L’arbre précédent est alors créé par :
ArbreBinaire *arbreB;

arbreB =
newArbreBinaire(
newArbreBinaire(
newArbreBinaire(
newArbreBinaire(NULL, 4, NULL), 7,
newArbreBinaire(NULL, 8, NULL)
), 3, NULL ), 3,
newArbreBinaire(
newArbreBinaire(NULL, 1, NULL), 0,
newArbreBinaire(
newArbreBinaire( newArbreBinaire(NULL, 6, NULL),
2, newArbreBinaire(NULL,7, NULL)), 5,
newArbreBinaire(NULL, 9, NULL) )));

mardi 7 décembre 2010 131

ARBRES BINAIRES
 Affichage : PREFIXE

void affichePrefixe (ArbreBinaire *unArbre){


/*
Affiche les valeurs portées par les sommets de
l'arbre binaire, en affichant la valeur portée par
la racine avant les valeurs portées par les sous-
arbres gauche et droit.
*/
if(!estVide(unArbre)){
printf("%.0f ", unArbre->info);
affichePrefixe(unArbre->gauche);
affichePrefixe(unArbre->droit);
}
}

mardi 7 décembre 2010 132

66
07/12/2010

ARBRES BINAIRES
 Affichage : POSTFIXE

void affichePostfixe (ArbreBinaire *unArbre){


/*
Affiche les valeurs portées par les sommets de
l'arbre binaire, en affichant la valeur portée par
la racine après les valeurs portées par les sous-
arbres gauche et droit
*/
if(!estVide(unArbre)){
affichePostfixe(unArbre->gauche);
affichePostfixe(unArbre->droit);
printf("%.0f ", unArbre->info);
}
}

mardi 7 décembre 2010 133

ARBRES BINAIRES
 Affichage : INFIXE

void afficheInfixe (ArbreBinaire *unArbre){


/*
Affiche les valeurs portées par les sommets de
l'arbre binaire, en affichant la valeur portée par
la racine entre les valeurs portées par les sous-
arbres gauche et droit.
*/
if(!estVide(unArbre)){
afficheInfixe(unArbre->gauche);
printf("%.0f ", unArbre->info);
afficheInfixe(unArbre->droit);
}
}

mardi 7 décembre 2010 134

67
07/12/2010

ARBRES BINAIRES
 Compte le nombre de sommets d’un arbre
Cas de base : (cas particulier) : arbre vide : résultat = 0
Cas général : 1 (sommet de l’arbre courant)
+ nb Sommets dans FilsG
+ nb Sommets dans FilsD
int compteSommet (ArbreBinaire *unArbre){
/*
Compte le nombre de sommets d’un arbre binaire
*/
if(estVide(unArbre))
return 0;
else return (1 + compteSommet(unArbre->gauche) +
compteSommet(unArbre->droit));
}

mardi 7 décembre 2010 135

ARBRES BINAIRES
APPLICATION

 Arbres binaires de recherche (ABR)


 Un arbre binaire de recherche est un arbre binaire
dans lequel la valeur de chaque sommet est :
◦ Supérieure [ou égale] à toutes les valeurs étiquetant les
sommets du sous-arbre gauche de ce sommet,
◦ Et Inférieure à toutes les valeurs étiquetant les sommets
du sous-arbre droit de ce sommet.

 Note: Dans un arbre binaire de recherche, le


parcours infixe fournit les contenus des nœuds en
ordre croissant.

mardi 7 décembre 2010 136

68
07/12/2010

ARBRES BINAIRES
APPLICATION
 Algorithme de construction d’un ABR
Soit info la valeur à placer dans l’ABR (l’ajout se fera toujours
sur une «feuille» : arbre binaire dont le FilsG et le FilsD sont
vides)
Si l’arbre est vide
Alors
En créer un, réduit à sa racine, étiquetée avec info.
Sinon
Si info ≤ valeur portée par la racine
Alors l’ajouter au sous-arbre gauche : si cet arbre n’est pas vide,
reprendre l’algorithme sur ce sous-arbre.
Sinon l’ajouter au sous-arbre droit : si cet arbre n’est pas vide,
reprendre l’algorithme sur ce sous-arbre.
Finsi
Finsi

mardi 7 décembre 2010 137

ARBRES BINAIRES
 APPLICATION
 Ajout d’une valeur dans un ABR

void ajoutABR(ArbreBinaire **cible, typeDonnee info){


if(estVide(*cible))
*cible = newArbreBinaire (NULL, info, NULL);
else
if(info <= (*cible)->info)
ajoutABR(&(*cible)->gauche, info);
else
ajoutABR(&(*cible)->droit, info);
}

mardi 7 décembre 2010 138

69
07/12/2010

ARBRES BINAIRES
APPLICATION

 Utilisation d’un ABR : trier une liste

TP :
- Créer une liste chainée quelque.
- Ajouter les éléments de la liste dans un ABR
- Afficher à l’aide de la primitive afficheInfixe les éléments de
l’arbre.

mardi 7 décembre 2010 139

FICHIERS SEQUENTIELS
Définitions et Propriétés
Fichier
Un fichier (angl.: file) est un ensemble structuré de données stocké
en général sur un support externe (disquette, disque dur, disque
optique, bande magnétique, ...).

Un fichier structuré contient une suite d'enregistre-


ments homogènes, qui regroupent le plus souvent plusieurs
composantes appartenant à un même ensemble. (champs).

Fichier séquentiel
Dans des fichiers séquentiels, les enregistrements sont mémorisés
consécutivement dans l'ordre de leur entrée et peuvent seulement être
lus dans cet ordre. Si on a besoin d'un enregistrement précis dans un
fichier séquentiel, il faut lire tous les enregistrements qui le précèdent,
en commençant par le premier.

mardi 7 décembre 2010 140

70
07/12/2010

FICHIERS SEQUENTIELS
Propriétés
Les fichiers séquentiels que nous allons considérer dans ce cours auront les propriétés
suivantes:
-Lesfichiers se trouvent ou bien en état d'écriture ou bien en état de lecture; nous ne
pouvons pas simultanément lire et écrire dans le même fichier.
-A un moment donné, on peut uniquement accéder à un seul enregistrement; celui qui se
trouve en face de la tête de lecture/écriture.
-Après chaque accès, la tête de lecture/écriture est déplacée derrière la donnée lue en
dernier lieu.

Fichiers standards
Il existe deux fichiers spéciaux qui sont définis par défaut pour tous les programmes:
- stdin le fichier d'entrée standard
- stdout le fichier de sortie standard
En général, stdin est lié au clavier et stdout est lié à l'écran, c.-à-d. les programmes lisent
leurs données au clavier et écrivent les résultats sur l'écran.
En UNIX et en MS-DOS, il est possible de dévier l'entrée et la sortie standard vers d'autres
fichiers ou périphériques à l'aide des symboles < (pour stdin ) et > (pour stdout)

mardi 7 décembre 2010 141

FICHIERS SEQUENTIELS
Accès aux fichiers séquentiels
Les problèmes traitant des fichiers ont généralement la forme suivante : un
fichier donné par son nom (et en cas de besoin le chemin d'accès sur le médium
de stockage) doit être créé, lu ou modifié.
La question qui se pose est alors:
Comment pouvons-nous relier le nom d'un fichier sur un support
externe avec les instructions qui donnent accès au contenu du
fichier ?
En résumé, la méthode employée sera la suivante:
Avant de lire ou d'écrire un fichier, l'accès est notifié par la com-
mande fopen. fopen accepte le nom du fichier (p.ex: « C:\ADRESSES.DAT") ,
négocie avec le système d'exploitation et fournit un pointeur spécial qui sera
utilisé ensuite lors de l'écriture ou la lecture du fichier.

Après les traitements, il faut annuler la liaison entre le nom du fichier et le


pointeur à l'aide de la commande fclose.
On peut dire aussi qu'entre les événements fopen() et fclose() le fichier est
ouvert.

mardi 7 décembre 2010 142

71
07/12/2010

FICHIERS SEQUENTIELS
Le type FILE*
Pour pouvoir travailler avec un fichier, un programme a besoin d'un certain
nombre d'informations au sujet du fichier :
- adresse de la mémoire tampon, position actuelle de la tête de lecture/écriture,
type d'accès au fichier : écriture, lecture, état d'erreur, . . .

Ces informations (dont nous n'aurons pas à nous occuper), sont rassemblées dans
une structure du type spécial FILE.
Lorsque nous ouvrons un fichier avec la commande fopen, le système génère
automatiquement un bloc du type FILE et nous fournit son adresse.
Tout ce que nous avons à faire dans notre programme est :
1. déclarer un pointeur du type FILE* pour chaque fichier dont nous avons
besoin,
2. affecter l'adresse retournée par fopen à ce pointeur,
3. employer le pointeur à la place du nom du fichier dans toutes les instructions
de lecture ou d'écriture,
4. libérer le pointeur à la fin du traitement à l'aide de fclose.

mardi 7 décembre 2010 143

FICHIERS SEQUENTIELS
Exemple : Créer et afficher un fichier séquentiel
Avant de rentrer dans les détails du traitement des fichiers, arrêtons-nous sur un
petit exemple comparatif qui réunit les opérations les plus importantes sur les
fichiers.

Problème
On se propose de créer un fichier qui est formé d'enregistrements contenant
comme information le nom d'une personne. Chaque enregistrement est donc
constitué d'une seule rubrique, à savoir, le nom de la personne.

L'utilisateur doit entrer au clavier le nom du fichier, le nombre de personnes et les


noms des personnes. Le programme se chargera de créer le fichier correspondant
sur disque dur.

Après avoir écrit et fermé le fichier, le programme va rouvrir le même fichier en


lecture et afficher son contenu, sans utiliser le nombre d'enregistrements introduit
dans la première partie.

mardi 7 décembre 2010 144

72
07/12/2010

FICHIERS SEQUENTIELS
Solution en langage C
#include <stdio.h>
main() {
FILE *P_FICHIER; /* pointeur sur FILE */
char NOM_FICHIER[30], NOM_PERS[30];
int C, NB_ENREG;
/*Première partie : Créer et remplir le fichier*/
printf("Entrez le nom du fichier à créer : ");
scanf("%s", NOM_FICHIER);
P_FICHIER = fopen(NOM_FICHIER, "w"); /* write */
printf("Nombre d'enregistrements à créer : ");
scanf("%d", &NB_ENREG);
C = 0;
while (C<NB_ENREG) {
printf("Entrez le nom de la personne : "); scanf("%s", NOM_PERS);
fprintf(P_FICHIER, "%s\n", NOM_PERS); C++;
}
fclose(P_FICHIER);

mardi 7 décembre 2010 145

FICHIERS SEQUENTIELS
Solution en langage C

/* Deuxième partie : Lire et afficher le contenu du fichier */
P_FICHIER = fopen(NOM_FICHIER, "r"); /* read */
C = 0;
while (!feof(P_FICHIER)) {
fscanf(P_FICHIER, "%s\n", NOM_PERS);
printf("NOM : %s\n", NOM_PERS);
C++;
}
fclose(P_FICHIER);
return 0;
}

mardi 7 décembre 2010 146

73
07/12/2010

FICHIERS SEQUENTIELS
Ouvrir et fermer des fichiers séquentiels

Avant de créer ou de lire un fichier, nous devons informer le


système de cette intention pour qu'il puisse réserver la mémoire
pour la zone d'échange et initialiser les informations nécessaires à
l'accès du fichier. Nous parlons alors de l'ouverture d'un fichier.

Après avoir terminé la manipulation du fichier, nous devons vider


la mémoire tampon et libérer l'espace en mémoire que nous
avons occupé pendant le traitement. Nous parlons alors de
la fermeture du fichier.

L'ouverture et la fermeture de fichiers se font à l'aide des


fonctions fopen et fclose définies dans la bibliothèque
standard <stdio>.

mardi 7 décembre 2010 147

FICHIERS SEQUENTIELS
Ouvrir un fichier en C – fopen

Lors de l'ouverture d'un fichier avec fopen, le système s'occupe de la réservation


de la mémoire tampon dans la mémoire centrale et génère les informations pour
un nouvel élément du type FILE.

L'adresse de ce bloc est retournée comme résultat si l'ouverture s'est déroulée avec
succès.

La commande fopen peut ouvrir des fichiers en écriture ou en lecture en


dépendance de son deuxième paramètre ("r" ou "w") :
<FP> = fopen ( <Nom> , "w" );
<FP> = fopen ( <Nom> , "r" );

* <Nom> est une chaîne de caractères constante ou une variable de type chaîne
qui représente le nom du fichier sur le support physique.

mardi 7 décembre 2010 148

74
07/12/2010

FICHIERS SEQUENTIELS
Ouverture en écriture

Dans le cas de la création d'un nouveau fichier, le nom du fichier est ajouté au
répertoire du médium de stockage et la tête de lecture/écriture est positionnée sur
un espace libre du médium.
Si un fichier existant est ouvert en écriture, alors son contenu est perdu.
Si un fichier non existant est ouvert en écriture, alors il est créé
automatiquement. Si la création du fichier est impossible alors fopen indique une
erreur en retournant la valeur zéro.

Autres possibilités d'erreurs signalées par un résultat nul:


- chemin d'accès non valide,
- pas de disque/bande dans le lecteur,
- essai d'écrire sur un médium protégé contre l'écriture,
-...
fichier sur le support physique.

mardi 7 décembre 2010 149

FICHIERS SEQUENTIELS
Ouverture en lecture

Dans le cas de la lecture d'un fichier existant, le nom du fichier doit être retrouvé
dans le répertoire du médium et la tête de lecture/écriture est placée sur le
premier enregistrement de ce fichier.

Possibilités d'erreurs signalées par un résultat nul:


- essai d'ouvrir un fichier non existant,
- essai d'ouvrir un fichier sans autorisation d'accès,
- essai d'ouvrir un fichier protégé contre la lecture,
-...

mardi 7 décembre 2010 150

75
07/12/2010

FICHIERS SEQUENTIELS
Fermer un fichier en langage C
fclose ( <FP> );
<FP> est un pointeur du type FILE* relié au nom du fichier que l'on désire
fermer.

La fonction fclose provoque le contraire de fopen:


Si le fichier a été ouvert en écriture, alors les données non écrites de la mémoire
tampon sont écrites et les données supplémentaires (longueur du fichier, date et
heure de sa création) sont ajoutées dans le répertoire du médium de stockage.

Si le fichier a été ouvert en lecture, alors les données non lues de la mémoire
tampon sont simplement 'jetées'.

La mémoire tampon est ensuite libérée et la liaison entre le pointeur sur FILE et
le nom du fichier correspondant est annulée.
Après fclose() le pointeur <FP> est invalide. Des erreurs graves pourraient donc
survenir si ce pointeur est utilisé par la suite!

mardi 7 décembre 2010 151

FICHIERS SEQUENTIELS
Lire et écrire dans des fichiers séquentiels

Fichiers texte
Les fichiers que nous employons dans le cadre de cette présentation sont des
fichiers texte, c.-à-d. toutes les informations dans les fichiers sont mémorisées
sous forme de chaînes de caractères et sont organisées en lignes. Même les valeurs
numériques (types int, float, double, ...) sont stockées comme chaînes de
caractères.

Pour l'écriture et la lecture des fichiers, nous allons utiliser les fonctions
standard fprintf, fscanf, fputc et fgetc qui correspondent
à printf, scanf, putchar et getchar si nous indiquons stdout respective-
ment stdin comme fichiers de sortie ou d'entrée.

mardi 7 décembre 2010 152

76
07/12/2010

FICHIERS SEQUENTIELS
Lire et écrire dans des fichiers séquentiels –
Traitement par enregistrements
Ecrire dans un fichier séquentiel en langage C - fprintf
fprintf( <FP>, "<Form1>\n", <Expr1>);
fprintf( <FP>, "<Form2>\n", <Expr2>);
...
fprintf( <FP>, "<FormN>\n", <ExprN>);
* <FP> est un pointeur du type FILE* qui est relié au nom du fichier cible.
* <Expr1>, <Expr2>, ... , <ExprN> représentent les rubriques qui forment un
enregistrement et dont les valeurs respectives sont écrites dans le fichier.
• <Form1>, <Form2>, ... , <FormN> représentent les spécificateurs de
format pour l'écriture des différentes rubriques.
Remarque
L'instruction :
fprintf(stdout, "Bonjour\n"); est identique à
printf("\Bonjour\n");

mardi 7 décembre 2010 153

FICHIERS SEQUENTIELS
Lire et écrire dans des fichiers séquentiels
Traitement par enregistrements
Lire dans un fichier séquentiel en langage C - fscanf
fscanf( <FP>, "<Form1>\n", <Adr1>);
fscanf( <FP>, "<Form2>\n", <Adr2>);
...
fscanf( <FP>, "<FormN>\n", <AdrN>);
* <FP> est un pointeur du type FILE* qui est relié au nom du fichier à lire.
* <Adr1>, <Adr2>, ... , <AdrN> représentent les adresses des variables qui
vont recevoir les valeurs des différentes rubriques d'un enregistrement lu dans le
fichier.
•<Form1>, <Form2>, ... , <FormN> représentent les spécificateurs de format
pour la lecture des différentes rubriques .
Remarque
L'instruction
fscanf(stdin, "%d\n", &N); est identique à :
scanf("%d\n", &N);

mardi 7 décembre 2010 154

77
07/12/2010

FICHIERS SEQUENTIELS
Lire et écrire dans des fichiers séquentiels

Traitement par caractères


La manipulation de fichiers avec les instructions fprintf et fscanf n'est pas assez
flexible pour manipuler de façon confortable des textes écrits. Il est alors
avantageux de traiter le fichier séquentiellement caractère par caractère.

Ecrire un caractère dans un fichier séquentiel - fputc


fputc( <C> , <FP> );
fputc transfère le caractère indiqué par <C> dans le fichier référencé par <FP> et
avance la position de la tête de lecture/écriture au caractère suivant.
* <C> représente un caractère (valeur numérique de 0 à 255) ou le symbole de fin
de fichier EOF (qu’on verra par la suite).
* <FP> est un pointeur du type FILE* qui est relié au nom du fichier cible.

mardi 7 décembre 2010 155

FICHIERS SEQUENTIELS
Lire et écrire dans des fichiers séquentiels
Traitement par caractères
La manipulation de fichiers avec les instructions fprintf et fscanf n'est pas assez
flexible pour manipuler de façon confortable des textes écrits. Il est alors
avantageux de traiter le fichier séquentiellement caractère par caractère.

Ecrire un caractère dans un fichier séquentiel - fputc


fputc( <C> , <FP> );
fputc transfère le caractère indiqué par <C> dans le fichier référencé par <FP> et
avance la position de la tête de lecture/écriture au caractère suivant.
<C> représente un caractère (valeur numérique de 0 à 255) ou le symbole de fin
de fichier EOF (qu’on verra par la suite).
<FP> est un pointeur du type FILE* qui est relié au nom du fichier cible.
Remarque
L'instruction
fputc('a', stdout); est identique à
putchar('a');

mardi 7 décembre 2010 156

78
07/12/2010

FICHIERS SEQUENTIELS
Lire et écrire dans des fichiers séquentiels
Traitement par caractères
Lire un caractère dans un fichier séquentiel - fgetc
<C> = fgetc( <FP> );
fgetc fournit comme résultat le prochain caractère du fichier référencé par <FP>
et avance la position de la tête de lecture/écriture au caractère suivant. A la fin du
fichier, fgets retourne EOF.

<C> représente une variable du type int qui peut accepter une valeur numérique
de 0 à 255 ou le symbole de fin de fichier EOF.
<FP> est un pointeur du type FILE* qui est relié au nom du fichier à lire.
Remarque
L'instruction
C = fgetc(stdin); est identique à
C = getchar();

mardi 7 décembre 2010 157

FICHIERS SEQUENTIELS
Lire et écrire dans des fichiers séquentiels
Détection de la fin d'un fichier en langage C - feof
feof( <FP> );
feof retourne une valeur différente de zéro, si la tête de lecture du fichier référencé par
<FP> est arrivée à la fin du fichier; sinon la valeur du résultat est zéro.<FP> est un
pointeur du type FILE* qui est relié au nom du fichier à lire.
Pour que la fonction feof détecte correctement la fin du fichier, il faut qu'après la
lecture de la dernière donnée du fichier, la tête de lecture arrive jusqu'à la position de la
marque EOF.
Nous obtenons cet effet seulement si nous terminons aussi la chaîne de format
de fscanf par un retour à la ligne '\n' (ou par un autre signe d'espacement).
Exemple
Une boucle de lecture typique pour lire les enregistrements d'un fichier séquentiel
référencé par un pointeur FP peut avoir la forme suivante:
while (!feof(FP)) {
fscanf(FP, "%s\n ... \n", NOM, ... );
. . .
}

mardi 7 décembre 2010 158

79
07/12/2010

FICHIERS SEQUENTIELS
Lire et écrire dans des fichiers séquentiels
Exemple
Le programme suivant lit et affiche le fichier "C:\AUTOEXEC.BAT" en le
parcourant caractère par caractère:
#include <stdio.h>
#include <stdlib.h>
main() {
FILE *FP;
FP = fopen("C:\\AUTOEXEC.BAT", "r");
if (!FP) {
printf("Impossible d'ouvrir le fichier\n");
exit(-1);
}
while (!feof(FP)) putchar(fgetc(FP));
fclose(FP);
return 0;
}
mardi 7 décembre 2010 159

Exercices
Exercice 1
Créer sur disque (C:\<votre_nom>\) puis afficher à l'écran le fichier INFORM.TXT dont les
informations sont structurées de la manière suivante :
Numéro de matricule (entier)
Nom (chaîne de caractères)
Prénom (chaîne de caractères)
Le nombre d'enregistrements à créer est à entrer au clavier par l'utilisateur.

Exercice 2
Ecrire un programme qui crée sur le disque (C:\<votre_nom>\) un fichier INFBIS.TXT qui
est la copie exacte (enregistrement par enregistrement) du fichier INFORM.TXT.

Exercice 3
Ajouter un nouvel enregistrement (entré au clavier) à la fin de INFORM.TXT et sauver le
nouveau fichier sous le nom INFBIS.TXT.

mardi 7 décembre 2010 160

80
07/12/2010

Exercices
Exercice 4
Ecrire un programme qui détermine dans un fichier un texte dont le nom est entré au clavier :
le nombre de caractères qu'il contient, le nombre de chacune des lettres de l'alphabet( sans distinguer
les majuscules et les minuscules), le nombre de mots, le nombre de paragraphes (c.-à-d.: des retours
à la ligne),
Les retours à la ligne ne devront pas être comptabilisés dans les caractères. On admettra que deux
mots sont toujours séparés par un ou plusieurs des caractères suivants: fin de ligne, espace,
ponctuation: (. : , ; ? !), parenthèses: ( ), guillemets: ", apostrophe: '.
Utiliser une fonction d'aide SEPA qui décide si un caractère transmis comme paramètre est l'un des
séparateurs mentionnés ci-dessus. SEPA restituera la valeur (logique) 1 si le caractère est un
séparateur et 0 dans le cas contraire. SEPA utilise un tableau qui contient les séparateurs à détecter.
Exemple:
Nom du fichier texte : A:LITTERA.TXT
Votre fichier contient:
12 paragraphes
571 mots
4186 caractères
dont
279 fois la lettre a
56 fois la lettre b . . .
3 fois la lettre z
et 470 autres caractères

mardi 7 décembre 2010 161

ALGORITHME DE TRI
1- TRI A BULLES
Le principe du tri à bulles est de comparer deux valeurs adjacentes et
d'inverser leur position si elles sont mal placées (si un premier nombre x
est plus grand qu'un deuxième nombre y et que l'on souhaite trier
l'ensemble par ordre croissant, alors x et y sont mal placés et il faut les
inverser).

Si, au contraire, x est plus petit que y, alors on ne fait rien et l'on compare
y à z, l'élément suivant. C'est donc itératif.

Et on parcourt ainsi la liste jusqu'à ce qu'on ait réalisé n-1 passages (n


représentant le nombre de valeurs à trier) ou jusqu'à ce qu'il n'y ait plus
rien à inverser lors du dernier passage.

mardi 7 décembre 2010 162

81
07/12/2010

ALGORITHME DE TRI
1- TRI A BULLES
Au premier passage, le plus grand élément de la liste est placé au bout du
tableau, au bon emplacement.

Pour le passage suivant, nous ne sommes donc plus obligés de faire une
comparaison avec le dernière élément ; et c'est bien plus avantageux
ainsi. Donc à chaque passage, le nombre de valeurs à comparer diminue
de 1.

mardi 7 décembre 2010 163

ALGORITHME DE TRI
1- TRI A BULLES
Implémentation – en C
void tri_bulles(int tab[], int taille){
int tab_en_ordre = 0;
while(!tab_en_ordre){
tab_en_ordre = 1;
for(int i=0 ; i < taille-1 ; i++){
if(tab[i] > tab[i+1]){
inverser(tab+i, tab+i+1);
tab_en_ordre = 0;
}
} void inverser(int *a, int *b){
int tmp;
taille--; tmp = *a;
} *a = *b;
} *b = tmp;
}

mardi 7 décembre 2010 164

82
07/12/2010

ALGORITHME DE TRI
1- TRI A BULLES
Au premier passage, le plus grand élément de la liste est placé au bout du
tableau, au bon emplacement.

Pour le passage suivant, nous ne sommes donc plus obligés de faire une
comparaison avec le dernière élément ; et c'est bien plus avantageux
ainsi. Donc à chaque passage, le nombre de valeurs à comparer diminue
de 1.

mardi 7 décembre 2010 165

ALGORITHME DE TRI
1- TRI PAR INSERTION
Le tri par insertion est un algorithme de tri classique dont le principe
est très simple.

C'est le tri que la plupart des personnes utilisent naturellement pour trier
des cartes : prendre les cartes mélangées une à une sur la table, et former
une main en insérant chaque carte à sa place.

Le tri par insertion est cependant considéré comme le tri le plus efficace
sur des entrées de petite taille. Il est aussi très rapide lorsque les données
sont déjà presque triées.

mardi 7 décembre 2010 166

83
07/12/2010

ALGORITHME DE TRI
1- TRI PAR INSERTION
Principe
Dans l'algorithme, on parcourt le tableau à trier du début à la fin. Au moment où
on considère le i-ème élément, les éléments qui le précèdent sont déjà triés.
Pour faire l'analogie avec l'exemple du jeu de cartes, lorsqu'on est à la i-ème étape
du parcours, le i-ème élément est la carte saisie, les éléments précédents sont la
main triée et les éléments suivants correspondent aux cartes encore mélangées sur
la table.

L'objectif d'une étape est d'insérer le i-ème élément à sa place parmi ceux qui
précèdent. Il faut pour cela trouver où l'élément doit être inséré en le comparant
aux autres, puis décaler les éléments afin de pouvoir effectuer l'insertion. En
pratique, ces deux actions sont fréquemment effectuées en une passe, qui consiste
à faire « remonter » l'élément au fur et à mesure jusqu'à rencontrer un élément
plus petit.

mardi 7 décembre 2010 167

ALGORITHME DE TRI
1- TRI PAR INSERTION
Implémentation – en C
void tri_insertion(int tab[], int taille){
int i, j, tmp;
for(i = 0; i < taille; i++){
tmp = tab[i];
j = i;
while(j>0 && tab[j-1]> tmp){
tab[j] = tab[j-1];
j--;
}
tab[j] = tmp; /*insertion*/
}
}

mardi 7 décembre 2010 168

84
07/12/2010

EXERCICES
Exercice :
Après avoir construit une liste chainée de nombres entiers. Trier la liste à
l’aide des algorithmes de tri présentés

mardi 7 décembre 2010 169

- FIN -

mardi 7 décembre 2010 170

85