Language C

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 126

I.U.T.

de Marne-La-Vallée

Introduction à l'informatique
et programmation en langage C
(DUT Génie Thermique et Energie)

Jean Fruitet
[email protected]

1999
Introduction à la programmation

I.U.T. de Marne-La-Vallée

Introduction à l'informatique
et programmation en langage C
(DUT Génie Thermique et Energie)

Jean Fruitet
[email protected]

Avertissement 3
Caractérisation d’un problème informatique 3
Introduction à l’informatique 5
Le codage binaire 7
Notion d’algorithme et de machine à accès direct 14
Langage C 19
Processus itératifs 43
Fonctions et sous-programmes 44
Notion de complexité 48
Des données aux structures de données : tableaux, calcul matriciel 50
Calcul numérique : fonctions numériques, résolution d'équation, intégration 61
Structures de données : ensembles, listes, piles, files, hachage, arbres, graphes 76
Algorithmes de tri 112
Bibliographie 123
Table des matiéres 122

Jean Fruitet - IUT de Marne La Vallée - 2


Introduction à la programmation

Avertissement

Ce cours s’adresse aux étudiants de première année de DUT de Génie Thermique et Energie
(GTE). Il leur est présenté en quelques dizaines d’heures —une trentaine— les rudiments de la
programmation numérique et des notions d’algorithmique. Ces étudiants n'étant pas destinés à une
carriére d’informaticien professionnel, je n’aborde pas l’algorithmique dans tous ses raffinements. En
particulier les notions pourtant fondamentales de preuve de programme et d’analyse de complexité
ne sont pas évoquées.
Ce cours est divisé en quatre parties :
- notion d'informatique et de codage ;
- structure d'un ordinateur : la machine à accès direct (MAD / RAM) ;
- langage de programmation : le langage C ;
- algorithmique numérique et structures de données.
Après quelques notions de théorie de l'information et de codage (codage binaire, représentation
des entiers et des flottants) j'introduis la programmation de fonctions numériques sur ordinateur PC
sous MS-DOS puis l'utilisation de quelques structures de données fondamentales (tableaux, piles,
files, arbres, graphes) et les principaux algorithmes de tri. Ce cours ne fait donc aucune place à la
technologie des ordinateurs, leur architecture, système d'exploitation et de fichiers. Il n'est pas non
plus question d'apprentissage de logiciels bureautiques (traitement de texte ou de tableur). Ce n'est
pas que ces connaissances ne soient pas nécessaires aux techniciens, mais je laisse à d'autres
enseignants le soin d'y contribuer.
S’agissant de la syntaxe d’un langage de programmation, j’introduis le langage RAM, pour passer
rapidement au langage C. J'insiste beaucoup dans ce cours sur la nécessité d'une programmation
structurée descendante. Cette démarche est recommandée depuis des lustres par tous les spécialistes.
Malheureusement l'expérience montre que livré à lui-même le programmeur moyen se permet des
libertés qui rendent rapidement ses programmes illisibles et inutilisables. Mais ce ne sera pas faute
d'avoir été prévenu...

Caractérisation d’un problème informatique

L'art de programmer, c'est l'art de faire résoudre des problèmes par des machines. Il s’agit bien
d’un art, au sens de l’artisan, qui passe par une longue période d’apprentissage et d’imitation. Dans
cet exercice certains individus ont des dispositions naturelles ; pour les autres un apprentissage
rigoureux fournit les rudiments d’une méthode. Le reste est affaire de travail et d’investissement
personnels.
Un ordinateur est dénué d’intelligence ; il ne peut donc résoudre que les problèmes pour lesquels
existe une méthode de résolution algorithmique, c’est-à-dire une recette déterministe. De plus, même
si la recette existe en théorie pour résoudre tel problème, encore faut-il que l’énoncé du problème —
l’espace des paramètres— et l’ensemble des solutions soient de dimension finie, en raison de la
limitation en taille de la mémoire des machines. Enfin, condition ultime, la mise en oeuvre d’un
algorithme doit avoir une durée finie. Un problème dont la solution nécessite de disposer d’un temps
infini n’est pas considéré comme résoluble par ordinateur. Ces trivialités vont donc limiter nos
ambitions de programmeur à une classe de problèmes assez restreinte, d’autant que ne nous
disposons pas de puissantes machines de calcul.

Jean Fruitet - IUT de Marne La Vallée - 3


Introduction à la programmation
2.1. Le traitement de l’information
L’informatique est la science du traitement de l’information. Une information est un élément de
connaissance susceptible d’être codé, transmis et traité de manière automatique. Le codage
numérique en nombres binaires étant adapté à la conception de machines électroniques, une partie
essentielle du cours d’informatique porte sur la représentation des nombres et la logique (algèbre de
Boole) binaires. Je considèrerai comme acquises les notions d’opération élémentaire (addition,
soustraction, multiplication et division réelle et euclidienne) sur les ensembles de nombres naturels,
relatifs, rationnels, réels et complexes, qui ne seront pas redéfinies, non plus que les opérations
ensemblistes union, intersection, complémentation, ni les notions de relation binaire, relation
d’équivalence et relation d’ordre partiel ou total. Concernant les notions de constante numérique, de
variable, d’instruction d’affectation, de test, de boucle, qui sont au centre des techniques de
programmation, elles seront redéfinies ou précisées selon les besoins.
2.2. Quelques exemples de problèmes
L’ordinateur fonctionne en binaire, mais il peut résoudre des problèmes autres que
numériques. Et bien que le calculateur électronique soit l’instrument privilégié de l’ingénieur, ce
n’est pas en programmant des problèmes numériques qu’on apprend le plus efficacement à
programmer. Pourtant il est de tradition dans les sections techniques et scientifiques de commencer
par des exercices numériques :
- Résoudre une équation du second degré à coefficients réels dans le corps de nombres
complexes.
- Programmer la division euclidienne de deux entiers en n’employant que des soustractions.
- Trouver le plus grand diviseur commun de deux nombres naturels (PGDC).
- Enumérer les n premiers nombres premiers.
- Tester la conjecture polonaise.
- Calculer le nième élément de la suite de Fibonacci.
- Calculer le minimum, le maximum, la moyenne et l’écart-type d’une distribution numérique.
- Calculer les zéros d’un polynome, tracer graphiquement le graphe d'une fonction numÉrique
- inverser une matrice.
D’autres problèmes qui ne sont pas strictement numériques sont pourtant tout aussi instructifs
pour l’art de la programmation. Ils seront soit traités soit évoqués :
- Trouver un mot dans un dictionnaire.
- Construire un arbre hiérarchique
- Ordonner une liste de mots.
- Fusionner deux listes de mots déjà ordonnés.
- Enumérer les parcours d’un graphe et trouver le plus court chemin entre deux sommets.
- Filtrer une image, etc.
Démarche
Partant d’un problème élémentaire nous montrerons comment le reformuler en des termes
susceptibles d’être traités par un ordinateur idéal. Puis nous coderons ces algorithmes en langage C.
Nous encourageons le lecteur à implanter ses propres solutions, à les modifier, éventuellement les
améliorer ou à les réutiliser pour d’autres applications.

Jean Fruitet - IUT de Marne La Vallée - 4


Introduction à la programmation
Tous les exemples fournis en C ont été testés sur compilateur Turbo C sur PC et Think C sur
Macintosh. Je remercie par avance celles et ceux qui voudront bien me transmettre remarques et
suggestions.

Introduction à l’informatique

L'Informatique est la science du traitement automatique de l'information.


La notion d'information est assez générale. Pour l'informatique elle prend un sens particulier : Une
information est un élément ou un système de connaissance pouvant être transmis au moyen d'un
support et d'un codage approprié et pouvant être compris.
Par exemple des hiéroglyphes (codage) sur un papyrus égyptien (support) constituent une
information sur la société égyptienne antique dés qu'on a été en mesure de les lire et d'en comprendre
le sens (Champollion au XIXéme siècle).
Une information est une fonction du temps, puisque le contenu d'un message est sujet à changer
au cours du temps. L'information "La mer est 'calme' dans le Golfe de Gascogne" est
particulièrement périssable... Quand le vent se léve et que la houle se forme, la mer devient 'agitée'.
L'état de la mer est une information qui peut prendre des valeurs différentes, au cours du temps.

Langage
La plupart des informations que les Humains échangent sont supportées par un langage, c'est-à-
dire des groupes de sons (les mots), qu'il faut assembler "d'une certaine manière" (grammaire) pour
que les phrases aient un sens... Avec l'invention de l'écriture, ces mots ont eu une transcription
(recodage) sous forme de symboles graphiques (des formes) dessinés ou imprimés.
Le concept de mot est fondamental en informatique, de même que celui de langage. Un langage
est un ensemble de mots construits avec les lettres choisies dans un alphabet. Les mots sont
assemblés en phrases selon des régles de grammaire précises qui définissent la syntaxe du langage.
Le sens attaché à ces phrases, c'est-à-dire leur signification, constitue la sémantique.
L'essentiel de ce cours va consister à expliquer comment sont commandées les machines que nous
nommons ordinateurs, capables d'exécuter des tâches complexes de traitement de l'information.
Traitement de l'information
Le traitement de l'information consiste en une suite d'opérations transformant une représentation
de cette information en une autre représentation plus facile à manipuler ou à interpréter.

Exemples :
"3*2" remplacé par "6"
"Mille neuf cent quatre vingt treize" est remplacé par "1993"
"La somme des carrés des côtés de l'angle droit d'un triangle rectangle est égale au carré de
l'hypoténuse" est remplacé par "Théoréme de Pythagore"
"Championne olympique 1992 et 1996 du 400 mètres féminin" est remplacé par "Marie-José
Pérec".
Dans une entreprise, traiter l'information peut consister à établir la paye, faire la facturation, gérer
le stock, dresser un bilan. Dans un atelier, diriger un robot. En météorologie, reconnaître un cyclone
sur une photo satellite...

Jean Fruitet - IUT de Marne La Vallée - 5


Introduction à la programmation

