07/12/2010
Algorithmique et structures de données Par Ghislain Akinocho Ingénieur Services 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
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 unee su un suit itee d’ d’in inst stru ruct ctio ions ns qu quii do doiv iven entt êt être re exécutées dans un ordre bien défini en vue de résoudre un problème donné.
L’algorithme L’algorith me doit conte contenir nir des inst instructi ructions ons 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 ir un unee ma maît îtri rise se de dess rè règl gles es de ba base se Avo 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 L’affectat ion des variables variables La lecture et l’écriture Les tests de contrô contrôle le Les bou boucl cles es
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 alg algori orithm thmee est ind indépe épenda ndant nt de to tout ut lang la ngag agee de pr prog ogra ramm mmat atio ion. n. Il pe peut ut se présenter présen ter sous plusieurs plusieurs formes 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 euxmê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
c
b
10 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 : "
mardi 7 décembre 2010
, age)
"
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}
SINON
{la condition est fausse}
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 () faire
fintq Le bloc d’instructions sera exécuté tant que la 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
tant que () Le bloc d’instructions sera exécuté tant que 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
jusqu’à
finpr NB: et sont des valeurs entières.
Le bloc d’instructions sera exécuté ( - + 1) fois.
mardi 7 décembre 2010
36
18
07/12/2010
Notions de base pour … Exemple : pour i n
0 jusqu’à 9 faire 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 :
a X ² + b X + 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 : 7 x 1 =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
mardi 7 décembre 2010
6
7
8
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 * : 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 : A 1 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 sousprogrammes, 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: fonction ( 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 ( 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 sousalgorithmes Algorithme exemple procédure ( param_i, …) {Zone de procédure}
déclaration
des
variables
de
la
DEBUT {Contenu du sous-algorithme}
FIN (…) {Mettre les autres sous algorithmes ici} {Zone de principal}
déclaration
des
variables
de
l’algorithme
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 CalculeDist CalculeDistanceO anceOP P (Point2D (Point2D P){ return sqrt((P.x * P.x) + (P.y * P.y)); }
int main(){ Point2D P;
float distanceOP; P = Sais Saisie iePo Poin int2 t2D( D() ) Affich AfficherP erPoin oint2D t2D (P); (P); dist distan ance ceOP OP = Calc Calcul uleD eDis ista tanc nceO eOP P (P) (P) printf("Dist Distan ance ce OP = %.2f %.2f", ", dist distan ance ceOP OP); ); retu return rn 0; }
mardi 7 décembre 2010
73
EXERCICES
Exercice 1 Définir une structure NombreRationnel permettant de coder un nombre rationnel, rationnel, avec numérateur et dénominate dénominateur. ur. On écrira écrira des fonctions 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énominate dénominateur ur commun. 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éfini Définirr une struct structure ure Panneau contenant contenant toutes toutes les informations informations relatives à un panneau de bois. b) Ecrire les fonctions de de saisie et d’affichage d’un panneau de bois. c) Ecrire une fonction qui calcule le volume en 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 liste chaîné chaînéee est un ensem ensemble ble de cellul cellules es liées liées entre entre elles elles par des pointeurs. Chaque cellule cellule est une structure contenant : - Une ou plusieurs données comme comme dans n’importe qu’elle qu’elle structure - Un pointeur pointeur suivant sur cellule suivante. suivante. suivant sur la cellule 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 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
Pointeurs « suivant »
Donnée 4
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 représentera une cellule.
{Les donnéesdans les cellules sont sont des réels (float)} (float)} typedef float float TypeDo TypeDonne nnee; e;
{Définition du type cellule} typede typedef f struct struct Cell{ / * D éf éf in in it it io io n d es es d on on né né es es * /
Type TypeDo Donn nnee ee donn donnee ee ; / * Po int e ur s u r la s tr u ct ur e e nt nt ra ra in in d e d éf éf in in ir ir * /
s u i va n te
de
mê m e
typ e
q ue
ce l le
q u ’on
e st
stru struct ct Cell Cell *sui *suiva vant nt; ;
}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 est vide) Type TypeCe Cell llul ule e *L; / * D éc écl ar ar at at io io n d ’u ’u ne ne l is iste * /
void AfficheDonnee (Type TypeCe Cell llul ule e *cellule){ pri printf ntf(" %f " , cel cellul lule->d e->do onne nnee); e);
} Type TypeDo Donn nnee ee Sais Saisie ieDo Donn nnee ee (){ (){ TypeDonnee donnee; puts ("Saisissez la donnee :" ); scanf("% scanf("%f", f", &donnee) &donnee); ; return return donnee 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 Figure 2). TypeCellule * InsererEnTete (TypeCellule *OldListe, TypeDonnee donnee){ TypeCellule *NewListe ; /* Nouvelle tête de liste */ /*Al /*Allo loca cati tion on de mémo mémoir ire e pour pour la nouv nouvel elle le cell cellul ule* e*/ / NewListe = (typeCellule*) malloc (sizeof(TypeCellule)): /*On met la donnée à ajouter dans la cellule*/ NewListeNewListe->donn >donnee ee = donnee; donnee; /*Le pointeur suivant de la nouvelle cellule pointe maintenant sur l’ancienne liste */ NewListeNewListe->suiv >suivant ant = OldListe; 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
NewListe
Donnée 1
Etc ….
Donnée n
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 type struct structure ure et la flèche pour une variable de type « pointeur » sur structure. 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; TypeD TypeDonn onnee ee donnee donnee; ; /*Dé /*Décl clar arat atio ion n d’un d’une e list liste e vide vide, , init initia iali lisa sati tion on à NULL NULL
obli obliga gato toir ire e */
TypeCellule *L = NULL; do{ donn donnee ee = Sais Saisie ieDo Donn nnee ee() (); ; L = Ins Inserer ererE EnTe nTete (L, (L, donn onnee); ee);
/ *I *I ns ns er er ti ti on on e n t êt êt e * /
puts puts(" ("Vo Voul ulez ez-v -vou ous s insé insére rer r un élém élémen ent t ?"); ?"); getch getchar ar (choix (choix); ); } while(choix == ‘o’ || choix == ‘O’); ret return urn 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
P
Donnée 3
Donnée 4
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 { / *O n r ech er ch e l a d er ni èr e ce ll ul e * / p = L;
while (p->suivant != NULL){ p = p -> sui va nt;
} /* 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 NULL
d’une liste vide, obligatoire */
initialisation
à
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 i nsertion 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 I n F irst 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 cell ule 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;
/ *O n r et ou rn e u ne l is te v id e* /
}
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; *pP = (*pP)->suivant free(q)
/*recupération de la valeur*/
/*passage au suivant*/
/*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 ( F irst I n F irst 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;
/ * p oint eur sur la c ell ul e sui vant e * /
}TypeCellule ;
typedef struct { TypeCellule *tete, *queue;
/* pointeur sur
la première
et
d er ni èr e c el lu le * /
}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
tête
Donnée 3
Donnée 4
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; / *c od e
d ’e rr eu r, l a f il e e st v id e* /
}
/* 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 ém or is at io n d e l a t et e d e f il e* /
pF->tete free(p);
pF->tete->suivant;
=
/*passage
au
suivant*/
/ *D es tr uc ti on d e l ’a nc ie nn e t et e d e f il e* /
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){ q = p;
/* Parcours de la liste */
/* 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
while (C){
void fonctionRecur (){ if (C){ /* … bloc … */ fonctionRecur();
/*… bloc … */ }
} }
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 u
=
n +1
2 =
4 − 7u n
v0 = v1 = 1 v 2 = v + v n+
n
n+1
, pour n > 0 , pour n > 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 sinon*/
1 (VRAI) si l’arbre est vide et 0 (FAUX)
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* ◦
/*Retourne l’arbre binaire formé par le sous arbre gauche (fils gauche)*/
ArbreBinaire* ◦
filsG(ArbreBinaire *cible);
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 sinon*/
1 (VRAI) si l’arbre est vide et 0 (FAUX)
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 sousarbres 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 sousarbres 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 sousarbres 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 (&(*cible)->droit, info); ajoutABR }
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'enregistrements 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: -Les
fichiers 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 commande 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 : déclarer un pointeur du type FILE* pour chaque fichier dont nous avons besoin,
1.
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 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
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 . 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") : = fopen ( , "w" ); = fopen ( , "r" );
* est une chaîne de caractères constante ou une variable de type chaîne qui représente le nom du fi chier 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 u n 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 ( ); 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 fi chier correspondant est annulée. Après fclose() le pointeur 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 respectivement 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( , "\n", ); fprintf( , "\n", ); ... fprintf( , "\n", );
* est un pointeur du type FILE* qui est relié au nom du fichier cible. * , , ... , représentent les rubriques qui forment un enregistrement et dont les valeurs respectives sont écrites dans le fichier. •
, , ... , 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( , "\n", ); fscanf( , "\n", ); ... fscanf( , "\n", );
* est un pointeur du type FILE* qui est relié au nom du fichier à lire. * , , ... , représentent les adresses des variables qui vont recevoir les valeurs des différentes rubriques d'un enregistrement lu dans le fichier. •, , ... ,
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 i nstructions 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( , );
fputc transfère le caractère indiqué par dans le fichier référencé par et avance la position de la tête de lecture/écriture au caractère suivant. * représente un caractère (valeur numérique de 0 à 2 55) ou le symbole de fin de fichier EOF (qu’on verra par la suite). * 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 i nstructions 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( , );
fputc transfère le caractère indiqué par dans le fichier référencé par et avance la position de la tête de lecture/écriture au caractère suivant. 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). 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 = fgetc( );
fgetc fournit comme résultat le prochain caractère du fi chier référencé par et avance la position de la tête de lecture/écriture au caractère suivant. A la fin du fichier, fgets retourne EOF. représente une variable du type int qui peut accepter une valeur nu mérique de 0 à 255 ou le symbole de fin de fichier EOF. 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( );
feof retourne une valeur différente de zéro, si la tête de l ecture du fichier référencé par est arrivée à la fin du fichier; sinon la valeur du résultat est zéro. 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 #include 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:\\) 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:\\) 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 c ontraire. SEPA utilise un tableau qui conti ent 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; } } taille--; } }
void inverser(int *a, int *b){ int tmp; 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