Académique Documents
Professionnel Documents
Culture Documents
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
1
07/12/2010
Objectif
Bonne base algorithme
Maîtrise des différentes structures de
données manipulées et utiles en
programmation
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é.
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
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
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, …
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.
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.
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.
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.
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
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.
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
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.
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
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
Notions de base
Les opérateurs : opérateurs arithmétiques
Exemple :
a 15
b 10
c a+b
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.
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.
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.
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
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
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
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
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 à
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
Notions de base
Exercices d’application
Ecrire un algorithme permettant de résoudre l’équation
suivante : aX + b = 0, a != 0 et b != 0.
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 …
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.
16
07/12/2010
Notions de base
tant que … faire
Exemple :
i 0
tant que (i<10) faire
i i + 1
fintq
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.
17
07/12/2010
Notions de base
faire … tant que
Exemple :
i 0
faire
i i + 1
tant que (i<10)
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.
18
07/12/2010
Notions de base
pour …
Exemple :
pour i 0 jusqu’à 9 faire
n n + 1
afficher ("Bonjour ", i, "fois")
finpour
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.
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
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
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
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
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
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
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
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.
Les pointeurs
Un pointeur est une variable spéciale qui peut contenir
l’adresse d’une autre variable.
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.
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.
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.
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 :
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 »
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++
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
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])
29
07/12/2010
30
07/12/2010
31
07/12/2010
32
07/12/2010
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.
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}
STRUCTURES
Utilisation d’une structure
L’on pourrait rajouter d’autres données des types que l’on souhaite à
la suite des données x, y dans la structure.
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;
}
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;
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;
}
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;
}
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;
}
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).
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
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;
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 */
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;
}
39
07/12/2010
LISTES CHAINEES
Insertion en tête de liste
Schéma récapitulatif
NewListe newData
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 */
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.
P Pointeur p->suivant
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.
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)
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 */
42
07/12/2010
LISTES CHAINEES
Insertion en fin de liste
Schéma récapitulatif :
NewCell Donnée 5
Chaînage
P
Figure 4 – Insertion en fin de liste
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;
}
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;
}
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
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.
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é.
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.
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.
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*/
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.
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.
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.
}
q = *pP /*Mémorisation d’adresse de la première cellule*/
return 0;
}
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.
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.
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é.
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.
}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 :
tête queue
Figure 4 – Gestion d’une file
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 */
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.
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.
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;
}
}
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.
return 0;
}
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).
q = p; /* Mémorisation de l’adresse */
free (q);
}
*pF = p;
}
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.
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.
55
07/12/2010
LA RECURSIVITE
Construction d’un algorithme récursif
LA RECURSIVITE
Introduction
Exemple :
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).
LA RECURSIVITE
Commentaires
On constate, et il le faut, que les paramètres de l'appel récursif
changent.
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.
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.
58
07/12/2010
LA RECURSIVITE
Intérêt des algorithmes récursifs
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
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.
ARBRES BINAIRES
Exemple
Signification du lien :
-Du plus générique au plus spécifique
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
ARBRES BINAIRES
Définition récursive d’un arbre binaire
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;
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éé*/
62
07/12/2010
ARBRES BINAIRES
Primitive : estVide
ARBRES BINAIRES
Primitive : info
63
07/12/2010
ARBRES BINAIRES
Primitive : filsG
ARBRES BINAIRES
Primitive : filsD
64
07/12/2010
ARBRES BINAIRES
Création d’un arbre binaire
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)
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) )));
ARBRES BINAIRES
Affichage : PREFIXE
66
07/12/2010
ARBRES BINAIRES
Affichage : POSTFIXE
ARBRES BINAIRES
Affichage : INFIXE
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));
}
ARBRES BINAIRES
APPLICATION
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
ARBRES BINAIRES
APPLICATION
Ajout d’une valeur dans un ABR
69
07/12/2010
ARBRES BINAIRES
APPLICATION
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.
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, ...).
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.
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)
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.
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.
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.
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);
…
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;
}
73
07/12/2010
FICHIERS SEQUENTIELS
Ouvrir et fermer des fichiers séquentiels
FICHIERS SEQUENTIELS
Ouvrir un fichier en C – fopen
L'adresse de ce bloc est retournée comme résultat si l'ouverture s'est déroulée avec
succès.
* <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.
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.
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.
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.
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!
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.
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");
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);
77
07/12/2010
FICHIERS SEQUENTIELS
Lire et écrire dans des fichiers séquentiels
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.
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();
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, ... );
. . .
}
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.
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
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.
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.
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;
}
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.
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.
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.
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*/
}
}
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
- FIN -
85