Ordinateur
Un ordinateur est une machine qui permet d'effectuer des traitements sur des données à l'aide de
programmes. Les données (les paramètres du problème, par exemple les notes des étudiants du cours
d'informatique) ainsi que le programme (par exemple le calcul de la moyenne des notes) sont fournis
à la machine par l'utilisateur au moyen de dispositifs de saisie (le clavier). Le résultat du traitement
est recueilli à la sortie de l'ordinateur (l'écran, l’imprimante) sous forme de texte.
Un peu d'histoire
C'est en 1945 que le principe des ordinateurs a été inventé. Mais on peut faire remonter ses
origines au boulier et aux premières machines à calculer mécaniques. Blaise Pascal (1623-1662)
inventa à l'âge de 18 ans une machine à base de roues dentées et d'engrenages qui réalise d'elle-même
les additions et les soustractions. Il suffit d'indiquer les chiffres et l'opération à faire.
Au XIXéme siècle l'anglais Babbage conçoit deux grandes machines dont le principe était correct,
mais qui ne purent être réalisées en raison de difficultés techniques et financiéres. Il était sans doute
trop tôt. Ce n'est qu'au XXème siècle que sous la pression du développement économique et des
besoins militaires (Deuxiéme guerre mondiale) des scientifiques et des ingénieurs s'attelérent à la
construction de gigantesques machines à calculer. La plus fameuse de ces machines fut la "Harvard
Mark 1" qui mesurait 16 mètres de long, pesait 5 tonnes et comprenait 800 000 éléments, et
pourtant n'avait pas plus de puissance qu'une simple calculette de poche actuelle !
La véritable révolution pour les machines à calculer viendra des progrès de l'électronique et de la
logique mathématique. Le premier calculateur électronique, l'ENIAC, destiné à calculer la trajectoire
de projectiles pour l'armée américaine, fut construit à partir de 1943. Il pesait 30 tonnes, comportait
17 468 tubes à vide et additionnait 5000 nombres en une seconde. Mais on ne peut considèrer cette
machine comme un ordinateur, car il n'était pas véritablement automatique et n'utilisait pas de
programme interne.1
Sur le plan technique les progrès décisifs seront réalisés dans les années 1950 avec l'invention en
1947 du transistor (qui donne son nom aux postes de radio portables). Les transistors sont des
composants électroniques qui remplace partout les lampes à vides ; rassemblés par dizaines puis
centaines de milliers sur des circuits intégrés ils permettent de réaliser des puces électroniques qui
envahissent les automates (lave linge, magnétoscopes, circuits d'allumage de voiture, calculettes...)
et les ordinateurs.
Sur le plan conceptuel, c'est aux anglais George Boole (1815-1864), inventeur de l'algèbre
binaire, l'algèbre de la logique, et Alan Turing (1912-1954) et aux américains d'origine européenne
John Von Neumann (1903-1957) et Norbert Wiener (1894-164) que nous devons l'avancée décisive
qui méne des calculateurs aux ordinateurs. Leurs travaux aboutissent à la construction du premier
ordinateur en 1948 à Manchester, en Grande-Bretagne, le "Manchester Mark 1".

Ce qui caractérise un ordinateur


La machine conçue par John Von Neumann comporte trois innovations majeures :
- elle a une mémoire importante, dans laquelle sont archivées les données et le programme.
- elle a un programme enregistré dans la mémoire, qui décrit l'ensemble des instructions à réaliser.

1 Philippe BRETON "Une histoire de l'Informatique" - Collection Points Sciences - Editions La Découverte, Le Seuil
1990.
Jean Fruitet - IUT de Marne La Vallée - 6
Introduction à la programmation
- elle a une unité centrale de commande interne qui organise le travail en appliquant les
instructions du programme et dirige les échanges de données avec l'extérieur de la machine.
Matériel et logiciel
Un ordinateur est constitué de composants matériels (hardware ) et de composants logiciels
(software ). Les composants matériels sont essentiellement des cartes électroniques, des circuits
intégrés, des câbles électriques, des supports de mémoires de masse (disques durs) et des dispositifs
d'entrée/sortie (périphériques : clavier, écran, imprimante). Les logiciels, qui pilotent le
fonctionnement des composant matériels, sont des programmes stockés sous forme codée dans la
mémoire de l'ordinateur. Pour être interprétés par l'unité centrale ces programmes doivent être
traduits dans le langage des machines, le langage binaire.

Le codage binaire
Toute l'information qui transite dans un ordinateur est codée avec des mots formés seulement de
deux symboles (ou de deux 'états') notés 0 et 1. Cela tient à la nature des composants électriques et
magnétiques utilisés pour coder l'information.
Dans les mémoires d'ordinateur, chaque unité élémentaire d'information peut être représentée par
un minuscule aimant. Chaque aimant est orienté soit dans un sens (état 0), soit dans le sens opposé
(état 1)...
C'est le même principe qui est appliqué aux échanges de données. Pour transmettre un 1, il faut
appliquer sur un conducteur électrique une différence de potentiel supérieure à quelques volts
pendant une période "assez" longue, de l'ordre de la micro seconde (1/1 000 000 éme de seconde).
Pour transmettre un 0, il faut maintenir une différence de potentiel inférieure à 1 volt pendant la
même durée.
Par exemple pour coder le nombre 13 en binaire, il faut les quatre chiffres binaires 1101. En effet
13 peut être décomposé comme une somme de puissances de 2
13 = 8 + 4 + 1
= 1 * 8 + 1 * 4 + 0 * 2 + 1 * 1 en décimal
= 1 * 23 + 1 * 22 + 0 * 21 + 1 * 20 on ne conserve que les coefficients
= 1 1 0 1 en binaire
Représentation des informations en binaire
Pour coder de l'information , que ce soient des nombres, (PI=3,141592...), du texte (ce cours),
des schémas, des images, des sons, des relations ("Pierre est le pére de Jacques et le frêre de
Marie"), les circuits électroniques d'un ordinateur ne peuvent utiliser que des mots en binaire.
Montrons d'abord comment il est possible de coder n'importe quel nombre entier naturel IN={0,
1, 2, 3, ... ,1 000, ..., 1 000 000, ...} en binaire.
Puis nous en ferons autant pour les lettres et les mots de la langue française. Enfin il faudra
montrer que les images, les sons et les relations aussi peuvent se coder en binaire.
Passer du décimal au binaire
Il suffit de décomposer un nombre décimal en une somme de puissances de 2. On peut par exemple
commencer par écrire la table des premières puissances de 2 :
20 = 1 21 = 2 22 = 2x2 = 4 23 = 2x2x2 = 8
24 = 25 = 2x...x2 = 26 = 27 =
28 = 29 = 210 = 211 =

Exercice 1 : Montrer que le nombre 256 est une puissance de 2.


Exercice 2 : Montrer que le nombre 131 est une somme de puissances de 2
Jean Fruitet - IUT de Marne La Vallée - 7
Introduction à la programmation
Exercice 3 : Donner la représentation binaire des 10 premiers nombres entiers :
0 = 0x20 -> 0; 1=1x20 -> 1 ; 2=1x21 -> 10
3 = 1x2 + 1x1 = 1x21 + 1x20 -> 11
4 = 1x4+ 0x2 + 0x1 = 1x22 + 0x21 + 0x20-> 100
5 = 1x4 + 0x2 + 1x1 =
6=
7=
8=
9=
10 =
Passer du binaire au décimal
Le codage binaire a un inconvénient majeur pour l'être humain, son manque de lisibilité...
Quelle est la représentation décimale du nombre dont la représentation binaire est : 1001 1110 ?
Réponse : 1x27+ 0x26 + 0x25 +1x24 + 1x23 + 1x22 + 1x21 + 0x20
= 1x128 + 0x64 + 0x32 +1x16 + 1x8 + 1x4 + 1x2 + 0x1
= 158
Exercice 4 : Même question pour 1111 1111
Exercice 5 : Combien de chiffres binaires faut-il pour représenter le nombre décimal 10000 ?
Opérations usuelles en binaire
Les deux opérations binaires de base sont
- l'addition
- la multiplication
Table d'addition binaire
0+0=0
0+1=1
1+0=1
1 + 1 = 0 avec une retenue de 1
Table de multiplication binaire
0x0=0
0x1=0
1x0=0
1x1=1
Exercice 6 :
En utilisant la table d'addition binaire calculez en binaire les nombres suivants :
1001 + 10100
1111 + 1
1010 + 111
1111 1111 + 1111 1111
Exercice 7 : En utilisant la table de multiplication et d'addition binaires calculez en binaire les
nombres suivants :
1001 x 1
1111 x 10
100 x 101
1111 x 1111
Opérations logiques
En logique binaire une variable logique (booléenne) est VRAI (TRUE = 1) ou FAUSSE (FALSE
= 0). Une expression logique est constituée de plusieurs variables logiques combinées par des
connecteurs (opérateurs) logiques :
Jean Fruitet - IUT de Marne La Vallée - 8
Introduction à la programmation
Les opérateurs logiques élémentaires sont :
- NON [NOT]
- ET [AND]
- OU [OR]
- OU EXCLUSIF [XOR]

Table de vérité du NON Table de vérité du ET


NON 0 = 1 0 ET 0 = 0
NON 1 = 0 0 ET 1 = 0
1 ET 0 = 0
Table de vérité du OU 1 ET 1 = 1
0 OU 0 = 0 Table de vérité du OU EXCLUSIF [XOR]
0 OU 1 = 1 0 XOR 0 = 0
1 OU 0 = 1 0 XOR 1 = 1
1 OU 1 = 1 1 XOR 0 = 1
1 XOR 1 = 0
Exercice 8 : Donner la valeur de vérité {VRAI ou FAUX} des assertions suivantes :
A : « 102 est un nombre pair »
B : « 11 est un multiple de 3 »
C : « 102 est un nombre pair » ET « 102 est divisible par 3 »
D : « 11 est multiple de 3 » ET « 102 est divisible par 3 »
E : « 108 est multiple de 9 » OU « 11 est divisible par 3 »
Les nombres entiers et les nombres réels décimaux
L'ensemble des entiers naturels : IN = {0,1,2..,100,101,...1000,...}
- IN est ordonné ; il a un plus petit élément 0 ;
- Il n'y a pas de plus grand élément : tout élément a un successeur

L'ensemble des entiers relatifs : Z = Z+ U Z-


Z = {...,-1000,...,-100,..., -3,-2,-1, 0, 1,2..,100,101,...1000,...}
- Z est ordonné
- il n'y a ni plus grand ni plus petit élément : tout élément de Z a un prédécesseur et un successeur.

Opérations sur les entiers


Opérateurs unaires :
- (moins) : opposé
succ : successeur renvoie le nombre suivant dans l'ordre des entiers
pred : prédécesseur renvoie le nombre précédent
maxint : renvoie le plus grand entier représenté sur la machine

Opérateurs binaires :
+ - * DIV MOD /
Opérateurs de comparaison :
= < <=> >=
Axiomes :
associativité de + et *; distributivité de * sur +; relation d'ordre

Jean Fruitet - IUT de Marne La Vallée - 9


Introduction à la programmation
Implémentation des entiers non signés
En raison des limitations d'espace mémoire, on ne peut représenter que des intervalles de
nombres.
Deux difficultés à résoudre : choix des intervalles et représentation des nombres négatifs
Implantation courante sur 2 octets: l'intervalle sélectionné est [0..65535]
Il s'agit de générer une représentation de ce nombre en base 2 occupant au plus deux octets, soit 16
bits.
Exemple pour n = 54710
On décompose 547 en puissances successives de 2 :
54710 = 512 + 32 + 2 + 1 = 1*29 +1*25 +1*21 +1*20
et on ne code que les coefficients de la décomposition, la représentation binaire de n est
Bin(n) = 0000 0010 0010 0011
Le plus grand nombre entier non signé représentable est donc :
1111 1111 1111 1111
= 1*215 +1*214 +1*213 +1*211 +1*210 +1*29 +1*28
+ 1*27 +1*26 +1*25 +1*24 +1*23 +1*22 +1*21 +1*20
= 1*216 - 1
La représentation décimale de ce nombre est donc
Dec(n) = 65 536 - 1 = 65 53510
Implémentation des entiers signés
L'intervalle sélectionné est [-32768.. +32767]
Soit un nombre n de Z. Il s'agit de générer une représentation de ce nombre en base 2 occupant
au plus deux octets, soit 16 bits. Si le nombre est positif et inférieur à 215 (32 768) on le représente
comme un entier non signé. Si le nombre est négatif et supérieur à -32768, le problème est de
représenter le signe et de pouvoir passer d'un nombre à son opposé de façon simple.
Une méthode très répandue est la méthode du complément à 2. On travaille MODULO 216
On représente Bin(216 + n) = 65 536 - 547 = 64 989
= 1111 1101 1101 1101
Une vérification de l'expression calculée consiste à effectuer une somme bit à bit de 547 et de -547
Si la représentation est correcte on doit trouver 0 :
0000 0010 0010 0011
+ 1111 1101 1101 1101
_____________________________
Report 0000 0000 0000 0000
Le report (ou retenue) n'est bien sûr pas représenté.
Donc un nombre entier signé représenté sur 16 bits dont le bit de poids fort est à 1 doit être
considéré comme un nombre NEGATIF : sa valeur est -(216 - Dec(n2))
Algorithme de conversion d'un nombre en binaire complément à 2 sur un octet :
Trouver l'opposé d'un nombre en complément à 2 :
Soit n = 0000 0101 = 510
Son opposé est -5 obtenu en inversant tous les 1 en 0 et tous les 0 en 1 et en ajoutant 1:
0000 0101 --> 1111 1010 + 1 = 1111 1011 = -5

Jean Fruitet - IUT de Marne La Vallée - 10


Introduction à la programmation
Vérification :
5 + (-5) = 0
0000 0101
+1111 1011
----------
0000 0000 report ignoré
Exercice :
Trouver les représentations binaires en complément à deux sur deux octets des nombres suivants :
-1; -2; 31000; -31000
-33000 est-il représentable ?
Les nombres réels.
L'ensemble IR ne peut pas être représenté de façon compléte en machine.
On retrouve les mêmes difficultés pour représenter les réels que pour représenter les entiers :
l'ordinateur n'a pas une mémoire infinie. On représente donc un sous-ensemble fini de IR. Tout
traitement (opération, représentation) peut être entachée d’erreur. Un calcul mathématique permet
d’estimer l’importance de cette erreur.
Opérations sur les réels :
Opération unaire : - (opposé)
Opérations binaires :+ - * /
Comparaisons = < > <= >=
Fonctions réelles : cos sin log puiss sqrt Axiomes
Corps ordonné
x * (y + z) = x * y + x * z: distributivité
On retrouve certains axiomes des entiers plus ceux des rationnels (inverse)
plus quelques caractéristiques intéressantes (densité)
La notation scientifique normalisée
On appelle notation normalisée d'un réel celle où le premier chiffre significatif est placé
immédiatement après la virgule (ou le point décimal).
Exemple : 1989 = 0.1989 E4
Pour stocker ce nombre en mémoire, il suffit de stocker l'exposant 4 et la partie décimale appelée
mantisse 1989

Exemple : écrire PI en notation normalisée π = 3.1415926535... = 0.31415926535 E1


Représentation des nombres réels en binaire
Tout nombre réel peut être représenté dans une base quelconque :
a=M*Be
avec B : base ; e : exposant ; M : mantisse (qui peut être une suite infinie de chiffres...)
En notation normalisée on a les conditions :
(1/B) <= | M | < 1 ou bien M=0
Les réels sont représentés en machine selon un standard défini par l' IEEE
Un réel est décomposé en
signe +- s
exposant caractéristique E
partie décimale mantisse F
x = (-1) . 2s E-127 . 1, F
En général on représente un réel sur 4 octets (32 bits)
le bit 0 de poids fort (le plus à gauche) est le signe s, soit 0 (positif) ou 1 (négatif)
Jean Fruitet - IUT de Marne La Vallée - 11
Introduction à la programmation
les bits 1 à 8 sont la caractéristique E qui est représentée par un entier binaire sur 8 bits décalé de
la valeur 127
les bits 9 à 31 sont pour exprimer la mantisse F en binaire,
Exemple : 0.8 sera codé :
s=0 E=126 F
. ........ .......................
0 01111110 10011001100110011001100 codage binaire
. ........ .......................
0 1 8 9 31 n° de bit
Décomposition d'un nombre réel décimal en binaire
Soit le nombre 0.8 à convertir en binaire. On constate d’abord que son signe est positif, donc
S=0. On cherche ensuite à le décomposer en une somme de puissances de 2.

0.8 = 20 x 0.8
0.8 x 2 = 1.6 donc 0.8 = 1.6 / 2 = 2-1 x 1.6 = 2-1 x 1 + 2-1 x 0.6
0.6 x 2 = 1.2 donc 0.8 = 2-1 x 1 + 2-2 x 1.2
0.2 x 2 = 0.4 donc 0.8 = 2-1 x 1 + 2-2 x 1 + 2-3 x 0.4
0.4 x 2 = 0.8 donc 0.8 = 2-1 x 1 + 2-2 x 1 + 2-3 x 0 + 2-4 x 0.8
0.8 x 2 = 1.6 donc ... on retrouve une expression déjà rencontrée qui va se répéter infiniment
0.8 = (-1)0 x 1. 10011001100110011.... x 2-1

Finalement on ne code que les chiffres binaires de la décomposition :


signe = 0 car 0.8 est positif
E = 126 car 126-127 = -1
F = 10011001100110011....

Exemple 2 : 0.75 en base 2 donnera


0.75 x 2 = 1.5 donc 0.75 = 2-1 x 1.5
0.5 x 2 = 1.0 donc 0.75 = 2-1 x 1 + 2-2 x 1.0
0.75 = (-1)° x 2 126-127 x 1.10000000000000000000000
Exercice : Montrer que le nombre décimal 0.1 n’a pas de représentation finie en binaire.
Réels en double précision
En augmentant le nombre d'octets attribués à la représentation des réels, on améliore la précision
en consacrant plus de bits à la mantisse, mais on n'augmente pas l'intervalle des réels représentés car
on conserve le même nombre de bits pour la caractéristique.
Les nombres rationnels
La représentation des réels a de gros défauts dés qu'il s'agit par exemple de comparer des nombres
manifestement égaux mais dont on ignore si l'ordinateur les identifiera.
x = 0.000 020 et y = 0.000 019 999...;
u = 20 ; v = 19 999...
Comment est représenté z = (x/y) et r = (u/v) ? Sont-ils perçus comme égaux ?
Il peut être avantageux de définir directement un type de données NOMBRE RATIONNEL qui
représentera de façon exacte tous les nombres équivalents à une fraction entiére...

Jean Fruitet - IUT de Marne La Vallée - 12


Introduction à la programmation
Codage des informations non numériques
Textes
Les textes peuvent être représentés en binaire à condition d'associer à chaque lettre de l'alphabet
une représentation numérique, par exemple son rang dans l'ordre alphabétique : A serait codé1 ; B,
de rang 2 serait codé 10 en binaire; C codé 11, etc. Mais ce codage est insuffisant car il faut aussi
pouvoir coder les signes de ponctuation . , ; ? ! , distinguer les minuscules des MAJUSCULES, les
caractères accentués, etc.
La table ASCII (American Standard Code for Interexchange Information) propose un codage de 128
caractères différents sur 7 bits. Dans la table ASCII la lettre A est codé 65 B est codé 66 C est
codé 67 ... Z est codé 90 a est codé 97 b est codé 98 ... 0 est codé 48 1 est codé 49 ... 9 est
codé 57 ...
Les caractères accentués des langues européennes ont nécessité l'ajout d'un huitiéme bit de codage,
d'où la table ASCII étendue avec 256 caractères. Mais ce n'était pas suffisant pour certaines langues
comme le japonais ; un codage sur 16 bits est en cours de normalisation sous le nom d'UNICODE. Il
supplantera dans l'avenir le code ASCII.
Les images
Image 12 lignes de 8 colonnes Recodage binaire
Le codage des images en noir et blanc ne présente pas 0001 1100
0011 1110
de difficulté. Il suffit d'observer à la loupe une 0110 1011
photographie de journal pour en comprendre le principe. Si 0011 1110
0011 1110
à une image est superposée une grille très fine, chaque 0001 1100
0000 1000
carré de la grille coloré en NOIR par l'image est codé 1, 0001 1100
0111 1110
chaque carré BLANC est codé 0. Il suffit donc de coder 1111 1111
toute l'image sous forme d’un tableau à deux dimensions de 1111 1101
1111 1111
0 et de 1.
Le codage des sons est plus compliqué. Il faut d'abord transformer chaque son en un signal
électrique continu (c'est le rôle du microphone), puis échantillonner ce signal électrique (discrétiser)
et le numériser. On dispose alors d'un série de nombres qui représentent le signal sonore...
Les relations
Le cours de bases de données (seconde année GTE) introduit le modéle entité/association qui permet
de représenter les informations "Marie est la mére de Pierre et de Françoise" ; "Pierre et Françoise
ont acheté ensemble le véhicule Renault Clio 1234 ZA 75", "Marie a une Citroën ZX" ; "la Twingo
3987 TT 51 n'est à personne", etc., sous forme de tables relationnelles.

PERSONNE n:m VEHICULE

"possède"
1:n

"est la mère de"


PERSONNE POSSEDE VEHICULE
Nom Mére Propriétaire Véhicule Immatriculation Marque Modéle
Marie ? Pierre 1234 ZA 75 1234 ZA 75 Renault Clio
Pierre Marie Françoise 1234 ZA 75 1001 AR 34 Citroën ZX
Françoise Marie Marie 1001 AR 34 3987 TT 51 Renault Twingo

Jean Fruitet - IUT de Marne La Vallée - 13


Introduction à la programmation

Notion d’algorithme
Un algorithme est un procédé automatique qui transforme une information symbolique en une
autre information symbolique. Seuls les problèmes qui sont susceptibles d'être résolus par un
algorithme sont accessibles aux ordinateurs.

DONNEES ---- transformation ------> RESULTAT


Entrée ----- algorithme ---------> Sortie

Ce qui caractérise l'exécution d'un algorithme, c'est la réalisation d'un nombre fini d'opérations
élémentaires (instructions) ; chacune d'elles est réalisable en un temps fini. La quantité de données
manipulées au cours du traitement est donc finie.
La notion d'opération élémentaire dépend du degré de raffinement adopté pour la description du
procédé. Ainsi, chaque algorithme peut être considéré comme une opération élémentaire dans un
procédé plus important.
Exemple d’algorithme : Factorisation de ax2+bx+c quand a≠0.
Algorithme A :
soient x1 et x2 les zéros de ax2+bx+c ;
alors ax2+bx+c = a (x-x1) (x-x2).
Algorithme B
soit ∆= b2-4ac;
si ∆ = 0 alors soient x1 et x2 égaux à -b/2a
sinon si ∆ > 0 alors soient x1=(-b+√∆)/2a et x2= (-b-√∆)/2a
sinon soient x1=(-b+i√(-∆))/2a et x2= (-b-i√(-∆))/2a;
alors ax2+bx+c = a (x-x1) (x-x2).

Traduction de l’algorithme dans un langage de programmation


Avant de faire traiter la factorisation de Programme
ax2+bx+c quand a ≠ 0 par un ordinateur, il faut source
traduire cet algorithme dans le langage binaire
susceptible d’être exécuté par la machine.
Cette transformation est le travail du
programmeur. Il dispose pour cela d’un COMPILATION
langage de programmation dit de haut niveau, Programme
c’est-à-dire qu’un être humain peut apprendre binaire
et manipuler sans trop de difficultés, et de
programmes spécialisés, appelés compilateurs,
qui font la conversion d’un fichier écrit dans le ..
langage de haut niveau en code binaire EXECUTIONS
exécutable par la machine cible. Une fois
compilé, le programme (s’il est correct) peut
Une factorisation de
être exécuté de multiples fois. Les résultats de 3x 2+11x-4 est
son exécution (les sorties) dépendent des 3(x-1/3)(x+4)
paramètres fournis en entrée.

Jean Fruitet - IUT de Marne La Vallée - 14


Introduction à la programmation

Avant de passer à la phase de programmation il est donc nécessaire de définir très précisément le
cahier des charges du programme, c’est-à-dire dans quelles conditions initiales il devra fonctionner,
comment il devra procéder (algorithme) et sous quelle forme seront présentés les résultats.
Types de données et structures de contrôle
Dans l’exemple de la factorisation ci-dessus, les entités manipulées sont des nombres (complexes
et réels), des coefficient constants (a, b, c), une “inconnue” (x), des variables x1 et x2, le symbole ∆
du discriminant et les opérations élémentaires sur l’ensemble des nombres réels (<, >, =, +, -, *, /).
On dira que les types de données sont des constantes et des variables de type réel. L’algorithme
emploie aussi des structures de contrôle conditionnelles : Si (condition) alors instruction sinon
instruction.. Un instruction est soit une affectation — ∆= b2-4ac; —, soit un test— si ∆ = 0 alors —
.Le langage de programmation devra fournir des équivalents de toutes ces entités. Enfin le langage
doit permettre de créer un programme qui reçoive des paramètres —saisie de la valeur des
coefficients— et retourne des résultats à l’utilisateur —affichage—.
Un langage de programmation graphique
Traduisons d’abord l’algorithme de factorisation dans un langage graphique élémentaire, bien
adapté à l’expression des algorithmes peu complexes.

Structures de contrôle Chaque boîte peut contenir une ou


initialisation;
plusieurs instructions exécutées
test
OUI séquentiellement, c’est-à-dire succes-
? sivement dans l’ordre de lecture de haut
NON
en bas. Un test est une expression de
type booléen, à savoir prenant la valeur
instruction; (Branchement) VRAI ou la valeur FAUX lors de son
évaluation. Enfin les branchements sont
parcourus dans le sens des fléches.
Factorisation d'un polynôme du second degré à coefficients réels
a,b,c Il suffit d’exécuter chaque instruction à
réels; ∆=b*b-4*a*c; la lettre, en suivant les fléches désignées
a≠0; à la suite de chaque test pour résoudre le
problème de factorisation. On constate
NON NON OUI que cette représentation fait l’économie
x1=-b/2*a; d’un test par rapport à la résolution
(∆>0) (∆=0)
x2=-b/2*a; mathématique.
OUI
Cependant peu nombreux sont les
x1=(-b+√∆)/2*a; x2=(-b-√∆)/2*a; programmes capables d’interpréter
directement un langage graphique, aussi
faut-il exprimer le programme dans un
x1=(-b+i√(-∆))/2*a; x2=(-b-i√(-∆))/2*a; langage littéral.

Afficher
a (x-x1) (x-x2);
FIN DU PROGRAMME;

Jean Fruitet - IUT de Marne La Vallée - 15


Introduction à la programmation

Modéle d'ordinateur abstrait et langage de programmation


Passons donc à la traduction de l’algorithme dans un langage de programmation élémentaire, le
langage de la machine RAM (Random Acces Machine : Machine à Accès Direct, voir le cours
d’algorithmique).
Les algorithmes sont écrits pour une machine abstraite qui a les caractéristiques d'une machine de
Von Neumann. Elle comprend :
- des organes d'entrée et de sortie (interface avec l'utilisateur) ;
- une mémoire unique pour le programme et les données ;
- une unité centrale comportant notamment un compteur d’instruction, un processeur arithmétique
et logique, des registres et des têtes de lecture et d'écriture et un moyen d'accès à la mémoire ;
- un jeu d'instructions exécutables par l'unité centrale.

Organe d'entrée

Tête de lecture
Mémoire

Unité centrale Programme

Compteur

UAL
Données

Tête d'écriture

Organe de sortie

La mémoire est une suite de cases dont les indices sont appelés adresses. Une partie de la
mémoire contient le programme, traduction de l'algorithme au moyen des instructions de la machine.
Le compteur d’instruction contient l'adresse de la prochaine instruction à exécuter. L'autre partie de
la mémoire contient les données.
Caractéristiques :
- l'unité centrale accéde directement (en temps constant) à une case mémoire à partir de son adresse
[random acces].
- La mémoire est infinie (mais le programme est fini).
- Chaque case mémoire contient une donnée élémentaire de taille arbitraire. On peut ainsi y
mémoriser des entiers arbitrairement grands.
- Chaque instruction s'exécute en temps constant.
Variables et types
Une case mémoire de la machine abstraite est aussi appelée une variable. Dans les programmes
on désigne une variable par un identificateur littéral plutôt que par son adresse, et, par abus de
langage, on dit "la variable x " plutôt que "la variable d'identificateur x ". Le type d'une variable
définit l'ensemble des valeurs qu'elle peut prendre —du point de vue implantation en machine, le type
décide aussi de la taille de l’espace mémoire occupé par la donnée.

Jean Fruitet - IUT de Marne La Vallée - 16


Introduction à la programmation
Types simples et types combinés
Les types simples sont
- l'entier {....,-1 000 000,... -1 000,... -3,-2,-1, 0, 1, 2, 3, ..10, ..,100, .., 1 000, .....}
- le booléen {VRAI, FAUX} [boolean {TRUE, FALSE}]
- le caractère {'0','1','2',..,'9',..,'A','B',..,'Z',..,'a','b',...}
- l'énuméré (Ex.: [violet,bleu,vert,jaune,rouge])
- le 'réel' flottant (Ex.: valeur approchée de PI 0.3141592 E+1)
A partir des types simples des opérateurs permettent de construire des types plus complexes :
- couples d'entiers (rationnels)
- couples de flottants (nombres complexes)
- chaînes de caractères (Ex.: "CECI EST UNE CHAINE DE CARACTERES")
- tableaux (Ex.: les 8 premières puissances de 2 en base 10 : [1, 2, 4, 8, 16, 32, 64, 128])
- ensembles (Ex.: ensemble de couples d'entiers :{ (0,1), (1,2), (2,4), (3,8), (4,16), (5,32), (6,64),
(7,128)}
- structures (Ex.: fiche de répertoire : (Nom, Prénom, Date de naissance, Adresse, Téléphone))
Sur chaque type est défini un domaine et un ensemble d'opérations :
- Addition, multiplication, successeur sur les entiers naturels
- Addition, soustraction, multiplication, successeur, prédécesseur, modulo sur les entiers relatifs
- Addition, soustraction, multiplication, division, exponentiation, logarithme sur les réels
- Opérations logiques ET, OU, NON sur les booléens
- Union, intersection, complémentation, produit cartésien sur les ensembles
- Concaténation sur les chaînes de caractères,
etc.
Opérations de bases
Les opérations de base sur tous les types de variables sont les opérations d'entrée-sortie et
l'affectation.. Soient x et y des identificateurs de variables.
lire(x) signifie : copier la valeur qui est en face de la tête de lecture (sur l'organe d'entrée) dans la
case-mémoire identifiée par x ; puis placer la tête de lecture sur la donnée suivante.
écrire(x) signifie : copier la valeur contenue dans la case mémoire identifiée par x sur l'organe de
sortie en face de la tête d'écriture ; puis avancer cette tête.
x = expression se lit “x reçoit expression” et signifie : évaluer l'expression et placer la valeur
obtenue dans la case-mémoire identifiée par x.. Si y apparaît dans l'expression, la valeur contenue
dans la case-mémoire correspondant à y est utilisée pour l'évaluation :
Ex : x = x + y : Le contenu de la case mémoire désignée par x est remplacé par la somme des
valeurs contenues dans les cases mémoires désignées par x et par y. Le contenu original de x est
perdu, celui de y est conservé. (2)
Les structures de contrôle du langage
- composition séquentielle
{ instruction1; instruction2; ...; instructionN;}
- composition conditionnelle :
{SI (condition) ALORS (instruction1) SINON (instruction2)}
- composition itérative
{POUR (énumération) FAIRE (instruction)}
{TANT QUE (test) FAIRE (instruction)}
- des appels de fonctions et sous-programmes .

2Pour préparer le lecteur aux conventions du langage C, nous conviendrons de noter l’affectation par un signe ‘=‘ et
l’égalité par ‘==‘.
Jean Fruitet - IUT de Marne La Vallée - 17
Introduction à la programmation
De façon plus précise la syntaxe du langage est définie par la grammaire formelle suivante
(simplifiée du langage C) :

<programme> ::= type du résultat identificateur (type paramètre);


{ suite d'instructions séparées par des points-virgules }
<instruction> ::= <instruction élémentaire> | { suite d'instructions séparées
par des points-virgules } |
si (test) <instruction> sinon <instruction> |
pour (i =d à f ) faire <instruction> |
tant que (test) faire <instruction>
<instruction variable =expression |
élémentaire> ::= lire(identificateur) | écrire(expression) |
retour(expression);
L'instruction retour(expression); a pour effet d'arrêter l'exécution de l'algorithme et de produire la
valeur de l'expression. Une expression est bien parenthésée au sens de l’algèbre et prend une valeur
du type des éléments qui la composent.

Avec ce langage, traduisons l’algorithme de factorisation. Nous obtenons une fonction qui
retourne un couple de polynômes (éventuellement complexes) de degré 1.
Fonction factorisation d’un polynôme réel de degré 2
couple de polynômes de degré 1 factorisation ( polynôme réel de degré 2 ax2+bx+c);
{
réel D;
D =b2-4ac
si (D==0)
retour((x+b/2a), (x+b/2a);
sinon si (D>0)
retour((x-(-b+√D)/2a), (x-(-b-D)/2a) );
sinon
retour((x-(-b+i√ (-D))/2a), (x-(-b-i√ (-D))/2a));
}
Traduit dans le langage de la machine RAM, l’algorithme de factorisation reste encore très
proche de ses origines mathématiques. L’ultime transformation va le traduire dans un langage
effectif, le langage C, pour obtenir un programme exécutable par une machine réelle.
Conclusion
Cet exposé de la factorisation d’un polynôme nous a permis de passer par les trois étapes de
création d’un programme numérique :
définition du problème,
rédaction de l’algorithme en termes mathématiques,
traduction dans un langage de programmation (la traduction en langage C sera abordée au
chapitre suivant).
Il faut insister ici sur l’extréme rigueur de la syntaxe des langages de programmation. Toutes les
constantes, variables et fonctions utilisées doivent être typées et définies avant appel. Les
instructions respectent une grammaire précise, qui caractérise le langage ; les fonctions disponibles
(plusieurs centaines en C) fournissent une grande variété d’outils dont la maîtrise ne peut s’acquérir
que peu à peu... Cet apprentissage ne doit pas être confondu avec une formation à l’art de la
programmation qui peut débuter avec des exercices plus simples et moins rebutants.

Jean Fruitet - IUT de Marne La Vallée - 18


Introduction à la programmation

Le langage C.
Le langage C est un langage d’ingénieur destiné à la création d’applications informatiques.
Beaucoup d’ouvrages lui ont été consacrés. Le plus important est dù aux créateurs du langage eux-
même, Denis Ritchie et Brian Kerninghan. On en trouvera la référence dans la bibliographie. Un
programme en C est constitué d’un (ou plusieurs) fichiers sources organisés d’une façon
conventionnelle. Voici une traduction en C de l’algorithme de factorisation. Les cadres (qui ne sont
pas nécessaires dans un programme, mais permettent ici de fixer les idées) délimitent cinq parties
fonctionnellement interdépendantes du fichier source.

/* entête : fichiers inclus */


#include <stdio.h> (1)
#include <math.h> /* etc. */

/* déclarations de constantes
et de variables globales */ (2)
#define FALSE 0
#define TRUE 1
float a, b, c;

/* prototypes de fonctions */
void factorisation(float a, float b, float c); (3)

void main (void) /* programme principal */


{
printf("Factorisation d'un polynôme réel de
degré 2\n");
printf("Entrez trois nombres réels a, b, c
(a°0)\n");
scanf("%f %f %f", &a, &b, &c); (4)
if (a==0) {
printf("Erreur de saisie\n");
exit(0);
}
factorisation(a, b, c);
} /* fin du programme */

/* définition des fonctions */


void factorisation(float a, float b, float c)
/* on assume que a est non nul */
{ (5)
float delta = b*b - 4*a*c;
/* delta est le discriminant */
/* c'est une variable locale */
printf("La factorisation donne \n");
if (delta==0)
printf(" (%f)(x-(%f))(x-(%f))\n",
a,-b/(2*a),-b/(2*a));
else if (delta>0)
printf(" (%f)(x-(%f))(x-(%f))\n",
a,(-b-sqrt(delta))/2*a, (-b+sqrt(delta))/2*a);
else
printf(" (%f)(x+(%f)+(%f)i)(x+(%f)-(%f)i)\n",
a, b/(2*a), sqrt(-delta)/2*a, b/(2*a),
sqrt(-delta)/2*a);
}

Le bloc (1) est celui des fichiers inclus. Sa première ligne, #include <stdio.h>, invoque la
bibliothèque des fonctions d’entrée-sortie (saisie au clavier scanf() et affichage à l’écran
Jean Fruitet - IUT de Marne La Vallée - 19
Introduction à la programmation
printf()). La deuxiéme ligne, #include <math.h>, fait appel aux fonctions
mathématiques (sqrt() : racine carrée).
Vient ensuite —bloc (2)— la définition des constantes et des variables globales, c’est-à-dire vues
depuis tous les points du programme :
#define FALSE 0
#define TRUE 1
float a, b, c;
Puis on trouve le prototype de la fonction factorisation() :
void factorisation (float a, float b, float c);
Celle-ci prend trois paramètres —a, b, c : les coefficients du polynôme à factoriser— de type
float; mais comme elle ne retourne aucune valeur, elle est typée void.
Enfin c’est le bloc (4) de la fonction main(). C’est le point d’entrée du programme. Tout
programme en Langage C a une fonction main() et une seule. Celle-ci affiche deux lignes de message
et lit ensuite le clavier —scanf(“%f %f %f”, &a, &b, &c);— jusqu’à l’entrée de trois
nombres ‘flottants’. Après avoir testé la condition (a0) la fonction factorisation() est
appelée et le programme se termine.
Le dernier bloc (5) est le code de la fonction factorisation(). Le lecteur reconnaîtra la
définition du discriminant et l’expression des différentes factorisations selon la valeur de . Nous
n’entrerons pas maintenant dans le détail de la syntaxe des fonctions printf() et scanf(), qui
sont parmi les plus compliquées du langage C. Je renvoie le lecteur aux ouvrages cités en référence
et au support du cours de langage C.
Les étapes suivantes consistent à compiler ce programme source, puis à lier le fichier objet
obtenu après compilation avec les bibliothèques standard et mathématique, ce qui produit un
programme exécutable. En cas d’erreur, ou pour modifier ce programme, il faut reprendre toute la
séquence en rééditant le fichier source...
C est étroitement associé à UNIX Le Système d'Exploitation (Operating System) UNIX développé
aux Laboratoires Bell (ATT Corporation - USA) par B.W. Kernigham et D.M. Ritchie dans les
années 70, a été écrit en C, développé pour l'occasion.
Unix est multi-tâches et multi-utilisateurs, sur mini et stations de travail. Sa diffusion a assuré le
succés de C chez les universitaires et les ingénieurs.
Unix est aujourd'hui fortement concurrent d'OS2 sur micros puissants...

Caractéristiques succintes du langage C


Le langage C est un langage des années 70, donc 'moderne' par rapport à FORTRAN ou BASIC,
procédural (description linéaire des états de la mémoire, comme Fortran, Basic, Lisp, Pascal...,
contrairement aux langages déclaratifs comme SQL ou Prolog (ensemble de faits et de règles +
moteur d'inférence).
C est structuré en blocs fonctionnels imbriqués. C fait appel à des variables locales à chaque bloc
ou à des variables globales à plusieurs blocs.
C supporte l'appel de fonctions et c'est un langage typé : les variables et les fonctions utilisées
doivent être déclarées et leur types et vérifié à la compilation. C est un langage compilé : le code
source est transformé en code objet et lié pour produire un exécutable.
Comparé à PASCAL, C est plus CONCIS mais plus obscur. C est mieux standardisé, mais les
compilateurs C sont plus libéraux que les compilateurs Pascal pour la vérification des types de
données.
Jean Fruitet - IUT de Marne La Vallée - 20
Introduction à la programmation
Pour s'assurer du portage d'un programme en C sur d'autres compilateurs, il est fortement
conseillé de respecter les spécifications ANSI et de tenir compte des avertissements (warnings) à la
compilation.
C est le langages des programmeurs système... mais sa disponibilité sur tous les systèmes
informatiques en fait un langage indispensable (avec Fortran pour les applications scientifiques).
C n'est pas un langage à objets comme C++ ou Java.

Evolutions
Le futur de C est lié à la programmation parallèle, au développement des réseaux et à la
Programmation Orientée Objets (C++, Java). L'apprentissage de C est un bon investissement pour
l'ingénieur logiciel ou le chercheur amené à utiliser des stations de travail, à condition de
programmer souvent.
Structures en blocs
Un bloc est une séquence d'une ou plusieurs instructions commençant par une accolade ouvrante {
terminée par une accolade fermante }.

Exemple de structure en blocs pour un programme de jeu d'échecs

/* Programme Jeu d'échecs */ Chaque Fonction() peut à son tour être


Initialiser_Variables décomposée en blocs :
Initialiser_Affichage
TANT_QUE (La_Partie_Continue) /* Fonction */
{ Déterminer_mouvement_Suivant()
SI (C_est_mon_Tour) {
{ Chercher_Tous_les_coups_Autorisés();
Déterminer_mouvement_Suivant (); POUR (Tous_ces_Coups)
Mettre_à_jour_Affichage (); {
Mettre_à_jour_Variables (); Evaluer_la_Position();
} SI (Meilleure_Position_Jusque_là)
SINON {
{
Attendre_Déplacement_Adverse(); Mettre_à_Jour_le_Mouvement_Sélectionn
Vérifier_Validité(); é();
Mettre_à_jour_Affichage(); }
Mettre_à_jour_Variables(); }
} RENVOI (Mouvement_Sélectionné);
} }

Variables locales et variables globales


Dans chaque bloc il est possible de définir des variables locales - dont l'accès n'est licite qu'à
l'intérieur du bloc considéré- dont la valeur est indéterminée sauf affectation explicite,
- dont l'emplacement mémoire est libéré à la sortie du bloc.
Les variables globales à plusieurs blocs sont connues
- dans le bloc englobant où elles sont définies,
- dans les blocs internes au bloc englobant, sauf en cas de masquage par une redéfinition sous le
même nom de variable.
Dans l'exemple suivant on vérifie que la variable globale i vaut 1 en entrée et en sortie du premier
bloc et n'est pas affectée par la variable i interne au deuxiéme bloc.

Jean Fruitet - IUT de Marne La Vallée - 21


Introduction à la programmation

/* Structure en Blocs -- EXO1.C */ L'exécution de ce programme produit :


#include <stdio.h>
main() /* Programme principal */ Entrée du 1er Bloc
{ /* Début du premier bloc */ Variable i=1
int i, n; /* Définition */ Nombre d'itérations 5
i=1; /* Affectations */ Entrée du 2éme Bloc: i redéfini et
n=5; non affecté=80
printf("Entrée du 1er Bloc\n"); 0 dans boucle
printf("Variable i=%d\n",i); 1 dans boucle
printf("Nombre d'itérations %d\n",n); 2 dans boucle
3 dans boucle
{ /* second bloc */ 4 dans boucle
int i; /*i Redéfini */ Sortie du 2éme Bloc: i=5
/* Oubli de l'affectation */ Sortie du 1er Bloc: i=1
printf("Entrée du 2éme Bloc: i
redéfini et non affecté=%d\n",i);
for (i=0; i<n; ++i)
printf(" %d dans boucle\n",i);
printf("Sortie du 2éme Bloc:
i=%d\n",i);
} /* Fin du second bloc */
printf("Sortie du 1er Bloc:
i=%d\n",i);
} /* Fin du premier bloc */

Jean Fruitet - IUT de Marne La Vallée - 22


Introduction à la programmation

Constantes et variables
Le langage C utilise les constantes numériques et les constantes caractères
- entiers : 0, 1, 2, -1, -2, etc.
- flottants : 0.0, 0.3141592E1,
- caractères : 'a', 'b', 'c',..., 'A', 'B', 'C', etc.,
- constantes chaînes de caractères : "CECI est une chaîne de caractères".
Les variables sont des adresses de la mémoire désignées par des identificateurs littéraux
commençant par une lettre (exemple : i, j, x, y, entier1, entier_2, bilan_energetique)
Mots réservés
Les mots réservés ne peuvent pas servir de noms de variables.
auto extern short break float sizeof case for static char
goto struct continue if switch default int typedef do long
union double register unsigned else return while
Mots réservés supplémentaires pour la norme ANSI
const signed volatile enum void
Mots réservés supplémentaires pour le compilateur Turbo C
asm huge pascal cdecl interrupt far near
Types de données
Il faut déclarer le type de toutes les variables et de toutes les fonctions, qui indique à la fois
l'intervalle de définition et les opérations licites
Types simples
Type Signification Taille (bits) Valeurs limites
int entier 16 -32768 à +32768
short entier 16 -32768 à +32768
long entier 32 -2 147 483 648 à +2 147 483 648
char caractère 8 -128..+127
float réel +-10 E-37 à +-10 E+38
double réel +-10 E-307 à +-10 E+308

Une variable entiére peut être déclarée 'unsigned'


unsigned int 16 0 .. 65535
Le type BOOLEAN est simulé en donnant la valeur 0 (FAUX) ou la valeur 1 (VRAI) à une variable
entiére.
Les constantes de type caractère ont une valeur entiére dans la table ASCII
char c1 = 'A',
c2 = '\x41'; /* représentation hexadécimale */

Caractères spéciaux
caractères nom symbole code code hexa décimal
\n newline LF 0A 10
\t tabulation HT 09 9
\b backspace BS 08 8
\r return CR 0D 13
\f form feed FF 0C 12
\a bell BEL 07 7
\\ backslash 5C 92
\' single quote 27 39
\" double quote 22 34

Jean Fruitet - IUT de Marne La Vallée - 23


Introduction à la programmation
Instruction d'affectation
L'assignation est une instruction qui affecte une valeur à une variable. Cette valeur peut provenir
de l'évaluation d'une constante, d'une expression ou d'une fonction.
Le symbole = désigne l'affectation (assignation).
int i, j, k; /* déclaration */
i = j = 5; /* assignation de 5 à j et de j à i */
k = 7; /* assignation de 7 à k */
Transtypage (cast)
C permet des assignations entre variables de types différents.Une variable déclarée char, occupant
un octet de mémoire, peut être transtypée en int, occupant deux octets de mémoire.
Régles de transtypage (casting)
char --> int le char se retrouve dans l'octet le moins significatif ; si le caractère a été déclaré
unsigned char, il n'y a pas d'expansion du signe, sinon on retrouve le bit de poids fort répété 8 fois
dans l'octet le plus significatif.
int --> char perte de l'octet le plus significatif
int --> long expansion du bit de signe
long --> int résultat tronqué (perte des deux octets les plus significatifs)
unsigned --> long les deux octets les plus significatifs sont mis à 0
int --> float exemple : 15 --> 15.0
float --> int perte de la partie décimale : 2.5 --> 2 Si la partie entiére du réel est supérieure à
32767 le résultat sera aberrant.
float --> double pas de difficulté
double --> float perte de précision
Quand des expressions mélangent les types, le transtypage est automatique.
Opérateurs
C emploie plusieurs opérateurs : arithmétiques, de comparaison, logiques... La priorité des
opérateurs entre eux permet d'évaluer d'une expression.
Opérateurs arithmétiques
Par ordre de priorité décroissante
Symbole Signification
* / multiplication division (entiére et réelle)
+ - addition soustraction
% reste de la division entiére (modulo)
Opérateurs de comparaison
Si une expression est FAUSSE elle renvoie une valeur nulle , si elle est VRAIE elle renvoie une
valeur non nulle.
Les opérateurs && et || permettent de combiner des expressions logiques
Par ordre de priorité décroissante :
Symbole Signification
! NON (inverse une condition)
> >= < <= sup / sup ou egal / inf / inf ou egal
= = != égal / différent
&& || ET logique / OU logique

Jean Fruitet - IUT de Marne La Vallée - 24


Introduction à la programmation
Table de vérité de l'opérateur ET AND &&
FAUX && FAUX == FAUX
FAUX && VRAI == FAUX
VRAI && FAUX == FAUX
VRAI && VRAI == VRAI

Table de vérité de l'opérateur OU : OR : ||


FAUX || FAUX == FAUX
FAUX || VRAI == VRAI
VRAI || FAUX == VRAI
VRAI || VRAI == VRAI
Incrémentation et décrémentation
L'instruction i = i +1; remplace la valeur de i par i+1 ; c'est une incrémentation qui peut aussi
s'écrire i++; ou ++i;
De même i = i - 1; peut s'écrire i - -; ou - - i;
Remarque: avec l'instruction i++, i est affecté puis incrémenté, avec ++i, i est incrémenté puis
affecté, avec l'instruction i--, i est affecté puis décrémenté, avec - -i, i est décrémenté puis affecté
et enfin avec i- - , i est affecté puis décrémenté.

Si (e1) et (e2) sont des expressions et "op" une opération prise parmi la liste + - * / % << >> & | ^
alors
(e1) = (e1) op (e2); peut s'écrire (e1) op = (e2);
On économise une évaluation de e1.

Attention aux parenthèses : x *= y + 1;


est équivalent à x = x * (y+1); et non à x = x * y + 1;
Autrement dit si x==3, y==4 le résultat de cette instruction remplacera x par 15 et non pas par 13 !
Structures conditionnelles
Pour les structures conditionnelles, la condition évaluée doit être de type entier (short, int, long)
ou char. Toute fonction qui renvoie une valeur d'un de ces types peut être testée dans la condition.
if / else
if (condition) /* commentaire de la condition si */
instruction;

if (condition)
{ /* début de bloc*/
instruction1;
instruction2;
} /* fin de bloc */

if (condition)
instruction_si;
else /* sinon */
instruction_sinon;

Jean Fruitet - IUT de Marne La Vallée - 25


Introduction à la programmation
Conditions imbriquées
if (condition1)
inst1;
else if (condition2)
inst2;
else if (condition3)
inst3;
else /* chaque else se rapporte au if le plus proche sauf si on utilise des accolades
comme dans l'exemple suivant */
inst4;

Regroupement d'instructions

if (cond1) /* premier if */
{
if (cond2)
inst1;
else if (cond3)
inst2;
}
else /* sinon se rapportant au premier if */
inst3;

Affectation conditionnelle
if (i>j) z = a; else z = b; est équivalent à z = (i>j) ? a : b;

Sélection (switch)
L'instruction switch est une sorte d'aiguillage. Elle permet de remplacer plusieurs instructions
imbriquées. La variable de contrôle est comparée à la valeur des constantes de chaque cas (case). Si
la comparaison réussit, l'instruction du case est exécutée jusqu'à la première instruction break
rencontrée.

switch (variable_controle)
{
case valeur1 : instruction1;
break; /* sortie du case */
case valeur2 : instruction2;
break;
case valeur3 : /* plusieurs */
case valeur4 : /* étiquettes */
case valeur5 : instruction3; /* pour la même instruction */
break;
default : instruction4; /* cas par défaut */
break; /* facultatif mais recommandé */
}

variable_controle doit être de type entier (int, short, char, long).


break fait sortir du sélecteur. En l'absence de break, l'instruction suivante est exécutée ; on peut
ainsi tester plusieurs cas différents et leur attribuer la même instruction.

Jean Fruitet - IUT de Marne La Vallée - 26


Introduction à la programmation

Boucles et sauts
Les boucles consistent à répéter plusieurs fois la même séquence d'instructions. La sortie de boucle
est réalisée en testant une condition (de type booléen VRAI ou FAUX).

While, Tant que .

while (condition)
instruction;

while (condition)
{
instruction1;
instruction2;
}
La condition est évaluée avant d'entrer dans la boucle. La boucle est répétée tant que la condition est
VRAIE.

For, Pour.
for (initialisation; condition d'arrêt; incrémentation)
instruction;
for (initialisation; condition d'arrêt; incrémentation)
{
instruction1;
instruction2;
...
}

La condition est évaluée avant d'entrer dans la boucle. L'incrémentation de la variable de contrôle est
faite à la fin de chaque tour de boucle.

Exemple :
int i;
for (i=1; i<10; i++)
printf("%d ",i); /* ce programme affiche 1 2 3 4 5 6 7 8 9 */

Do ... While, Faire ... tant que.

do {
instruction1;
instruction2;
} while (condition d'arrêt);

La condition est évaluée après le passage dans la boucle.


Exit
Un programme peut être interrompu par l'instruction exit(code de retour); La valeur du code
de retour est un entier qui peut être testée par le programme appelant.

Jean Fruitet - IUT de Marne La Vallée - 27


Introduction à la programmation
Tableaux
Un tableau est une suite de cellules consécutives en mémoire pouvant contenir des données de
type identique. La taille du tableau et le type des données doivent être déclarés en même temps que
le tableau. Le nombre de dimensions n'est pas limité. L'indice (adresse relative de chaque cellule par
rapport au début du tableau) doit être une expression entiére ; la première cellule a l'indice 0.
Tableaux à une dimension
Exemples :
int t[5];
Nom du tableau est t ; les cellules sont de type entier (int) ; la taille 5 ; indexée de 0 à 4
long l[2];
Nom du tableau est l; les cellules sont de type entier (long) ; la taille 2; indexée de 0 à 1
char hexa[17];
Nom du tableau est hexa ; cellules de type char ; taille 17

Un tableau peut être affecté en même temps qu'il est déclaré :


int tabint[5] = {0, 10, 100,}; /* les autres cellules à 0 */
char binaire[2] = {'0', '1'};
Affectation
Pour initialiser un tableau, il faut désigner ses différentes cellules :
t[0] = tabint[4] = 10000; /* affectation en cascade */
hexa[0] = '0';
for (i=0; i<5; i++)
tabint[i] = i*i;
Attention : les dépassements des bornes d'indice des tableaux ne sont pas détectés à la compilation.
C'est donc au programmeur d'ajouter les instructions de contrôle des bornes avant d'affecter une
variable à un tableau.
Chaînes de caractères
Les chaînes sont des tableaux de caractères terminés par le caractère NULL ('\0').
Il faut déclarer leur taille, c'est-à-dire le nombre de caractères plus un pour stocker la fin de
chaîne.
char decimal[11] /* déclaration : 11 = 10 caractères + '\0' */
= "0123456789"; /* cette affectation : est équivalente à */
char decimal[11] = {'0','1','2','3','4','5','6','7','8','9','\0'};
char alpha[6] = "ABCDE" 5 caractères; composée de 6 octets identiques au code ASCII de
chaque caractère sot 65 66 67 68 69 00 en décimal, et 0x41 0x42 0x43 0x44 0x45 0x00 en
hexadécimal.
Tableaux à plusieurs dimensions
Deux dimensions (matrice)
int a[3][3] = {{1,2,-1},{-2,3,4},{5,0,6}};
a[0] désigne la 1ére ligne : {1,2,0}.
a[0][2] désigne le 3éme élément de la 1ére ligne : 1.
a[2][1] désigne le 2éme élément de la 3éme ligne : 0.

Trois dimensions
int parallelepipede[3][2][4]; 3 couches de 2 lignes de 4 colonnes chacune.
parallelepipede[0][0][0] désigne la première colonne de la première ligne de la première couche.
parallelepipede[2][1][3] désigne la dernière colonne de la dernière ligne de la dernière couche...

Jean Fruitet - IUT de Marne La Vallée - 28


Introduction à la programmation
Pointeurs
Un pointeur est une variable qui contient l'adresse d'une autre variable.
Selon le type de donnée contenu à l'adresse en question, on aura un pointeur d'entier, de float,
double, char, ou de tout autre type. En accédant à cette adresse, on peut accéder indirectement à la
variable et donc la modifier.
Déclaration de pointeur
Pour déclarer un variable pointeur on utilise l'opérateur * placé après le type du pointeur.
Un pointeur sur entier est déclaré par int *p;
Un pointeur sur réel float *p;
Un pointeur sur caractère char *p;
Pointeur sur pointeur de type double double **tab;
la variable tab est un pointeur pointant sur un pointeur qui pointe sur un flottant double !

La déclaration int *p signifie que *p est un entier et donc que p est un pointeur sur un entier.

Opérateur adresse (&)


On dispose de deux opérateurs unaires pour manipuler les pointeurs, & et *
L'opérateur d'adresse & renvoie l'adresse de l'objet désigné.
int i, j; /* déclarations d'entiers i et j */
int *p1, *p2; /* déclaration de pointeurs sur des entiers */
i = 5; /* affectation */
j = 2 * i; /* affectation */
p1 = &i; /* p1 contient l'adresse de i */
p2 = &j; /* p2 pointe sur j */
Cet opérateur & ne peut être employé que sur les variables et les éléments de tableaux ; les
constantes &3 et les expressions &(i+1) sont interdites.
Opérateur valeur (*)
L'opérateur * considère son opérande comme un pointeur et en renvoie le contenu.
p1 = &i; reçoit l'adresse de i; donc pointe sur i
j = *p1; j prend la valeur contenue à l'adresse pointée par p1, donc la valeur de i ;:cette
instruction est équivalente à j = i;
int *p, *q, x, y; Partout où un entier peut être employé, *p peut l'être aussi :
p = &x; /* p pointe sur x */
x = 10; /* x vaut 10 */
y = *p1 - 1;/* y vaut 9 */
*p += 1; /* incrémente x de 1 : x vaut 11 */
(*p)++; /* incrémente aussi de 1 la variable pointée par p, donc x vaut 12. */
Attention aux parenthèses :
*p++ est TRES DIFFERENT de (*p)++
car ++ et * sont évalués de droite à gauche et *p++ incrémente le pointeur p (l'adresse) et non le
contenu de p
*p = 0; /* comme p pointe sur x, maintenant x vaut 0 */
q = p; /* p et q pointent maintenant sur la même adresse donc *q devient zéro ! */
Pointeurs et tableaux
En C le compilateur transforme toute déclaration de tableau en un pointeur sur le premier élément
du tableau.
int tabint[10]; est donc équivalent à int *tabint; (à la réservation mémoire près).
Jean Fruitet - IUT de Marne La Vallée - 29
Introduction à la programmation
Exemples :
int i, j, *p;
p = &tabint[0]; p pointe sur le premier élément de tabint
i = *p; recopie le contenu de tabint[0] dans i
p++; p pointe sur le second élément de tabint
p+j; adresse de tabint[j]
*(p+j); contenu de tabint[j]
Tableaux de pointeurs
Un tableau de pointeurs est un tableau dont les éléments sont des pointeurs.
char *mois[13];
int *valeurs[5];
Allocation dynamique de mémoire
Quand on utilise des pointeurs il est indispensable de toujours savoir quels éléments sont
manipulés dans les expressions et si de l'espace mémoire a été réservé par le compilateur pour
stocker les objets pointés. En effet, alors qu'une déclaration de tableau réserve de l'espace mémoire
dans l'espace des données pour la variable et ses éléments, celle d'un pointeur ne réserve que la place
pour une adresse !

La déclaration double x[NOMBRE]; réserve un espace de stockage pour NOMBRE éléments de


type réel double et la variable x[0] stocke l'adresse de l'élément n° zéro (le premier élément).
Par contre la déclaration double *x; ne réserve que le seul espace destiné au pointeur x, AUCUN
espace n'est réservé pour une ou plusieurs variables en double précision.

La réservation d'espace est à la charge du programmeur qui doit le faire de façon explicite, en
utilisant les fonctions standard malloc(), calloc(), ou realloc().
Ces fonctions sont prototypées dans <stdlib.h> et <alloc.h>
char * malloc( unsigned taille); réserve taille octets, sans initialisation de l'espace
char * calloc( unsigned nombre, unsigned taille); réserve nombre éléments de taille octets
chacun ; l'espace est initialisé à 0.
void * realloc( void *block, unsigned taille); modifie la taille affectée au bloc de mémoire
fourni par un précédent appel à malloc() ou calloc().
void free( void *block); libére le bloc mémoire pointé par un précédent appel à malloc(), calloc()
ou realloc().
Ces fonctions doivent être transtypées pour réserver de l'espace pour des types de données qui
sont différents du type (char *). Elles retournent le pointeur NULL (0) si l'espace disponible est
insuffisant.

Exemples :
char *s, *calloc();
s = (char *) calloc(256, sizeof(char)); /* réserve 256 octets initialisés à '\0' */
float *x;
x = (float *) malloc(sizeof(float)); /* réserve un espace pour un réel de type double */
L'allocation dynamique de mémoire se fait dans le tas (mémoire système dynamique) ; la taille de
celui-ci varie en cours d'exécution, et peut n'être pas suffisante pour allouer les variables, provoquant
le plantage du programme. Il faut donc tester le retour des fonctions d'allocation et traiter les erreurs.

Jean Fruitet - IUT de Marne La Vallée - 30


Introduction à la programmation
Exemple de traitement d'allocation dynamique.

#define BUFSIZE 100


long *numero;
if (!(numero = (long *) calloc(BUFSIZE, sizeof(long))) )
/* Si échec à réservation de BUFSIZE emplacement de type long */
{
fprintf(stderr, "ERREUR espace mémoire insuffisant !\n");
exit (1); /* fin anticipée du programme ; code de retour 1 */
}
else /* le programme continue */

L'espace alloué à une variable peut être libéré par free(), dont l'argument est un pointeur réservé
dynamiquement.
free(s);
free(x);
free(numero);
Programme principal et fonctions
Les fonctions permettent de décomposer un programme en entités restreintes. Une fonction peut
se trouver :
- dans le même module de texte (toutes les déclarations et instructions qui la composent s'y
trouvent),
- être incluse automatiquement dans le texte du programme (#include)
- ou compilée séparément puis liée (linked) au programme.
Une fonction peut être appelée à partir du programme principal, d'une autre fonction ou d'elle-
même (récursivité). Elle peut retourner une valeur (int par défaut) ou ne pas en retourner (déclarée
void, assimilable à une procédure Pascal). Une fonction peut posséder ses propres variables ; elle n'a
qu'un seul point d'entrée et peut avoir plusieurs points de sortie.
Déclaration de fonction avec prototypage
Le prototypage permet au compilateur de faire une vérification de type au moment de la définition
et de l'appel.
/* prototype de fonction : entête de déclaration suivi par ';' déclaration avec
les types des arguments entre les parenthèses */
type_retourné nom_fonction (type variable1, type variable2, ...)';'

Définition
La définition de la fonction (la liste des instructions) reprend le même texte que pour le prototype
sans ';'
type_retourné nom_fonction (type variable1, type variable2, ...)
{
/* corps de la fonction */
/* déclarations de variables locales à la fonction */
/* initialisations */
/* instructions; */
return (valeur_retournée);
}

Arguments et valeur retournée.


Les arguments de la fonction sont passés par VALEUR ; une copie provisoire de chaque
argument est passée à la fonction qui ne peut donc pas modifier l'argument initial d'une fonction
appelante.
Mais si on passe un pointeur (une adresse) à une fonction,celle-ci modifiera ainsi l'argument : c'est
un passage par VARIABLE ou par ADRESSE. On obtient le même résultat en passant un tableau à
Jean Fruitet - IUT de Marne La Vallée - 31
Introduction à la programmation
la fonction, puisque les tableaux sont considérés par le compilateur comme un pointeur sur le
premier élément du tableau.
Une fonction déclarée void ne retourne rien. La valeur retournée peut être ignorée par la fonction
appelante.
Si la fonction ne contient pas de return( ); la valeur retournée n'a pas de sens particulier.

/* Appel de fonction */
#include <stdio.h> /* Entrées/Sorties */

/* prototype */
int demo (int i, double n);

void main(void) /* Programme principal */


{
int i; /* Définitions */
double n;
i=2; /* Affectations */
n=10.0;
printf("APPEL DE FONCTIONS \n");
printf("Valeurs avant appel\n");
printf("Variable i=%d\n",i);
printf("Variable n=%f\n",n);
if (i)
printf("Valeur renvoyée par fonction =%d\n",demo(i,n));
printf("Valeurs après appel\n");
printf("Variable i=%d\n",i);
printf("Variable n=%f\n",n);
printf("\n");
}

int demo (int i, double n) /* définition de fonction */


{
n = n / i;
printf("\nValeurs dans la fonction\n");
printf(" Variable i=%d\n",i);
printf(" Variable n=%f\n",n);
return((int)n);
}

-----------------------------------------------
APPEL DE FONCTIONS
Valeurs avant appel
Variable i=2
Variable n=10.000000

Valeurs dans la fonction


Variable i=2
Variable n=5.000000
Valeur renvoyée par fonction =5
Valeurs après appel
Variable i=2
Variable n=10.000000
----------------------------------------------

Remarque :
La fonction demo(i,n) fait la division de n par i, retourne le résultat transtypé entier mais ne
modifie ni la valeur de n ni celle de i. Le contrôle de i avant la division est indispensable pour éviter
une division par zéro.
L'exemple suivant propose une fonction qui modifie (par permutation) les éléments d'un tableau;
le tableau est passé à la fonction qui renvoie un tableau modifié.
/* Appel de fonction */
Jean Fruitet - IUT de Marne La Vallée - 32
Introduction à la programmation
#include "stdio.h" /* Entrées/Sorties */
#define MAXTAB 10 /* Constante */

int echange (int i, int n[]); /* prototype de fonction */

/* définition de la fonction */
int echange (int i, int n[]) /* un tableau en argument */
{
int j, aux;
for (i=0, j=MAXTAB-1; i < j; i++, j--)
{
printf(" Echange %4d et %4d\n",n[i],n[j]);
aux = n[i];
n[i] = n[j];
n[j] = aux;
}
return(i);
}

void main(void) /* Programme principal */


{
/* Définitions */
int i;
int tabint[MAXTAB] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; /* Affectations */
printf("APPEL DE FONCTION PAR VARIABLE \n");
printf("Valeurs du tableau avant appel\n");
for (i=0; i<MAXTAB; i++)
printf("%4d%c",tabint[i], (i%5==4 || i==MAXTAB-1) ? '\n' : ' ');

printf("Nombre d'échanges = %d\n",demo(i,tabint));


printf("Valeurs après appel\n");
for (i=0; i<MAXTAB; i++)
printf("%4d%c",tabint[i], i%5==4 || i==MAXTAB-1) ? '\n' : ' ');
}

---------------------------------------------
APPEL DE FONCTION PAR VARIABLE

Valeurs du tableau avant appel


0 1 2 3 4
5 6 7 8 9
Echange 0 et 9
Echange 1 et 8
Echange 2 et 7
Echange 3 et 6
Echange 4 et 5
Nombre d'échanges = 5
Valeurs après appel
9 8 7 6 5
4 3 2 1 0
---------------------------------------------

Pointeurs et arguments de fonctions


En raison de l'appel par valeur, une fonction ne peut pas modifier ses arguments. L'utilisation de
pointeurs permet de tourner la difficulté.
Soit par exemple la fonction echange(x,y) qui est censée échanger ses arguments :
void echange (int x, int y) /* INCORRECT */
{
int aux;
aux = y;
y = x;
x = aux;
}

Jean Fruitet - IUT de Marne La Vallée - 33


Introduction à la programmation
L'appel de echange(i,j) avec i et j entiers n'aura aucun effet car ces paramètres sont passés par
valeur et ne sont pas modifiés en dehors de echange().

void echange (int *x, int *y) /* CORRECT */


{
int aux;
aux = *y;
*y = *x;
*x = aux;
}
Maintenant l'appel de echange(&i,&j) modifie les variables i et j car les adresses des paramètres
sont passées à la fonction (on parlera de passage par VARIABLE ou par ADRESSE).
Fonctions récursives
Si le texte de la fonction fait appel à la fonction elle-même, la fonction est récursive. A chaque
appel récursif, l'ensemble des variables locales est sauvegardé dans une pile et restauré au sortir de
l'appel récursif. C'est le compilateur qui se charge de gérer la pile ; l'utilisateur doit veiller à ce que
les appels récursifs se terminent . Sinon une erreur d'exécution se produit quand la pile des appels est
pleine (stack overflow).
/* version récursive de la fonction factorielle */
#include <stdio.h>
unsigned long fact(unsigned long n); /* factorielle */

void main(void)
{
unsigned long u;
printf("Entrez un nombre ");
scanf("%ld ",&u);
printf("Factorielle %ld : %ld\n",u, fact(u));
}

unsigned long fact(unsigned long n)


{
if (n <=1)
return (1);
else
return (n*fact(n-1));
}
Quelle est la condition d'arrêt de la récursion ? Combien d'appels récursifs pour (4!) ?
Arguments sur la ligne de commande
Un programme en langage C à une seule entrée : c'est la fonction main(), qui doit être unique
dans tous les fichiers du programme, et se charge d'appeler les autres fonctions.
On peut transmettre des paramètres à un programme C par le biais des paramètres de cette
fonction :
void main(int argc, char *argv[])
argc est le nombre d'arguments de la commande + 1. Si argc > 1, la commande a des arguments.
argv est un pointeur sur un tableau de chaînes de caractères contenant les arguments de la
commande :
argv[0] est le nom de la commande.
argv[1] est le premier argument
argv[argc-1] est le dernier argument.

Jean Fruitet - IUT de Marne La Vallée - 34


Introduction à la programmation
Exemple : Que fait le programme ci-dessous ?
#include <stdio.h>
#include <stdlib.h>
char *nom_de_mois(int n)
{
static char *mois[13] =
{"erreur", "janvier", "février",
"mars", "avril", "mai", "juin",
"juillet", "août", "septembre"
"octobre", "novembre", "décembre"
};
return ((n < 1 || n > 12) ? mois[0] : mois[n]);
}

main(int argc, char *argv[])


{
char *nom_de_mois();
if (argc > 1)
printf( "<%s>\n", nom_de_mois(atoi(argv[1])));
else
printf("Usage %s numéro\n", argv[0]);
}

Structure
Une structure est une collection de données regroupées sous un seul nom.
Déclaration
Le type hms (heure, minute, seconde)
struct hms { /* déclaration et nom de la structure */
int h, m, s; /* champs de la structure */
} heurelocale; /* nom de variable */
Le type date utilise aussi le type hms :
struct date {
char *nom_jour,
nom_mois[4];
int jour,
mois,
annee;
struct hms heure; /* structure dans une structure */
}; /* le nom de variable est facultatif */
Affectation
L'opérateur . (point) permet d'accéder aux composantes d'une structure.
heurelocale.h = 10;
heurelocale.m = 07;
heurelocale.s = 55;

printf("%02d:%02d%:%02d\n",heurelocale.h,heurelocale.m,heurelocale.s) ;

L'opérateur -> (Fléche = MoinsSupérieur) permet d'accéder aux composants d'un pointeur sur une
structure.
struct date * ddnaissance = (struct date) *calloc(1, sizeof(struct date));
strcpy(ddnaissance->nom_jour, "Lundi");
strcpy(ddnaissance->nom_mois, "MAR");
ddnaissance->jour=19; ddnaissance->mois=3; ddnaissance->annee=1999;

Union
Une union est une variable qui peut contenir (à différents moments) des objets de types et de
tailles différents.
Exemple :
Jean Fruitet - IUT de Marne La Vallée - 35
Introduction à la programmation
union mot_machine {
char quatrecar[4];
int deuxentier[2];
long unlong;
} *mot;
Cette union, dont l'encombrment mémoire est de 4 octets, peut être initialisée de différentes façons.
Soit octet par octet :
mot->quatrecar[0] = 'a';
mot->quatrecar[1] = 'b';
mot->quatrecar[2] = 'c';
mot->quatrecar[3] = 'd';
Soit par séries de deux octest :
mot->deuxentier[0] = 0;
mot->deuxentier[1] = 1;
soit d'un seul coup :
mot->unlong = 1234567;
De plus l'espace mémoire désigné par l'union peut être interprété selon chacun de ces aspects par le
compilateur...
Directives de compilation
Typedef
Le mot réservé typedef crée de nouveaux noms de types de données (synonymes de types déjà
définis).
Exemple : typedef char * STRING; fait de STRING un synonyme de "char * "
#define
#define et #include sont des commandes du préprocesseur de compilation.
#define est un mot réservé qui permet de définir une macro
Exemple :
#define ESC 27
A la compilation la chaine ESC sera remplacée par 27 dans tout le fichier contenant cette définition
de macro.
Attention : une macro n'est pas une instruction donc il ne faut pas terminer la définition d'une
macro par le caractère ';'

#define sum(A,B) ((A) + (B))


Cette macro remplace tous les appels de sum() avec deux paramètres par l'expression somme. Les
parenthèses ( ) évitent les erreurs d'association...
#include
#include <nom.h>
Inclusion du fichier nom.h situé dans le répertoire spécifié par l'option de compilation -I en
TURBOC.

#include "nom.h"
Inclusion du fichier nom.h cherché dans le répertoire courant puis dans les répertoires spécifiés par
l'option de compilation -I en TURBOC.
Directives de compilatio
Les directives de compilation permettent d'inclure ou d'exclure du programme des portions de
texte selon 'évaluation de la condition de compilation.

#if defined (symbole) /* inclusion si symbole est défini */


#ifdef (symbole) /* idem que #if defined */
#ifndef (symbole) /* inclusion si symbole non défini */

Jean Fruitet - IUT de Marne La Vallée - 36


Introduction à la programmation
#if (condition) /* inclusion si condition vérifiée */
#else /* sinon */
#elif /* else if */
#endif /* fin de si */
#undef symbole /* supprime une définition */

Exemple :
#ifndef (BOOL)
#define BOOL char /* type boolean */
#endif

#ifdef (BOOL)
BOOL FALSE = 0; /* type boolean */
BOOL TRUE = 1; /* définis comme des variables */
#else
#define FALSE 0 /* définis comme des macros */
#define TRUE 1
#endif

#if 0
Cette partie du texte sera ignorée par le compilateur
cela permet de créer des commentaires imbriqués et d'écarter momentanément du
texte source des parties deprogramme.
#endif

Les entrées/ sorties et les fichiers


Pour des questions de portabilité du langage C, les entrées sorties (affichage ; saisie de caractères
; lecture/écriture de fichiers) qui sont tout à fait dépendantes du matériel, n'ont pas été inclusent dans
le langage... Il n'y a pas de mots réservés pour les réaliser.
Cependant chaque compilateur fournit une bibliothèque de fonctions standard réalisant les principales
entrées sorties. Cette bibliothèque est désignée sous le nom de fichier stdio.h
Tout fichier source qui fait référence à une fonction de la bibliothèque standard doit donc contenir la
ligne #include <stdio.h> en début de fichier.
Un façon élégante de considèrer les entrées / sorties - c'est-à-dire le clavier, l'écran, les mémoires
de masse - c'est de les traiter soit comme des fichiers séquentiels (clavier, écran) soit comme des
fichiers à accès direct (la mémoire). Le langage C fournit pour chaque type de fichier une collection
de fonctions réalisant les opérations élémentaires de lecture et d'écriture.
Ces entrées / sorties ont pour support des flux (de caractères, de blocs, etc.). Un flux est représenté
de façon logique par un type FILE du fichier stdio.h.
FILE est un nom pour une structure dont les champs (tampon, caractère courant, nom du flux...)
sont utilisés par les fonctions d'entrée sortie 3.
Trois flux sont prédéfinis : stdin, stdout, stderr, qui sont respectiveemnt l'entrée standard, la sortie
standard et la sortie erreur standard. A ces flux sont associés par le système d'exploitation, et ceci
indépendamment du programme, des fichiers ou des périphériques au moment du lancement du
programme. Les affectations par défaut sont le clavier (stdin) et l'écran (stdout et sdterr), mais elles
peuvent être modifiées par des redirections.
Fonctions d'entrées / sorties
Les principales fonctions d'entrées / sorties [input / output] concernent la lecture des caractères au
clavier et l'écriture des caractères à l'écran.

3 Je ne saurais trop conseiller à l'apprenti programmeur l'excellent ouvrage de Jacquelin Charbonnel "Langage C, les
finesses d'un langage redoutable" Collection U Informatique - Armand Colin 1992
Jean Fruitet - IUT de Marne La Vallée - 37
Introduction à la programmation
Les fonctions de lecture getchar(), gets() et scanf() lisent leurs données sur stdin.
Les fonction getc(), fgets() et fscanf() leur sont apparentées pour les flux non
prédéfinis de type FILE *.
Les fonctions d'écriture putchar(), puts() et printf() écrivent leurs données sur la
sortie standard stdout. Les fonctions putc(), fputc() et fprintf() s'adressent à des
flux non prédéfinis, de type FILE *.
Les fonctions fread() et fwrite() permettent de lire et d'écrire des blocs d'enregistrements.
La fonction fseek() positionne le pointeur de flux à une position donnée (déplacement de la tête
de "lecture / écriture") ; la fonction ftell() indique la position courante du pointeur de flux.
Lecture du clavier / écriture à l'écran : getchar() et putchar(), gets() et puts()
getchar() : lecture (par le processeur) d'un caractère au clavier, "l'entrée standard"
Syntaxe : int getchar(void);
Renvoie un entier sur l'entrée standard (cet entier est un code ASCII) ; renvoie EOF quand elle
rencontre la fin de fichier ou en cas d'erreur.
putchar() : écriture (par le programme) d'un caractère sur l'écran, "la sortie standard"
Syntaxe : int putchar(int c);
Place son argument sur la sortie standard. Retourne c si la sortie est réalisés, EOF en cas d'erreur.
Les entrées de getchar() et les sorties putchar() peuvent être redirigées vers des fichiers.
gets() : lecture d'une chaine de caractères au clavier.
Syntaxe : char *gets(char *s);
Lit une chaine depuis l'entrée standard stdin et la place dans la chaîne s. La lecture se termine à la
réception d'un NEWLINE (saut de ligne) lequel est remplacé dans s par un caractère ASCII nul '\0'
de fin de chaîne. Renvoie NULL quand elle rencontre la fin de fichier ou en cas d'erreur.
puts() : écriture d'une chaine de caractères à l'écran.
Syntaxe : int puts(const char *s);
Recopie la chaine s dans la sortie standard stdout et ajoute un saut de ligne. Renvoie le dernier
caractère écrit ou EOF en cas d'erreur.
Formatage des entrées clavier / sorties écran : printf() et scanf()
Les fonctions de la famille de printf() et de scanf() permettent les sorties et les entrées
formatées de chaînes de caractères. Un tableau de leurs caractéristiques assez complexes est donné
en annexe.
Fichiers de données
Il s'agit de fichiers non prédéfinis, par exemple des données à traiter par programme.
On distingue selon le type de support et de contenu
• les fichier séquentiels : séquence d'enregistrements auxquels on accéde dans l'ordre séquentiel,
c'est-à-dire du premier au dernier en passant par tous les enregistrements intermédiaires. Par
exemple une bande magnétique (K7 audio). Le clavier est vu par l'ordinateur comme un fichier
séquentiel en lecture. L'écran est un fichier en écriture (pas nécessairement séquentiel).
• les fichier à accès direct : séquence d'enregistrements sur mémoire externe ou interne auxquels on
accéde directement, par l'adresse individuelle de chaque enregistrement (mémoire vive RAM :
Random Access Memory) ou par indirection (tableau indexé).
• les fichier binaires : ce sont des fichiers structurés sous forme d'enregistrements (struct en langage
C), dont le contenu doit être interprété par le programme qui les a créés.
• les fichiers textes : ce sont des documents textuels en ASCII, qu'une commande comme TYPE
peut afficher.

Jean Fruitet - IUT de Marne La Vallée - 38


Introduction à la programmation
Opérations sur les fichiers
Un fichier est caractérisé par un nom et une adresse logique attribués par le propriétaire du fichier
et convertis en adresse physique absolue dans le système de fichiers de l'ordinateur (rôle du système
d'exploitation [Operating System]).
Les opération de base sur les fichiers sont :
- création du fichier (attribution d'un nom et d'une adresse logique / physique) ;
- ouverture du fichier en écriture, en lecture, en mode ajout ;
- lecture d'un enregistrement ;
- écriture d'un enregistrement ;
- positionnement de la tête de lecture (le tampon) en début, en fin, à une adresse relative au début ou
à la fin du fichier ;
- fermeture du fichier ;
- suppression du fichier.
L'accès aux enregistrements constitutifs du fichier à traiter se fait au travers d'une fenêtre appelée
tampon [buffer] (de lecture ou d'écriture, selon l'opération réalisée), qui est une variable du même
type que les enregistrements constitutifs du fichier, ce qui conduit à voir ce tampon comme
contenant un élément du fichier.
Descripteurs et flux
La gestion des fichiers étant étroitement dépendante de la plateforme matérielle et logicielle
(système d'exploitation), nous ne présenterons ici que ce qui concerne MS-DOS. On dispose de
fonctions équivalentes dans les autres systèmes d'exploitation. Deux méthodes de gestions de fichiers
sont possibles dans l'envionnement MS-DOS :
- par descripteur de fichier [handler]
- par association au fichier d'un bloc de contrôle [File Control Block] et d'un pointeur de flux
[FILE *]
Fonctions C utilisant un descripteur
Ce sont des fonctions de bas niveau. Leur emploi est plutôt destiné à la programmation système.
Fonctions C utilisant un flux
Ces fonctions accédent à des flux, donc manipulent des objets de type FILE - type défini dans le
fichier stdio.h.
fopen() : ouvre un fichier dont le nom et le mode sont passés en paramètres.
Syntaxe : FILE *fopen(char * nomfichier, char * mode);
mode définit les attributs du fichier ; il peut prendre les valeurs
"r" fichier binaire, ouverture en lecture [read] à partir du début
"w" fichier binaire, création et ouverture en écriture [write] (s'il existe le fichier est vidé)
"a" fichier binaire, ouverture en ajout [append] à partir de la fin du fichier
"r+" fichier binaire, ouverture en lecture / écriture à partir du début
"w+" fichier binaire, recréé en lecture / écriture [write] (s'il existe le fichier est vidé)
"a+" fichier binaire, ouverture en lecture / écriture à partir de la fin

"rt" fichier texte, ouverture en lecture [read] à partir du début


"wt" fichier texte, création et ouverture en écriture [write] (s'il existe le fichier est vidé)
"at" fichier texte, ouverture à partir de la fin du fichier
"r+t" fichier texte, ouverture en lecture / écriture à partir du début
"w+t" fichier texte, recréé en lecture / écriture (s'il existe le fichier est vidé)
"a+t" fichier texte, ouverture en lecture / écriture à partir de la fin
Un pointeur sur un tampon de type FILE est créé et renvoyé par la fonction fopen() qui
retourne NULL si l'opération a échoué.

Jean Fruitet - IUT de Marne La Vallée - 39


Introduction à la programmation
Exemple :
#include <stdio.h> char nomfichier[MAXNOM] = "biblio.txt";
#include <errno.h> if ((fichier = fopen(nomfichier, "a+t")) != NULL)
#include <stddef.h> { /* le fichier est accessible */
#define MAXNOM 40 fprintf(fichier,"%s, %d pages\n","Langage C",50);
#define MAXMODE 4 fclose(fichier);
int main(void) }
/* ouverture d'un else
fichier texte en mode fprintf(stderr,"Ouverture de %s impossible
ajout */ { \n",nomfichier);
FILE *fichier; }

fwrite() : écriture d'enregistrements dans le fichier


Syntaxe : int fwrite(type_tampon *tampon, int longueur, int nombre,
FILE *fichier);
le tampon a le même type que les enregistrements dans le fichier
la longueur est obtenue avec sizeof(type_tampon)
nombre est le nombre d'enregistrements écrits à la fois,
fwrite() retourne le nombre d'enregistrements écrits dans le fichier ou -1 en cas d'erreur.
fread() : lire des enregistrements dans un fichier
Syntaxe : int fread(type_tampon *tampon, int longueur, int nombre,
FILE *fichier);
le tampon a le même type que les enregistrements dans le fichier
la longueur est obtenue avec sizeof(type_tampon)
nombre est le nombre d'enregistrements lus à la fois,
fread() retourne le nombre d'enregistrements lus dans le fichier ou -1 en cas d'erreur.
fclose() : fermeture du fichier
Syntaxe : int fclose(FILE *fichier);
fclose() retourne 0, ou -1 en cas d'erreur.
fseek() : déplacement de la tête de lecture sur le fichier
Syntaxe : int fseek(FILE *fichier, long distance, int position);
fseek() fait pointer le pointeur associé au fichierà la position située distance octets au-delà de
l'emplacement position.
distance : différence en octets entre position d'origine du pointeur fichier et la nouvelle position. En
mode texte, distance doit être 0 ou une valeur renvoyée par ftell().
position : une des trois positions d'origine pour le pointeur de fichier (Début : SEEK_SET, Curseur :
SEEK_CUR, Fin : SEEK_END)
L'opération suivant un appel à fseek() sur un fichier ouvert en mode mise à jour peut aussi bien être
une lecture qu'une écriture.
fseek() renvoie 0 si succés (pointeur repositionné), non 0 si échec.
Voir aussi fgetpos(), fopen(), fsetpos(), ftell(), lseek(), rewind(), setbuf(), tell().

Exemple : Déterminer la taille d'un fichier


#include <stdio.h> printf("La taille du fichier %s est %ld octets\n",
long filesize(FILE *f); filesize(fichier));
fclose(fichier);
int main(void) { }
FILE *fichier; return (0); }
fichier= fopen(
"biblio.txt", "a+t"); long filesize(FILE *f) {
if (fichier != NULL) { long curpos, l;
fprintf(fichier,"%s, %d curpos = ftell(f); /* indique la position courante */
pages\n","Langage fseek(f, 0, SEEK_END); /* fin du fichier */
C",50); l = ftell(f); /* récupére la position en octets */
fprintf(fichier,"%s, %d fseek(f, curpos, SEEK_SET); /* replace la tête */
pages\n","Algorithmique" return (l);
,80); }

Jean Fruitet - IUT de Marne La Vallée - 40


Introduction à la programmation

Formatage des entrées / sorties dans des fichiers : fprintf() et fscanf()


Les fonctions fprintf() et de fscanf() permettent les sorties et les entrées formatées sur les fichiers.
Elles s'emploient comme les fonctions printf() et scanf() en indiquant en plus la référence du flux
traité.
Exemple : Lecture d'un fichier de températures temp.dat

/* temp.dat --- ft=fopen("temp.dat", "rt");


17/11/1997 13 if (ft==NULL)
18/11/1997 18 return(-1); /* erreur de lecture */
19/11/1997 17 while (! feof(ft)) /* jusqu'à la fin du fichier */ {
--------------- */ fscanf(ft,"%s %d\n",jour,&temp);
#include <stdio.h> printf("La température du %s est %d degrés\n", jour,
temp);
int main(void) }
{ fclose(ft);
int temp; return (0);
char jour[12]; }
FILE *ft;

Compilation
La compilation de programmes en C s'effectue en plusieurs
étapes.
1°) Précompilation
Le précompilateur supprime les commentaires des fichiers sources, inclut les fichiers #include et
substitue les définitions #define dans tout le fichier <Source> (*.C)
2°) Compilation (compile)
Les fichiers sont alors recodés en fichiers <Objet> (binaire) (*.OBJ). Les noms de fonctions sont
remplacés par des étiquettes.
3°) Liaison (link)
Les différents fichiers objets sont ensuites "liés", c'est-à-dire que les étiquettes d'appel de fonctions
sont remplacées par l'adresse de celles-ci dans leur propre fichier objet. On obtient un fichier
exécutable (*.EXE).

La compilation est interrompue en cas d'erreur fatale. Les "warnings" sont des erreurs non fatales
dont il faut tenir compte pour avoir un programme à peu près fiable et portable.
Mais ce n'est pas parce que la compilation s'est passée sans erreur que le programme est correct. Le
compilateur ne vérifie pas les débordements de tableau (Range Check error), ni bien sûr les erreurs
d'allocation en mémoire dynamique ou la cohérence du programme.
Interdépendance de fichiers
Il est possible en C de partager un programme en plusieurs fichiers.

Exemple de fichiers interdépendants.


TEST.H ===========================
#define TRUE 1
#define FALSE 0
extern void test(char *msg);
/* la fonction est déclarée extern :
son code de définition est dans un autre fichier */

TEST.C ===========================
/* Code de la fonction test()
Peut être inclus dans une bibliothèque (Library) test.lib
avec l'utilitaire TLIB.EXE ou directement nommé dans le
fichier projet (en TURBOC) */

Jean Fruitet - IUT de Marne La Vallée - 41


Introduction à la programmation
#include <stdio.h>
void test(char *msg)
{
printf("%s\n",msg);
}

DEMO.C =========================
/* --- Fichier Source du Programme Principal DEMO.C ----
Programme exécutable appelant la fonction test()
Doit contenir une fonction main() */
#include <stdio.h>
#include "test.h" /* déclaration de la fonction externe test() */

void main (void)


{
test("ceci est un test");
}
=================================

Pour que la compilation se passe correctement, le compilateur doit pouvoir retrouver ses petits...
En TURBOC sous éditeur vous pouvez définir un fichier de PROJET (Alt P) associé à DEMO.C qui
indique les dépendances entre les différents fichiers du programme.
DEMO.PRJ ========================
demo.c (test.h)
test.c
=================================
COMPILATION :
de test.c avec Alt C : compile to obj
de demo.c avec F9

Jean Fruitet - IUT de Marne La Vallée - 42


Introduction à la programmation

Processus itératifs.
Dans tous les exercices suivants, nous reprenons la même méthode pour introduire de nouvelles
notions de programmation : énoncer un problème numérique, en donner un algorithme, le traduire
dans les différents langages de programmation à notre disposition.
Exemple : Ecrire un programme qui affiche les 10 premiers nombres entiers naturels [0, 1,
2, .., 9].
1. Rappels mathématiques
L’ensemble des entiers naturels IN est infini. Il a un plus petit élément, noté 0 ; tout élément a un
successeur. Il n’y a pas de plus grand élément. Les éléments sont ordonnés. IN={0, 1, 2, 3, 4, ..., n,
(n+1), ...}
Enoncer l’ensemble des entiers naturels n’est pas une tâche que puisse réaliser un ordinateur, car
cet ensemble est infini. Par contre il n’y a pas de difficulté à énoncer un sous-ensemble de IN. Il nous
faut cependant introduire une recette de génération des entiers, c’est-à-dire un algorithme. Il est
assez légitime de considèrer la construction de IN à partir de son plus petit élément, 0, en appliquant
l’axiome “tout élément a un successeur”.
Soit le premier élément 0 Début du processus
successeur(0) = 0 + 1 == 1
successeur(1) = 1 + 1 == 2
successeur(2) = 2 + 1 == 3
..
successeur(8) = 8 + 1 == 9 Fin du processus.

On a mis en évidence un processus répétitif, nous dirons ‘itératif’, selon un terme propre à
l’informatique. Toute constante en membre droit d’une affectation passe en membre gauche à
l’itération suivante. Nous devons résoudre deux difficultés. Initialiser le processus, puis l’interrompre
quand la borne 9 est atteinte, ce qui implique de compter le nombre d’itérations.
2. Types de données et algorithmes
Définissons les types de données qui seront utilisés.
0 et 1 sont des constantes numériques. Nous utilisons aussi deux opérateurs, l’addition entre
entiers et l’affectation. Enfin il nous faut définir un compteur, qui comptera les itérations et
permettra d’interrompre le processus. Ce programme pourrait donc se traduire par “Initialiser un
compteur à 0. Tant que le compteur est inférieur à 9, incrémenter le compteur de 1”.
Traduit de cette façon, le programme est un processus dynamique, c’est-à-dire qui évolue au
cours du temps. Le compteur, une variable entiére, prend successivement les valeurs 0, 1, .., 9.
L’affichage de la valeur du compteur répond au problème. Il reste à traduire cet algorithme en
programme (4) ce qui est fait à la figure suivante.

4 Il faut insister ici sur le caractère très spécial de l’instruction d’affectation qui se trouve dans la boîte (1). Il ne s’agit
pas d’une égalité au sens mathématique. On doit plutôt la traduire par : “le contenu de l’adresse mémoire désignée par
l’identifieur ‘compteur’ est augmenté de 1”. Pour nous conformer à la syntaxe du langage C nous représenterons
dorénavant l’affectation (ou assignation) par le symbole ‘=‘, et l’égalite (comparaison) par ‘==‘.

Jean Fruitet - IUT de Marne La Vallée - 43


Introduction à la programmation

ENTIER Ecrire(compteur);
(1) Traduit en langage de la machine RAM cela
compteur; compteur=compteur+1; donne :
compteur=0;

ENTIER compteur;
POUR (compteur=0 à 9)
OUI
{
(compteur <= 9)
ECRIRE(compteur);
}
NON FIN;
FIN DU PROGRAMME;
et en langage C:
#include <stdio.h> /* Fichier inclus : bibliothèque des Entrées/Sorties */

void main(void) /* Programme principal */


{
int compteur; /* Déclaration */
for (compteur=0; compteur<10; compteur++)
printf("%d \n",compteur);
} /* Fin du programme */

Nous aurions évidemment pu traduire cet algorithme en utilisant les structures de contrôle TANT
QUE (test) FAIRE instruction ; nous en laisserons le soin au lecteur.

Fonctions et sous-programmes
L’exercice suivant va introduire la notion de fonction en programmation.
Certains problèmes sont tellement complexes qu’il est préférable de les décomposer en sous-
problèmes. Cette forme de programmation, dite programmation descendante, s’attache à exprimer le
cadre général de résolution d’un algorithme puis à détailler les sous-programmes résolvant les
questions techniques. Donnons un exemple.

Exemple Ecrire un programme qui affiche les 10 premiers nombres premiers [2, 3, 5, 7,.., 29].
Comme convenu, commençons par un rappel mathématique. Un nombre premier est un nombre
entier naturel qui n’est divisible que par 1 et par lui-même (1 n’est pas considéré comme premier).
Résoudre l’exercice peut se traduire à première vue par :
TANT QUE (compteur<10)
{
Générer_un_nouvel_entier_premier;
Incrémenter(compteur);
}
Le sous-programme Générer_un_nouvel_entier_premier; nécessite d’être formulé plus
précisément : étant donné un nombre (éventuellement premier), il s’agit de trouver le nombre
premier immédiatement supérieur.

nombre=1;
TANT QUE (compteur<10)
{
nombre = Génére_premier_suivant(nombre);
Incrémenter(compteur);
}
ce qui, traduit en langage graphique, donne
Jean Fruitet - IUT de Marne La Vallée - 44
Introduction à la programmation

ENTIER compteur; nombre = (1)


compteur=0; GENERE_PREMIER_SUIVANT
ENTIER nombre; (nombre);
nombre=1; compteur=compteur+1;

OUI
(compteur <= 9)

NON

FIN DU PROGRAMME;
La boucle est triviale, à peu de chose près c’est celle de l’exercice précédent, avec un compteur
comptabilisant les nombres premiers générés.
Par contre le sous-programme de la boîte (1), générant des nombres premiers, doit maintenant être
précisé.
Pour tester si un nombre n’est pas premier, il suffit de lui trouver un diviseur autre que 1 et lui-
même. Ainsi à l’exception du nombre entier 2, tous les nombres pairs sont non premiers ; idem des
multiples de 3, 5, 7, etc. C’est le crible d’Eratosthéne, qui consiste à éliminer les multiples d’un
nombre premier de la liste des nombres entiers. A la “fin” seuls les nombres premiers subsistent.
Mais ce procédé ne convient pas à un ordinateur, car l’élimination des nombres pairs est en soi un
processus infini. Il faut utiliser une autre méthode, tester par exemple pour chaque nombre s’il est
divisible par l’un de ses prédécesseurs inférieurs à n (ou inférieurs ou égaux à n pour gagner
quelques itérations)... Cet algorithme n’est pas très efficace, mais il est simple à programmer. Il fait
appel à une fonction qui retourne VRAI si un nombre est premier, FAUX sinon.
On introduit ici la notion de fonction, qui par construction est un sous-programme retournant une
valeur d’un type élémentaire (entier, réel, booléen, caractère, énuméré, pointeur).
Programmons donc la fonction GENERE_PREMIER_SUIVANT(n) qui retourne le nombre premier
strictement supérieur à n :
ENTIER GENERE_PREMIER_SUIVANT(ENTIER n)

n n=n+1;

NON
(2) (PREMIER(n))

OUI

RETOUR(n);
Le test (2) fait appel à une fonction, PREMIER(n), qu'il nous faut programmer. Cette fonction
retourne VRAI si n est premier et retourne FAUX sinon, elle est donc de type BOOLEEN, c'est
pourquoi elle est utilisable dans le test. L’algorithme de cette fonction consiste à rechercher si une
division par un entier compris entre 1 et n — 2, 3, etc.— “tombe juste”...

Jean Fruitet - IUT de Marne La Vallée - 45


Introduction à la programmation
BOOLEEN premier(ENTIER n)
ENTIER diviseur;
diviseur=1;

NON
diviseur=diviseur+1; (diviseur<n) RETOUR(VRAI);

OUI

NON
((n MODULO diviseur)==0) (3)

OUI
RETOUR(FAUX);

En langage RAM ces fonctions s’écrivent :

BOOLEEN premier (ENTIER n)


/* n est un entier strictement supérieur à 1 */
{
ENTIER diviseur;
diviseur = 2;
TANT QUE (diviseur < n)
{
SI ((n MODULO diviseur)==0)
RETOUR (FAUX);
SINON
diviseur = diviseur + 1;
}
RETOUR(VRAI);
}

ENTIER genere_premier_suivant (ENTIER n)


/* retourne le nombre premier strictement supérieur à n */
{
n = n + 1;
TANT QUE ( NON premier(n))
n= n + 1;
RETOUR(n);
}

PROGRAMME Liste_10_Premiers_Version1
{
ENTIER nombre, compteur;
nombre=1;
compteur=0;
TANT QUE (compteur < 10)
{
nombre=genere_premier_suivant(nombre);
ECRIRE(nombre);
compteur=compteur+1;
}

Jean Fruitet - IUT de Marne La Vallée - 46


Introduction à la programmation
Ce programme est donc très proche de celui de la génération des dix premiers entiers étudié
auparavant. Seule la ligne nombre=GENERE_PREMIER_SUIVANT(nombre);
est modifiée. C'est bien sûr elle qui fait presque tout le travail !
Expliquons en détail sa syntaxe...
GENERE_PREMIER_SUIVANT(nombre) est une fonction. Elle reçoit en paramètre d'entrée
un nombre entier qui doit être supérieur à 0. Elle calcule son successeur, (n+1), teste la primalité de
ce nombre et retourne ce nombre si le test réussit ; sinon le successeur est testé, jusqu'au succés du
test. Le succés est garanti car la suite des nombres premiers n’a pas de majorant... Le nombre ainsi
généré est retourné au programme appelant et placé dans la case mémoire désignée par l'identifieur
'nombre'. L'instruction ECRIRE(nombre) affiche ensuite ce résultat.

Simplification du programme
Après avoir minutieusement détaillé ce programme, nous pouvons essayer de le simplifier.
Remarquons d'abord que la fonction
GENERE_PREMIER_SUIVANT ()
est une simple boucle qui peut être remplacée par une instruction et un test. Le programme devient :
PROGRAMME Liste_10_Premiers_Version2
{
ENTIER nombre, compteur;
nombre=1;
compteur=0;
TANT QUE (compteur < 10)
{
nombre=nombre + 1;
SI (premier(nombre))
ECRIRE(nombre);
compteur=compteur+1;
}

Puis, en considérant qu'au delà de 3, les valeurs successives de n sont des nombres impairs on peut
réécrire ce programme :

PROGRAMME Liste_10_Premiers_version3
{
ENTIER nombre, compteur;
nombre=3;
compteur=2;
ECRIRE(2);
ECRIRE(3);
TANT QUE (compteur < 10)
{
nombre=nombre + 2;
SI (premier(nombre))
ECRIRE(nombre);
compteur=compteur+1;
}

C'est la version que nous traduisons en Langage C.

/* Liste des dix premiers nombres retourne VRAI si n est premier */


premiers */
/* Fichier Prem_10.c */ void main(void) /* Programme
principal */
#include <stdio.h> {
#define VRAI 1 int compteur, nombre; /*
#define FAUX 0 Déclarations */
nombre = 3;
int premier(int n); compteur = 2;
/* n strictement supérieur à 1

Jean Fruitet - IUT de Marne La Vallée - 47


Introduction à la programmation
printf("10 nombres premiers : 2, 3, int premier(int n) /* assume que n>1
"); *)
{
while (compteur < 10) int diviseur=2;
{ while (diviseur < n)
nombre++; {
if (premier(nombre)) if ((n % diviseur)==0)
{ return(FAUX);
compteur++; else
printf("%d, ",nombre); diviseur++;
} }
} return(VRAI);
} /* Fin du programme */ }

Ce programme peut être amélioré en recherchant les diviseurs de n inférieurs à Racine(n). Nous
laisserons le lecteur s’y exercer...

Notion de complexité. Amélioration d’un algorithme.


Nous avons observé dans le calcul des nombres premiers qu’un algorithme pouvait être parfois
amélioré en tirant partie de certaines propriétés. Cela nous amène a préciser la notion d’efficacité
d’un algorithme.
Par définition, on appelle complexité en temps le nombre d’instructions élémentaires mises en
oeuvre dans un algorithme. Une instruction élémentaire sera une affectation, un comparaison, un
saut. L’évaluation d’une expression, par exemple, “nombre = indice * 2 + 1;” est décomposable en
une multiplication, une addition et une assignation, soit trois instructions. Mais comme le décompte
précis de toutes les instructions d’un programme risque d’être assez pénible, et qu’entre deux
exécutions du même algorithme avec un jeu de paramètres différent, le nombre d’instructions
exécutées peut changer, on se contentera en général d’apprécier un ordre de grandeur de ce nombre
d’instructions. C’est ce qu’on désignera sous le terme de complexité en temps de l’algorithme.
(deux instructions élémentaires étant supposées avoir la même durée).
Exemple : Programmer une fonction qui retourne le PGCD de deux entiers naturels non nuls.
Pour trouver le plus grand commun diviseur de deux entiers a et b non nuls il suffit de lister les
diviseurs de a qui divisent b et d’en trouver le plus grand. Par exemple pour a=12 et b=18 on trouve
D12= {1, 2, 3, 4, 6, 12} ; D18= {1, 2, 3, 6, 9, 18} soit le pgcd(12,18)=6.
Programmons cet algorithme.

ENTIER pgc_diviseur (ENTIER a, ENTIER b)


/* on considère que a>0; b>0; a<=b */
{ENTIER diviseur , pgcd;
POUR (diviseur=1 A a) FAIRE
SI ((a MODULO diviseur)==0)
ALORS SI ((b MODULO diviseur)==0)
ALORS pgcd = diviseur;
RETOUR(pgcd);
}

Evaluons la complexité de cet algorithme sur un jeu de données. Les cas à considèrer sont :
a et b premiers entre eux ;
a diviseur de b ;
a et b non premiers entre eux sans qu’aucun ne soit diviseur de l’autre ;
Notons entre parenthèses () le nombre d’instructions. Un test sur le modulo compte pour 3 car il
faut l’évaluer, comparer sa valeur à 0 et faire un saut.

Jean Fruitet - IUT de Marne La Vallée - 48


Introduction à la programmation
a b diviseur (a% div.) (b% div.) pgcd Instructions
3 4 1 (1) 0 (3) 0 (3) 1 (1) (8)
2 (1) 1 (3) (4)
3 (1) 0 (3) 1 (3) RETOUR 1 (5)
(1) Total (17)
a b diviseur (a% div.) (b% div.) pgcd
3 6 1 (1) 0 (3) 0 (3) 1 (1) (8)
2 (1) 1 (3) (4)
3 (1) 0 (3) 0 (3) 3 (1) RETOUR 3 (8)
(1) Total (20)
a b diviseur (a% div.) (b% div.) pgcd
4 6 1 (1) 0 (3) 0 (3) 1 (1) (8)
2 (1) 0 (3) 0 (3) 2 (1) (4)
3 (1) 1 (3) (4)
4 (1) 0 (3) 1 (3) RETOUR 2 (8)
(1) Total (24)
On peut aussi envisager les cas limites :
a=1 et b>1 : le pgcd retourné est 1 après 8 instructions.
a=b : le pgcd retourné est a, et le nombre d’instructions est de l’ordre de a*6, si on considère
qu’en moyenne il y a 6 instructions par ligne du tableau.
On voit sur cet exemple que la complexité de l’algorithme est proportionnelle au nombre de
boucles effectuées dans l’instruction “POUR (diviseur=1 A a) FAIRE ...” qui dépend du plus petit
des deux nombres a et b, chaque boucle coûtant entre 4 et 8 instructions, selon le résultat du
modulo. On dira que cet algorithme est en O(n) -prononcer “GRAND O de n”- c’est-à-dire d’une
complexité linéaire, avec n le plus petit des deux entiers considérés.
Amélioration de l’algorithme.
Considérant que le PGCD est le plus grand des diviseurs communs il suffit de renverser l’ordre de
parcours de la boucle et de s’arrêter dés qu’un diviseur commun est trouvé. L’algorithme devient :

ENTIER pgc_diviseur (ENTIER a, ENTIER b)


/* on considère que a>0; b>0; a<=b */
{ENTIER diviseur = a;

TANT QUE (diviseur>=1) FAIRE


{
SI ((a MODULO diviseur)==0)
ALORS SI ((b MODULO diviseur)==0)
ALORS RETOUR(diviseur);
diviseur = diviseur -1;
}
}
Si a est diviseur de b, cet algorithme produit la réponse au premier tour de boucle ; sinon sa
complexité est en moyenne meilleure, sauf pour les nombres premiers entre eux, pour lesquels il n’y
a aucun gain.
En conclusion, dans le moins favorable des cas les deux algorithmes sont d’une complexité en O(n).

Jean Fruitet - IUT de Marne La Vallée - 49


Introduction à la programmation

Des données aux structures de données


Le programme de génération des nombres premiers peut être grandement amélioré si plutôt que
de rediviser tous les nombres par leurs prédécesseurs on emploie les nombres premiers déjà trouvés
comme diviseurs. Autrement dit si dans la fonction premier(nombre) ce sont des nombres premiers
qui sont les diviseurs testés. Cela suppose de ranger en mémoire la liste des nombres premiers au fur
et à mesure qu’ils sont générés. Mais la solution qui consisterait à définir au préalable autant de
variables que de nombres à trouver n’est ni élégante ni pratique. Un tableau d’entiers est une
structure de données bien adaptée à nos besoins.
Tableaux.
Le terme de tableau a une signification spéciale en informatique. Au sens mathématique usuel du
terme, un tableau est une matrice de nombres, c’est-à-dire un objet à deux dimensions (lignes et
colonnes), le mot vecteur étant réservé aux objets représentables par une liste de coordonnées, c’est-
à-dire la liste des nombres (scalaires) obtenus par projection du vecteur sur chacun des axes de
l’espace considéré. En informatique, ce même objet est dénommé tableau à une dimension, une
matrice étant assimilée à un tableau à deux dimensions. Et bien sûr la notion peut se généraliser aux
tableaux à d dimensions. Mais quel que soit le nombre de dimensions, tous les tableaux
« informatiques » sont des objets destinés à stocker des données informatiques en mémoire de
l’ordinateur. Par définition un tableau de n éléments est une collection de n cellules mémoires
consécutives, de même type (de la même taille). La première cellule du tableau reçoit l’adresse 0, la
suivante l’adresse 1, etc. Les n cellules sont donc indexées de 0 à n-1. L’adressage direct des
éléments d’un tableau permet d’accéder en temps constant à chacun d’eux, sans qu’il soit nécessaire
de parcourir toutes les cellules précédant celle qu’on veut consulter.
Un tableau à une dimension est un « vecteur »

12 9 13 ... 5 15
0 1 2 6 7
Liste des notes de l’élève Dupont

Un tableau à deux dimensions est une matrice


Colonne
0 1 2 3 4

0
10 0 0 0 0 0

Ligne
1
20 90 0 40 0 0

2
30 20 10 20 50 0

La cellule d’adresse (1,3) contient la valeur 40.

Jean Fruitet - IUT de Marne La Vallée - 50


Introduction à la programmation
Tableau à trois dimensions
0

Colonne 1
Ligne
10 10 10 Profondeur 0
0

1 10 10 10

2 10 10 10

Déclaration d’un tableau.


Avant toute utilisation dans un programme, les structures de données doivent être déclarées au
compilateur. Selon le langage de programmation, cette déclaration prend différentes formes. Le
principe étant de fournir au compilateur une indication de l’espace mémoire qui sera réservÉ au
stockage des données. Nous verrons plus loin que le type d’un tableau n’est pas nécessairement un
type simple. Il faut donc distinguer la déclaration du type élémentaire (une cellule) et celle du tableau
composé de ces mêmes cellules (qui est aussi le type du tableau).
Un tableau est une suite de cellules consécutives en mémoire
Contenu de la cellule

B o n n e

0 1 2 3 4

Début du tableau Adresse de la cellule

Chaque cellule peut contenir une variable du type de la cellule.


Exemple : En langage RAM, un tableau d’entiers de 100 cellules sera déclaré :
ENTIER table_entier[100];
tab1e_entier est un identifieur, dont la syntaxe doit respecter la syntaxe des noms de variables. 100
est nécessairement une constante numérique entiére. ENTIER est un mot réservé caractérisant le
type du tableau.
On retrouve une déclaration similaire en langage C :
int table_entier[100];
Il faut insister ici sur le caractère statique d’une telle déclaration. En effet il n’est pas possible en
cours d’exécution du programme de modifier la taille du tableau, même s’il s’avére que l’espace
réservé est insuffisant.
Tableau de tableaux
On a dit qu’un tableau est constitué de cellules contigües en mémoire qui peuvent elles-mêmes être
d’un type non simple.
Exemple : Déclaration d’un tableau de tableaux de réels.
Soit par exemple à stocker les 10 notes obtenues en informatique par 30 étudiants. Les déclarations
suivantes sont équivalentes en espace mémoire :
REEL notes_eleves[30][10];
REEL eleves_notes[10][30];
Dans le premier cas on considère un tableau à deux dimensions de réels constitué par 30 lignes de 10
colonnes ; dans le second cas de 10 lignes de 30 colonnes.

Jean Fruitet - IUT de Marne La Vallée - 51


Introduction à la programmation
Assignation à un tableau
Assigner une valeur à un tableau c’est placer cette valeur dans une cellule disponible du tableau.
Pour cela on dispose du mécanisme d’indexation. Chaque cellule est désignée par son indice, c’est-à-
dire son rang à partir de la première cellule, qui elle a l’indice 0 (5).
Exemple : Placer les 5 premiers nombres impairs dans un tableau d’entiers.
{
ENTIER tab[5];
ENTIER indice;
POUR (indice=0 à 4) FAIRE
tab[indice] = (indice * 2) + 1;
}

En fin de processus le tableau contient [1, 3, 5, 7, 9]. La première cellule est tab[0] ; elle vaut 1. La
cellule tab[3] contient 7.

Exercice
Ecrire en C un programme qui range les 100 premiers nombres premiers dans un tableau.
Algorithme : Initialiser le tableau à 2, 3, 5, 7 puis générer la suite des nombres entiers impairs, en
testant pour chaque nombre s’il est divisible par l’un de ses prédécesseurs premiers rangés dans le
tableau et inférieur ou égal à Racine(n) (justifiez d’abord cet algorithme).

Corrigé do
{
/* Nombres premiers */ j=0;
#include <stdio.h> while ((n%premier[j]) &&
#include <math.h> /* F. Mathéma. */ (premier[j]<=sqrt(n)) && (j<i))
#define MAXTAB 100 j++;
if (n%premier[j])
void main(void) /* Programme {
principal */ printf(" %d,",n);
{ premier[i++]=n;
int i=4, j, n=9; }
int premier[MAXTAB]={2,3,5,7,}; /* n+=2;
Déclarations */ } while (i<MAXTAB);
printf("2, 3, 5, 7,");
} /* Fin du programme */

5 Nous conserverons la convention du langage C qui est de numéroter 0 la première cellule.Par conséquent pour un
tableau de 10 cellules, la dernière a l’indice 9.
Jean Fruitet - IUT de Marne La Vallée - 52
Introduction à la programmation

Calcul matriciel (6)


Une matrice réelle est un tableau à deux dimensions M x N (M lignes et N colonnes) d'éléments
de type flottant. Un vecteur réel est une matrice réelle à une dimension Nx1 (vecteur colonne de N
lignes et 1 colonne) ou 1xN (vecteur ligne de 1 ligne et N colonnes). Une matrice carrée réelle
d'ordre N est un tableau NxN à deux dimensions, dont le nombre de lignes et de colonnes sont égaux
à N.
Un certain nombre d'opérations algébriques sont définies sur les matrices, selon les tailles
respectives des lignes et des colonnes :
- somme et différence de matrices de même dimension et de même taille ;
- produit d'une matrice MxN par un vecteur colonne Nx1 ;
- produit d'un vecteur ligne 1xN par une matrice NxM ;
- produit d'une matrice MxN par une matrice NxP ;
- triangularisation d'une matrice ;
- diagonalisation ;
- transposition ;
- inversion,
- puissance d'une matrice carrée ;
- calcul du déterminant d'une matrice.
Le calcul matriciel ayant énormément d'applications importantes, nous présentons quelques
algorithmes et des représentations informatiques de matrices.
Opérations sur les matrices
Type de données matrice
Une matrice NxM est un tableau de flottants à deux dimensions
#define MAXLIGNE 5
#define MAXCOL 3
typedef float mat_MxN[MAXLIG][MAXCOL];
Une matrice carrée d'ordre N est un tableau de flottants
#define ORDRE 5
typedef float mat_carree[ORDRE][ORDRE];
Un vecteur ligne est une matrice à une dimension
#define MAXELEMENT 10
typedef float vecteur_1xN[MAXELEMENT];
Un vecteur colonne est aussi une matrice à une dimension, mais ce sont les éléments d'une colonne
qui sont rangés dans le tableau !
On peut aussi transformer les matrices NxM en un tableau à une dimension, de taille N*M, structure
bien adaptée pour représenter aussi bien les matrices MxN que NxM, mais avec l'inconvénient
important d'avoir à gérer l'adressage dans le tableau, m[i][j] devant alors s'écrire m[i*MAXCOL+j].
#define MAXLIGNE 5
#define MAXCOL 3
typedef float m[MAXLIG * MAXCOL];

6 Ce chapitre est repris in extenso de l'excellent ouvrage de Cohen J.H., Joutel F., Cordier Y., Jech B. "Turbo Pascal,
initiation et applications scientifiques", Ellipses, 1989. J'ai seulement traduit les programmes en C.

Jean Fruitet - IUT de Marne La Vallée - 53


Introduction à la programmation
On aura intérêt dans tous les cas à définir un type structuré contenant à la fois le tableau des données
et la dimension de la matrice.
Par exemple on pourra aussi bien représenter
1.0 1.2 0.5 0.0 1.0 1.2 0.5 -5.1
-1.4 0.9 2.1 0.0 0.0 0.9 2.1 0.7
0.0 0.0 0.0 0.0 0.0 0.0 -3.5 4.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 1.0
avec la même structure de données.
#define MAXLIGNE 4
#define MAXCOL 4
typedef struct matrice { int l; int c; float m[MAXLIGNE] [MAXCOL]; }t_matrice;

Avec cette méthode la première matrice 2x3 est déclarée et initialisée par :
matrice mat_2x3 = {2, 3, {1.0, 1.2, 0.5, 0.0, -1.4, 0.9, 2.1, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0}};
Bien sûr toutes les valeurs ne sont pas significatives, mais le tableau de données doit être complété
avec des valeurs nulles, et c'est par programme qu'on se restreint aux lignes et colonnes utiles...
Produit de deux matrices
Il suffit de trois boucles imbriquées pour calculer le produit de deux matrices.
Avant appel il faut réserver l'espace mémoire pour la matrice produit
z = (t_matrice *) calloc(1, sizeof (t_matrice));

/* Produit de deux matrices */ /* sources */


/* D'après Cohen J.H., Joutel F.,
Cordier Y., Jech B. "Turbo Pascal, int produit_matrice (t_matrice *x,
initiation et applications t_matrice *y, t_matrice *z)
scientifiques", Ellipses, 1989 */ /* retourne 0 si erreur ; resultat
dans z, dont l'allocation
#include <stdio.h> doit etre faite avant appel */
#include <conio.h> {
int i, j, k;
#define MAXLIGNE 4 if (x->c != y->l)
#define MAXCOL 4 return (0);
typedef struct matrice { int l; int /* erreur produit impossible */
c; double m[MAXLIGNE] [MAXCOL]; } z->l=x->l;
t_matrice; z->c=y->c;
for (i=0; i < x->l; i++)
t_matrice unite for (k=0; k < y->c; k++)
= {3, 3, { 1.0, 0.0, 0.0, 0.0, {
0.0, 1.0, 0.0, 0.0, z->m[i][k] = 0.0;
0.0, 0.0, 1.0, 0.0, for (j=0; j < x->c; j++)
0.0, 0.0, 0.0, 1.0}}; z->m[i][k]=z->m[i][k]
+ x->m[i][j] * y->m[j][k];
}
t_matrice x = {2, 3, { 1.0, 1.2, return (1);
0.5, 0.0, }
-1.4, 0.9, 2.1, 0.0,
0.0, 0.0, 0.0, 0.0, void affiche_matrice(t_matrice *x)
0.0, 0.0, 0.0, 0.0}}; {
int i, j;
t_matrice y = {3, 3, { 1.0, 1.4, printf("\t------\n");
0.5, 0.0, for (i=0; i < x->l; i++)
-1.4, 1.0, 2.0, 0.0, {
-0.5,-2.0, 1.0, 0.0, printf("\t");
0.0, 0.0, 0.0, 0.0}}; for (j=0; j < x->c; j++)
{
/* prototype */ printf("%2.2f ",x->m[i][j]);
int produit_matrice (t_matrice *x, }
t_matrice *y, t_matrice *z); printf("\n",x->m[i][j]);
void affiche_matrice(t_matrice *x); }
}

Jean Fruitet - IUT de Marne La Vallée - 54


Introduction à la programmation
/* Programme principal */ z = (t_matrice *) calloc(1, sizeof
(t_matrice));
void main (void)
{ if (produit_matrice (&x, &y, z))
t_matrice *z; affiche_matrice(z);
if (produit_matrice (&y, &x, z))
clrscr(); affiche_matrice(z);
affiche_matrice(&x); getch();
affiche_matrice(&y); }

Puissance d'une matrice carrée


On peut utiliser l'algorithme dichotomique récursif de calcul de la puissance entiére d'un réel en
l'adaptant au produit de matrices.
Si n=0 xn = Identité
Sinon
si n est impair xn = x . xn/2 . xn/2
si n est pair xn = xn/2 . xn/2
Résolution d'un système d'équations (méthode de Cramer)
Pour résoudre un système d'équations linéaires Ax = b, où A est une matrice régulière carrée
d'ordre N, et x et b des vecteurs colonnes à N lignes, on transforme le système originel par
manipulation de lignes (méthode du pivot partiel) ou par manipulation de lignes et de colonnes
(méthode du pivot de Gauss). Le système d'équations obtenu est Tx = c où T est une matrice
triangulaire qui posséde les mêmes solutions que le premier système et se résoud alors facilement par
substitutions.
La méthode du pivot partiel
Soient A une matrice carrée et x et b deux vecteurs colonnes.
a11 ... a1n b1 x1
A = a21 ... a2n , b= b2 , et x= x2
. . . .
an1 ... ann bn xn
Si a11 est non nul, en multipliant la première équation par les coefficients appropriés et en
retranchant les équations obtenues aux autres équations du système, on obtient un système
équivalent, mais où x1 n'apparaît plus que dans la première équation:
Eliminons x1 dans la iéme équation. Multiplions la première équation par (ai1/a11) et retranchons
l'équation qui en résulte à cette équation, nous obtenons :
ai2(1) * x2 + ai3(1) * x3 + ... + ain(1) * xn = bi(1)

aij(1) = aij - (ai1/a11) * a1j avec j ∈{2, ..., n} et bi(1) = bi- (ai1/a11) * b1
Si, enfin, tous les éléments de la première colonne de A sont nuls, la matrice n'est pas régulière et
on s'arrête.
Si tout s'est bien passé, on obtient un nouveau système, équivalent au premier : A(1)x=b(1) avec
a11(1) a12(1) ... a1n(1)
(1)
A = 0 a22(1) ... a2n(1)
. . .
(1)
0 an2 ... ann(1)
a11(1) ≠ 0 s'appelle le pivot de la première élimination.
On recommence alors l'opération sur le système
Jean Fruitet - IUT de Marne La Vallée - 55
Introduction à la programmation
(1) (1) (1)
a22 a2n x2 b2
. . * . = .
an2(1) ann(1)
xn bn(1)
qui est un système de Cramer, si la matrice A était régulière...
On aboutit en n-1 étapes à un système triangulaire, avec les éléments non nuls sur la diagonale :
a11(1) a12(1) ... a1n(1) b1(1)
0 a22(2) ... a2n(2) * x2 = b2(2)
. . . . .
0 0 ... ann(n-1) xn bn(n-1)
où aii(i) , (i ∈{1, ..., n-1}), sont les pivots successifs des différentes éliminations (le coefficient
ann(n-1) s'appelle aussi un pivot par souci de simplification).
On résout ce système directement pour trouver la solution du système de départ.
xn = (bn(n-1)) / (ann(n-1)) et xi = (bi(i) - ∑j=i+1 à n aij(i)) / (aii(i)) avec i = n-1, n-2, .., 1
En principe les pivots doivent être simplement choisis parmi les éléments non nuls de la première
colonne de la matrice considérée, mais pour des raisons numériques, on prend l'élément qui posséde
la plus grande valeur absolue : en effet par exemple en supposant a11 non nul et en le chosissant
comme pivot, on obtient comme nouveaux éléments de la iéme ligne :
aij(1) = aij - (ai1/a11) * a1j avec j ∈{2, ..., n}
Les coefficients de la matrice A et du vecteur b pouvant être des approximations (ce qui sera le
cas si A et b proviennent d'une étape numérique antérieure), on obtient (en supposant que ai1 ≠ 0) en
ne considérant que les erreurs du premier ordre dues aux approximations :
∆aij(1) = ∆aij - (ai1/a11) ∆ai1- (ai1/a11) ∆a1j + (ai1 aij/a211) ∆a11
= ∆aij + (ai1/a11) (a1j ∆a11/a11 - a11 ∆ai1/ai1 - ∆a1j)
Le terme (ai1/a11) apparaît donc comme un coefficient multiplicatif d'un terme dont un majorant
de la valeur absolue est
|a1j| ( |∆a11/a11| + |∆ai1/ai1|) + |∆a1j|
est symétrique en a11 et ai1. En supposant, en outre, que les termes a1j et ai1 (j ∈{2, ..., n}) ont des
tailles et des incertitudes semblables, on a intérêt à prendre le rapport (ai1/a11) le plus petit possible
en valeur absolue (une étude plus approfondie confortant ce choix). De plus, les éléments de la
matrice, dans l'une des étapes de l'élimionation, sont obtenues par soustraction. On peut donc
craindre que plus les coefficients sont petis, plus les erreurs relatives qui leur sont attachées sont
importantes.
Ces deux raisons qui se renforcent mutuellement, justifient le choix du pivot préconisé. Pour
améliorer la méthode on peut, c'est la méthode du pivot, chercher le plus grand élément, en valeur
absolue, dans la matrice, et permuter les lignes et les colonnes pour l'amener en première position.
Algorithme
Il y a deux étapes : triangularisation de la matrice de départ (méthode du pivot partiel) puis
résolution d'un système triangulaire d'équations.
Résolution
#define N 5
typedef double matrice_carree[N][N];
typedef double vecteur[N];

void resolution_triangulaire (int ordre, matrice_carree a, vecteur b, vecteur x)


{
Jean Fruitet - IUT de Marne La Vallée - 56
Introduction à la programmation
int i, j;
for (i=ordre-1; i>=0; i--)
{
for (j=i+1; j<ordre; j++)
b [i] = b [i] - a[i][j] * x [j];
x[i] = b[i] / a[i][i];
}
}

Triangularisation
La triangularisation de la matrice est une étape préalable à la résolution d'un système d'équations
linéaires.
Implantation
/* Résolution d'un système d'équations }
Méthode du pivot partiel */ }

#include <stdio.h> /* -------------------------------- */


#include <math.h> void affiche_vecteur(int ordre,
#include <conio.h> vecteur x)
{
#define EPSILON 0.0000001 int i, j;
printf("\t------\n");
#define N 3 for (i=0; i < ordre; i++)
printf("\t%2.3f ",x[i]);
typedef float matrice[N][N]; printf("\n");
typedef float vecteur[N]; }

matrice unite = /* -------------------------------- */


{ 1.0, 0.0, 0.0, void affiche_systeme(int ordre,
0.0, 1.0, 0.0, matrice a, vecteur b)
0.0, 0.0, 1.0}; {
matrice a = int i, j;
{ 1.0, 2.0, 3.0, printf("\n");
2.0, 3.0, 1.0, for (i=0; i < ordre; i++)
3.0, 2.0, 1.0}; {
vecteur b = for (j=0; j < ordre; j++)
{ 1.0, 1.0, 1.0}; {
printf("\t%2.3f * x%d ",a[i][j],
/* Prototypes */ i);
void affiche_matrice(int ordre, if (j<ordre-1)printf(" +");
matrice x); }
void affiche_vecteur(int ordre, printf("= %2.3f\n",b[i]);
vecteur x); }
void affiche_systeme(int ordre, printf("\n");
matrice a, vecteur b); }
void resolution_triangulaire (int
ordre,
matrice a, vecteur b, vecteur x); /* ------------------------------- */
int gauss_partiel(int ordre, matrice void resolution_triangulaire (int
a, ordre, matrice a, vecteur b, vecteur
vecteur b, vecteur x); x)
{
/* Sources */ int i, j;
for (i=ordre-1; i>=0; i--)
/* -------------------------------- */ {
void affiche_matrice(int ordre, for (j=i+1; j<ordre; j++)
matrice x) b [i] = b [i] - a[i][j] * x [j];
{ x[i] = b[i] / a[i][i];
int i, j; }
printf("\t------\n"); }
for (i=0; i < ordre; i++)
{
for (j=0; j < ordre; j++) /* -------------------------------- */
printf("\t%2.3f ",x[i][j]); int gauss_partiel(int ordre, matrice
printf("\n"); a, vecteur b, vecteur x)

Jean Fruitet - IUT de Marne La Vallée - 57


Introduction à la programmation
/* triangularise et resout une système for (i= k+1; i<ordre; i++) /*
d'équations lineaires ; la taille de eliminer dans la colonne k les termes
la matrice carree est ordre x ordre ; sous la ligne k */
Les entrees sont modifiees par effet {
de bord ! temp = a[i][k] / a[k][k];
retourne 0 si erreur */ for (j=k; j< ordre; j++)
{ a[i][j] = a[i][j] - a[k][j] *
int i, j, k, imax; temp;
float maxi, temp; b[i] = b[i] - b[k] * temp;
}
for (k=0; k<ordre; k++) }
{
imax = k; /* matrice triangulaire superieure */
maxi = fabs(a[k][k]); resolution_triangulaire (ordre, a, b,
/* recherche pivot dans colonne k */ x);
for (i=k+1; i<ordre; i++) return (1);
if (fabs(a[i][k]) > maxi) }
{
imax = i; /* -------------------------------- */
maxi = fabs(a[i][k]); /* Programme principal */
}
if (maxi < EPSILON) void main (void)
{ {
for (i=0; i<ordre; i++) vecteur x;
x[i] = 0.0 ;
return (0); /* matrice singuliere clrscr();
*/ printf("Système à résoudre \n");
} affiche_systeme(N, a, b);
for (j=k; j<ordre; j++) if (gauss_partiel(N, a, b, x))
/* echange de la ligne k et de la {
ligne imax */ printf("Triangularisation\n");
{ affiche_matrice(N,a);
temp = a[k][j]; a[k][j] = affiche_vecteur(N,b);
a[imax][j]; a[imax][j] = temp; printf("Solution du système \n");
} affiche_vecteur(N, x);
temp = b[k]; b[k] = b[imax]; b[imax] }
= temp; /* second membre */ getch();
}

Calcul du déterminant
La méthode de Gauss permet également de calculer le déterminant et l'inverse d'une matrice
carrée régulière d'ordre N. En effet, échanger deux lignes d'une matrice transforme le déterminant en
son opposé ; ajouter à une ligne une combinaison linéaire des autres lignes ne modifie pas le
déterminant.
Ainsi, lorsque dans la méthode du pivot partiel on obtient une matrice triangulaire, le déterminant
de celle-ci est, au signe près, le déterminant de la matrice dont on était parti ; ce signe dépend du
nombre d'échanges de lignes fait lors de la recherche des différents pivots : si p est ce nombre le
déterminant cherché est le produit de (-1)p et des n pivots trouvés.
Exercice : Ecrire la fonction double determinant(int ordre, matrice_carree
x) qui calcule le déterminant d'une matrice carrée d'ordre N par l'algorithme du pivot.
Inversion de matrice
Les manipulations des lignes d'une matrice reviennent à multiplier celle-ci à gauche par des
matrices convenables. Il n'est pas difficile de prouver que le produit de ces matrices est la matrice
inverse de la matrice de départ lorsque par des manipulations de lignes on transforme cette matrice
en l'identité. Pour effectuer ce produit (et en garder la trace) il suffit donc d'appliquer l'ensemble de
ces manipulations à la matrice identité : le résultat final est bien ma matrice inverse cherchée.
Le code de la fonction
int matrice_inverse(int ordre, matrice_carree a, matrice_carre inv)
se déduit donc directement de la fonction gausspartiel() :
Jean Fruitet - IUT de Marne La Vallée - 58
Introduction à la programmation

/* Résolution d'un système d'équations int matrice_inverse(int ordre, matrice


Méthode du pivot partiel */ input, matrice inv)
/* inversion de matrice par la methode
#include <stdio.h> du pivot ; la taille de la matrice
#include <math.h> carree est ordre x ordre ; la matrice
#include <conio.h> entree est prèservée ; retourne 0 si
erreur */
#define EPSILON 0.0000001 {
int i, j, k, imax;
#define N 3 float maxi, temp;
matrice a;
typedef float matrice[N][N]; /* recopie des entrees */
for (i=0; i<ordre; i++)
matrice unite = for (j=0; j<ordre; j++)
{ 1.0, 0.0, 0.0, a[i][j] = input[i][j];
0.0, 1.0, 0.0,
0.0, 0.0, 1.0}; /* matrice inverse */
matrice a = for (i=0; i<ordre; i++)
{ 1.0, 2.0, 3.0, for (j=0; j<ordre; j++)
2.0, 3.0, 1.0, inv[i][j] = 0.0;
3.0, 2.0, 1.0}; for (i=0; i<ordre; i++)
inv[i][i] = 1.0;
/* matrice identite */
/* Prototypes */
int produit_matrice (int ordre, for (k=0; k<ordre; k++)
matrice x, matrice y, matrice z); {
void affiche_matrice(int ordre, imax = k;
matrice x); maxi = a[k][k];
int matrice_inverse(int ordre, matrice /* recherche pivot dans colonne k */
a, matrice inv); for (i=k+1; i<ordre; i++)
if (fabs(a[i][k]) > fabs(maxi))
/* Sources */ {
imax = i;
/* ------------------------------ */ maxi = a[i][k];
int produit_matrice (int ordre, }
matrice x, matrice y, matrice z) if (fabs(maxi) < EPSILON)
/* retourne 0 si erreur ; resultat {
dans z */ return (0);
{ /* matrice singuliere */
int i, j, k; }
for (i=0; i < ordre; i++) for (j=0; j<ordre; j++)
for (k=0; k < ordre; k++) /* echange ligne k et ligne imax */
{ {
z[i][k] = 0.0; temp = a[k][j]; a[k][j] =
for (j=0; j < ordre; j++) a[imax][j];
z[i][k] += x[i][j] * y[j][k]; a[imax][j] = temp;
} temp = inv[k][j];
return (1); inv[k][j] = inv[imax][j];
} inv[imax][j] = temp;
}
/* ------------------------------ */ for (j=0; j<ordre; j++)
void affiche_matrice(int ordre, /* normalisation */
matrice x) {
{ a[k][j] = a[k][j] / maxi;
int i, j; inv[k][j] = inv[k][j] / maxi;
printf("\t------\n"); }
for (i=0; i < ordre; i++) for (i=0; i<ordre; i++)
{ /* eliminer dans la colonne k les
for (j=0; j < ordre; j++) termes hors diagonale */
printf("\t%2.3f ",x[i][j]); if (i != k)
printf("\n"); {
} temp = a[i][k];
} for (j=0; j< ordre; j++)
{
a[i][j] = a[i][j] - a[k][j] *
/* ----------------------------- */ temp;
Jean Fruitet - IUT de Marne La Vallée - 59
Introduction à la programmation
inv[i][j] = inv[i][j] -
inv[k][j] * temp;
}
}
}

return (1);
}

/* ---------------------------- */
/* Programme principal */

void main (void)


{
matrice inv;
matrice z;
int i, j, k;

clrscr();
printf("Matrice à inverser \n");
affiche_matrice(N, a);
if (matrice_inverse(N, a, inv))
{
printf("Matrice inverse \n");
affiche_matrice(N,inv);
printf("Verification \n");
produit_matrice (N, a, inv, z);
affiche_matrice(N,z);
}
getch();
}

Jean Fruitet - IUT de Marne La Vallée - 60


Introduction à la programmation

Calcul numérique et programmation de fonctions mathématiques


Jusqu’à présent pour présenter les concepts fondamentaux d’itération, instruction conditionnelle
et décomposition en sous-programmes nous avons traité de problèmes d'arithmétiques et d'algèbre.
Abordons maintenant quelques notions de calcul numérique.
Cela pourra paraître paradoxal mais l’ordinateur, qui fut conçu comme un calculateur numérique,
n’est pas vraiment l’outil idéal pour effectuer des calculs. Ou plutôt l’ordinateur à tout faire d’usage
courant n’est pas adapté aux calculs très précis sur les très grands nombres. Cela vient des
limitations en espace mémoire et du mode de représentation des nombres en binaire. Mais ce défaut
n’est pas propre à l’ordinateur. Par exemple le nombre (1/3) n’a pas de représentation décimale
exacte, ce qui ne nous empêche pas de calculer 15 * 0,3333. Bien sûr il vaudrait mieux calculer 15/3
= (5*3) / 3 = 5 * (3/3) = 5... Il faudra donc distinguer entre calculs formels, qui mettent en jeu des
expressions algébriques, et calculs numériques, qui utilisent des approximations binaires
(retranscrites en décimal à l’affichage).
En ce sens l’ordinateur calculateur est mieux adapté à l’évaluation de grandeurs physiques
(éventuellement approchées) qu’au calcul mathématique exact, bien que des programmes comme
Mathematica ou Maple soient spécialisés en calcul formel.
Je renvoie le lecteur à son cours d’informatique pour les questions de codage des nombres en
binaire. Le programmeur devra toujours avoir à l’esprit (et prendre en compte dans ses programmes)
que l’intervalle de définition des entiers sur 2 octets est [-32768 .. 32767] et que les nombres réels
ne sont que partiellement représentés par le type FLOTTANT, c’est-à-dire par un couple (mantisse,
exposant) de longueurs fixes.
Commençons par un exercice de calcul numérique simple :
Exercice
Ecrire en C un programme qui range 10 nombres réels saisis au clavier dans un tableau, puis
en affiche le minimum, le maximum, la moyenne et l’écart-type.

Rappels :
La moyenne (ou espérance) d’une distribution (Xi) s’obtient en calculant
1 n −1
x = ∑ xi
n i =0
L’écart type est la racine carrée de la variance :
n −1
1
σ=
n

i =0
(x i − x ) 2
Le minimum (respectivement le maximum) d’une distribution est la grandeur Xmin (Xmax) qui est
inférieure (respectivement supérieure) ou égale à toutes les autres valeurs Xi.
La seule difficulté de l’exercice consiste à définir correctement les structures de données permettant :
1) de stocker la distribution en mémoire
REEL tab[10]; et en langage C float tab[10];
2) restituer les grandeurs demandées.
REEL moyenne(); float moyenne();
REEL min(); float min();
REEL max(); float max();
REEL sigma(); float sigma();

Le tableau de réels s’impose. Mais le type réel n’étant pas disponible en langage C, il faudra utiliser
le type flottant (float). Pour que le programme conserve une certaine généralité, on définira une

Jean Fruitet - IUT de Marne La Vallée - 61


Introduction à la programmation

constante entiére TAILLE de valeur 10 et on écrira les calculs sous forme de fonctions
indépendantes lisant leurs entrées dans la variable globale tableau[], recevant le nombre d’éléments
du tableau en paramètre, et retournant un flottant.

Jean Fruitet - IUT de Marne La Vallée - 62


Introduction à la programmation
Corrigé {
int i;
/* Moyenne, Min, Max, Ecart-type de float m = tableau[0];
10 nombres décimaux rangés dans un for (i=1; i<n; i++)
tableau de flottants */ if (tableau[i]<m)
m=tableau[i];
#include <stdio.h> return (m);
#include <math.h> }

/* variables globales */ float max_tab(int n)


#define TAILLE 10 {
float tableau[TAILLE]; int i;
float m = tableau[0];
/* macro : carré de deux nombres */ for (i=1; i<n; i++)
#define CARRE(x) ((x)*(x)) if (tableau[i]>m)
m=tableau[i];
/* prototypes */ return (m);
float min_tab(int n); }
float max_tab(int n);
float moyenne_tab(int n); float moyenne_tab(int n)
float ecart_type_tab(int n); {
int i;
void main(void) /* Prog. principal */ float m = 0.0;
{ for (i=0; i<n; i++)
int a, i; /* variables locales */ m += tableau[i];
return (m/n);
printf("Entrez une liste de %d }
nombres réels \n", TAILLE);
for (i=0; i<TAILLE; i++) float ecart_type_tab(int n)
scanf("%f",&tableau[i]); {
int i;
printf("Min : %f, MAX : %f, Moyenne : float m = moyenne_tab(n);
%f, Ecart-type : %f\n", float s=0.0;
min_tab(TAILLE), max_tab(TAILLE), for (i=0; i<n; i++)
moyenne_tab(TAILLE), s += CARRE(tableau[i] - m);
ecart_type_tab(TAILLE)); return (sqrt(s)/n);
}
}
/* Fin du programme */
float min_tab(int n)

Remarques
La bibliothèque mathématique est indispensable pour la fonction sqrt() qui retourne la racine
carrée d’un nombre flottant. La déclaration de constante #define TAILLE 10 permet de
modifier aisément en une ligne la taille du tableau float tableau[TAILLE]. La définition
de macro
#define CARRE(x) ((x)*(x)) rend la lecture du programme plus claire.
Les fonctions
float min_tab(int n);
float max_tab(int n);
float moyenne_tab(int n);
float ecart_type_tab(int n);
calculent entre l’indice 0 et l’indice (n-1) du tableau, ce qui permet une certaine souplesse.

Par contre il faudra veiller, si on reprend ces fonctions dans un autre programme, à ce que le
paramètre n ne soit jamais nul à l’appel de moyenne_tab (int n) et ecart_type_tab
(int n) sous peine d’avoir une division par zéro et l’arrêt brutal du programme.

Jean Fruitet - IUT de Marne La Vallée - 63


Introduction à la programmation
Calcul des termes d’une suite et problèmes de débordement
Le calcul des termes d’une suite numérique a déjà été abordé pour les nombres premiers. Si une
suite est définie de façon récurrente, le programme informatique est trivial. Par contre les problèmes
de dépassement de représentation des entiers peuvent rendre le calcul absurde. Voyons cela sur un
exemple.
Conjecture polonaise
Ecrire un programme qui teste la conjecture polonaise :
" La suite (ui) converge vers 1"
avec u0= n entier strictement positif;
SI (ui est pair) ui+1= ui/2;
SINON ui+1= 3ui + 1;

Considérons la suite (u0= 3) ; on obtient la suite (ui) = (3,10, 5, 16, 8, 4, 2, 1).


Considérons la suite (u0= 7) ; on obtient la suite (ui) = (7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5,
16, 8, 4, 2, 1).
Considérons la suite (u0= 9) ; on obtient la suite (ui) = (9, 28, 14, 7, 22, 11, 34, 17, 52, 26, 13, 40,
20, 10, 5, 16, 8, 4, 2, 1).

Considérons la suite (u0= 15001) ; on obtient la suite (ui) = (15001, 45004, ...).
45004 est en dehors de l’intervalle de définition des entiers sur deux octets [-32768.. 32767]. Donc
même si l’algorithme est correct, le calcul n’est pas possible. Il faudrait programmer les opérations
avec des entiers longs (4 octets), ce qui ne fait que repousser les difficultés à des nombres plus
grands. Et l’utilisation de nombres flottants ne résoudrait rien.

Fonctions récursives
Certaines suites ont une définition récurrente qui se prête bien à la programmation dite récursive.
Factorielle
Programmer factorielle(n) notée (n!) définie par :
(0!) = 1; (1!) = 1; (2!) = 2 x (1!); ...; n!= n x ((n-1)!)
Cela se traduit directement en langage C par

int factorielle (int n)


{
if (n<=1)
return(1);
else
return(n * factorielle(n-1));
}
Nous dirons que la fonction factorielle est une fonction récursive car sa définition contient un
appel à la fonction factorielle elle-même.
Une fonction récursive doit contenir un test d’arrêt de la récursion, sinon elle entre dans une suite
infinie d’appels récursifs (en pratique, jusqu’à ce que tout l’espace mémoire disponible pour la pile
des appels soit occupé). Le test d’arrêt pour la fonction factorielle est :
if (n<=1) return(1);
Ce type de programmation est très naturel et nous aurons d’autres occasions de l’employer.
Malheureusement ce programme ne peut calculer au delà de (7!), pour des questions de dépassement
de représentation. On retombe là dans une situation déjà évoquée.

Jean Fruitet - IUT de Marne La Vallée - 64


Introduction à la programmation
Exercices de programmation numérique
• Surface et volume d’une sphére
Ecrire un programme qui calcule et affiche la surface et le volume d’une sphére au moyen des
formules :
Surface = 4 r 2
Volume = 4 r 3 /3
Pour la valeur de on pourra utiliser la fonction arctan(1) = /4.
• Polynôme
Evaluer le polynôme suivant :
y = (x-1)/x + 1/2 ((x-1)/x)2 + 1/3((x-1)/x)3 + 1/4 ((x-1)/x)4
Pour simplifier le programme, définir une variable U telle que :
U= (x-1)/x.
• Racine carrée d’un nombre réel positif
On peut calculer la racine carrée de x par la formule itérative suivante :
racine = (x / racine + racine) / 2.
où la première approximation de la racine est égale à 1.
Cette boucle est exécutée jusqu’à ce que la valeur absolue de (x / (racine * racine) -1) devienne
inférieure à un ε donné.
Ecrire une fonction racine_carree (x) qui retourne la racine carrée de x par cette méthode.
• Surface d’un triangle
La surface d’un triangle peut se faire de la manière suivante :
Surface = racine carrée de(S (S-a) (S-b) (S-c)), avec S = (a+b+c) / 2 et a, b , c les côtés du
triangle.
Ecrire un programme qui calcule et affiche la surface d’un triangle après avoir lu les valeurs a, b et c.
On pourra utiliser la fonction racine_carree() définie dans l’exercice précédent.
Cette exercice ne présente aucune difficulté...
• Limite d’une suite
i =∞


1
Sn = 2
Calculez la limite de la suite i =1 i
sachant que l’on arrête l’exécution lorsque
S n+1 − S n < ε avec ε un réel proche de zéro .

Commençons par l’analyse de ce problème. Les premiers termes de la suite sont :


1 1 1 1 1 1 1
S 1 = = 1 ; S 2 = 1 + = 1+ ; S 3 = 1+ + = 1+ +
12 2 2 4 4 3 2 4 9
1
S n = S n−1 +
n 2
1
S i = S i−1 +
On peut donc mettre en évidence une formule de récurrence : i 2 . Le calcul s’arrête
1

quand
S n+1 − S n < ε c’est-à-dire quand n 2

Programmons cet exercice.

PROGRAMME suiteS(ENTIER n)

Jean Fruitet - IUT de Marne La Vallée - 65


Introduction à la programmation
{
ENTIER i=1;
REEL S=0.0;
REEL EPSILON=0.000001;

TANT QUE ((1/(i*i)>EPSILON) FAIRE


{
S=S + 1 / (i*i);
i=i+1;
}
ECRIRE (S);
}

• Valeur approchée de π 2 /6
i =∞


1 2
Sn = π
2
Vérifiez expérimentalement que i =1 i est une bonne approximation de 6 .
Pour la valeur de π on pourra utiliser la fonction arctan(1) = π/4.
Cet exercice est une variante du précédent. Il suffit de tester la différence entre Sn et */6 jusqu’à
ce qu’elle soit inférieure en valeur absolue à un ε donné.
• Valeur approchée de log(n)
n


1
Sn =
i =1
i
Calculer à une précision fixée la différence entre la somme et Ln(n).
Dans cet exercice, la convergence de Sn vers Ln(n) —logarithme népérien de n— n’est pas du tout
garantie. Il faut donc choisir soigneusement le test d’arrêt, par exemple en vérifiant la croissance
respective de la série et de la fonction.
• Termes d’une suite
Ecrire un programme qui calcule et affiche les valeurs de la suite (un)n1 (n donné par l’utilisateur)
définie par

u n = 1+ 2 + 3 + 4 + . .. + n
Contrairement à l’exercice précédent, il n’y a pas de formule de récurrence immédiate entre les
termes de la suite Un ; par contre il est facile d’évaluer le n-iéme terme de la suite par une itération
avec un invariant de boucle sous le radical :
U = n
de i = n à 1 faire
U = (i − 1) + U
Ce qui donne en langage RAM :

REEL suiteU(ENTIER n)
{ PROGRAMME ListeUn(ENTIER n)
ENTIER i=n; {
REEL U=n; ENTIER i=1;
TANT QUE (i>0) FAIRE REEL S=0.0;
{ REEL EPSILON=0.000001;
U=(i-1)+√U;
i=i-1; POUR (i=1 jusqu’à n) FAIRE
} {
RETOUR (U); ECRIRE(suiteU(n);
} }

Jean Fruitet - IUT de Marne La Vallée - 66


Introduction à la programmation
}

• Puissance n-iéme d’un nombre


Programmer une fonction Puissance(x,n) qui retourne la puissance entiére positive n d’un nombre
réel x en tirant partie de la propriété :
x0=1; xn=xp xp si n=2p et xn=x xp xp si n=2p+1
Estimer la complexité de l’algorithme.

C’est un exemple de programmation récursive.


REEL Puissance (REEL x, ENTIER n)
/* n entier positif ou nul */
{
SI (n==0)
RETOUR (1);
SINON {
SI ((n MODULO 2) == 0 )
RETOUR(Puissance(x, n/2) * Puissance(x, n/2));
SINON
RETOUR(x * Puissance(x, n/2) * Puissance(x, n/2));
}
}
Chaque appel récursif divisant le paramètre n par deux, il y a 2*log2(n) appels, soit une complexité
en O(log2(n)), meilleure que celle du produit x*x..*x qui est en O(n).
• Nombre d’or
1+ 5
φ =
Soit 2 le nombre d’or et (un)n la suite de Fibonacci définie par u0 = 0, u1 = 1 et un =
un-1 + un-2 pour n2.
φn
Ecrire un programme qui affiche n, un et 5
La suite de Fibonacci peut se programmer de façon récursive.

ENTIER Fibonacci (ENTIER n)


{
SI (n==0)
RETOUR(0);
SINON SI (n==1)
RETOUR (1);
SINON
RETOUR(Fibonacci(n-2) + Fibonacci(n-1));
}

φn
On pourra utiliser la fonction Puissance(x,n) de l’exercice précédent pour calculer 5.
Nous laissons au lecteur le soin de vérifier la convergence des deux suites.
• Décomposition d’un cube en somme de nombres impairs
Le mathématicien grec Nikomakhos (1er siècle après JC) écrit dans son Introduction Arithmétique
que tout cube est égal à la somme de nombres impairs consécutifs. Par exemple :
13 = 1 = 1
23 = 8 = 3 + 5
33 = 27 = 7 + 9 + 11
Jean Fruitet - IUT de Marne La Vallée - 67
Introduction à la programmation
43 = 64 = 31 + 33
= 1 + 3 + 5 + 7 + 9 + 11 + 13
= 13 + 15 + 17 + 19
Ecrire un programme qui lit un entier n et donne une décomposition de n3 (on pourra utiliser une
boucle TANT QUE et un BOOLEEN trouvé qui indiquera que l’on a trouvé une solution).

Le cas des cubes pairs est trivial, il suffit de décomposer n3 en (n3/2 - 1) et (n3/2 +1). Pour les cubes
impairs, on peut chercher à décomposer à partir de 1 :
S = (1 + 3 + ... + (2p-1) + (2p+1)) en incrémentant p jusqu’à ce que S n 3...En cas de
dépassement, il faut alors éliminer les nombres impairs les plus faibles à gauche :
S = ((2q-1) + (2q+1) + ... + (2p-1) + (2p+1)) jusqu’à ce que S n 3. En incrémentant
successivement q et p on finit par atteindre n3 exactement.
Résolution de f(x)=0 (7)
Soit f une fonction continue dans l’intervalle [a,b] avec a<b et telle que f(a).f(b)<0 ; il existe donc
un réel x compris strictement entre a et b vérifiant f(x) = 0. Trouver cette valeur x c’est résoudre
l’équation f(x)=0. Plusieurs méthodes numériques s’y emploient. La méthode dichotomique est la
plus simple à programmer. Cependant le test d’arrêt doit être soigneusement sélectionné pour éviter
les erreurs dues à la représentation approchée des nombres réels par des flottants...
Méthode dichotomique
Considérons c = (a+b) / 2. Si f(c).f(a) 0, il existe une racine de l’équation dans l’intervalle ]a,
c], sinon il en existe une dans l’intervalle ]c, b[. On recommence alors la recherche dans l’intervalle
qui contient sûrement une racine, intervalle dont la longueur est la moitié de celle de l’intervalle de
départ. En un nombre fini d’étapes, on arrive à encadrer une racine de l’équation par deux réels dont
la différence est plus petite que la précision demandée pour la recherche.

f(b) y=f(x)

f(c)

a c b

f(a)

c = (a+b)/2.
PROGRAMME Dichotomie(REEL a, REEL b, REEL epsilon)
{
REEL c;
SI (f(a)*f(b)) < 0.0
{
TANT QUE(b-a>epsilon)
{
c=(a+b) / 2;

7J.-H. Cohen, F. Joutel, Y. Cordier, B. Jech, “Turbo Pascal, initiation et applications scientifiques”, Elipses, pp127-
131
Jean Fruitet - IUT de Marne La Vallée - 68
Introduction à la programmation
SI (f(a)*f(c)<=0.0)
b=c;
SINON
a=c;
}
}
}
L’inconvénient de cette méthode est que si f(x) est de croissance (respectivement décroissance) lente
la précision sur x sera très longue à atteindre. D’autre part il peut se produire que (b/2) et (a/2)
soient représentés par le même nombre flottant (la même représentation binaire). En ce cas
l’algorithme echoue ! On préférera donc tester une valeur de f(x) proche de zéro à ε près.

Exercice : Programmer cet algorithme en Langage C pour les fonctions y= x5/100 et y=2sin(x)-
1 sur un intervalle convenable avec une précision de 10-5. Indiquer le nombre d’itérations.
/* Résolution de f(x) = 0 */ printf("Calcul à EPSILON=%2.6f
/* Méthode Dichotomique */ près\n",EPSILON);
/* f continue dans l'intervalle [a,b] fa=FONCTION(a);
et (f(a).f(b)<0 fb=FONCTION(b);
La méthode consiste à calculer c= if ((fa*fb)>=0.0)
(a+b)/2 et f(c) et à remplacer a par {
c si f(a).f(c)>=0, b par c sinon. */ printf("Méthode dichotomique
inapplicable \n");
#include <stdio.h> return(0);
#include <math.h> }

#define EPSILON 0.00001 do


#define A -3.0 {
#define B 3.0 d++;
#define FONCTION(x) (pow((x),5.0) - c = (a+b)/2.0;
1) fc= FONCTION(c);
/* expression de la fonction f(x) */ /* printf("i:%d c:%f
#define MSG_FONCTION "x5/100" fc:%f",d,c,fc); */
/* texte de la fonction */ if ((fa*fc) >= 0.0)
{
int main (void) a = c;
{ fa = fc;
int d=0; }
float a=A, b=B, c; else
float fa, fb, fc; b = c;
} while (fabs(fc)>EPSILON);
printf("Méthode dichotomique printf("\n\t%d itérations,
f(x)=%s\n\ta=%2.3f racine:%2.6f \n",d,c);
b=%2.3f\n",MSG_FONCTION,a,b); return(1);
}
Remarque : Pour rendre le programme plus souple on définit la fonction à traiter sous forme de
macro-instructions :
#define EPSILON 0.00001
#define A -3.0
#define B 3.0
#define FONCTION(x) (pow((x),5.0) - 1) /* expression de la fonction
f(x) */
#define MSG_FONCTION "x5/100" /* texte de la fonction */

Il suffit de modifier ces cinq ligne de programme pour traiter tout autre fonction.

Méthode du balayage
f continue dans l’intervalle [a,b] et f(a)f(b)<0.
Jean Fruitet - IUT de Marne La Vallée - 69
Introduction à la programmation
La méthode consiste à déterminer n intervalles sur [a,b], à calculer c = a + k*(b-a)/n et d = a +
(k+1)*(b-a)/n et f(c) et f(d) tant que f(c).f(d)>0 et à remplacer a par c et b par d sinon.
Méthode par balayage avec n=3

y=f(x)
f(b)
f(d)

f(c)

a c d b
X
f(a)

Méthode des parties proportionnelles


Les méthodes de recherche par dichotomie convergent lentement. Pour les améliorer on essaie de
trouver le point c qui partage l’intervalle de départ le plus près possible d’une racine de l’équation.
b−a
c = a − f (a )
On trouve que f (b) − f (a) est un bon candidat, égal à la racine si la fonction est
affine.

f continue dans l’intervalle [a,b] et f(a)f(b)<0.


La méthode consiste à calculer c, intersection de la droite ((a, f(a)), (b,f(b))) avec l’axe des x ; puis
f(c) et à remplacer a par c si f(a).f(c) 0, b par c sinon.
Y

f(b)
y=f(x)

f(c)

a c b X

f(a)

Méthode de Newton
La méthode de Newton abandonne l’idée de l’encadrement, pour essayer d’augmenter la vitesse
de convergence. Cette méthode n’assure plus la découverte systématique de la racine, mais
lorsqu’elle le fait elle est en général plus rapide que les méthodes précédentes.
Supposons f suffisament dérivable. La formule de Taylor appliquée au point a s’écrit :
h2
f (a + h ) = f (a) + hf ' (a) + f "(a + θh), θ ∈]0, 1[
2
Si on suppose que a+h est une racine, h est solution de l’équation f(a+h) ; en supposant h
f (a )
h =−
suffisament petit , on en déduit, en première approximation : f ' (a )

Jean Fruitet - IUT de Marne La Vallée - 70


Introduction à la programmation
On espére ainsi une aproximation d’ordre 2 en h de la racine (si f”(a+θh) est suffisament petit).

Implantation
f continue et dérivable sur l’intervalle [a,b].
Dérivée f’ de signe constant (0) sur ]a, b[
La méthode consiste à construire la tangente T en B(b, f(b)) et à prendre comme valeur approchée
de la racine l’intersection de T avec l’axe des X. b est remplacé par cette valeur et on réitére le
processus jusqu’à coïncidence.

f(b) y=f(x)

a
c b X
f(c)
f(a)

/* Résolution de f(x) = 0 */ printf("Méthode de Newton


/* Méthode de Newton */ f(x)=%s=0\n\t a=%2.3f
#include <stdio.h> b=%2.3f\n",MSG_FONCTION,a,b);
#include <math.h> printf("Calcul à EPSILON=%2.6f
près\n",EPSILON);
#defineEPSILON0.00001 do
#defineA -3.0 {
#defineB 3.0 d++;
#defineFONCTION(x) (pow((x),5.0) - 1) fb= FONCTION(b);
/* A remplacer par la fonction dfb=DERIVEE(b);
calculée */ if (dfb != 0.0)
#defineDERIVEE(x) (5*pow((x),4.0)) b =
intersection_tangente_x(b,fb,dfb);
/* A remplacer par dérivée calculée else
*/ exit(0);
#defineMSG_FONCTION "(x^5 - 1)" } while (fabs(fb)>EPSILON);
/* A remplacer par texte de fonction printf("\t%d itérations,
*/ racine:%2.6f \n",d,b);
}
/* prototype */
float intersection_tangente_x(float float intersection_tangente_x(float
a, float fa, float dfa); a, float fa, float dfa)
/* renvoie l'abscisse du point
void main (void) d'intersection de la droite passant
{ par[(a, fa)] et de pente dfa, avec
int d=0; l'axe des x. Assume que dfa != 0 */
float a=A, b=B; {
float fb, dfb; return (a - fa / dfa);
}

Jean Fruitet - IUT de Marne La Vallée - 71


Introduction à la programmation

Intégration numérique
Soient a et b deux réels (a<b), f une fonction numérique continue sur [a,b] ; on cherche à approximer
b
∫a f (t )dt . Prenons une subdivision régulière de [a,b] en n intervalles : posons
b− a
h= et x k = a + kh ,
n pour tout entier k compris entre 0 et n.
On a :
b n−1 x k+1
∫a f (t)dt = ∑ ∫x k
f (t)dt
k =0

Méthode des rectangles


Appliquons la formule de la moyenne sur l’intervalle [xk,xk+1], k∈[1, 2, 3, ..., n-1] :
x k +1
∃ξ k ∈[ x k , x k +1 ] : ∫x k
f (t )dt = ( x k +1 − x k ) f (ξ k )

Le réel ξ k étant en général inconnu, on cherche une approximation.


La plus simple des approximations que l’on peut faire est de choisir xk ou xk+1, c’est la méthode
communément appelée méthode des rectangles (intéressante quand la fonction est monotone,
puisque l’on obtient une borne de la valeur de l’intégrale) ; une autre possibilité est (xk+xk+1)/2, qui
est le centre de l’intervalle (méthode des rectangles modifiés) et on peut penser a priori que ce choix
est meilleur puisque l’incertitude est alors (xk-xk+1)/2 = h/2.
On montre qu’un majorant de l’erreur commise, en prenant comme valeur approchée de l’intégrale
l’une des valeurs hf(xk) ou hf(xk+1) , est h2M’/2, où M’ est un majorant de f’(en supposant f
continûment dérivable, tandis que des méthodes d’analyse plus élaborées montrent que le choix du
milieu de l’intervalle conduit à une incertitude de h3M”/24 où M” est un majorant de f”(s’il
existe). On voit donc que si la fonction est suffisamment régulière, le choix du milieu est meilleur.
En itérant cette approximation sur chaque subdivision, on obtient alors un majorant de l’erreur totale
qui est M’(b-a)2/2n (méthode des rectangles ordinaires) et M”(b-a)3/24n 2 (méthode des rectangles
modifiée).
Méthode des rectangles ordinaires
En prenant, par exemple, systématiquement les bornes gauches des sous-intervalles, l’approximation
est :
n−1
h ∑ f (a + kh )
k=0

Méthode des rectangles modifiée


L’approximation est ici :
n−1
h
h ∑ f ( a + 2 + kh)
k=0

Méthode des trapèzes


On approxime la fonction elle-même par une fonction affine sur l’intervalle [xk,xk+1], k∈[1, 2, 3, ...,
n-1] (interpolation linéaire) :

Jean Fruitet - IUT de Marne La Vallée - 72


Introduction à la programmation
f ( x k +1 ) − f ( x k )
f (x ) = f1 ( x ) = ( x − xk ) + f ( xk )
x k+1 − x k
on pense alors obtenir :
x k+ 1 xk +1 f ( x k ) + f ( x k +1 )
∫x k
f (t) dt = ∫x k
f 1 (t )dt = h
2
On trouve qu’un majorant de l’erreur faite est h3M”/12 où M” est une majorant de f” (si f est de
classe C2). En itérant l’opération sur tout l’intervalle [a,b], on obtient donc une incertitude totale
M”(b-a)3/12n 2.
L’approximation obtenue est donc :
n −1
h
1
2
(
f (a ) + ∑ f (a + kh ) + f ( b) )
2
1
k=1

Méthode de Simpson
C’est une généralisation de la méthode des trapèzes consistant à approximer la fonction sur chaque
sous-intervalle [xk,xk+1] de la subdivision par un polynôme du second degré (interpolation de
Lagrange avec les points xk, (xk+xk+1)/2 et xk+1).
Interpolation (8)
Supposons qu’en étudiant un certain phénomène, on ait démontré l’existence d’une dépendance
fonctionnelle entre des grandeurs x et y exprimant l’aspect quantitatif de ce phénomène ; la fonction
y=f(x) n’est pas connue, mais on a établi en procédant à une série d’expériences que la fonction
y=f(x) prend respectivement les valeurs y0, y1, y2, y3, ..., yn quand on donne à la variable
indépendante les valeurs différentes x0, x1, x2, x3, ..., xn appartenant au segment [a, b].
Le problème qui se pose est de trouver une fonction aussi simple que possible (un polynôme par
exemple), qui soit l’expression exacte ou approchée de la fonction inconnue y=f(x) sur le segment [a,
b]. D’une manière plus générale le problème peut être posé comme suit : la valeur de la fonction
y=f(x) est donnée par n + 1 points différents x0, x1, x2, x3, ..., xn du segment [a, b] :
y0 = f(x0), y1 = f(x1), ..., yn = f(xn) ;
on demande de trouver un polynôme P(x) de degré n exprimant d’une manière approchée la fonction
f(x).
Il est tout naturel de choisir le polynôme de manière qu’il prenne aux points x0, x1, x2, x3, ..., xn les
valeurs y0, y1, y2, y3,. .., yn de la fonction f(x).
Y
y=f(x)

y0 y1
yn

y=P(x)

0 ax x x x b X
0 1 2 n

Ce problème, qui s’appelle “problème d’interpolation de la fonction”, peut être formulé de la


manière suivante : trouver pour une fonction donnée f(x) un polynôme P(x) de degré n qui prenne
aux points x0, x1, x2, x3, ..., xn les valeurs y0 = f(x0), y1 = f(x1), ..., yn = f(xn).
A cette fin, choisissons un polynôme de degré n de la forme

8 N. Piskounov, “Calcul différentiel et intégral”, T1, Editions Mir, pp.264


Jean Fruitet - IUT de Marne La Vallée - 73
Introduction à la programmation
P( x ) = C0 ( x − x1 )( x − x 2 ). . . ( x − x n ) + C1 (x − x 0 )( x − x 2 ). . . ( x − x n ) +
+C 2 ( x − x 0 )(x − x 1 )(x − x 3 ). . . ( x − x n ) + C n ( x − x1 )(x − x 2 ). . . ( x − x n−1 ) (1) eet
déterminons les coefficients C 0 , C1 , . . ., Cn de manière que soient vérifiées les conditions
P( x 0 ) = y 0 , P( x1 ) = y1 , . .. , P( x n ) = y n . (2 )
Faisons dans la formule (1) x=x0 ; alors, en vertu des égalités (2), nous avons :
y 0 = C0 ( x 0 − x1 )(x 0 − x 2 ).. .(x 0 − x n ),
d’où
y0
C0 =
( x 0 − x1 )(x 0 − x 2 ). . . (x 0 − x n )

Faisons ensuite x=x1, nous avons :


y1 = C1 ( x 1 − x 0 )( x1 − x 2 ). . . (x 1 − x n )
d' où
y1
C1 =
(x 1 − x 0 )( x1 − x 2 ). . . ( x1 − x n )
En procédant de cette manière, nous trouvons successivement
y2
C2 =
( x 2 − x 0 )( x 2 − x1 ). .. (x 2 − x n )
. .. . . . .. . .. .. . . . . ..
yn
Cn =
( x n − x 0 )( x n − x1 ). .. (x n − x n−1 )

En substituant les valeurs ainsi trouvées des coefficients dans la formule (1) nous avons :
( x − x1 )( x − x 2 ). .. ( x − x n ) ( x − x 0 )( x − x 2 ). . . ( x − x n )
P( x ) = y0 + y
( x 0 − x1 )( x 0 − x 1 ). .. ( x 0 − x n ) ( x 1 − x 0 )( x1 − x 2 ). .. ( x 1 − x n ) 1
( x − x 0 )( x − x 1 ). .. ( x − x n−1 )
+. .. .. . . + yn . ( 3)
( x n − x 0 )( x n − x1 ). . . ( x n − x n−1 )

Cette formule est appelée formule d’interpolation de Lagrange. Rappelons que si aux points
considérés (x0, y0), (x1, y1), ..., (xn, yn), P(x) et f(x) coïncident, en tout autre point de l’intervalle
[a, b] il peut en aller tout autrement.
Il existe d’autres formules d’interpolations, dont celle de Newton.

Pour programmer la formule d’interpolation de Lagrange il faut saisir les points (x0, y0), (x1, y1),
..., (xn, yn), dans un tableau de couples de flottants et calculer le tableau des coefficients.

/* Interpolation de Lagrange */ /* assume que les points saisis ont


#include <stdio.h> des abscisses différentes */
{
#define MAXPOINT 10 int i;
for (i=0; i<n; i++)
float point2D[MAXPOINT][2]; /* {
tableau des coordonnées des points 2D printf("Abscisse du point N°
*/ %d:",i);
float coeff_lagrange[MAXPOINT]; /* scanf("%f",&point2D[i][0]);
coefficients du polynôme */ printf("Ordonnée du point N°
%d:",i);
void saisie_points(int n) scanf("%f",&point2D[i][1]);
Jean Fruitet - IUT de Marne La Vallée - 74
Introduction à la programmation
} printf("\n");
}
}
float polynome_lagrange(int n, float
int coefficients_lagrange(int n) x)
/* le nombre de points est passé en /* retourne la valeur du polynôme
paramètre */ pour une valeur x de la variable */
/* retourne 0 en cas d’erreur */ {
{ int i,j;
int i,j; float c, p=0.0;
float d_lagrange; for (i=0; i<n; i++)
for (i=0; i<n; i++) {
{ c=coeff_lagrange[i];
d_lagrange = 1; for (j=0; j<n; j++)
for (j=0; j<n; j++) if (j!=i)
if (j!=i) c*=(x-point2D[j][0]);
d_lagrange*=(point2D[i][0]- p+=c;
point2D[j][0]); }
if (d_lagrange != 0.0) return(p);
}
coeff_lagrange[i]=point2D[i][1] /
d_lagrange;
else void main (void)
return (0); {
} int i,j,n;
return (1); float x;
} printf("Interpolation polynômiale
de Lagrange\n");
float affiche_lagrange(int n) printf("Nombre de points à
/* Expression littérale du polynôme interpoler [%d]\n" , MAXPOINT);
*/ scanf("%d",&n);
{ saisie_points(n);
int i,j; if (coefficients_lagrange(n))
printf("P(x)="); {
for (i=0; i<n-1; i++) /* Expression du polynôme */
{ affiche_lagrange(n);
printf("(%f) /* Utilisation du polynôme */
",coeff_lagrange[i]); printf("Entrez une valeur pour
for (j=0; j<n; j++) la variable x \n");
if (j!=i) scanf("%f",&x);
printf("(x- printf("P(%f)=%f",x,
(%f))",point2D[j][0]); polynome_lagrange(n,x));
printf(" +\n"); }
} else
printf("(%f) ",coeff_lagrange[n- printf("Erreur dans la saisie
1]); des points\n”);
for (j=0; j<n-1; j++) }
printf("(x-
(%f))",point2D[j][0]);

Jean Fruitet - IUT de Marne La Vallée - 75


Introduction à la programmation

Structures de données
Il est souvent nécessaire en programmation de stocker des informations complexes. Par exemple
un répertoire téléphonique. Chaque fiche rassemble les informations caractéristiques d’une entité, en
l’occurence une personne, nom, prénom, adresse, date de naissance, téléphone. Chaque champ
d’une fiche est un attribut, exactement défini par son domaine (les valeurs acceptables), sa taille
(espace mémoire occupé), et sa valeur (ou occurence). L’information est structurée, puisque chaque
fiche est organisée d’une manière figée. Le répertoire téléphonique lui-même est une collection de
fiches de même structure.

[CHAMP] Nom Prénom Adresse Date de nais. Téléphone


[TAILLE] 20 car. 10 car. 40 car. date 10 num.
Dupont T. Moulinsart 28/2/1900 12 34 56 78
Dupond D. Moulinsac 24/12/1901 98 76 54 32
Chaque ligne (ou tuple) du tableau à deux dimensions correspond à une fiche.

Structures
Le type du langage C struct permet de représenter en machine la fiche que nous venons
d’évoquer (en Pascal lui correspond le type record — enregistrement).

struct fiche {
char nom[20]; char prenom[10]; char adresse[40];
char ddn[6]; long tel;
} personne;
On peut aussi inclure dans la définition d’une structure la référence à d’autres structures, par une
sorte d’imbrication. Par exemple près avoir défini un type de données date plus approprié,
struct date { int jour; int mois; int annee; } ;
s’y référer dans la définition d’un type fiche.
struct fiche {
char nom[20]; char prenom[10]; char adresse[40];
struct date ddn; char tel[10];
} personne;
Une personne est alors une variable occupant 86 octets en mémoire (20+10+40+6+10).
La mise à jour de la fiche de Dupond D. est réalisée par les instructions :
strcmpy(personne.nom,”Dupond”);
strcmpy(personne.prenom,”D.”);
strcmpy(personne.adresse,”Moulinsac”);
personne.ddn.jour=24;
personne.ddn.mois=12;
personne.ddn.annee=1901;
strcmpy(personne.tel,”98765432”);
Nous renvoyons le lecteur à son cours de langage C pour les détails de program-mation.
Exercices
• Le type <Date>
On considère le type <date> défini par une structure C
typedef struct date
{int annee; int mois; int jour;} date;
et la déclaration :
date date_courante = (1997, 10, 26);
1) Quelles opérations sur ce type de données peut-on définir ?
Jean Fruitet - IUT de Marne La Vallée - 76
Introduction à la programmation
2) Est-il possible d’additionner deux dates ? Que signifierait :
(1994, 10, 26) + (1994, 10, 26) ?
3) La différence de deux dates est une <durée> :
long duree; /* exprime le nombre de jours entre deux dates */
duree = difference_date((1994, 10, 26), (1994, 09, 26));
duree = 30; /* en jours */

Connaissant la date de naissance d’une personne et la date courante, il est possible d’exprimer l’âge
de cette personne en jours :
age = difference_date(date_naissance, date_courante);

Exercice 1: Ecrire en C les fonctions


date *saisie_date(void);
void affiche_date(date d);
long difference_date(date d1, date d2);
date *calcule_date(date d, long duree);
date *ajoute_date_duree(date d, long duree);
Exercice 2: Ecrire en C un programme qui calcule le calendrier.
Exercice 3 : Définir le type de donnée <heure>
• Le type <Nombre rationnel>.
Définition
Les nombres rationnels (ensemble Q) sont définis à partir des entiers relatifs par une relation
d'équivalence. Soient deux couples (a,b) et (c,d) d'entiers relatifs tels que b et d ne soient pas nuls.
On appelle nombre rationnel q, noté (a/b), la classe d'équivalence des couples de nombres entiers
relatifs tels que : a*d = b*c
Autrement dit on peut assimiler un nombre rationnel à une fraction entiére:
 a   c 
= = q ∈Q ⇔ a ⋅ d = c ⋅ b avec a, b, c, d ∈Z et b ≠ 0 et d ≠ 0
 b  d
1) Donnez l’équivalent rationnel de deux tiers ; un quart ; 0,5 ; -0,3
2) Le nombre π est-il un rationnel ? Estimez l’erreur commise en prenant pour π la fraction (22/7) ?
3) Quelles sont les opérations autorisées sur les nombres rationnels ?

On définit les opérations suivantes sur les rationnels :


(a/b) * (c/d) = (a*c) / (b*d)
(a/b) / (c/d) = (a*d) / (b*c)
(a/b) + (c/d) = ((a*d) + (b*c)) / (b*d)
(a/b) - (c/d) = ((a*d) - (b*c)) / (b*d)
inverse(a/b)= b/a si a0, sinon indéfini
Q a une structure d’ordre
Opérateurs de comparaison : > >=< <=
|a/b| |c/d| si et seulement si |a*d| |b*c|
si (a/b) ( c/d) alors ((-1)*(a/b)) ((-1) * (c/d))
Implémentation
- Le couple (a,b) —rationnel (a/b)— est défini par une structure en langage C :
typedef struct {int n ; int d;} rationnel;
Les opérations peuvent être programmées comme des fonctions ; le produit de deux rationnels par
exemple sera :
rationnel *produit(rationnel x, z = (rationnel *) calloc(1,
rationnel y ) sizeof(rationnel));
{ *(z->n) = x.n * y.n;
rationnel *z; *(z->d) = x.d * y.d;
return(z);

Jean Fruitet - IUT de Marne La Vallée - 77


Introduction à la programmation
} z = (rationnel *) calloc(1,
sizeof(rationnel));
rationnel *inverse(rationnel x) *(z->n) = x.d;
/* retourne un pointeur NULL si *(z->d) = x.n;
inversion impossible */ return(z);
{ }
rationnel *z; else
if (x.n) return(NULL);
{ }

Exercice 1 : Programmer l'égalité, la division, l'addition, la soustraction et la simplification (en


utilisant le pgcd) des rationnels.
Exercice 2 : Programmer une calculette en nombres rationnels capable d’évaluer l’expression ((21/3)
- (4/8)) / (-24/12) en entrant la séquence :
(21,3) - (4,8) = 13/2
/ (-24,12) = -13/4

Jean Fruitet - IUT de Marne La Vallée - 78


Introduction à la programmation

Ensembles
La gestion d’informations structurées est un domaine dans lequel les ordinateurs excellent, au prix
d’une représentation astucieuse des données. Nous allons passer en revue quelques structures de
données classiques.
Définition
Un ensemble est une collection d’objets, appelés éléments de l’ensemble.
Classiquement, un ensemble est défini
• soit en intention, par une propriété vérifiée par tous les éléments de l’ensemble.
Exemples :
- ensemble des entiers naturels impairs
I = {n / n=2p+1} avec p entier naturel ;
- ensemble des nombre pairs
M2 = {n / n=2p} ;
- ensemble des multiples de trois
M3 = {n / n = 3p}
- ensemble des étudiants de Terminale B2 ;
- ensemble des ouvrages de philosophie, etc.
• soit en extention, par l’énumération exhaustive des éléments (ce qui suppose que l’ensemble soit
énumérable).
Exemple :
- Arc_en_ciel = {violet, indigo, bleu, vert, jaune, orange, rouge} ;
- Alphabet = {a, b, c, d, e, ...} ;
- Famille Duraton = {Annie, Bernard, Camille, Dominique, Edmond} ;
Cours des monnaies Change = {(Mark, 3.4553), (Ecu, 6.33), (Dollar, 4.89), (FB, 0.1680), ...}
Opérations sur les ensembles
L’algèbre sur les ensembles repose sur quelques opérations élémentaires dont :
• L’appartenance d’un élément à un ensemble (opérateur ∈)
Exemple : (Lire, 0.00306) ∈ Change ; Patrick ∉ Duraton
• La réunion de deux ensembles, opérateur ∪
Exemple : Alphabet = Voyelle ∪ Consonne
• L’intersection de deux ensembles, opérateur ∩
Exemple : M6 = M2 ∩ M3

Rappelons que la notion d’ensemble n’implique pas de relation d’ordre entre les éléments (bien
qu’un ensemble puisse être ordonné), et qu’un ensemble n’admet pas de doublons.
Représentation des ensembles : tableaux, listes, piles, files
Il faut distinguer d’une part la représentation en machine des éléments d’un ensemble, et d’autre
part l’implémentation des opérations élémentaires (appartenance, union, intersection). Ces
opérations dépendront en partie de la représentation des données (les éléments).
• Tableaux
La représentation sous forme de tableau est la plus immédiate. En reprenant le cas d’un répertoire
téléphonique, constitué de 200 fiches on peut définir :
struct fiche personne[200];
Appartenance
Jean Fruitet - IUT de Marne La Vallée - 79
Introduction à la programmation
Vérifier l’appartenance d’un élément consistera alors à parcourir tout le tableau (soit n comparaisons
s’il y a n fiches...)
Il faut disposer d’une fonction permettant de comparer deux éléments, qui doit être adaptée aux
données à comparer, mais dont la spécification est en général toujours la même :
si a==b, comparer(a,b) retourne 0
si a<b, comparer(a,b) retourne -1
si a>b, comparer(a,b) retourne +1.

#define FAUX 0
#define VRAI 1
int comparer(struct fiche e1, struct fiche e2);
/* retourne -1 si e1<e2 ; 0 si e1==e2; 1 si e1>e2 */

int appartient(struct fiche elt, int nbelt, struct fiche e[])


/* retourne FAUX si l’élément est absent */
{
int i;
for (i=0; i<nbelt; i++)
if (comparer(elt, e[index])==0)
return(VRAI);
return(FAUX);
}

Réunion
La réunion de deux ensembles peut se concevoir de différentes façons, par exemple en insérant un à
un les éléments de l’un des ensembles dans l’autre ensemble.
int inserer(struct fiche elt,
int *p, struct fiche e[])
{
e[(*p)++]=elt;
}

int union_ensemble (int nbeltA, int nbeltB, struct fiche eA[], struct fiche
eB[])
{
int i;
for (i=0; i<nbeltA; i++)
if (! appartient(eA[i], nbeltB, eB)
inserer(eA[i], &nbeltB, eB);
}

Intersection
L’intersection de deux ensembles nécessite de créer un nouvel ensemble :
int inter_ensemble(int nbeltA, int nbeltB, int *nbelC, struct fiche eA[], struct
fiche eB[], struct fiche eC[])
{
int i;
for (i=0; i<nbelementA; i++)
if (appartient(eA[i], nbeltB, eB))
inserer(eA[i], &nbelC, eC);
}

Complémentaire
Le complémentaire de A dans B peut se programmer en calculant B-A ; il faut disposer en ce cas
d’une fonction de suppression d’un élément d’un ensemble. L’autre méthode consiste à créer un
nouvel ensemble C ne contenant que les éléments de B n’appartenant pas à A :
int difference_ensemble(int nbeltA, int nbeltB, int *nbelC, struct fiche eA[],
struct fiche eB[], struct fiche eC[])
{

Jean Fruitet - IUT de Marne La Vallée - 80


Introduction à la programmation
int i;
for (i=0; i<nbeltB; i++)
if (! appartient(eB[i], nbeltA, eA))
inserer(eB[i], &nbelC, eC);
}

Complexité en temps des opérations ensemblistes


Les opérations union, intersection, complémentaire utilisent l’opérateur appartenance, qui est en
O(n). Pour des ensembles de taille respective n et m éléments, ces opérations sont donc en O(n*m),
ce qui est coûteux au delà de quelques dizaines d’éléments...
La représentation des ensembles par des tableaux est pratique pour les ensembles dont la taille est
réduite, déterminée a priori, et n’évoluant pas trop en cours d’exécution ; son inconvénient principal
tient au coût élevé de la recherche d’un élément, si l’ensemble n’est pas ordonné. Il faut dans le cas
contraire envisager d’autres structures de données mieux adaptées. Nous allons en étudier quelques-
unes
• Listes chaînées
Si la taille de l’ensemble est susceptible d’évoluer de façon dynamique (à l’exécution), ou difficile
à déterminer a priori, il est plus judicieux de représenter un ensemble par une liste chainée par
pointeur.
Définition
Une liste est une suite (finie) de cellules contenant un élément. Chaque élément a donc une
position à l’intérieur de la liste. Tout élément de la liste (sauf le premier et le dernier) a un successeur
et un prédécesseur. Un élément peut apparaître plusieurs fois (sauf si la liste implante un ensemble)...
En Langage C, on définit le type abstrait cellule comme une structure
struct cellule {
type_donnee val;
struct cellule *suiv ; } ;
dont le premier champ —val— contient un élément de l’ensemble, et dont le deuxiéme champ —
suiv— est un pointeur sur la cellule suivante. Un ensemble est une liste de cellules chaînées par
pointeur comme les perles d’un collier.
Tete de Liste

100 200 20

350 300 600

355

L’ordre sur les éléments n’est pas nécessaire pour implanter le type ensemble mais alors la liste ne
doit pas contenir de doublon. Du point de vue informatique, l’ajout d’un nouvel élément nécessite la
création d’une nouvelle cellule, ce qui se réalise de façon dynamique par allocation de mémoire —
fonction malloc() ou calloc()— en cours d’exécution. Par ce procédé la taille de l’ensemble n’est
limitée que par l’espace mémoire disponible ; elle n’a pas à être fixée a priori.

Allocation
Considérons par exemple un ensemble de nombres entiers. Une cellule sera définie par :

Jean Fruitet - IUT de Marne La Vallée - 81


Introduction à la programmation
typedef struct cellule {
int elt;
struct cellule *suiv ; } liste;

Une fonction alloc_cellule(int x) d'allocation mémoire d'une cellule retour-nant un


pointeur sur une cellule dont la valeur est x. sera définie en C par :

liste * alloc_cellule (int x)


{
liste *c;
c = (liste *) calloc(1, sizeof(liste));
if (c!=NULL)
{
c->elt=x; /* assignation de x */
c->suiv = NULL;
}
return (c);
}

Appartient
Il nous faut écrire une fonction int appartient(int x , listel )
qui renvoie VRAI si la valeur x appartient à la liste l , FAUX sinon.
Vérifier si x appartient à la liste, cela consiste à comparer x avec l’élément en tête de liste, puis à
parcourir la liste en suivant le chaînage, soit jusqu’à trouver l’élément recherché, soit jusqu’à
atteindre la fin de liste. La programmation récursive s’impose.

int appartient (int x, liste *l)


{
if ((l==NULL)
return (FAUX);
else if (l->elt==x)
return (VRAI);
else
return (appartient(x, l->suiv));
}

Variante de la fonction appartient(), la fonction


liste position_element(int x , listel )
renvoie un pointeur sur la cellule contenant x s'il se trouve dans la liste, et NULL sinon.
liste *position_element(int x, liste *l)
{
if ((l==NULL)
return (NULL);
else if (l->elt==x)
return (l);
else
return (position_element(x, l->suiv));
}

Réunion, intersection, complémentaire


Insertion d’un élément
La réunion, l’intersection et le complémentaire nécessitent de disposer d’une fonction d’insertion
d’un élément dans une liste. Si aucun ordre n’est imposé, l’insertion en tête est la moins coûteuse.
liste * insere_en_tete(int x , liste *l )
insére un nouvel élément en tête de liste l et retourne un pointeur sur la liste.

liste *insere_en_tete(int x , liste *l )


{
liste *c;

Jean Fruitet - IUT de Marne La Vallée - 82


Introduction à la programmation
c = (liste *) calloc(1, sizeof(liste));
if (c!=NULL)
{
c->elt=x; /* assignation de x */;
c->suiv=l; /* assignation de suivant */
l=c; /* chainage */
}
return (l);
}

Insertion en tête de liste

....

(3) (2)
Tête de liste

x
nouvelle_cellule

(1)
Insertion en tête : (1) créer une cellule ; (2) chaîner avec la première cellule de la liste ; (3) faire
pointer la tête de liste vers la nouvelle cellule.

Si l’ordre des éléments doit être respecté, l’insertion dans le corps de la liste s’impose. Il faut alors
repérer la cellule précédant immédiatement la nouvelle cellule à insérer, et modifier les liens comme
indiqué sur le schéma suivant.

liste *insere_en_place(int x , liste *l )


{
liste *c, *aux;
aux=l;
/* tester si insere en tête */
if ((aux==NULL) || ((aux->elt)>x))
return(insere_en_tete(x,l));
/* sinon rechercher la position de x */
while ((aux->suiv)!=NULL) && ((aux->suiv->elt)<x))
aux=aux->suiv;
/*1*/ c = (liste *) calloc(1, sizeof(liste));
if (c!=NULL)
{
c->elt=x; /* assignation de x */;
/*2*/ c->suiv=aux->suiv; /* assignation de suivant */
/*3*/ aux->suiv=c; /* chainage */
}
return (l);
}

Jean Fruitet - IUT de Marne La Vallée - 83


Introduction à la programmation

Insertion dans le corps de la liste

(3) (2)

cellule_courante

x
nouvelle_cellule

(1)

Suppression d’un élément.


La suppression d’un élément nécessite de l’isoler dans la liste, puis de supprimer le lien de cet
élément avec son prédécesseur et son successeur.
Nous laissons cet exercice au lecteur.

Suppression
d'une cellule

(1)

(2)
cellule_courante

x
cellule_supprimée

(3)

Programmons les opérations Réunion, Intersection et Complémentaire.

liste *union_liste(liste *a, liste liste *inter_liste(liste *a, liste


*b) *b)
{ {
liste *aux, *c=NULL; liste *aux, *c=NULL;
/* recopie des éléments de a dans c /* recopie des éléments de b
*/ appartenant à a dans c */
aux=a; aux=b;
while (aux!=NULL) while (aux!=NULL)
{ {
c=insere_en_tete(aux->elt, c); if (appartient(aux->elt, a))
aux=aux->suiv; c=insere_en_tete(aux->elt, c);
} aux=aux->suiv;
/* recopie des éléments de b dans c }
*/ return (c);
aux=b; }
while (aux!=NULL)
{ liste *difference_liste(liste *a,
if (! appartient(aux->elt, a)) liste *b)
insere_en_tete(aux->elt, c); {
aux=aux->suiv; liste *aux, *c=NULL;
} /* recopie des éléments de b
return (c); n’appartenant pas à a dans c */
} aux=b;
while (aux!=NULL)

Jean Fruitet - IUT de Marne La Vallée - 84


Introduction à la programmation
{ }
if (! appartient(aux->elt, a)) return (c);
c=insere_en_tete(aux->elt, c); }
aux=aux->suiv;

Complexité
La complexité de ces opérations est en O(n*m), avec n (respectivement m) éléments dans A
(respectivement B).

Le programme complet suivant génére deux ensembles d’entiers tirés au hasard, et en calcule
l’union, l’intersection et la différence.
#include <stdio.h> return (appartient(x, l-
#include <stdlib.h> >suiv));
}
#define VRAI 1
#define FAUX 0
liste *position_element(int x, liste
*l)
typedef struct cellule { {
int elt; if (l==NULL)
struct cellule *suiv ; } liste; return (NULL);
else if (l->elt==x)
return (l);
else
liste * alloc_cellule (int x ) ; return (position_element(x, l-
int appartient (int x, liste *l); >suiv));
liste *position_element(int x, liste }
*l);
liste *insere_en_tete(int x, liste *l
); liste *insere_en_tete(int x , liste
*l )
liste *insere_liste(int x, liste *l {
); liste *c;
liste *union_liste(liste *a, liste c = (liste *) calloc(1,
*b); sizeof(liste));
liste *inter_liste(liste *a, liste if (c!=NULL)
*b); {
liste *difference_liste(liste *a, c->elt=x; /* assignation de x
liste *b); */;
int affiche_liste(liste *a); c->suiv=l; /* assignation de
suivant */
l=c; /* chainage */
liste * alloc_cellule (int x ) }
{ return (l);
liste *c; }
c = (liste *) calloc(1,
sizeof(liste)); liste *insere_liste(int x , liste *l
if (c!=NULL) )
{ /* verifie si x appartient a l avant
c->elt=x; /* assignation de x d'insérer */
*/ {
c->suiv = NULL; if (! appartient(x, l))
} l=insere_en_tete(x, l);
return (c); return (l);
} }

int appartient (int x, liste *l)


{ liste *union_liste(liste *a, liste
if (l==NULL) *b)
return (FAUX); {
else if (l->elt==x) liste *aux, *c=NULL;
return (VRAI); /* recopie des éléments de a dans c
else */
aux=a;

Jean Fruitet - IUT de Marne La Vallée - 85


Introduction à la programmation
while (aux!=NULL) }
{
c=insere_en_tete(aux->elt, c); /* affiche une liste d'entiers */
aux=aux->suiv; int affiche_liste_r(liste *l)
} /* version récursive */
/* recopie des éléments de b dans c {
*/ if (l != NULL)
aux=b; {
while (aux!=NULL) printf("%d ",l->elt);
{ affiche_liste_r(l->suiv);
if (! appartient(aux->elt, a)) }
c=insere_en_tete(aux->elt, printf("\n");
c); }
aux=aux->suiv;
} void main (void)
return (c); {
}
liste *a, *b, *c;
liste *inter_liste(liste *a, liste int i, j;
*b) int n;
{
liste *aux, *c=NULL; /* premier ensemble */
/* recopie des éléments de b a=NULL;
appartenant à a dans c */ printf("Tapez un nombre [1..25]
aux=b; ?\n");
while (aux!=NULL) scanf("%d",&n);
{ srand(n);
if (appartient(aux->elt, a)) for (i=0; i<n; i++)
c=insere_en_tete(aux->elt, a=insere_liste(rand() % 10, a );
c); j=affiche_liste(a);
aux=aux->suiv; printf("\n%d éléments\n",j);
}
return (c);
} /* deuxiéme ensemble */
b=NULL;
liste *difference_liste(liste *a, printf("Tapez un nombre [1..25]
liste *b) ?\n");
{ scanf("%d",&n);
liste *aux, *c=NULL; /* création de la liste b */
/* recopie des éléments de b for (i=0; i<n; i++)
n’appartenant pas à a dans c */ b=insere_liste(rand() % 10, b );
aux=b; j=affiche_liste(b);
while (aux!=NULL) printf("\n%d éléments\n",j);
{
if (! appartient(aux->elt, a))
c=insere_en_tete(aux->elt, printf("/* union */\n");
c); c=NULL;
aux=aux->suiv; c=union_liste(a, b);
} j=affiche_liste(c);
return (c); printf("\n%d éléments\n",j);
}

/* affiche une liste d'entiers ; printf("/* intersection */\n");


retourne le nombre d'éléments */ c=NULL;
int affiche_liste(liste *l) c=inter_liste(a, b);
/* version itérative */ j=affiche_liste(c);
{ printf("\n%d éléments\n",j);
int n=0;
liste *c;
c=l; printf("/* différence */\n");
while (c != NULL) c=NULL;
{ c=difference_liste(a, b);
printf("%d ",c->elt); j=affiche_liste(c);
c=c->suiv; printf("\n%d éléments\n",j);
n++; }
}
return (n);
Jean Fruitet - IUT de Marne La Vallée - 86
Introduction à la programmation

• Pile et file
Si l’ordre d’insertion et de suppression des éléments dans la liste importe, deux structures de
données sont particulièrement adaptées : la pile et la file.
Pile [stack, lifo]
Une pile est une liste qui respecte la régle “dernier arrivé, premier sorti”, alors que la file respecte
la régle “premier arrivé, premier sorti”.
La pile est une structure de données pour laquelle l’ajout et la suppression d’un élément ne sont
autorisés qu’à une seule extrémité, appelée sommet de la pile. Les opérations sur les listes sont —P
est une pile, x un élément— :
Vider(P) [clear()] P ne contient plus aucun élément.
Vide(P) VRAI ssi P est vide.
Premier(P) retourne le premier élément de P.
Dépiler(P) [pop()] retourne le premier élément de P et supprime celui-ci de P.
Empiler(x, P) [push()] place x dans P comme premier élément.
Opérations sur une pile
x sommet

sommet
dépiler
... ...

2 2
empiler
1 1

0 0

Il est assez facile de réaliser l’implantation d’une pile à l’aide d’un tableau. Il faut seulement définir
une variable auxiliaire, le pointeur de pile, qui indique l’adresse du sommet de la pile (qui est aussi le
nombre d’éléments de celle-ci).

/* Pile de flottants */ return(pile[pp-1]);


#include <stdio.h> }
#include <stdlib.h>
typepile pop(void)
#define MAXELEMENT 20 /* ne doit pas être utilisé si la
typedef float typepile; pile est vide */
{
typepile pile[MAXELEMENT]; pp--;
return(pile[pp]);
int pp=0 ; /* pointeur de pile */ }

void clear(void) void push(typepile x)


{ /* ne doit pas être utilisé si pile
pp=0; pleine */
} {
pile[pp]=x;
int pile_vide(void) pp++;
{ }
return (pp==0);
} /* programme principal */
void main (void)
int pile_pleine(void) {
{ int i, j;
return (pp==MAXELEMENT); int n;
}
/* remplir la pile */
typepile premier_pile(void) printf("Tapez un nombre [1..25]
{ ?\n");
Jean Fruitet - IUT de Marne La Vallée - 87
Introduction à la programmation
scanf("%d",&n);
srand(n);
for (i=0; i<n; i++)
if (!pile_pleine())
push(rand() % 10);

printf("\n%d éléments\n",pp);
/* vider la pile */
while(! pile_vide())
printf("%3.2f ",pop());
printf("\n");

Jean Fruitet - IUT de Marne La Vallée - 88


Introduction à la programmation

File, queue [queue, fifo]


Une file est une structure de données pour laquelle l’ajout et la suppression d’un élément ne sont
autorisés qu’aux seules extrémités, appelées la tête et la queue de la file. Les éléments sont ajoutés
en queue de file et sont retirés en tête de file. Les opérations sur les listes sont —F est une file, x un
élément—:
Vider(F)F ne contient plus aucun élément.
Vide(F) VRAI ssi F est vide.
Premier(F) retourne le premier élément de F.
Dernier(F) retourne le dernier élément de F.
Défiler(F) retourne le premier élément de F et supprime celui-ci de F.
Enfiler(x, F) place x dans F comme dernier élément.

On dispose de deux index, tête et queue, et d’un tableau de taille MAXELEMENT, dont les adresses
sont désignées modulo MAXELEMENT pour obtenir une structure de données circulaire...
File Sens de la file qui se vide par la
0 1 2 3 tête
.... MAXELT-1

x x x x

tête queue Les nouveaux


arrivants s'ajoutent en
queue
x x
x
x
x
.... tête
3
2
queue
1 Sens de la file
0
MAXELT-1

File circulaire
/* File circulaire de flottants */ }
#include <stdio.h>
#include <stdlib.h> int file_pleine(void)
{
#define MAXELEMENT 20 return ((qf+1) % MAXELEMENT == tf);
typedef float typefile; }

typefile file[MAXELEMENT]; typefile premier_file(void)


{
int tf=0; /* tete de file */ return(file[tf]);
int qf=0; /* queue de file */ }

void clear(void)
{
tf=0;
qf=0;
}

int file_vide(void)
{
return (tf==qf);
Jean Fruitet - IUT de Marne La Vallée - 89
Introduction à la programmation
typefile dernier_file(void) qf=(qf+1) % MAXELEMENT;
{ }
if (qf>0)
return(file[qf-1]); /* programme principal */
else void main (void)
return(file[MAXELEMENT-1]); {
} int i, j, n;

typefile defile(void) /* remplir la file */


/* ne doit pas être utilisé si la printf("Tapez un nombre [1..25]
file est vide */ ?\n");
{ scanf("%d",&n);
if (tf<MAXELEMENT-1) srand(n);
{ j=0;
tf=tf+1; for (i=0; i<n; i++)
return(file[tf-1]); if (!file_pleine())
} {
else enfile(rand() / 10.0);
{ j++;
tf=0; }
return(file[MAXELEMENT-1]); printf("\n%d éléments\n",j);
}
while(! file_vide())
} printf("%3.2f ",defile());
printf("\n");
void enfile(typefile x) }
/* ne doit pas être utilisé si file
pleine */
{
file[qf]=x;

L’implantation d’une pile ou d’une file sous forme de liste chaînée ne présente pas de difficulté
particulière.

Jean Fruitet - IUT de Marne La Vallée - 90


Introduction à la programmation

• Hachage
Vecteur booléen
Soit E un sous-ensemble de l’ensemble d’entiers {0, 1, 2,...,N}, par exemple E = {2, 7, 9}
Pour représenter l’ensemble E, on utilise une fonction caractéristique F:
F :E --> {0, 1}
x --> F(x)==0 si x n’appartient pas à E
x --> F(x)==1 si x appartient à E

Dansnotre exemple E = {2, 7, 9}

0 0 1 0 0 0 0 1 0 1 0 ... 0
0 1 2 3 4 5 6 7 8 9 10 ... N

Si pour représenter la fonction F on utilise un vecteur de bits, il faut modifier la déclaration


précédente en regroupant 8 valeurs consécutive en un seul octet. Ainsi

0 0 1 0 0 0 0 1 0 1 0 ... 0
0 1 2 3 4 5 6 7 8 9 10 ... N

peut se représenter par

binaire 0010 0000 1010 0000 0000 0000 ....


hexadécimal 20 A0 00 ...

Tri par distribution


Le tri par distribution s’aparente au tri postal.
Soit E un sous-ensemble de l’ensemble d’entiers {0, 1, 2,...,N-1}. L’ensemble E a n éléments —
Card(E)=n— à classer dans l’ordre usuel sur les entiers (ordre croissant). La méthode procéde en
deux étapes :
- distribution de E sur un vecteur de bits par une fonction caractéristique F
- collecte des éléments non nuls du vecteur de bits parcouru dans l’ordre croissant
E={ 9, 2, 7}
distribution

0010000101 F

collecte
E’={ 2, 7, 9}

La complexité de cet algorithme est proportionnelle au cardinal de E —O(n)—


Hachage
Pour mémoriser des ensembles d’objets (pas nécessairement des entiers) sur lesquels s’appliquent
les opérations ensemblistes AJOUTER, SUPPRIMER, ELEMENT, on reprend l’idée du vecteur
booléen (ou vecteur de bits). Mais ici la fonction caractéristique, appelée fonction de hachage, est
calculée pour un attribut de l’objet, qui peut être numérique, considéré comme une clé d’accès à

Jean Fruitet - IUT de Marne La Vallée - 91


Introduction à la programmation
l’objet. Typiquement, ce sera le cas des objets d’une base de données, dont les tuples sont
accessibles par une clé unique.
Fonction de hachage
La fonction de hachage répartit les objets en paquets [buckets] —d’où son nom— dont l’adresse
est rangée dans une table de hachage. La fonction de hachage établit une relation, pas
nécessairement injective, entre la valeur de la clé et son indice dans la table ; plusieurs clés peuvent
donc avoir la même valeur de hachage (collision).

On mémorise un sous-ensemble E du domaine D avec une table T de taille N.


La fonction h associe à chaque clé x une valeur de l’intervalle [0.. N-1]
D --> [0,1, ... ,N-1]
x --> h(x)
Le choix de la fonction de hachage doit être considéré avec soin. La taille de la table doit, si
possible, être de l’ordre de grandeur de l’ensemble E.Toutes les valeurs entre 0 et N-1 doivent être
équiprobables, c’est-à-dire que pour toute clé x et pour tout i entre 0 et N-1, la probabilité que h(x)
ait pour valeur i doit être égale à 1/N (h uniforme).
Exemple
Un sous-ensemble E du domaine D des prénoms
E = {Anne, Guy, Lou, Luc}
D = { x / x est un prénom codé en ASCII }
h(x) = (Somme des codes ASCII de x ) MODULO 8
Une table de 8 éléments suffit.

Calcul des valeurs de hachage


h(Anne)= (65+110+110+101) % 8 = 2
h(Guy)= (71+117+121) % 8 = 5
h(Lou)= (76+111+117) % 8 = 0
h(Luc)= (76+117+99) % 8 = 4

T Lou Anne Luc Guy


0 1 2 3 4 5 6 7
Si la fonction n’est pas injective ( xy et h(x)=h(y)) il se produit des collisions.
h(Paul)= (80+97+117+108) % 8 = 2 = h(Anne)
Exercice : Calculez la valeur de hachage de Noe et placez-le dans la table.
Adressage dispersé
Pour éviter les collisions, on cherche à éparpiller au maximum les données, dans une table qui est de
l’ordre de grandeur du domaine.
Dans le cas des prénoms, avec une fonction de hachage
h(x) = (Somme(Ascii(x[i])) % N
quelle taille N donner à la table pour disperser 200 prénoms ? Quel est l’ordre de grandeur de D ? —
Il y a au moins un prénom par jour du calendrier !—.
Re-hachage
Pour résoudre le problème des collisions, une méthode simple consiste à reporter les clés qui
entrent en collision sur les cases libres du tableau. Formellement, cela revient à associer à chaque
valeur x une suite d’indices dans la table ; le premier indice est h(x) ; les indices suivants sont
obtenus par un nouveau hachage ou en recherchant la première case libre suivante, circulairement...
( (h(x)+k) modulo N / k = 0, 1, 2, ...)

T Lou Anne Paul Luc Guy Noe


Jean Fruitet - IUT de Marne La Vallée - 92
Introduction à la programmation
0 1 2 3 4 5 6 7

Après ajout de Paul et Noe, les cases 2, 3 et 6 sont occupées par des éléments ayant une valeur de
hachage identique.
Pour retrouver Noe, il faut :
- calculer h(Noe) = 2
- comparer T[h(Noe)] et Noe
si identité SUCCES
sinon parcourir le tableau circulairement jusqu’à
- trouver Noe : SUCCES
- trouver une case libre : ECHEC

Suppression d’un élément


La suppression de l’élément Anne libére une case. Mais alors le fil conducteur jusqu’à Noe est
coupé. Donc les cases libérées doivent être marquées VIDE, et les case qui n’ont jamais été
occupées marquées BLANC.

T Lou BLAN VIDE Paul Luc Guy Noe BLAN


C C
0 1 2 3 4 5 6 7
Quelques fonctions de hachage
Une fonction de hachage doit être uniforme (la probabilité que h(x)=i doit être proche de 1/N),
déterministe (pour une clé donnée elle calcule toujours la même valeur), et facilement calculable.
- Extraction de bits
La clé est toujors représentée par une suite de bits, dont on peut extraire p bits, résultant un
nombre entre 0 et 2p-1. Elle est simple à calculer mais ne donne de bons résultats que si les bits
écartés ne sont pas significatifs.
- Compression de bits
La représentation de la clé est découpée en q tranches de p bits, que l’on combine par une
opération telle que l’addition ou le ‘ou exclusif’ ;
si x = t[1]t[2]...t[q] alors h(x) = t[1] xor t[2] xor ... xor t[q].
Pour briser certaines répétitions de bits, on peut décaler circulairement les tranches avant de les
combiner.
- Division
On prend le reste de la division par N de la représentation de la clé: h(x) = x mod N. Il faut faire
attention aux effets indésirables d’accumulation.
- Multiplication
soit un nombre réel r, tel que 0 < r < 1, on calcule
h(x) = Troncature(((x*r) mod 1) * N)
La valeur de r ne doit pas être trop proche de 0 ou de 1 pour éviter des accumulations aux
extrémités du tableau. De bonnes valeurs pour r sont (5 - 1) / 2 et 1 - (5 - 1) / 2.
Hachage ouvert
On peut résoudre le problème des collisions en associant une liste chaînée à chaque case de la
table de hachage.

Jean Fruitet - IUT de Marne La Vallée - 93


Introduction à la programmation

Hachage ouvert
0 Lou

1
VIDE

2 Anne Noe

3 Paul

4 Luc

5 Guy

6 VIDE

7 VIDE

• Tri lexicographique
On considère un ensemble de mots ; une table indexée par les lettres de l’alphabet et une fonction de
hachage qui à un mot fait correspondre sa iéme lettre.
Donner un algorithme permettant de classer les mots dans l’ordre lexicographique (alphabétique).
Indication : pour classer des mots de même longueur, il suffit de les classer successivement suivant la
dernière lettre, puis avant-dernière lettre, etc.
• Dictionnaire d’un texte
On considère un texte T constitué de mots (chaîne ASCII alphanumérique) séparés par des codes
<ESPACE>, <TAB>, <NL>, <CR>
Programmer en C le dictionnaire du texte par fonction de hachage fermée.
A chaque mot sera associé un triplet
<mot, position de la première occurrence, nombre d’occurrences>
Les mots seront “case sensitive” (MAJUSCULES et minuscules)
Proposer plusieurs fonctions de hachage et afficher la distribution des valeurs de hachage et le
nombre de collisions en fonction de la taille de la table de hachage...

Jean Fruitet - IUT de Marne La Vallée - 94


Introduction à la programmation

Arbres et ensembles ordonnés


Il est souvent nécessaire d’ordonner les éléments d’un ensemble, par exemple pour améliorer la
rapididé d’une recherche... Or le maintien d’un ordre entre les éléments d’un tableau ou d’une liste
est relativement coûteux. Pour un tableau par exemple l’insertion d’un élément nécessite de
déterminer sa place, en parcourant le tableau depuis le début (k comparaisons) puis de décaler les (n-
k) éléments successeurs pour ménager une place. Donc une complexité en O(n) avec n le nombre
d’éléments du tableau.
Les arbres de recherche sont des structures de données qui permettent de réduire la complexité en
temps —mais pas la complexité de la programmation !— des algorithmes d’insertion et de recherche
en accédant aux données par dichotomie.
Exemples d’arbres
La structure de fichiers sous DOS —inspirée d’UNIX— organise les fichiers en collections
hiérarchisées appelées répertoires [directory]. Chaque répertoire (sauf la racine qui n’a pas de pére)
a un seul répertoire pére et zéro ou plusieurs sous-répertoires fils. Les fichiers de données
proprement dits sont les feuilles de l’arbre.
C:\ (Racine)
IO.SYS racine
+--IO.SYS \
+--DOS.SYS DOS.SYS
+--CONFIG.SYS CONFIG.SYS
+--AUTOEXEC.BAT sous-répertoire
AUTOEXEC.BAT DOS TURBOC
+--DOS
+--COMMAND.COM
+--KEYB.EXE COMMAND.COM
+--EDIT.COM KEYB.EXE
+--FORMAT.EXE EDIT.COM BIN INCLUDE LIB SRC
+-- ... ...
+--TURBOC ESSAI_1.C
TCC.EXE
+--BIN
TC.EXE ESSAI_1.EXE
+--TCC.EXE fichiers
TCINST.EXE ...
+--TC.EXE
...
+--TCINST.EXE
+--ESSAI_1.EXE
+-- ...
+--INCLUDE
+-- ... La structure de fichiers et répertoires DOS
+--LIB
+-- ...
+--SRC
+--ESSAI_1.C
+--ESSAI_2.C
+--ESSAI_1.OBJ
+--ESSAI_1.EXE
L’intérêt de cette organisation est de laisser à l’utilisateur le soin de regrouper les fichiers à sa
convenance tout en maintenant une structure hiérarchique.
Un arbre généalogique représentant la relation de parenté “est la mére de” est aussi une structure
d’arbre quelconque. Par contre la relation “a pour parents” ne crée pas un arbre (au sens
informatique) mais un graphe, un noeud pouvant avoir deux ascendants.

Jean Fruitet - IUT de Marne La Vallée - 95


Introduction à la programmation
Représentation symbolique des arbres
CECI N'EST PAS UN ARBRE
Arbre vide Arbre quelconque car 9 a deux pères
23 23

12 -2 10 12 -2 10
Arbre singleton

23 78 22 -77 78 22 -77

9 9 9

Arbre binaire Arbre binaire de recherche


racine
23 12

12 10 9 23

-2 78 22 -77 -2 10 22 78

9 -77 9
9
sous-arbre gauche sous-arbre droit
Chaque étiquette d'un noeud du sous-arbre gauche
est inférieure ou égale à l'étiquette de la racine qui est
inférieure aux étiquettes du sous-arbre droit... cette
propriété est maintenue dans l'arbre tout entier.

Par définition un arbre est une structure de données constituée d’un noeud appelé racine et de
sous-arbres fils de la racine. C’est donc une définition récursive.
Chaque noeud d’un arbre a un ou zéro pére et zéro ou plusieurs fils. Un noeud sans pére est la
racine de l’arbre. Un noeud sans fils est une feuille. Un noeud ayant un pére et au moins un fils est
un noeud interne. L’étiquette d’un noeud est un élément de l’ensemble représenté par l’arbre. Les
liens entre un noeud et ses sous-arbres fils sont les branches de l’arbre.
La hauteur de l’arbre est le plus long chemin (sans retour en arriére) entre la racine et une feuille.

Un arbre vide n’a aucun noeud. Un singleton a une racine sans fils. Un arbre binaire est un arbre
dont chaque noeud a au plus deux fils. Un arbre binaire de recherche est un arbre binaire dont les
étiquettes sont ordonnées selon une clé et tel que toutes les clés du sous-arbre gauche sont
inférieures ou égales à la clé de la racine, et celles du sous-arbre gauche sont supérieures, et ceci
pour tous les sous-arbres...
La hauteur de l’arbre permet d’estimer le nombre n d’éléments de celui-ci. En particulier si A est un
arbre binaire complet de hauteur h (tous les noeuds ont zéro ou deux fils) il a au plus
20 + 21 + 22 + .. + 2h-1 + 2h éléments
Card(A) Σ i=0,h 2i = 2h+1 -1

Jean Fruitet - IUT de Marne La Vallée - 96


Introduction à la programmation

Arbre binaire de recherche complet


racine

12 1 noeuds

9 23 2 noeuds
hauteur :3

10 78 ...
-2 22

sous-arbre au plus 2*2*2


-77 9
noeuds
feuilles

Définition axiomatiques des arbres


• Ensembles
Arbre : (N, P), N : ensemble de noeuds, P : relation “pére de”
Arbre+ : Arbre - { }
Arbre2 : Arbres binaires
Element : Etiquette des noeuds de l’arbre.
LA : Liste d’arbres.
• Opérations
Arbre-vide : ...--> Arbre : Créer un arbre vide
Racine : Arbre --> Noeud : Retourner le noeud racine d’un arbre
Fils : Arbre --> LA : Retourner la liste des sous-arbres de la racine
Gauche : Arbre2+---> Arbre2 : Retourner le sous-arbre gauche de la racine
Droit : Arbre2+--> Arbre2 : Retourner le sous-arbre droit de la racine
Cons : Noeud x LA -->Arbre+ : Retourner l’arbre construit à partir du noeud
Cons2 :Noeud x Arbre2 x Arbre2 -->Arbre2+ : Retourner l’arbre binaire construit
Vide : Arbre --> {VRAI, FAUX} : Tester si un arbre est vide
Elt : Noeud --> Element : Retourner l’étiquette d’un noeud
Nouveau : Element --> Noeud : Créer un nouveau noeud

Jean Fruitet - IUT de Marne La Vallée - 97


Introduction à la programmation
Représentation informatique
La représentation informatique des arbres peut se faire à l’aide de tableaux. On gagne alors en
simplicité de programmation ce qu’on perd en souplesse. Mais les arbres étant très bien adaptés à la
programmation avec allocation dynamique de mémoire il est préférable d’implanter les arbres sous
forme de cellules (record, structure) et de pointeurs.

Arbre quelconque Arbre binaire


23 D

12 -2 10 S A

-2 22 -77 Z A T

9 B

Pour éviter d’avoir à gérer un nombre variable de pointeurs par noeud d’un arbre quelconque, on
peut préférer la structure suivante — fils gauche, frére droit—

Arbre quelconque
Pointeur vers un frère
23

Pointeur vers le premier fils

12 -2 10

-2 22 -77

Implantation en Langage C

• Il faut d’abord représenter un Noeud.


Par exemple pour les arbres quelconques :

typedef struct cellule {


type_element elt;
struct cellule * fils_gauche;

Jean Fruitet - IUT de Marne La Vallée - 98


Introduction à la programmation
struct cellule * frere_droit;} noeud;

Et pour un arbre binaire :

typedef struct cellule {


type_element elt;
struct cellule * fils_gauche;
struct cellule * fils_droit;} noeud;

• Un Arbre est un pointeur sur un Noeud, la racine de l’arbre :


typedef noeud *arbre;

• Un Arbre vide est un pointeur NULL :


arbre = NULL;

• Opérations
• Vide consiste à tester si la racine est un pointeur NULL :
int vide (arbre racine)
{
return (racine == NULL);
}
• Elt : consiste à retourner l’étiquette d’un noeud :
par exemple pour les arbres quelconques :
type_element element(arbre racine)
{
return (racine->elt);
}
• Nouveau : il faut faire une allocation mémoire et placer l’étiquette. En cas d’erreur d’allocation le
pointeur renvoyé est NULL (l’arbre est vide) :
arbre nouveau_binaire(type_element elt, arbre racine)
{
racine = (struct cellule *) calloc(1, sizeof (struct cellule));
if (! vide(racine))
{
racine->elt = elt;
racine->fils_gauche= NULL;
racine->fils_droit=NULL;
}
return(racine);
}
• Cons : il faut relier un noeud à un ou plusieurs sous arbres.
arbre cons_binaire(arbre racine, arbre ss_arbre_g, arbre ss_arbre_d)
{
racine->fils_gauche = ss_arbre_g;
racine->fils_droit = ss_arbre_d;
return(racine);
}

• Insertion
Les opérations d’insertion, suppression et recherche d’un élément se programment de façon
récursive. Dans le cas d’un arbre binaire de recherche, l’insertion est guidée au cours de la descente
dans l’arbre par la relation d’ordre sur les clés.
Il faut donc disposer d’une fonction de comparaison des clés, qui de façon classsique en langage C
retourne un entier négatif, nul ou positif selon l’ordre sur les clés :
int clef(type_element e)
/* renvoie la valeur de la clé */
{
return (e.cle);
Jean Fruitet - IUT de Marne La Vallée - 99
Introduction à la programmation
}

int compare(type_element e1, type_element e2)


{
if (clef(e1) < clef(e2) ) return (-1);
else if (clef(e1) == clef(e2) ) return (0);
else return (1);
}

arbre inserer_bin_recherche(type_element elt, arbre racine)


{
if (vide(racine))
racine=nouveau_binaire(elt, racine);
else
if (compare(elt, racine->elt) <= 0)
racine_fils_gauche = inserer_bin_recherche(elt, racine-
>fils_gauche);
else
racine->fils_droit = inserer_bin_recherche(elt, racine->fils_droit);
return(racine);
}

La complexité de cet algorithme est proportionnelle à la hauteur de l’arbre et donc en O(log2n) pour
un arbre binaire de recherche équilibré.

• Suppression
La suppression d’un élément doit conserver l’arbre binaire de recherche. Il faut donc remplacer
l’étiquette supprimée par l’étiquette la plus à droite du sous-arbre gauche.

E Max FG

FG FD FG FD

Max FG

La suppression de l'étiquette E nécessite de remplacer celle-ci


par la plus grande étiquette du sous-arbre gauche...
La programmation de cette opération assez délicate sera laissée à la sagacité du lecteur !
• Parcours d’arbre
Un parcours en profondeur consiste à descendre dans l’arbre depuis la racine en commençant par le
premier fils (le fils gauche pour un arbre binaire), de façon récursive, puis à traiter les autres fils.
Selon l’ordre d’affichage de l’étiquette des noeuds —avant, entre ou après les fils— l’affichage sera
préfixe, infixe ou suffixe.

Jean Fruitet - IUT de Marne La Vallée - 100


Introduction à la programmation

Parcours en profondeur d'un arbre binaire de recherche

12

9 23

-2 10 22 78

-77 9

Parcour s préfixe : 12, 9, -2, -77, 9, 10, 23, 22, 78

Parcours infixe : -77, -2, 9, 9, 10, 12, 22, 23, 78

Parcours suffixe : -77,9, -2, 10, 9, 22, 78, 23, 12


Le parcours infixe affiche les éléments dans l’ordre croissant.
void infixe(arbre racine)
{
if (! vide(racine))
{
infixe(racine->fils_gauche);
printf(“%d\t”,racine->elt);
infixe(racine->fils_droit);
}
Exercices
A est un arbre binaire de recherche dont les données sont des entiers.
1) Donner en langage C la définition de A.
2) Dessiner A après insertion des nombres 100, 20, 30, 150, 110, 10, 25, 45, 150, 200.
3) Quelle est la hauteur de l'arbre A ?
4) Que devient l'arbre A si
- on supprime 30 ?
- on supprime 100 ?
- on rajoute 5 et 120 ?
5) Ecrire une fonction affichant les éléments d’un arbre binaire de recherche dans l’odre
décroisssant.
6) Le parcours en largeur consiste à afficher la valeur de chaque noeud au fur et à mesure de la
descente dans l'arbre, puis à traiter chaque fils dans l'ordre gauche puis droite. Pour l'arbre de la
question 2 un parcours en largeur produit la liste :
100, 20, 10, 30, 25, 45, 150, 110, 200
Ecrire en C la fonction
void parcours_largeur(type_arbre *racine);
7) Ecrire une fonction “taille” retournant le nombre de noeuds d’un arbre.
8) Ecrire une fonction “hauteur” retournant la hauteur d’un arbre.

Jean Fruitet - IUT de Marne La Vallée - 101


Introduction à la programmation
/* JF */ racine->fg = NULL;
/* Arbre binaire de recherche */ racine->fd = NULL;
#include <stdio.h> }
#include <stdlib.h> else
#include <errno.h> perror("Erreur allocation
memoire\n");
#define MAXCAR 20 }
#define MAXHACHE 1000; else if (strcmp(element, racine-
>elt)<=0)
typedef struct cellule { racine->fg=insere(element,
char elt[MAXCAR]; racine->fg);
struct cellule * fg; else
struct cellule * fd; racine->fd=insere(element,
} *arbre; racine->fd);
return (racine);
#define MAXHAUTEUR 100 }

struct liste {char n[MAXCAR]; struct arbre recherche (char *element, arbre
liste * suiv;} *ta[MAXHAUTEUR]; /* racine)
tableau de listes */ {
int test;
FILE *f; if (racine!=NULL)
{
/* PROTOTYPES */ test = strcmp(racine->elt,
element);
arbre insere(char *element, arbre if (test==0)
racine); /* insere un element dans return (racine);
l'arbre binaire */ else if (test<0)
arbre recherche (char *element, arbre return( recherche(element,
racine); /* recherche un element */ racine->fd));
arbre min_arbre(char *element, arbre else
racine); /* supprime le min de return( recherche(element,
l'arbre */ racine->fg));
arbre supprime(char *element, arbre }
racine); /* supprime element de else return (NULL);
l'arbre */ }
void affiche_p(arbre racine); /*
affiche en profondeur infixe */ arbre min_arbre(char *element, arbre
racine)
struct liste * /* supprime la valeur minimum de
insere_queue_liste(struct liste * l, l'arbre et la place dans element */
char n[]); /* ajoute eun element n {
queue de liste */ arbre aux;
void cree_l(int h, arbre racine); if (racine!=NULL)
/* transforme un arbre en {
tableau de listes */ if (racine->fg==NULL)
void affiche_liste(struct liste *l); {
/* affiche le tableau de aux=racine;
listes niveau par niveau */ strcpy(element, racine->elt);
void affiche_l(arbre racine); racine=racine->fd;
/* affiche un arbre en largeur free(aux);
*/ }
else
/* FONCTIONS */ {
racine->fg=min_arbre(element,
arbre insere(char *element, arbre racine->fg);
racine) }
/* insere un element dans un arbre */ }
{ return(racine);
if (racine==NULL) /* creer l'arbre }
*/
{ arbre supprime(char *element, arbre
racine = (struct cellule *) racine)
calloc(1, sizeof(struct cellule)); /* supprime element de l'arbre */
if (racine != NULL) {
{ int test;
strcpy(racine->elt, element); arbre aux;
Jean Fruitet - IUT de Marne La Vallée - 102
Introduction à la programmation
aux = racine; else
if (racine!=NULL) {
{ strcpy(l->n, n);
test = strcmp(racine->elt, l->suiv=NULL;
element); /* printf("Insertion %s\t",n); */
if (test==0) }
{ }
/* remplacer cet element */ else
if (racine->fd==NULL) {
/* faire monter le sous-arbre aux=l;
gauche */ while (aux->suiv != NULL)
{ aux=aux->suiv;
aux=racine; aux->suiv=(struct liste *)
racine=racine->fg; calloc(1, sizeof(struct liste));
free(aux); if (aux->suiv==NULL)
} perror("Erreur allocation
else if (racine->fg==NULL) memoire \n");
/* faire monter le sous-arbre else
droit */ {
{ strcpy(aux->suiv->n, n);
aux=racine; aux->suiv->suiv=NULL;
racine=racine->fd; /* printf("Adjonction %s\t",n);
free(aux); */
} }
else /* remplacer par min_fd */ }
{ return (l);
racine->fd = }
min_arbre(racine->elt, racine->fd);
} void cree_l(int h, arbre racine)
} {
else /* rechercher sur les fils if (racine!=NULL)
*/ {
if (test<0)
racine->fd=supprime(element, ta[h]=insere_queue_liste(ta[h],raci
racine->fd); ne->elt);
else cree_l(h+1,racine->fg);
racine->fg=supprime(element, cree_l(h+1,racine->fd);
racine->fg); }
} }
return (racine);
} void affiche_liste(struct liste *l)
{
void affiche_p(arbre racine) while (l != NULL)
{ {
if (racine!=NULL) printf("%s\t", l->n);
{ l= l->suiv;
affiche_p(racine->fg); }
printf("%s\t",racine->elt); }
affiche_p(racine->fd);
} void libere_liste(struct liste *l)
} {
struct liste *aux;
while (l != NULL)
/* affichage en largeur */ {
aux=l->suiv;
struct liste * free(l);
insere_queue_liste(struct liste * l, l = aux;
char n[]) }
{ }
struct liste *aux;
if (l==NULL) /* creer la liste */ void libere_l(void)
{ {
l=(struct liste *) calloc(1, int i ;
sizeof(struct liste)); for (i=0; i<MAXHAUTEUR; i++)
if (l==NULL) {
perror("Erreur allocation libere_liste(ta[i]);
memoire \n"); ta[i]=NULL;
Jean Fruitet - IUT de Marne La Vallée - 103
Introduction à la programmation
} printf("Etiquette a inserer
} ?\n");
if (scanf("%s",str)!=0)
void affiche_l(arbre racine) {
{ printf("Insertion de %s\n
int i ; ",str);
libere_l(); racine= insere(str,
cree_l(0,racine); racine);
printf("\n"); }
for (i=0; i<MAXHAUTEUR; i++) break;
if (ta[i]!=NULL) case 's':
{ case 'S' :
printf("%d\t",i); rewind(stdin);
affiche_liste(ta[i]); str[0]=0;
printf("\n"); printf("Etiquette a
} supprimer ?\n");
} if (scanf("%s",str)!=0)
{
/* MAIN */ printf("Suppression de
%s\n ",str);
racine= supprime(str,
void main (int argc, char *argv[]) racine);
{ }
int i, c; break;
char str[20]; case 'a' :
struct cellule *racine; case 'A' :
i=0; printf("Affichage en
profondeur \n");
affiche_p(racine);
if (argc>1) printf("\n");
{ fflush(stdout);
if ((f=fopen(argv[1], "r"))!=NULL) break;
{ case 'l' :
printf("Ouverture en lecture de case 'L' :
%s\n",argv[1]); printf("Affichage en largeur
while ((c=getc(f))!=EOF) \n");
{ printf("Niveau\tValeurs
if ((c==' ') || (c=='\n') || \n");
(c=='\t')) affiche_l(racine);
{ fflush(stdout);
str[i]=0; break;
i=0; default : break;
printf("%s\n",str); }
racine=insere(str, racine); } while ((c!='q') && (c!='Q') );
}
else }
str[i++]=c;
}
fclose(f);
}
}

do
{
printf("MENU : i:inserer s:supprimer
a:arbre l:liste q:quitte\n");
do {
c=getchar();
} while ((c=='\n') || (c=='\t') ||
(c==' '));
/* boucler sans rien faire */

switch (c) {
case 'i':
case 'I' :
rewind(stdin);
str[0]=0;
Jean Fruitet - IUT de Marne La Vallée - 104
Introduction à la programmation

Graphes
Les graphes interviennent chaque fois qu'on veut représenter et étudier un ensemble de liaisons
(orientées ou non) entre les éléments d'un ensemble fini d'objets.
Par exemple représenter un réseau routier, électrique, un circuit électronique, un réseau
informatique, l'ordonnancement de tâches, une application hyper-média, etc.
Après des définitions et notions fondamentales sur les graphes, nous présenterons quelques
algorithmes élémentaires.
Définition
Un graphe G est un couple (S,A) où :
- S est un ensemble fini de sommets ;
- A est un sous-ensemble de SxS, ensemble des arcs de G.
On prendra généralement pour S un segment [1,n].

Exemple
Représentation du graphe orienté
G1 = (S1,A1) = ({1,2,3,4,5}, {(1,2), (1,3), (2,2), (2,4), (3,1), (4,2), (4,4), (5,4)})

2 4

1 3
5
Un arc (x,y) représente une liaison orientée entre l'origine x et l'extrémité y. Si (x,y) est un arc, x est
le prédécesseur de y et y est le successeur de x ; si x=y l'arc est une boucle.
Graphe non orienté
On dit qu'un graphe G = (S,A) est non orienté si et seulement si, pour tout si et sj de l'ensemble des
sommets S, si (si, sj) est un arc de A, alors (sj, si) aussi. Les arcs s'appellent alors des arêtes et sont
représentées par des paires {si, sj}, non orientées bien sûr.

Exemple
Le graphe non orienté G2 = (S2, A2), représenté graphiquement par :
S2 = {1,2,3,4,5};
A2 = {(1,5), (2,3), (2,4), (3,2), (3,5), (4,2), (4,5), (5,1), (5,3), (5,4)}

1 5 2

Propriétés
Deux sommets x et y d'un graphe orienté (respectivement non orienté) sont adjacents s'ils sont les
extrémités d'un arc (respectivement d'une arête).
Jean Fruitet - IUT de Marne La Vallée - 105
Introduction à la programmation
Soit un graphe G=(S,A) et T un sous-ensemble de S. L'ensemble des arcs de A dont une extrémité
est dans T et l'autre est dans S-T est appelé le cocycle associé à T. L'ensemble des sommets de S-T
adjacents à au moins un sommet de T est la bordure de T.
Le graphe G=(S,A) est dit biparti s'il existe un sous-ensemble de sommets T tel que A= cocycle(T).

Un sous-graphe du graphe G = (S,A) est un couple G' = (S',A') pour lequel S' est inclus dans S, et A'
inclus dans A. Le sous-graphe G' est un graphe partiel de G si S'=S. Si A' est l'ensemble des arcs de
A dont les deux extrémités sont dans S', le sous-graphe G' est dit induit par S'.

Exemple :
G3 = (S3, A3) = ({1,2,3,4,5}, {(1,4), (1,5), (2,4), (3,5), (4,3), (5,2)})

(a) (b)
1 4 1 4

2 2

5 3 3

(c) (d)
1 4 4
1

3 3

(a) Graphe orienté G (b) Un sous-graphe de G


(c) Le graphe partiel induit par les sommets {1, 2, 3, 4}
(d) Le sous-graphe induit par les arcs {(1,4) , (4,3)}
Le graphe G est biparti parce que tout arc est incident à T = {1, 2,
3} (i.e. a l'une de ses extrémités dans T)
Implémentation d'un graphe
Trois représentations reviennent principalement : la matrice d'adjacence, la matrice d'incidence et la
liste des successeurs.
Matrice d'adjacence
On associe à un graphe G = ([1,n], A) une matrice carrée d'ordre n à valeurs dans {0,1}, appelée
matrice d'adjacence, telle que quels que soient les sommets i, j de l'ensemble des sommets [1,n], la
valeur de l'élément mi,j de la matrice est égal à 1 si et seulement si (i,j) est un arc de G, et 0 sinon.
Exemple :
G1 = ({1,2,3,4,5}, {(1,2), (1,3), (2,2), (2,4), (3,1), (4,2), (4,4), (5,4)})
M1 1 2 3 4 5
1 0 1 1 0 0
2 0 1 0 1 0
3 1 0 0 0 0
4 0 1 0 1 1
5 0 0 0 1 0
Matrice d'adjacence M1 du graphe orienté G1.

Jean Fruitet - IUT de Marne La Vallée - 106


Introduction à la programmation
G2 = ({1,2,3,4,5}, {(1,5), (2,3), (2,4), (3,2), (3,5), (4,2), (4,5), (5,1), (5,3), (5,4)})
M2 1 2 3 4 5
1 0 0 0 0 1
2 0 0 1 1 0
3 0 1 0 0 1
4 0 1 0 0 1
5 1 0 1 1 0
Matrice d'adjacence M2 du graphe non orienté G2 (la matrice M2 est symétrique).
Matrice d'incidence
Si G est un graphe sans boucle, sa matrice d'incidence "sommets-arcs" (G) est une matrice
|S|x|A| à valeurs dans {-1,0,1}, telle que l'élément dx,a de la matrice est égal à 1 si x est l'origine de
l'arc a, -1 si x est l'extrémité de l'arc a et 0 sinon...

Exemple : matrices d'adjacence et d'incidence de G3.


G3 = (S3, A3) avec S3= {1,2,3,4,5} liste des sommets
et A3= {1,2,3,4,5,6 } liste des arcs numérotés 1=(1,4); 2=(4,3) ; 3=(3,5) ; 4=(1,5) ; 5=(2,4) ;
6=(5,2)

M3 1 2 3 4 5 3 1 2 3 4 5 6
1 0 0 0 1 1 1 1 0 0 1 0 0
2 0 0 0 1 0 2 0 0 0 0 1 -1
3 0 0 0 0 1 3 0 -1 1 0 0 0
4 0 0 1 0 0 4 -1 1 0 0 -1 0
5 0 1 0 0 0 5 0 0 -1 -1 0 1

1 4
1
5
4 2
2
6
3
5 3

Jean Fruitet - IUT de Marne La Vallée - 107


Introduction à la programmation
Liste des successeurs ou liste d'adjacence
On peut aussi décrire un graphe par un tableau (q1, q2, ... qn) où l'élément qi est un pointeur sur la
liste des successeurs du sommet i.

Exemple :
1 2 3 4 5
1 4

2
5 4 5 3 2

5 3

Liste des successeurs pour le graphe G3

Chemins, chaînes, circuits, cycles


Soit G un graphe orienté.
Un chemin d'origine x et d'extrémité y est une suite finie non vide de sommets c = (s0, s1, s2 .,.., sp )
telle que s0,=x et sp=y et pour k= 0, 1, .., p-1, (sk , sk+1) est un arc du graphe. La longueur du
chemin est p ; c'est le nombre d'arcs (non nécessairement distincts) empruntés par ce chemin. Un
chemin est simple si les arcs sont deux à deux distincts et il est élémentaire si ce sont les sommets
qui sont deux à deux distincts... Si p est 1 et que s0= sp le chemin est un circuit (il revient au
départ).

Dans le cas de graphes non orientés, l'équivalent d'un chemin est une chaîne et celui d'un circuit est
un cycle. Un graphe non orienté est connexe si pour tout couple de sommets il existe une chaîne
ayant ces deux sommets pour extrémités. Autrement dit on peut aller de tout sommet du graphe à
tout autre... Par extension un graphe orienté est connexe si sa version non orientée (obtenue en
supprimant les orientations et les boucles) est connexe. La connexité est une relation d'équivalence
entre les sommets du graphe et ses classes d'équivalences sont appelées composantes connexes du
graphe.

Fermeture transitive d'un graphe


La fermeture transitive d'un graphe G est le graphe G' où deux sommets si et sj sont reliés par un arc
si et seulement si il existe un chemin dans G allant de si à sj .
Algorithme de fermeture transitive

Les méthodes de test d'existence d'un chemin entre deux sommets sont fondées sur le lemme de
König qui assure que s'il existe un chemin entre deux sommets d'un graphe, il en existe alors un de
longueur inférieure ou égale à N, nombre de sommets du graphe. Autrement dit il existe un chemin
élémentaire...

Jean Fruitet - IUT de Marne La Vallée - 108


Introduction à la programmation

2
4

1
5

Graphe G'1, fermeture transitive du graphe orienté G1

Arcs initiaux
Arc créés par transitivité
Pour calculer la fermeture transitive d'un graphe, nous allons définir d'abord deux opérations sur
les matrices d'adjacence à valeurs booléennes :
Soient deux matrices carrées M=(mi,j) et N=(ni,j) d'ordre N à valeurs booléennes {0,1} ; on définit
leur somme S et leur produit P par les formules :
si,j = MIN (1, mi,j+ ni,j)
pi,j = MIN (1, (m i,k x nk,j)1kn )
Pour calculer la fermeture transitive d'un graphe G de N sommets associé à une matrice M, on
définit une suite de matrices d'ordre N, à valeurs dans {0,1} définie par :
M1 = M
Mp = M x Mp-1
On montre qu'il existe un chemin de longueur k entre deux sommets i et j de G si et seulement si on
a Mk(i,j)=1.
Cette remarque combinée avec le lemme de König fournit un algorithme : la somme booléenne des n
matrices M1, M2 ... Mn n'est autre que la matrice associée à la fermeture transitive de G.

Exercice 1
Ecrire un programme qui posséde les fonctionnalités suivantes :
- Saisie de la matrice d'adjacence associée à un graphe G.
- Affichage de la matrice de la fermeture transitive de G.
- Réponse à la question : “Existe-t'il un chemin de longueur k entre les sommets i et j ?”.

Exercice 2 et le parcours du graphe le long de leurs arcs en suivant


1) Saisie d'un graphe l'orientation de ceux-ci.
2) Affichage d'un graphe
3) Coloriage Question 1
Représenter graphiquement le graphe G1 = (S1,A1)
1) Saisie. S1=[1..8]
Un graphe orienté G est un ensemble de sommets S et A1={(1,2), (2,1), (2,4), (3,3), (3,5), (4,4), (5,3), (5,5),
un ensemble d'arcs A. (6,1),
Les sommets sont donnés sous forme d'une liste de (6,2), (6,4), (6,7)}
l'intervalle [1..n], n nombre de sommets. Les arcs sous
forme d'une liste de couples (a,b), avec a sommet Plusieurs méthodes permettent de représenter un
origine et b sommet extrémité de l'arête. graphe :
Exemple a) Liste (ou table) de sommets et d'arcs (comme ci-
G = (S,A) avec S=[1..6] A={(1,2), (2,5), (3,2), (3,6), dessus)
(4,3), (5,6)} b) Matrice d'adjacence
Les principales opérations sur les graphes orientés sont c) Liste d'adjacence
la lecture des étiquettes associees aux sommets ou aux
arcs, l'insertion ou la suppression de sommets et d'arcs, Question 2

Jean Fruitet - IUT de Marne La Vallée - 109


Introduction à la programmation
Représentez les graphes G et G1 par une matrice
d'ajacence et par une liste d'adjacence. Question 6
Combien de couleurs nécessite le coloriage de G1 ?
Question 3 Question 7
Définir le type type_sommet du sommet d'un graphe Ecrire les fonctions C de coloriage d'un graphe d'au
orienté. plus MAXSOMMET et MAXARETE representé par
Définir le type type_indice de la position d'un sommet a) Tableau d'arêtes
dans la liste des sommets adjacents a un sommet du b) Matrice d'ajacence
graphe. c) Liste d'adjacence
Ces fonctions devront fournir pour chaque sommet le
Question 4 numéro de couleur attribué et le nombre de couleurs
Ecrire les fonctions C de saisie d'un graphe d'au plus utilisées.
MAXSOMMET et MAXARETE représenté par Question 8
a) Tableau d'arcs Proposer une heuristique pour rechercher un coloriage
type_sommet sommets_graphe[MAXSOMMET]; optimal...
type_sommet aretes_graphe[MAXARETE][2] Données
b) Matrice d'ajacence Le graphe des carrefours proposé en cours fera
type_sommet l'affaire...
mat_adj[MAXSOMMET][MAXSOMMET];
c) [FACULATIF] liste d'adjacence Exercice 3
typedef struct arete{type_sommet a; type_sommet b;
struct arete *suivant;}arete; 1) Saisie d'un graphe
arete *liste_adj[MAXSOMMET]; 2) Parcours en profondeur
3) Parcours en largeur
On prendra soin de définir les fonctions de base de
manipulation des graphes pour chacune des Un graphe orienté G est un ensemble de sommets S et
implantations : un ensemble d'arcs A.
type_indice premier(type_sommet s); Les sommets sont donnés sous forme d'une liste de
retourne l'indice du premier sommet rencontre dans la l'intervalle [1..n], n nombre de sommets. Les arcs sous
liste des sommets forme d'une liste de couples (a,b), avec a sommet
adjacents à s (son indice est 0 dans cette liste si on origine et b sommet extrémité de l'arête.
utilise une liste d'ajacence).
type_indice suivant(type_sommet s, type_indice i); Représentez graphiquement le graphe G2
retourne l'indice du sommet adjacent à s qui suit le G2 = (S2,A2)
sommet d'indice i dans la liste des sommets adjacents à S2=[1..7]
s. A2={(1,2), (2,1), (2,4), (3,3), (3,5), (4,3), (4,6),
type_sommet sommet(type_sommet s, type_indice (5,3), (5,5), (6,1), (6,2), (6,7)}
i);
retourne le sommet d'indice i dans la liste des sommets 1) Implantation d'un graphe
adjacents à s.
Question 1
2) Affichage. Définir le type type_sommet du sommet d'un graphe
L'affichage du graphe est destiné à vérifier la qualité de orienté.
la saisie. Le programme devra aussi afficher les Définir le type type_indice de la position d'un sommet
sommets isolés... dans la listedes sommets adjacents à un sommet du
Question 5 graphe.
Ecrire les fonctions C d'affichage de la liste des arcs Définir les fonctions de base de manipulation des
d'un graphe d'au plus MAXSOMMET et MAXARETE graphes pour la matrice d'adjacence et la liste
representé par d'adjacence...
a) Tableau d'arcs
b) Matrice d'ajacence type_indice premier(type_sommet s);
c) Liste d'adjacence retourne l'indice du premier sommet rencontré dans la
liste des sommets
3) Coloriage. adjacents à s (son indice est 0 dans cette liste si on
Colorier un graphe non orienté consiste à attribuer une utilise une liste d'ajacence).
couleur à chaque sommet de telle sorte que deux type_indice suivant(type_sommet s, type_indice i);
sommets adjacents n'aient pas la même couleur... Un retourne l'indice du sommet adjacent à s qui suit le
coloriage optimal est celui qui nécessite le moins de sommet
couleurs distinctes... d'indice i dans la liste des sommets adjacents à s.
Exemple : le graphe G nécessite 2 couleurs.

Jean Fruitet - IUT de Marne La Vallée - 110


Introduction à la programmation
type_sommet sommet(type_sommet s, type_indice représentation par matrice d'adjacence et pour une
i); représentation par liste d'adjacence. Cette fonction
retourne le sommet d'indice i dans la liste des sommets affichera les sommets visités dans l'ordre préfixe et les
adjacents à s. arcs.

2) Parcours en profondeur Question 4


