Académique Documents
Professionnel Documents
Culture Documents
FICHIERS
Stocker les données dans des fichiers pour pouvoir les utiliser plus
tard.
Tout fichier peut être schématisé par un ruban défilant devant une tête de L/ E.
Elément accessible ou élément courant est l’ élément en face de la tête de L/ E.
Un fichier étant une suite finie, on introduit une marque sur le ruban notée -| (fin de
fichier).
2
tête de lecture/ écriture
sens de défilement
x1 x2 x3 x4 …. xn -|
-----f- -----------------------f+----------------
L’élément courant (x3) permet, par définition, de
décomposer un fichier en deux sous-fichiers : f = f- || f+
Où f- est le sous-fichier déjà lu et f+ est le sous-fichier
qui reste à lire. L’élément courant est le premier élément
de f+.
Dans cet exemple : f- = <x1, x2>, f+ = <x3,…,xn>,
l’élément courant est x4.
Notation f—est définie par : f- = f--|| val.
f—est le fichier f- avant exécution de l’action : lire(f,val)
3
1.2. Actions primitives d’accès
Elles donnent un modèle du comportement dynamique d’un fichier.
1.2.1. Déclaration
Etapes pour traiter un fichier :
- déclarer un pointeur de type FILE*,
- affecter l’adresse retournée par fopen à ce pointeur,
- employer le pointeur à la place du nom du fichier dans toutes les
instructions de lecture ou d’écriture,
-libérer le pointeur à la fin du traitement par fclose.
4
a) Ouvrir un fichier séquentiel
FILE * fopen(char *nom, char *mode) ;
On passe 2 chaînes de caractères :
nom : celui figurant sur le disque, par exemple toto.dat.
mode : r lecture seule ;
w écriture seule, destruction de l’ancienne si elle existe.
a+ lecture et écriture d’un fichier existant (mise à jour), pas de
création d’une nouvelle version, le pointeur est positionné à la fin du
fichier.
6
e) Détecter la fin du fichier
#include<stdio.h>
#include<stdlib.h>
main()
{ FILE *f ; // char car ; …
f = fopen(C:\\AUTOEXEC.BAT, r) ;
if(!f)
{ printf(Impossible d’ouvrir le fichier\n) ;
exit(-1) ;
}
while( !feof(f))putchar(fgetc(f)) ; /* ou { fscanf(f,”%c”, &car) ;
fclose(f) ; printf("%c", car) ;
} } */
8
Tableau 1 : Résumé sur fichier caractère et texte
Action C
ouverture en écriture f = fopen(nom, w)
ouverture en lecture f = fopen(nom,r)
fermeture fclose(f)
fct finfichier feof(f)
écriture fprintf(f,…, expression)
fputc
9
e) Détruire un fichier
int remove(char *nom) ;
retourne 0 si la fermeture s’est bien passée.
Exemple : remove(c:\\toto.dat) ;
f) Renommer un fichier
int rename(char *oldName, char *newName) ;
retourne 0 si la fermeture s’est bien passée.
10
1.3. Algorithmes traitant un seul fichier
Convention : la somme des éléments d’un fichier vide est égale à zéro.
On exécute, à l’initialisation, les actions suivantes :
f=fopen("elem.dat", "r") ; s=0;
La suite des algorithmes est inchangée.
a) construction de f- = f1k-1
f=fopen("fic.dat", "r");
i=1;
while((i<k) && (!feof(f)))
{ fscanf(f, "%d", &val);
i+=1;
}
16
• i == k, feof(f)
On est à la fin de fichier, et on a trouvé que le rang de l’élément était égal à k. Le
fichier a donc k-1 éléments, le sous-fichier f+ est vide et le kième élément n’existe pas : f
= f1k-1, trouve prend la valeur faux et valk est indéfinie.
•i == k, !feof(f)
signifie qu’on a trouvé le kième élément sans atteindre la fin du fichier.
On a donc : (f- = f1i-1, i = k) f- = f1k-1
Il s’ensuit que f+ = f1n avec k n ; fscanf(f, "%d", &valk) permet à valk de prendre
la valeur du kième élément et trouve doit prendre la valeur true.
•i < k, feof(f)
On a parcouru tous les éléments du fichier sans trouver le kième élément.
f- = f1n, f+ est vide, trouve prend la valeur false et valk est indéfinie.
•i < k, !feof(f)
Ce cas ne peut jamais se produire car l’expression (i=k)||((feof(f)) est fausse et n’est
pas donc une assertion.
i == k feof(f) résultat
vrai vrai trouve = false
vrai faux trouve=true; fscanf(f, "%d", &valk)
faux (i<k) vrai trouve = false
faux (i<k) faux impossible (à cause de while)
En examinant les 3 premières lignes du tableau, on constate que la valeur de trouve est
identique è celle de !feof(f). Il faut donc continuer l’algorithme en utilisant un test sur
la valeur du prédicat.
Par contre, utiliser un test sur la valeur de i==k, entraîne une erreur dans le cas de la
1ière ligne. On aurait donc écrit un algorithme qui aurait fonctionné dans la plupart des
cas, sauf si par hasard on avait rencontré le cas d’un fichier contenant k-1 éléments.
21
Tableau 3 : Tableau de sortie
Ecrire un algorithme qui vérifie si un fichier est trié ou non. L’en-tête est
le suivant : booléen trie(FILE *f).
23
Algorithme vérifiant qu’un fichier est trié.
24
1.3.4.2. Algorithme d’accès à un élément dans un fichier trié
En général, l’accès associatif dans un fichier ordonné est plus rapide que
dans le cas d’un fichier non ordonné.
On cherche à écrire une fonction dont l’en-tête est :
booléen accest(FILE *f, int val)
25
Algorithme
26
2. Récursivité et structures de données avancées
S’initialiser à la programmation récursive.
Construire des algorithmes classiques et fondamentaux sur des structures de données plus
avancées telllistes chaînées, les piles, les files d’attente, les tables et les arbres.
2.1. Listes linéaires chaînées
es que les 2.1.1. Définition
Une cellule : un couple composé d’une information et d’une adresse.
Une liste linéaire chaînée ou liste chaînée est un ensemble de cellules chaînées entre elles.
C’est l’adresse de la première de ces cellules qui détermine la liste. Cette adresse se trouve dans
une variable que nous appellerons souvent liste.
Par convention, on notera liste+ la suite des éléments contenus dans la liste dont l’adresse de la
première cellule se trouve dans la variable liste.
Exemple : la liste linéaire (a, b, c, d) est schématisé
liste
a b c d
a) schéma récursif
Pour écrire des algorithmes récursifs, on utilise la définition suivante d’une liste chaînée :
• soit une liste est vide (liste == NULL),
• soit une liste est composée d’une cellule (la 1 ière) chaînée à une sous-liste (obtenue après
suppression de la 1ière cellule).
Exemple : (a,b,c,d) est composée d’une cellule contenant a et d’une sous-liste contenant (b, c, d).
Cette sous-liste a pour adresse liste->suivant+.
Premier parcours
- on traite la 1ière cellule,
- on effectue le parcours de la sous-liste liste->suivant+.
Algorithme de parcours d’une liste à l’endroit
void parcours1(pointeur l)
{ if(l != NULL)
{ traiter(l->info);
parcours1(l->suivant);
}
}
Deuxième parcours
- on effectue le parcours de la sous-liste liste->suivant +.
- on traite la 1ière cellule.
Algorithme de parcours de la liste à l’envers
32
void parcours2(pointeur l)
{ if(l != NULL)
{ parcours2(l->suivant);
traiter(l->info);
}
}
Où traiter est une fonction quelconque.
b) Schéma itératif. Le 2ième parcours n’est pas simple à obtenir en itératif. Il est nécessaire de
disposer d’une pile. Algorithme du schéma itératif du 1ier parcours.
void parcours1(pointeur l)
{ while(l != NULL)
{ traiter(l->info);
l=l->suivant;
}
}
Schéma itératif de parcours1, protection de l’adresse de la 1 ière cellule
void parcours1 (pointeur* l)
{ pointeur p;
p=(*l); //protection de l’adresse
while(p != NULL)
{ traiter(p->info);
p=p->suivant;
}
33
}
2.1.4. Algorithmes d’écriture des éléments d’une liste
Ecrire sous forme récursive deux algorithmes d’écriture des éléments d’une liste.
a) première version
On utilise le 1ier parcours parcours1. On écrit les éléments de la liste à partir du 1ier
élément.
void ecritliste1(pointeur l)
{ if(l != NULL)
{ printf("%d", l->info);
ecritliste1(l->suivant);
}
}
b) deuxième version
On utilise le 2ième parcours parcours2 qui permet d’obtenir l’écriture des éléments à
partir du dernier élément.
void ecritliste2(pointeur l)
{ if(l != NULL)
{ ecritliste2(l->suivant);
printf("%d", l->info);
}
} 34
2.1.5. Exemples d’algorithmes sur les listes chaînées
2.1.5.1. Algorithme de calcul de la longueur d’une liste
a) schéma itératif
int longue1(pointeur l)
{ int nomb=0;
while(l != NULL)
{ nomb += 1;
l = l->suivant;
}
return nomb;
}
b) schéma récursif
Raisonnement :
•liste+ est vide
sa longueur est alors égale à zéro.
return 0 ; terminé
•liste+ n’est pas vide
elle est composée d’une cellule (1ier élément) chaînée à la sous-liste liste->suivant+.
La longueur de la liste est donc égale à 1 plus la longueur de la liste liste-> suivant+.
return (1 + longue2(liste->suivant));
35
int longue2(pointeur l)
{ if(l == NULL) return 0;
else return (1+longue2(l->suivant));
}
liste->suivant
Raisonnement
• liste+ est vide. Algorithme est terminé, le kième élément n‘existe pas et pointk prend la valeur
NULL : return liste; *
• liste+ n’est pas vide.
°° k == 1. Algorithme est terminé et pointk prend la valeur de l’adresse contenue dans
la variable liste : return liste;
°° k 1. On rappelle la fonction pour chercher le k-1ième élément dans la sous-liste
liste->suivant+ : return (pointk(liste->suivant, k-1));
39
Accès par position, schéma récursif.
pointeur pointk(pointeur l, int k)
{ if((l==NULL) || (k==1))return l;
else return (pointk(l->suivant, k-1));
}
Raisonnement
• liste+ est vide. L’algorithme est terminé, point délivre la valeur NULL (val liste+).
return NULL;
• liste+ n’est pas vide.
°° liste->info == val. L’algorithme est terminé et point délivre la valeur contenue dans la
variable liste. return liste;
°° liste->info val. On recherche la présence de val dans la sous-liste liste->suivant+.
return (point(liste->suivant, val));
Accès par valeur, schéma récursif
pointeur point(pointeur l, int val)
{ if(l == NULL) return l;
else
{ if(l->info == val) return l;
else return (point(l->suivant, val));
}
}
On notera qu’il est impossible de mettre en facteur l’action retour l ; à cause des risques de
l’incohérence dus aux conditions l==NULL et l->info qui ne sont pas indépendantes. 41
c) Accès associatif dans une liste triée
Définition d’une liste triée
• une liste vide est triée,
• une liste d’un élément est triée,
• une liste de plus d’un élément est triée si tous les éléments consécutifs vérifient la relation
d’ordre : liste NULL, liste->suivant NULL, liste->info liste->suivant->info.
-schéma récursif. On écrit une fonction d’en-tête pointeur point (pointeur l, t val).
• liste+ est vide. La valeur val n’est pas présente dans liste+, return NULL; *
• liste+ n’est pas vide.
°° liste->info<val, on doit chercher la 1ière occurrence de val dans liste->suivant+.
return point(liste->suivant, val);
°° liste->info>val, la valeur val n’est pas dans la liste : return NULL;*
°° liste-> info == val, la cellule d’adresse liste contient la 1 ière occurrence de val.
return liste ; *
pointeur point(pointeur l, int val)
{ if(l == NULL) return NULL;
else
{ if(l->info<val) return point (l->suivant, val);
else
{ if(l->info>val) return NULL;
else return l;
}
}
42
}
4.1.6. Algorithmes de mise à jour dans une liste
Ces algorithmes ont une grande importance en raison de leur facilité de mise en œuvre.
En effet, la mise en œuvre d’une liste n’entraîne que la modification d’un ou deux
pointeurs sans recopie ni décalage des éléments non concernés par la mise à jour.
4.1.6.1. Algorithmes d’insertion dans une liste
a) Insertion d’un élément en tête de liste
C’est un cas très particulier car l’insertion en tête modifie l’adresse de la liste.
[Schéma]
Créer la cellule d’adresse p.
Le champ info (information) reçoit la valeur de l’élément.
Terminer par la réalisation des 2 liaisons (a) et (b) dans cet ordre. Les éléments suivants
sont décalés automatiquement d’une position.
void insertete (pointeur *l, int elem)
{ pointeur p ;
p = (pointeur)malloc(sizeof(struct cellule)); //création d’une cellule d’adresse p.
p->info = elem; //remplissage du champ info
p->suivant = *l; //liaison a
*l = p; //liaison b
}
43
b) Algorithme d’insertion d’un élément en fin de liste
- schéma itératif
Si la liste est vide, on est ramené à une insertion en tête. Dans le cas général, la liste
n’est pas vide.
[schéma]
Après avoir créé une cellule d’adresse p contenant l’information elem, on doit effectuer
les liaisons (a) et (b). Pour pouvoir effectuer la liaison (b), il faut connaître l’adresse
der de la dernière cellule.
Disposant de la fonction dernier dont l’en-tête est pointeur dernier(pointeur liste),
nous pouvons écrire l’algorithme.
void inserfin(pointeur *l, int elem)
{ pointeur der, p;
if((*l) == NULL)insertete(*l, elem);
else
{ der = dernier (*l);
p = (pointeur)malloc(sizeof(struct cellule)) ; // 1
p->info = elem; // 2
p->suivant = NULL; // fin de liste 3
der->suivant = p; // chaînage en fin de liste 4
}
} 44
Les actions 1, 2, 3 et 4 correspondent à insertete ((&der)->suivant, elem). En effet, insérer
en fin de liste est équivalent à insérer en tête de la liste qui suit (c’est-à-dire la liste vide).
insertion en fin de liste, schéma itératif version 2.
void inserfin (pointeur *l, t elem)
//Spécification (l+ = la+) (l+ = la+ ||elem)
{ pointeur der, p ;
if ((*l) == NULL) insertete (*l, elem) ;
else
{ der = dernier (*l) ;
insertete ((&der)->suivant, elem) ;
}
}
c) Ecriture de la fonction dernier
- schéma itératif
On effectue un parcours de tous les éléments de la liste en préservant à chaque fois l’adresse
de la cellule précédente.
pointeur dernier (pointeur l)
{ pointeur precedent ;
while(l != NULL)
{ precedent = l ;
l = l->suivant ;
}
return precedent ;
} 45
- schéma récursif
Le raisonnement est :
• liste ne contient qu’une seule cellule (l-> suivant == NULL). La liste
contient l’adresse de la dernière cellule.
return l ; *
• liste contient plus d’une cellule (l->suivant NULL).
return dernier(l->suivant) ;
Algorithme
pointeur dernier (pointeur l)
{ if(l->suivant == NULL)return l;
else return dernier (l->suivant);
}
- schéma itératif
L’insertion d’un élément à la kième place consiste à créer les liaisons (a) et (b) dans
cet ordre. Les éléments suivants sont automatiquement décalés d’une position.
Pour réaliser la liaison (b), il faut connaître l’adresse de la cellule précédente (k-
1ème). L’insertion n’est possible que si k[1..n+1] où n est le nombre d’éléments de
la liste. Il faudra prévoir l’insertion en tête (k==1) car elle modifie l’adresse de la
liste. On utilisera la fonction pointk pour déterminer l’adresse de la k-1ème cellule.
47
Insertion d’un élément à la kième place, schéma itératif.
void insertk (pointeur *l, int k, int elem, booléen *possible)
{ pointeur p, precedent;
*possible = false;
if(k == 1)
{ insertete ((*l), elem) ; *possible = true;
}
else
{ precedent = pointk(*l, k-1);
if(precedent != NULL)
{ p = (pointeur)malloc(sizeof(struct cellule)); //1
p->info = elem; //2
p->suivant = precedent ->suivant; //3
precedent->suivant = p; //4
*possible = true;
}
}
}
On peut également constater que l’exécution des actions 1, 2, 3, 4 de l’algorithme
correspond à l’exécution de l’action insertete pour la liste precedent->suivant+ en
écrivant à la place de ces actions, l’action insertete((&precedent)->suivant, elem); 48
- schéma récursif
• k == 1, On effectue une insertion en tête de liste+
insertete(liste, elem); *
•k1
°° liste+ est vide, l’insertion est impossible *
°° liste+ n’est vide
insertk(liste->suivant, k-1, elem, possible);
49
Insertion de elem après la première occurrence de val, schéma non récursif.
void supptete(pointeur* l)
{ pointeur p ;
p = *l ;
*l = (*l)->suivant;
free(p);
}
57