Ecrire la fonction de parcours en largeur d'abord pour
Question 2 les deux modes de représentation.
Donner la liste des sommets visités dans un parcours en
profondeur d'abord en visitant les sommets de 1 à 7. Question 5
Ecrire une fonction détectant les circuits dans un
Question 3 graphe.
Ecrire la fonction de parcours en profondeur en
utilisant les fonctions définies au 1 pour une

Jean Fruitet - IUT de Marne La Vallée - 111


Introduction la programmation

Tris
La recherche d’un élément dans un ensemble est bien améliorée si les éléments sont ordonnés.
C’est en particulier le cas des recherches de mots dans un dictionnaire. Ordonner les élément
consiste à effectuer une permutation entre les éléments de sorte qu’après celle-ci les éléments soient
classés —triés— en croissant (respectivement décroissant)...

Données :
N, nombre fini des éléments.
Les éléments sont tous du même type et occupent un espace mémoire fini.
Les données sont par exemple représentées dans un tableau.

a[0], a[1], ..., a[N-1] est la liste initiale


b[0], b[1], ..., b[N-1] est la liste finale telle que b[0]< b[1] <... < b[N-1].
P est une permutation qui ordonne les éléments :
b[i] = a[P(i)] avec P une permutation de [0, N[.

Pour effectuer le réarrangement, il faut disposer d’une relation d’ordre total sur les éléments.
Relation d’ordre
Une relation d’ordre notée R sur les éléments d’un ensemble E est une relation binaire ayant les
propriétés :
Réflexivité : aRa
Transitivité : si aRb et si bRc alors aRc
Antisymétrie : si aRb et si bRa alors a=b.
En général une relation d’ordre est notée <. Elle est d’ordre total si tous les éléments de l’ensemble
sont comparables deux à deux.

Pour les données complexes, la relation d’ordre sera définie sur une clé, c’est-à-dire sur une liste
d’attributs qui identifient de façon unique chaque élément de l’ensemble.

Exemples de relations d’ordre


Droite réelle D.
On peut définir un ordre sur la droite euclidienne en choisissant un point O appelé origine et un
vecteur unitaire u lié à l’origine. Cela définit sans ambigüité un sens de parcours.
A chaque point M on associe son abscisse réelle x, c’est-à-dire la mesure algébrique du vecteur OM
dans le repére (O,u). La relation d’ordre sur IR induit une relation d’ordre entre les points de la
droite.
M1 “avant ou confondu avec ” M2 ssi x1x2.

La droite euclidienne D

M2 IR
u
O x2
M1
x1

Jean Fruitet - IUT de Marne La Vallée - 112


Introduction la programmation
Plan euclidien
On peut définir une relation d’ordre entre les points du plan à deux dimension P de plusieurs façons.
Par exemple en utilisant des coordonnées polaires.
Avec un système de coordonnées cartésiennes, M[x, y], par rapport à un repére (O, u, v) avec u et v
unitaires non colinéaires, il n’est pas évident qu’on puisse déterminer une relation d’ordre unique.

La figure montre que la relation d’ordre des projections sur une seule droite —par exemple OX
(droite orientée par u)— ne suffit pas à ordonner sans ambiguité les points M1, M2 et M3.
Par contre en combinant la relation d’ordre sur les projections sur OX avec une relation d’ordre sur
les projections sur OY, on peut induire un ordre total sur P.
Selon OX : M1 < M3 < M2
Le plan euclidien X
M2
y3 IR
y2 u x2
M3 O
v
x1=x3
y1

M1 Y
IR
Selon OY : M3 < M2 < M1
Pour ordonner le Plan euclidien il faut définir un ordre
total : par exemple
ordre selon OX, et si les abscisses sont égales, ordre
selon OY.
En ce cas M3 < M1 < M2

Fiche de recensement
Un recensement porte sur une population. Chaque élément est représenté par une fiche : (Numéro,
Nom, Prénom, Date de naissance, Adresse, Sexe).

Plusieurs ordres sont possibles, selon le numéro, selon le nom et le prénom, etc.
Il importe de définir précisément quelle sera la clé puis on induira l’ordre sur les objets à partir de
l’ordre sur les clés.

Opérations élémentaires pour le tri.


Toute opération de tri implique de disposer d’une fonction de comparaison entre deux éléments
(ordre des clés), et d’une fonction d’échange (permutation) entre éléments.
Algorithmes de tri
Les algorithmes de tri ont fait l’objet de nombreuses recherches. Ils différent principalement par :
- la simplicité de mise en oeuvre,
- l’efficacité.
En général ce sont deux contraintes contradictoires. Plus un algorithme est efficace, plus il est
sophistiqué et délicat à implanter.
Nous commencerons par présenter des algorithmes dits “naïfs” car très immédiats.
Tri par sélection
Soit une liste L quelconque, et une liste vide T.
L= {-5, 3, 2, 4, 1} ; T= {}.

Jean Fruitet - IUT de Marne La Vallée - 113


Introduction la programmation
Principe :
Pour trier L, retirer l’élément minimum de L,
L = L - {MIN(L)};
et le placer à la fin de la liste triée T.
T= T U {MIN(L)};
Recommencer l’opération jusqu’à ce que L soit vide.
La liste résultante T est triée...
Exemple
L= {-5, 3, 2, 4, 1}; T= {};
L= { 3, 2, 4, 1}; T= {-5};
L= {3, 2, 4 }; T= {-5, 1};
L= {3, 4 }; T= {-5, 1, 2};
L= {4}; T= {-5, 1, 2, 3};
L = {}; T= {-5, 3, 2, 4, 1};

Programme C :

void tri_selection(typelement a[], int N)


{
int i,j;
int mini; /* indice du minimum de la liste */
for (i=0; i<N-1; i++)
{
mini=i;
for (j=i+1; j<N; j++)
if (a[j]<mini)
mini=j;
echange(&a[i],&a[mini]);
}

Complexité de l’algorithme.
La recherche du minimum d’une liste non triée de N éléments nécessite de parcourir toute la liste et
N-1 comparaisons. La longueur de la liste diminue à chaque itération. Donc le nombre de
comparaisons est donné par :
(N-1) + (N-2) + .... + 1, soit N (N-1) / 2 comparaisons.
L’algorithme naïf est en O(N2) dans tous les cas. S’il y a 1000 éléments, cela fait un million de
comparaisons !

Cette complexité peut être améliorée si la structure de données utilisée permet une recherche
accélérée de l’élément minimum. C’est le cas du tri maximier ou tri par tas.

Tri maximier ou tri par tas [Heap sort]


Le tri par tas est aussi un tri par sélection, sur place, dont l’efficacité est due à la structure de
données astucieuse, un arbre maximier. Sa complexité est O(n log n).

Principe :
On opére en deux étapes :
1. On fabrique un tas [heap], c’est-à-dire un arbre binaire partiellement ordonné dit arbre “maximier”
qui peut être représenté par un tableau tel que a[i] a[2i] et a[i] a[2i+1] pour 1iN. Si on
représente les éléments 2i et 2i+1 comme les fils du noeud i, alors a est un arbre binaire équilibré
pour lequel la clé d’un noeud n’est jamais dépassée par les clés de ses descendants. Pour cela on
tamise successivement a[N/2], ..., a[1].
Jean Fruitet - IUT de Marne La Vallée - 114
Introduction la programmation
2. Puis on extrait successivement le maximum de l’arbre, qui par construction se trouve en a[1] en
l’échangeant avec a[M] pour M = N, N-1, ..., 1. Et comme cette opération a supprimé la propriété
de tas, on la rétablit en tamisant le nouveau a[1].

Arbre maximier
a[i] • a[2i] et a[i] • a[2i+1]
1 99 1 99
60 2
2 60 3 59 3 59
40 4
4 40 5 50 6 29 7 39
5 50
29 6
8 10 9 20 10 20 11 30 12 19 13 9 14 39 15 19
7 39
10 8
9 20
20 10
11 30

Le tamisage
Le tamisage de a[k] consiste à échanger itérativement a[k] avec le maximum de a[2k] et de a[2k+1]
jusqu’à ce que la condition de tas soit satisfaite.

La procédure tamiser(k, M) insére a[k] parmi a[k] ... a[M] :

Exemple :
Tamisage Heap sort Liste triée
1 24 24 81 81 5 45 45 2 24 2 5 2 2
2 5 45 45 45 45 5 5 5 5 5 2 5 5
3 81 81 24 24 24 24 24 24 2 24 24 24 24
4 2 2 2 2 2 2 2 45 45 45 45 45 45
5 45 5 5 5 81 81 81 81 81 81 81 81 81

Programme C :
for (k= (N/2)-1; k>=0; k- -)
void tamiser(int k; int M) tamiser(k, N);
{ for (k=N-1; k>0; k- -)
int j, v; {
v = a[k]; echange(&a[0], &a[k]);
while (k<= M/2) tamiser(0,k);
{ }
j = 2*k; }
if ((j<M) && (a[j+1] > a[j]))
j++; Complexité :
if (v>=a[j]) O(n log n).
break;
a[k] = a[j]; /* Tri du TAS ou tri maximier [Heap
k = j; sort] */
} /* JF */
a[k] = v; #include <stdio.h>
} #include <stdlib.h>

void heapsort(int N) #define MAXELEMENT 10


{
int k; int T[MAXELEMENT + 1];
Jean Fruitet - IUT de Marne La Vallée - 115
Introduction la programmation
}
void echanger(int *a, int *b) }
{
int temp;
temp=*a; void TriTas(int n)
*a=*b; /* tri par tas de n elements ranges
*b=temp; dans le tableau T */
} {
int i;
void range_tas(int premier, int for (i=n /2; i>=1; i--)
dernier) range_tas(i,n);
/* Suppose que T[premier], ..., for (i=n ; i>=2; i--)
T[dernier] possedent la propriete des {
maximiers, sauf peut-etre pour les echanger(&T[1], &T[i]);
fils de T[premier] */ range_tas(1, i-1);
/* La procedure fait descendre }
T[premier] jusqu'a restauration de }
l'ordre partiel sur tout l'arbre */
{
int r; /* ------------------------- MAIN ---
r=premier; --------------- */
while (r <= dernier / 2) void main (void)
{ {
if (dernier == 2*r) int i;
{ srand (1);
if (T[r]>T[2*r]) for (i=1; i<=MAXELEMENT; i++)
echanger(&T[r], &T[2*r]); {
r = dernier; T[i]=rand();
} }
else if ((T[r]>T[2*r]) &&
(T[2*r]<=T[2*r+1])) printf("\nLISTE A TRIER ------\n");
{ for (i = 1; i<=MAXELEMENT; i++)
echanger(&T[r], &T[2*r]); printf("%d ",T[i]);
r = 2*r; printf("\n--------------------\n");
}
else if ((T[r]>T[2*r+1]) && TriTas(MAXELEMENT);
(T[2*r+1]<T[2*r]))
{ printf("\nLISTE TRIEE ------\n");
echanger(&T[r], &T[2*r+1]); for (i = MAXELEMENT; i>=1; i--)
r = 2*r+1; printf("%d ",T[i]);
} printf("\n--------------------\n");
else }
r = dernier; /* sortie du
while */

Tri à bulle
Le tri à bulle (buble sort) consiste à comparer les éléments consécutifs et à les échanger si l'ordre
recherché est violé (les bulles légéres remontent vers le haut).
Le tri peut se faire sur place, et la fin du tri survient quand plus aucun échange n’a eu lieu au cours
d’une traversée de la liste.

Principe :
Faire venir en a[i] l’élément minimum de a[i], a[i+1], ..., a[N-1] par échange d’éléments adjacents.
Exemple :
0 5 2 2 2 2 1 1
1 2 5 1 1 1 2 2
2 1 1 5 3 3 3 3
3 3 3 3 5 4 4 4
4 4 4 4 4 5 5 5

Programme C :
Jean Fruitet - IUT de Marne La Vallée - 116
Introduction la programmation
for (i=0; i<N-1; i++)
for(j=N-1; j>i; j--)
{
if (a[j]<a[j-1])
echange(&a[j-1],&a[j]);
}

Exercice
- Ecrire un algorithme de tri à bulle amélioré en détectant la fin du tri (aucun échange n'a eu lieu lors
de la dernière passe) et en ne repassant pas sur les valeurs qui sont déjà ordonnées.
- Quelle est sa complexité dans le meilleur des cas, dans le pire des cas, et en moyenne ?

Tri par insertion

Principe : A la i-éme étape, insérer a[i] parmi a[1], a[2], ..., a[i-1] déjà triés.

Exemple :
Indice
1 2 3 4
0 5 2 1 1 1
1 2 5 2 2 2
2 1 1 5 3 3
3 3 3 3 5 4
4 4 4 4 4 5

Programme C:
void tri-insertion(typelement a[], int N)
{
typelement v; /* une variable auxiliaire */
int i,j;
for (i=1; i<N; i++)
{
v=a[i];
j=i;
while ((j>0) && (a[j-1]>v))
{
a[j]=a[j-1];
j--;
}
t[j]=v;
}
}

Complexité : O(N2) au pire des cas et en moyenne.

Tris sur les Listes


Une liste est une structure de données constituée de cellules chaînées les unes aux autres par
pointeurs. Une cellule est un enregistrement :

typedef struct cellule {


int val;
struct cellule *suiv ;
} *liste;
Jean Fruitet - IUT de Marne La Vallée - 117
Introduction la programmation

La tête de liste est désignée par un pointeur tête de type liste ; c’est une cellule qui est chaînée aux
cellules suivantes par l'intermédiaire du pointeur suiv.
La dernière cellule n'a pas de successeur. Elle pointe sur NULL.

tête

val val
val

cellule cellule ... NULL

On dira qu'une liste de valeurs <n1, n2, ..., nk > est triée si n1<=n2 <= ...<= nk .

Tri par insertion


- Ecrire une fonction liste insere( liste l, int x )
qui, si on l'appelle avec les paramètres x et l pointant sur la liste <n1, n2, ..., nk > triée, rend un
pointeur sur une liste de la forme <n1, n2, ...,ni, x, ni+1, ..., nk > elle aussi triée, et laissant
intacte la liste l. On écrira cette fonction de façon récursive.

Exercice 2 : Tri par insertion et modification


- Ecrire une fonction void insere_modifie( liste l, int x )
qui, si on l'appelle avec les paramètres x et l pointant sur la liste <n1, n2, ..., nk > triée, rend la liste l
modifiée et triée de la forme <n1, n2, ...,ni ,x, ni+1, ..., nk >. On écrira cette fonction de façon
itérative. Justifier l’utilisation d’un pointeur sur l..

Exercice 3 : Fonction de tri récursive


- Ecrire une fonction de tri : liste tri_ins ( liste l )
qui fonctionne de la façon suivante : si son argument est vide, elle rend la liste vide. Sinon elle insére
(au moyen de la fonction insere) la tête de son argument (premier entier dans la liste) dans la liste
obtenue en triant (en utilisant tri_ins) la queue de son argument (suite de la liste passée en
argument).

Exercice 4 : Fonction de tri itérative


Ecrire une fonction de tri :liste tri_ins_iterative (liste l )
de façon itérative qui utilise une liste auxiliaire, initialement vide, à laquelle elle ajoute
successivement au moyen de la fonction insere_modifie tous les éléments de son argument, qu'elle
parcourt jusqu'au bout. Puis elle retourne cette liste auxiliaire.

Exercice 5 : Complexité
- Que pensez-vous de ces deux fonctions de tri, du point de vue de la clarté des algorithmes
(comment prouver leur correction) ?
- Du point de vue de la consommation d'espace mémoire ?
- Du point de vue du nombre de comparaisons ?

Jean Fruitet - IUT de Marne La Vallée - 118


Introduction la programmation
Shellsort
Le shell sort est un tri par insertion, mais au lieu de placer chaque élément à sa place dans la sous-
liste triée de tous les éléments qui le précédent, on le place dans une sous-liste d'éléments qui le
précédent distants d'un certain incrément incr, que l'on diminue au cours de passages successifs sur
la liste. Cela a pour effet de déplacer très rapidement les éléments très éloignés de leur position
finale.
Au premier passage on crée des sous-listes d'éléments distants de n/2 qu'on trie séparément ; au
2éme passage des sous-listes d'éléments distants de n/4, qu'on trie à leur tour séparément, etc. A
chaque passage, l'incrément est divisé par deux et toutes les sous-listes triées par insertion
séquentielle. Le tri s'arrête lorsque l'incrément est nul.

- Montrer que si deux éléments t[i ] et t[i+n/2k ] ont été échangés au kiéme passage, alors ils restent
triés par la suite.
- Programmer un shell-sort sur un tableau tiré aléatoirement de 1000 valeurs. Comparer avec le tri à
bulle sur le même ensemble de données (nombre de comparaisons et d’échanges).

Tri par fusion

Ce tri procéde suivant le principe "diviser pour résoudre".


On doit disposer de deux fonctions récursives :
1) Découpage récursif
Une fonction récursive dont la déclaration est :
void decoupe(liste l, liste p, liste i )
telle qu'après l'appel sur des arguments l = <n1, n2, ..., nk > , p, i, la variable p contienne la liste <n2
,n4, ...> des éléments de rang pair de l, et i contienne la liste <n1, n3, ....> des éléments de rang
impair de l..

2) Fusion
Une fonction dont la déclaration est :
liste fusion ( liste n , liste m)
telle que le résultat de l'appel de fusion sur deux listes triées (hypothése essentielle) n = <n1, n2, ...,
nk > et m = <m1, m2, ..., mp > soit une liste triée de longueur k+p contenant tous les éléments de n
et de m (autrement dit une permutation triée de la liste <n1, n2, ..., nk , m1, m2, ..., mp >). Mais
attention, on veut que le nombre de permutations réalisées pour effectuer cette opération soit borné
par k+p.

Cette fonction pourra, au choix, être écrite de façon récursive ou itérative.

3) Tri par fusion


A l'aide des deux fonctions précédentes écrire la fonction récursive de tri par fusion :
liste tri_fusion (liste l )
dont le principe est le suivant : si l'argument est vide ou n'a qu'un seul élément, on retourne
l'argument comme résultat. Sinon on découpe l'argument en deux sous-listes au moyen de decoupe,
on rappelle tri_fusion sur chaque partie, et on obtient ainsi deux listes triées l et l' (expliquer
pourquoi ?)
On applique la fonction fusion à ces deux listes et le résultat obtenu est, si fusion est correctement
écrite, une liste triée.

Jean Fruitet - IUT de Marne La Vallée - 119


Introduction la programmation
Tri rapide [quick-sort ]

Ce tri procéde aussi comme le précédent selon le principe "diviser pour résoudre". Il différe
essentiellement par la phase "diviser";

1) Découpage ordonné
- Ecrire une fonction de découpage :
void decoupe_ordre ( liste l, int x , liste i, liste s)
telle que, après appel sur les arguments l = <n1, n2, ..., nk > , x , i, s , la variable i contienne la liste
des éléments qui sont inférieurs ou égaux à x, et la variable s contienne la liste des éléments de l qui
sont strictement supérieurs à x. L'entier x est appelé pivot . Cette fonction opére donc une partition
de l.

2) Quick-sort.
- Ecrire une fonction récursive de tri :
liste tri_quick (liste l )
dont le principe est le suivant : si l'argument est vide, ou n'a qu'un seul élément, on rend l'argument
comme résultat. Sinon, l'argument est de la forme <n, n1, n2, ..., nk > avec (k>=1 ). On utilise la
fonction decoupe_ordre pour partitionner la liste <n1, n2, ..., nk > en utilisant n comme pivot. On
obtient alors deux listes. Appelons les l1 et l2 . Tous les éléments de l1 sont inférieurs ou égaux à n,
et tous ceux de l2 sont supérieurs à n.
On trie ces deux listes au moyen de tri_quick et on obtient deux listes triées l'1 et l'2 . On renvoie
alors la concaténation de l'1 et n .l'2 qui est une liste triée.

Complexité

Les algorithmes de tri à bulle sont en O(n*n). L’algorithme quick-sort en O(n log2(n)).

Jean Fruitet - IUT de Marne La Vallée - 120


Introduction la programmation

Bibliographie
Informatique et calcul numérique
Breton Ph., Dufourd G, Heilman E., "Pour comprendre l'informatique",Hachette, 1992
Cohen J.H., Joutel F., Cordier Y., Jech B. "Turbo Pascal, initiation et applications scientifiques",
Ellipses, 1989
Leygnac C., Thomas R. "Applications de l'informatique, études de thèmes en mathématiques,
physique et chimie", Bréal, 1990
Piskounov N., “Calcul différentiel et intégral”, Editions Mir, 2 tomes.

Algorithmique
Aho A., Hopcroft J., Ulman J., “Structures de données et algorithmes” - InterEditions 1987
Beauquier D., Berstel J., Chretienne Ph., “Eléments d’algorithmique” - Masson 1992.
Carrez C. “Des structures aux bases de données” - Dunod 1990.
Crochemore M. “Méthodologie de la programmation et algorithmique” - UMLV 1990.
Perrin D. "Cours d'Informatique DEUG SSM Module M2" - UMLV 1997
Programmation
PATTIS R. E. “Karel the Robot, a gentle intrduction to the art of programming” - John Wilez &
Sons, 1995.
Kerninghan B. , Ritchie D. "Le langage C" - Masson 1984
BORLAND, "Turbo C 2.0 "Manuel de l'utilisateur"
BORLAND, "Turbo C 2.0 "Manuel de référence"
Leblanc G., "Turbo C" - Eyrolles 1988
Charbonnel J., "Langage C - Les finesses d'un langage remarquable" - Armand Colin 1992

Jean Fruitet - IUT de Marne La Vallée - 121


Introduction la programmation

Table des matiéres

Introduction à la programmation informatique 1

Avertissement 3

Caractérisation d’un problème informatique 3

Démarche 4

Introduction à l’informatique 5

Langage 5
Traitement de l'information 5
Ordinateur 6
Un peu d'histoire 6
Ce qui caractérise un ordinateur 6
Matériel et logiciel 7

Le codage binaire 7

Représentation des informations en binaire 7


Passer du décimal au binaire 7
Passer du binaire au décimal 8
Opérations usuelles en binaire 8
Opérations logiques 8
Les nombres entiers et les nombres réels décimaux 9
Opérations sur les entiers 9
Implémentation des entiers non signés 10
Implémentation des entiers signés 10
Algorithme de conversion d'un nombre en binaire complément à 2 sur un octet : 10
Les nombres réels. 11
Opérations sur les réels : 11
La notation scientifique normalisée 11
Représentation des nombres réels en binaire 11
Décomposition d'un nombre réel décimal en binaire 12
Réels en double précision 12
Les nombres rationnels 12
Codage des informations non numériques 13
Textes 13
Les images 13
Les relations 13

Notion d’algorithme 14

Jean Fruitet - IUT de Marne La Vallée - 122


Introduction la programmation
Traduction de l’algorithme dans un langage de programmation 14
Types de données et structures de contrôle 15
Un langage de programmation graphique 15
Modéle d'ordinateur abstrait et langage de programmation 16
Variables et types 16
Types simples et types combinés 17
Opérations de bases 17
Les structures de contrôle du langage 17
Fonction factorisation d’un polynôme réel de degré 2 18
Conclusion 18

Le langage C. 19

Caractéristiques succintes du langage C 20


Evolutions 21
Structures en blocs 21
Variables locales et variables globales 21
Constantes et variables 23
Mots réservés 23
Types de données 23
Instruction d'affectation 24
Transtypage (cast) 24
Opérateurs 24
Opérateurs arithmétiques 24
Opérateurs de comparaison 24
Incrémentation et décrémentation 25
Structures conditionnelles 25
if / else 25
Conditions imbriquées 26
Regroupement d'instructions 26
Affectation conditionnelle 26
Sélection (switch) 26
Boucles et sauts 27
Exit 27
Tableaux 28
Tableaux à une dimension 28
Affectation 28
Chaînes de caractères 28
Tableaux à plusieurs dimensions 28
Pointeurs 29
Déclaration de pointeur 29
Opérateur adresse (&) 29
Opérateur valeur (*) 29
Pointeurs et tableaux 29
Tableaux de pointeurs 30
Allocation dynamique de mémoire 30

Jean Fruitet - IUT de Marne La Vallée - 123


Introduction la programmation
Programme principal et fonctions 31
Déclaration de fonction avec prototypage 31
Définition 31
Arguments et valeur retournée. 31
Pointeurs et arguments de fonctions 33
Fonctions récursives 34
Arguments sur la ligne de commande 34
Structure 35
Déclaration 35
Affectation 35
Union 35
Directives de compilation 36
Typedef 36
#define 36
#include 36
Directives de compilation 36
Les entrées/ sorties et les fichiers 37
Fonctions d'entrées / sorties 37
Fichiers de données 38
Compilation 41
1°) Précompilation 41
2°) Compilation (compile) 41
3°) Liaison (link) 41
Interdépendance de fichiers 41

Processus itératifs. 43
1. Rappels mathématiques 43
2. Types de données et algorithmes 43

Fonctions et sous-programmes 44

Notion de complexité. Amélioration d’un algorithme. 48

Des données aux structures de données 50

Tableaux. 50
Déclaration d’un tableau. 51
Tableau de tableaux 51
Assignation à un tableau 52
Calcul matriciel () 53
Opérations sur les matrices 53
Résolution d'un système d'équations (méthode de Cramer) 55
Calcul du déterminant 58
Inversion de matrice 58

Calcul numérique et programmation de fonctions mathématiques 61

Calcul des termes d’une suite et problèmes de débordement 64


Jean Fruitet - IUT de Marne La Vallée - 124
Introduction la programmation
Conjecture polonaise 64
Fonctions récursives 64
Factorielle 64
Exercices de programmation numérique 65
• Surface et volume d’une sphére 65
• Polynôme 65
• Racine carrée d’un nombre réel positif 65
• Surface d’un triangle 65
• Limite d’une suite 65
• Valeur approchée de π2/6 66
• Valeur approchée de log(n) 66
• Termes d’une suite 66
• Puissance n-iéme d’un nombre 67
• Nombre d’or 67
• Décomposition d’un cube en somme de nombres impairs 67
Résolution de f(x)=0 () 68
Méthode dichotomique 68
Méthode du balayage 69
Méthode des parties proportionnelles 70
Méthode de Newton 70
Intégration numérique 72
n−1
b x k+1
∫a f (t)dt = ∑ ∫x k
f (t)dt
k =0 72
Méthode des rectangles 72
Méthode des trapèzes 72
Interpolation () 73

Structures de données 76

Structures 76
Exercices 76
Ensembles 79
Définition 79
Opérations sur les ensembles 79
Représentation des ensembles : tableaux, listes, piles, files 79
Arbres et ensembles ordonnés 95
Exemples d’arbres 95
Représentation symbolique des arbres 96
Définition axiomatiques des arbres 97
Représentation informatique 98
Implantation en Langage C 98
Graphes 105
Définition 105
Graphe non orienté 105
Propriétés 105
Implémentation d'un graphe 106
Matrice d'adjacence 106
Jean Fruitet - IUT de Marne La Vallée - 125
Introduction la programmation
Matrice d'incidence 107
Liste des successeurs ou liste d'adjacence 108
Chemins, chaînes, circuits, cycles 108
Fermeture transitive d'un graphe 108

Tris 112

Relation d’ordre 112


Exemples de relations d’ordre 112
Droite réelle D. 112
Plan euclidien 113
Fiche de recensement 113
Opérations élémentaires pour le tri. 113
Algorithmes de tri 113
Tri par sélection 113
Tri maximier ou tri par tas [Heap sort] 114
Tri à bulle 116
Tris sur les Listes 117
Tri par insertion 118
Shellsort 119
Tri par fusion 119
Tri rapide [quick-sort ] 120
Complexité 120

Bibliographie 121

Informatique et calcul numérique 121


Algorithmique 121
Programmation 121

Table des matiéres 122

Jean Fruitet - IUT de Marne La Vallée - 126

Vous aimerez peut-être aussi