Vous êtes sur la page 1sur 101

Informatique pour Tous

Cours et exercices

Kamal Haïdar,
Lycée Janson-de-Sailly,
kamal.m.haidar@gmail.com
Table des Matières

I Démarrer en Python 1

1 Programmation impérative en Python 3


1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.1 Notion d’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.2 Présentation de Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.3 Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2 Les types usuels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.1 Le type bool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.2 Les types numériques : int, float, complex . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.3 Le type str . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.4 Le type list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.2.5 Le type tuple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3 Les variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.3.1 Notion de référence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.3.2 Règles pour nommer une variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3.3 Compléments sur les références . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.4 Les fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4.1 Généralités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.4.2 Portée des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.5 Les test conditionnels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.6 Les structure de boucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.6.1 Les boucles itératives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
1.6.2 Les boucles conditionnelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

2 Présentation de modules usuels 21


2.1 Module random . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.2 Module time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.3 Module matplotlib . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.4 NumPy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.4.1 Le type array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.4.2 Quelques opérateurs d’algèbre linéaire . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.4.3 Fonctions universelles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.5 SciPy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28

iii
II Machines numériques 29

3 Machines numériques 31
3.1 Description d’une machine numérique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.1.1 L’ordinateur personnel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
3.1.2 La tablette . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
3.2 Le système d’exploitation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.2.1 Description . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.2.2 Structure de fichiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3.3 Compléments sur les différents types de mémoire . . . . . . . . . . . . . . . . . . . . . . . 33

4 Représentation des nombres en machine 35


4.1 Représentation binaire en mathématiques . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.1.1 Représentations binaire et décimale . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.1.2 Bit, octet, byte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.2 Représentation des nombres entiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.2.1 Représentations des entiers non signés et signés - Limites . . . . . . . . . . . . . . 36
4.2.2 Représentation des entiers en complément à deux . . . . . . . . . . . . . . . . . . . 37
4.3 Représentation de nombres réels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.3.1 Représentation des nombres binaires à virgule . . . . . . . . . . . . . . . . . . . . . 39
4.3.2 Représentation en virgule flottante - Norme IEEE 754 . . . . . . . . . . . . . . . . 39
4.3.3 Approximations et erreurs dans la représentation en virgule flottante . . . . . . . . 41
4.4 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

III Algorithmique 45

5 Algorithmes classiques 47
5.1 Statistiques élémentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.1.1 Moyenne d’une série statistique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.1.2 Variance d’une série statistique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.2 Algorithmes de recherche séquentielle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.2.1 Recherche du maximum dans un tableau de nombres . . . . . . . . . . . . . . . . . 48
5.2.2 Recherche d’un élément dans un tableau . . . . . . . . . . . . . . . . . . . . . . . . 49
5.2.3 Recherche dichotomique dans un tableau trié . . . . . . . . . . . . . . . . . . . . . 50
5.2.4 Recherche d’un mot dans une chaine de caractères . . . . . . . . . . . . . . . . . . 51

6 Analyse algorithmique 53
6.1 Terminaison et correction d’algorithmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6.1.1 Le problème de l’arrêt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6.1.2 Terminaison . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
6.1.3 Correction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.2 Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.2.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
6.2.2 Complexité asymptotique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.2.3 Classe de complexité asymptotique d’un algorithme . . . . . . . . . . . . . . . . . . 56
6.3 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

iv
IV Bases de données relationnelles 63

7 Bases de données relationnelles 65


7.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
7.1.1 Inadéquation des structures de données “plates” . . . . . . . . . . . . . . . . . . . . 65
7.1.2 Représentation de données . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
7.2 Modèle de données relationnelle - vocabulaire . . . . . . . . . . . . . . . . . . . . . . . . . 66
7.3 Algèbre relationnelle et instructions MySQL . . . . . . . . . . . . . . . . . . . . . . . . . . 68
7.3.1 La sélection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
7.3.2 La projection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7.3.3 Le produit cartésien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
7.3.4 La réunion, l’intersection et la différence . . . . . . . . . . . . . . . . . . . . . . . . 70
7.3.5 La jointure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
7.3.6 La division . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
7.3.7 Les fonctions d’agrégation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
7.4 Systèmes de gestion de base de données (SGBD) . . . . . . . . . . . . . . . . . . . . . . . 74
7.4.1 L’architecture client/serveur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
7.4.2 L’architecture 3-tiers (ou trois couches) . . . . . . . . . . . . . . . . . . . . . . . . 75
7.5 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

V Analyse numérique 77

8 Résolution d’équations 79
8.1 Algorithme de recherche du zéro d’une fonction monotone . . . . . . . . . . . . . . . . . . 79
8.2 Méthode de Newton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80

9 Méthodes d’intégration numérique 81


9.1 Méthode des rectangles et sommes de Riemann . . . . . . . . . . . . . . . . . . . . . . . . 81
9.2 Méthode des trapèzes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82

10 Équations différentielles - Méthode d’Euler 83


10.1 Équations différentielles ordinaires d’ordre 1 . . . . . . . . . . . . . . . . . . . . . . . . . . 83
10.2 Présentation de la méthode d’Euler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
10.3 Implémentation de la méthode scalaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
10.4 Implémentation de la méthode vectorielle . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
10.5 Propriétés et limites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
10.6 Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89

11 Résolution de systèmes linéaires - Pivot de Gauss 91


11.1 Opérations élémentaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
11.2 Algorithme du pivot de Gauss . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
11.2.1 Phase de descente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
11.2.2 Phase de remontée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
11.2.3 Algorithme du pivot de Gauss . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
11.3 Complexité asymptotique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
11.4 Annexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95

v
vi
Partie I

Démarrer en Python

1
Chapitre 1
Programmation impérative en Python

1.1 Introduction
1.1.1 Notion d’algorithme
Définition 1.1.1
Un algorithme est une séquence finie et ordonnée d’opérations qui, prenant un ensemble (fini) de
valeurs en entrée, produit un ensemble (fini) de valeurs en sortie.

Un algorithme répond alors à un problème (de calcul, de décision, etc), en ayant connaissance de l’entrée
et de la sortie.
Exemple 1.1.2
Une recette de cuisine, une notice de montage d’un meuble sont des algorithmes.
L’entrée est l’ensemble des ingrédients (resp. l’ensemble des matériaux à assembler) et la sortie est le
plat cuisiné (resp. le meuble monté).

Définition 1.1.3
Un langage de programmation est un ensemble de notations destinées à formuler des algorithmes.
Il est muni d’une orthographe, d’un vocabulaire et d’une grammaire.

1.1.2 Présentation de Python


Python est un langage de programmation :

• impérative structurée, paragdigme de programmation qui décrit ordonne les instructions en


séquences pour modifier l’état du programme,

• orienté-objet, tout est objet : une variable peut contenir une référence à un élément quelconque
du langage (nombre, méthode, module, ...),

• de haut-niveau, plus proche du langage algorithmique que du langage machine,

• interprété, traduit ligne par ligne en langage machine, au moment de l’exécution, contrairement
aux langages compilés qui traduisent les programmes en une seule fois,

• à typage dynamique, on ne précise pas le type des variables lors de leur définition, il est inféré
par l’interpréteur,

• multi-plateforme et portable (Windows, Unix, etc),

• gratuit (même en projet commercial),

• évolutif (la licence est libre).

3
1.2 Les types usuels Chapitre 1. Programmation impérative en Python

Exemple 1.1.4
Python est utilisé par Google, Industrial Light & Magic (Lucas Film), la NASA, etc.
C’est le langage de commande des logiciels Open Office, Blender ou encore Corel Paint Shop Pro.

1.1.3 Framework
Le framework choisi au lycée pour travailler en Python est Pyzo. Il contient un environnement de
développement intégré (IDE) appelé IEP (Interactive Editor for Python) ainsi qu’une version 3.* du
langage Python ainsi qu’une série de modules et est disponible à l’adresse suivante :

http://www.pyzo.org/downloads.html

Il est nécessaire de distinguer deux composantes fondamentales de cet environnement : le shell, encore
appelé console ou terminal (plusieurs peuvent tourner simultanément) et l’éditeur.

• Le shell est une interface (contenant un interpréteur) entre vous et le système d’exploitation qui
permet d’exécuter des instructions en Python à la ligne de commande,

• L’éditeur permet quant à lui d’écrire, de modifier et d’enregistrer (dans un fichier) du code (série
d’instructions en Python) avant d’être exécuté dans un shell. Tout passe donc par un terminal !

1.2 Les types usuels


Les données numériques sont stockées selon leur type.

1.2.1 Le type bool


• Le type bool ne contient que deux valeurs (dites booléennes, du mathématicien britanique Georges
Boole) : True (vrai) et False (faux).

• Les opérations logiques possibles sur le type bool sont (par ordre de priorité) : la disjonction or,
la conjonction and et la négation not.

Exemple 1.2.1

>>> 1 < 2 # premier booléen


True
>>> type (1 < 2) # t y p e de l ’ o b j e t 1 < 2
<class ’bool ’>
>>> (1 < 0) or (3 <= 4) # l e s e c o n d terme e s t v r a i
True
>>> (1 < 0) and (3 < 4) # l e p r e m i e r terme e s t f a u x
False
>>> 1 == 1.0 # t e s t par c o n v e r s i o n de t y p e i n t −> f l o a t
True
>>> not (1 == 1.0) # le t e s t d ’ é g a l i t é s ’ effectue via ==
False
>>> True == 1 # l e t y p e b o o l e s t un sous−t y p e du t y p e i n t . . .
True
>>> False == 0 # . . . True c o r r e s p o n d à 1 e t F a l s e à 0
True

4 Informatique MPSI/PCSI
Chapitre 1. Programmation impérative en Python 1.2 Les types usuels

Il existe huit opérateurs de comparaisons en machine :

x == y x est égal à y (test d’égalité à distinguer de l’affectation !)


x != y x est différent de y
x > y x est strictement supérieur à y
x < y x est strictement inférieur à y
x >= y x est supérieur ou égal à y
x <= y x est inférieur ou égal à y
x is y x et y sont des alias d’un même objet (test d’égalité d’objet)
x in y x appartient à y (cf. types séquentiels)

Remarque 1.2.2

• Deux objets de types différents ne peuvent pas être considérés comme égaux, à moins qu’ils
soient de types numériques.

• Les opérateurs de comparaison ont la même priorité (supérieure à celle des opérateurs booléens).

• Les opérateurs de comparaison peuvent s’écrire à la chaine.


Par exemple l’instruction x <= y < z est équivalente à x <= y and y < z (à la seule différence
que la variable y n’est évaluée qu’une seule fois).

1.2.2 Les types numériques : int, float, complex


Il y a trois type numériques :

• le type int (abréviation d’integer) représentant les nombres entiers (relatifs) avec une précision
illimitée,

• le type float représentant les nombres à virgule flottante1 , généralement stockés en double précision
(64 bits),

• le type complex représentant des nombres complexes dont les parties réelle et imaginaire sont des
nombres flottants.

Le tableau ci-dessous présente les opérations usuelles sur les types numériques (dans l’ordre des priorités
croissantes).

x + y somme de x et y
x - y différence de x par y
x * y produit de x par y
x / y quotient flottant de x par y
x // y quotient de la division euclidienne de x par y (pour y de type int)
x % y reste de la division euclidienne de x par y (pour y de type int)
-x opposé de x
abs(x) valeur absolue/module de x
z.conjugate() conjugué du nombre complexe z
x ** y exponentation de x par y

1
cf. chapitre "Représentation des nombres en machine"

Informatique MPSI/PCSI 5
1.2 Les types usuels Chapitre 1. Programmation impérative en Python

Exemple 1.2.3
>>> 2 ∗ 5 // 6 − 4 ∗∗ 4 # noter l a p r i o r i t é des opérations
−255
>>> 43 / 7 # d i v i s i o n dans l e s f l o t t a n t s
6.142857142857143
>>> 43 // 7; 43 % 7 # q u o t i e n t / r e s t e de l a d i v i s i o n e u c l i d i e n n e
6
1
>>> abs(( −2) ∗∗ 7)
128
>>> (1j ) ∗∗ 2 # l a n o t a t i o n j d é s i g n e l e i mathématique
( −1+0j)
>>> (1 + 2j). real # partie réelle
1.0
>>> (1 + 2j). imag # partie imaginaire
2.0
>>> 0.1 + 0.1 + 0.1 # p r é c i s i o n d e s c a l c u l s en f l o t t a n t s !!!
0.30000000000000004

Remarque 1.2.4 (Typage dynamique)


Lorsqu’on effectue une opération binaire sur deux objets de types numériques différents, le terme
dont le type est le moins large est converti dans le type le plus large.

Il existe de plus des constructeurs de type - à utiliser avec parcimonie - permettant de convertir un
objet d’un type donné vers un autre (lorsque cela est possible) : les fonctions int, float et complex.
Exemple 1.2.5
>>> int (1.56)
1
>>> int( − 1.56) # partie entière ?
−1
>>> float (1)
1.0
>>> complex (3.5 ,4)
(3.5+4 j)

1.2.3 Le type str


Le type str (abréviation de string) est un type séquentiel qui permet de stocker des chaines de caractères.
Une chaine de caractères se définit de deux manières :
• par extension en encadrant les caractères de la chaine par un même symbole ’ ou ",

• par conversion, à l’aide de la fonction de conversion str.


Exemple 1.2.6 (Définition d’une chaine de caractères)
>>> txt = "Hello world !" # d é f i n i t i o n par e x t e n s i o n
>>> txt
’Hello world !’
>>> type(txt) # t y p e de l a v a r i a b l e t x t
<class ’str ’>
>>> str (123) # d é f i n i t i o n par c o n v e r s i o n
’123 ’
>>> "" # c h a i n e de c a r a c t è r e s v i d e
’’

6 Informatique MPSI/PCSI
Chapitre 1. Programmation impérative en Python 1.2 Les types usuels

Les opérations usuelles sur le type str sont :

• l’accès à un caractère d’une chaine par son indice,

• le calcul de la longueur d’une chaine (nombre de caractères),

• l’extraction d’une sous-chaine d’une chaine de caractères par les indices de début et fin,

• la concaténation de deux chaines de caractères,

• le test d’appartenance d’un caractère à une chaine.

On peut accéder à chacun des caractères d’une chaine par son indice. Attention, en informatique, la
numérotation commence à partir de 0 !
Exemple 1.2.7 (Accès à un caractère par son indice)

>>> txt [0]; txt [1]; txt [2] # a c c è s par l e d é b u t ( i n d i c e s >= 0)


’H’
’e’
’l’
>>> txt[ −1]; txt[ −2]; txt[−3] # a c c è s par l a f i n ( i n d i c e s < 0)
’!’
’ ’
’d’

La longueur d’une chaine de caractères est calculée par la fonction len (abréviation de length).
Exemple 1.2.8 (Calcul de longueur d’une chaine)

>>> len(txt)
13

Remarque 1.2.9 (À retenir ! )


Si n désigne la longueur d’une chaine de caractères ch, l’instruction ch[n] lève l’erreur IndexError :
le caractère d’indice n n’existe pas !

• Le premier caractère de la chaine a pour indice 0 ou -n.

• Le dernier caractère de la chaine a pour indice n - 1 (et non n) ou -1.

>>> txt [13]


Traceback (most recent call last ):
File "<console >", line 1, in <module >
IndexError : string index out of range

0 1 2 3 n-4 n-3 n-2 n-1

-n -n+1 -n+2 -n+3 -4 -3 -2 -1

On peut extraire une sous-chaine d’une chaine de caractères par sclicing : si ch désigne une chaine de
caractères, alors ch[a:b] est sous-chaine de ch composée de tous les caractères de ch de l’indice a inclus
à l’indice b exclu.

Informatique MPSI/PCSI 7
1.2 Les types usuels Chapitre 1. Programmation impérative en Python

Exemple 1.2.10 (Extraction d’une sous-chaine)

>>> txt2 = "C’est parti !" # d é f i n i t i o n d ’ une c h a i n e de c a r a c t è r e s


>>> txt2 [4:9] # e x t r a c t i o n d ’ une sous−c h a i n e
’t par ’

À l’aide de l’opérateur binaire +, on peut concaténer deux chaines de caractères, i.e. les mettre bout-à-
bout de manière à créer une seule chaine.
Exemple 1.2.11 (Concaténation de chaines de caractères)

>>> txt3 = "Hello "


>>> txt4 = "world !"
>>> txt3 + txt4 # c o n c a t é n a t i o n de deux c h a i n e s
’Hello world !’
>>> "Am i creepy ? " ∗ 3 # c o n c a t é n a t i o n de 3 c o p i e s ( s u p e r f i c i e l l e s )
’Am i creepy ? Am i creepy ? Am i creepy ? ’

On peut tester l’appartenance d’un caractère à une chaine grâce au mot-clé in.
Exemple 1.2.12 (Test d’appartenance à une chaine)

>>> "b" in "abcd" # t e s t d ’ a p p a r t e n a n c e du c a r a c t è r e " b "


True
>>> b in "abcd" # l a v a r i a b l e b n ’ e s t pas d é f i n i e
Traceback (most recent call last ):
File "<console >", line 1, in <module >
NameError : name ’b’ is not defined
>>> b = "z" # d é f i n i t i o n de l a v a r i a b l e b
>>> b in "abcd" # t e s t d ’ a p p a r t e n a n c e du c a r a c t è r e " z "
False
>>> "gg" in "eggs" # t e s t d ’ a p p a r t e n a n c e d ’ une sous−c h a i n e
True

Une chaine de caractères n’est pas un objet mutable.


Exemple 1.2.13 (Non mutabilité d’une chaine de caractères)

>>> txt [0] = ’h’


Traceback (most recent call last ):
File "<console >", line 1, in <module >
TypeError : ’str ’ object does not support item assignment

8 Informatique MPSI/PCSI
Chapitre 1. Programmation impérative en Python 1.2 Les types usuels

1.2.4 Le type list


Le type list est un type séquentiel permettant de stocker des tableaux dynamiques (la longueur
n’est pas fixe) et mutables.
Une “liste” se définit de plusieurs manières :

• par extension (de manière explicite), en encadrant les éléments de la liste par des crochets [ et ]
et en les séparant par des virgules,

• par compréhension, par une syntaxe proche de la syntaxe mathématique,

• par constructeur, à l’aide de la fonction de conversion list (rare).


Exemple 1.2.14 (Définitions d’une liste)
>>> liste0 = [] # l i s t e vide
>>> liste0
[]
>>> liste1 = [1, 1, 2, 3, 5, 8, 13, 21] # d é f i n i t i o n par e x t e n s i o n
>>> type( liste1 )
<class ’list ’>
>>> [k ∗∗ 2 for k in range (0 ,11)] # d é f i n i t i o n par compréhension
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> list("abcde") # d é f i n i t i o n par c o n s t r u c t e u r
[’a’, ’b’, ’c’, ’d’, ’e’]

Les types séquentiels (les listes, les chaines de caractères, les tuples, etc) partagent un grand nombre de
syntaxes, comme illustré dans l’exemple suivant.
Exemple 1.2.15 (Syntaxes communes aux types séquentiels)
>>> liste4 = [1, 1, 2, 3, 5, 8, 13, 21, 34]
>>> liste4 [0] # a c c è s à un é l é m e n t par son i n d i c e
1
>>> liste4 [6] # a c c è s à un é l é m e n t par son i n d i c e
13
>>> liste4[−1] # a c c è s à un é l é m e n t par son i n d i c e
34
>>> len( liste4 ) # l o n g u e u r d ’ une s é q u e n c e
9
>>> liste4 [1:7] # e x t r a c t i o n d ’ une sous−s é q u e n c e
[1, 2, 3, 5, 8, 13]
>>> liste4 [:4]
[1, 1, 2, 3]
>>> liste4 [6:]
[13, 21, 34]
>>> 7 not in liste4 # t e s t d ’ appartenance
True
>>> [’a’, ’b’, ’c’] + [’d’, ’e’, ’fghij ’] # c o n c a t é n a t i o n de s é q u e n c e s
[’a’, ’b’, ’c’, ’d’, ’e’, ’fghij ’]

Le type list est dynamique, sa taille est donc modifiable (sans changer d’objet), comme l’illustre l’exemple
suivant.

Informatique MPSI/PCSI 9
1.2 Les types usuels Chapitre 1. Programmation impérative en Python

Exemple 1.2.16 (Modification d’une liste)

>>> liste = [1, 2, 3, 4] # d é f i n i t i o n par e x t e n s i o n


>>> liste. append (5) # a j o u t en f i n de l i s t e par l a méthode append
>>> liste
[1, 2, 3, 4, 5]
>>> del liste [3] # s u p p r e s s i o n de r é f é r e n c e
>>> liste
[1, 2, 3, 5]
>>> liste.pop (1) # suppresion avec renvoi
2
>>> liste
[1, 3, 5]
>>> liste [1] = 2 # m o d i f i c a t i o n d ’ un é l é m e n t 1
>>> liste
[1, 2, 5]
>>> liste [1] = [2 ,3 ,4] # m o d i f i c a t i o n d ’ un é l é m e n t 2
>>> liste
[1, [2, 3, 4], 5]
>>> liste [1:2] = [2 ,3 ,4] # m o d i f i c a t i o n d ’ un é l é m e n t 3
>>> liste
[1, 2, 3, 4, 5]

Remarque 1.2.17 (À retenir ! )


Pour ajouter un élément en fin de liste, on préférera la méthode (fonction ou opérateur propre à un
type) append à la concaténation par la droite par une liste à un élément.
En effet l’instruction liste.append(x) s’effectue en temps constant (par rapport à la longueur de
la liste) tandis que l’instruction liste = liste + [x] s’effectue en temps linéaire par rapport à la
longueur de la liste : une copie (superficielle) de la liste est réalisée.
Toute syntaxe de la forme liste = liste + [x] est donc à proscrire !

1.2.5 Le type tuple


Le type tuple (terme anglais et suffixe de quintuple, sextuple, etc) est un type séquentiel permettant
d’implémenter des n-uplets, i.e. une collection ordonnée d’objets.
Un tuple se définit de plusieurs manières :

• par extension, en séparant ses éléments par des parenthèses et en les encadrant par des parenthèses
( et ) (facultatif),

• par constructeur, à l’aide de la fonction de conversion tuple.


Exemple 1.2.18 (Définitions d’un tuple)

>>> triplet = 1, 2, 3 # d é f i n i t i o n par e x t e n s i o n


>>> triplet # parenthèses f a c u l t a t i v e s
(1, 2, 3)
>>> tuple ([1 ,2 ,3]); tuple ("abc") # d é f i n i t i o n par c o n s t r u c t e u r
(1, 2, 3)
(’a’, ’b’, ’c’)

Les tuples ne sont pas des objets mutables contrairement aux listes avec lesquelles ils partagent de
nombreuses propriétés et syntaxes.

10 Informatique MPSI/PCSI
Chapitre 1. Programmation impérative en Python 1.3 Les variables

Exemple 1.2.19 (Non mutabilité des tuples)

>>> triplet = 1, 2, 3
>>> triplet [1] = 0
Traceback (most recent call last ):
File "<console >", line 1, in <module >
TypeError : ’tuple ’ object does not support item assignment

1.3 Les variables


1.3.1 Notion de référence
En Python, affecter une valeur à une variable ne consiste pas à stocker cette valeur dans un endroit de la
mémoire dédiée à la variable ! En Python, une variable est une étiquette (un nom) liée une référence
permettant de localiser la valeur associée. L’opérateur de liaison entre un identifiant (une variable) et son
contenu (sa valeur) est le symbole “=”. La syntaxe est de la forme :
variable = valeur

Cette opération a lieu en deux étapes :

(i) le membre de droite est évalué par l’interpréteur; le résultat est stocké en mémoire, référencé par
un identifiant;

(ii) la référence est liée à la variable écrite dans le membre de gauche.

La lecture d’une telle syntaxe s’effectue donc de droite à gauche.


Exemple 1.3.1

>>> x = 0
>>> x, id(x), type(x) # valeur , i d e n t i f i a n t , type
(0, 4297326592 , <class ’int ’>)
>>> x = x + 1 # i n c r é m e n t a t i o n de x
>>> x, id(x), type(x) # c a r t e d ’ i d e n t i t é de x
(1, 4297326624 , <class ’int ’>)
>>> x = x − 2 # d é c r é m e n t a t i o n de x ( de 2)
>>> x, id(x), type(x) # c a r t e d ’ i d e n t i t é de x
(−1, 4297326560 , <class ’int ’>)
>>> x += 1 # remplace x = x + 1
>>> x −= 3 # remplace x = x − 3
>>> x ∗ = 3 # remplace x = x ∗ 3
>>> x /= 2 # remplace x = x / 2

Exercice 1.3.2
Déterminer le contenu des variables à la fin de l’exécution de chacun des deux scripts suivants :

>>> a = 10 >>> a = 3
>>> b = 17 >>> b = 4
>>> c = a − b >>> c = a
>>> a = 0 >>> a = b
>>> c = b + a + 1 >>> b = c

Informatique MPSI/PCSI 11
1.3 Les variables Chapitre 1. Programmation impérative en Python

Il existe en Python une syntaxe rapide et efficace pour lier des valeurs à plusieurs variables : l’affectation
parallèle.
Exemple 1.3.3 (Affectation parallèle)

>>> a, b, c = 0, False , " Gandalf "


>>> a
0
>>> b
False
>>> c
’Gandalf ’

Il ne s’agit ni plus ni moins que de la liaison d’un tuple à un n-uplet de variables.


On pourra remarquer que les affectations parallèles sont encore possibles avec le type list.

1.3.2 Règles pour nommer une variable


Pour nommer correctement des variables, il convient de suivre les règles, conventions et conseils suivants2 .
Remarque 1.3.4 (Règles de nommage des variables)

• Un nom de variable est une séquence de caractères constituée de chiffres et de lettres ordinaires
de l’alphabet (i.e. sans accent, sans cédille, sans espace, ni caractères spéciaux).

• Cette séquence doit nécessairement commencer par une lettre (et non un chiffre). Il convient
que cette lettre soit une minuscule à moins de ne définir une constante (dans ce cas, toutes les
lettres sont en majuscule).

• La casse est prise en compte. Ainsi hauteur, Hauteur et HAUTEUR sont des noms de variables
distincts.

• Il est impossible d’utiliser l’un des 29 mots réservés suivants :

and assert break class continue def


del elif else except exec finally
for from global if import in
is lambda not or pass print
raise return try while yield

• Il convient de limiter les noms de variables à un caractère. Il est préférable d’utiliser des noms
de variables clairs et explicites.

• Lorsque les noms de variables sont assez longs, on pourra utiliser la convention CamelCase (ou
plus précisément lowerCamelCase) qui consiste à écrire une variable comme concaténation de
mots dont la première lettre est une majuscule. Cette convention permet une meilleure lecture.
Exemple : PlayStation.
Cette convention est en concurrence avec l’insertion du symbole underscore (noté _) entre les
mots liés.
On pourra alors écrire : maxHauteur (convention lowerCamelCase) ou max_hauteur (convention
“underscore”).

2
Pour plus d’informations sur le sujet, on invite le lecteur à consulter le Style Guide for Python Code, à l’adresse http:
//www.python.org/dev/peps/pep-0008/.

12 Informatique MPSI/PCSI
Chapitre 1. Programmation impérative en Python 1.3 Les variables

1.3.3 Compléments sur les références


On peut vite se faire une fausse idée sur la façon dont on peut manipuler les variables. L’exemple ci-dessous
présente les erreurs classiques liées à une mauvaise compréhension de la notion de référence.
Exemple 1.3.5

>>> liste1 = [0, 1, 2, 3]


>>> liste2 = liste1
>>> liste2 . append (4)
>>> liste1 , liste2
([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])
>>> id( liste1 ), id( liste2 )
(4369028424 , 4369028424)

On peut penser que l’instruction liste2 = liste1 effectue une copie de liste1. Cette copie n’est en
réalité par indépendante de l’originale. Pire ! il s’agit d’un alias : deux noms de variables pour le même
objet en machine (cf. les identifiants de référence). En réalité, le symbole = permet de copier des références
et non des valeurs.
Pour notre exemple, on peut simplement contourner le problème. En effet, l’opération d’extraction d’une
sous-séquence crée une copie, dite superficielle.
Exemple 1.3.6

>>> liste3 = [0, 1, 2, 3]


>>> liste4 = liste1 [:] # c o p i e s u p e r f i c i e l l e de l i s t e 1
>>> liste4 . append (4)
>>> liste3 , liste4
([0, 1, 2, 3], [0, 1, 2, 3, 4])
>>> id( liste3 ), id( liste4 )
(4369028872 , 4369027272)

Les choses se compliquent lorsqu’on considère une liste contenant une liste !
Exemple 1.3.7

>>> liste5 = [1, "a", [4 ,5 ,6]]


>>> alias = liste5
>>> copieSuperficielle = liste5 [:]
>>> liste5 [0]
1
>>> liste5 [1]
’a’
>>> liste5 [2]
[4, 5, 6]
>>> liste5 [2][1]
5
>>> liste5 [2][1] = 0

>>> liste5
[1, "a", [4 ,0 ,6]]
>>> alias
[1, "a", [4 ,0 ,6]]
>>> copieSuperficielle
[1, "a", [4 ,0 ,6]]

Informatique MPSI/PCSI 13
1.4 Les fonctions Chapitre 1. Programmation impérative en Python

Pour comprendre ce qu’il s’est passé, il faut suivre les identifiants.


Exemple 1.3.8

>>> id( liste5 ), id(alias), id( copieSuperficielle )


(4369029704 , 4369029704 , 4369029512)
>>> id( liste5 [2]) , id( copieSuperficielle [2])
(4369029768 , 4369029768)

La première instruction donne une information : une copie superficielle et son originale sont deux objets
distincts en mémoire.
La seconde instruction indique que, lorsqu’on réalise une copie superficielle, on crée une nouvelle
séquence dont les éléments sont identiques en mémoire. La copie superficielle n’est donc pas
indépendante de l’originale.
Il existe une manière de copier de façon à rendre la copie entièrement indépendante de l’originale : la
copie profonde. La syntaxe sera vue en TP.

1.4 Les fonctions


1.4.1 Généralités
Il est possible de définir (au sens mathématique, avec une syntaxe volontairement proche des mathé-
matiques) des fonctions qui, à partir d’un ou plusieurs arguments d’entrée, renvoient un ou plusieurs
arguments de sortie3 .
Pour cela on utilise le mot-clé def qui permet de définir une fonction, ainsi que le mot-clé return 4 qui
renvoie en sortie un objet (un nombre, une liste, une chaine de caractères, etc) 5 .
Remarque 1.4.1
La définition d’une fonction s’écrivant sur plusieurs lignes, l’écriture d’une fonction dans un terminal
peut s’avérer assez pénible (notamment pour la modification des lignes déjà écrites).
On préférera définir une fonction dans l’éditeur, exécuter ce code dans un terminal puis utiliser cette
fonction dans ce terminal ou dans l’éditeur selon les besoins.

Exemple 1.4.2
On définit la fonction cubique dans l’éditeur.
def cube(x):
""" r e n v o i e l e c u b e du nombre x """
return x ∗∗ 3
On exécute ensuite ce code dans un shell et on l’utilise de différentes manières.
>>> ( executing lines 1 to 3 of "<tmp 1>")
>>> cube (3)
27
>>> cube(−2)
−8
>>> help(cube)
Help on function cube in module __main__ :
cube(x)
renvoie le cube du nombre x

3
il est possible de ne rien renvoyer (ou, plus précisément, de renvoyer un argument de type NoneType).
4
Le mot-clé return n’est pas une fonction ! D’où l’absence de parenthèses...
5
Par abus de langage, on dit qu’une fonction renvoie plusieurs objets lorsqu’elle renvoie un tuple (dont les coordonnées sont
ces objets).

14 Informatique MPSI/PCSI
Chapitre 1. Programmation impérative en Python 1.4 Les fonctions

Il est aussi possible de définir une fonction avec des paramètres par défaut. On précise la valeur par défaut
de ces paramètres dans la définition.
Exemple 1.4.3

def puissance (x, n=10, p = 3):


""" r e n v o i e x à l a p u i s s a n c e N modulo p
par d é f a u t : n = 10 , p = 3 """
return x ∗∗ n % p
On peut alors utiliser cette fonction en appelant précisant ou non la valeur de certains paramètres.
>>> puissance (2) # x = 2 , n = 10 , p = 3
1
>>> puissance (2, 11) # x = 2 , n = 11 , p = 3
2
>>> puissance (2, p=4) # x = 2 , n = 10 , p = 4
0
>>> puissance (2, p=3, n=12) # x = 2 , n = 12 , p = 3
1

Remarque 1.4.4 (Docstring )


La docstring est une chaine de caractères encadrée par des triples guillemets (pour la distinguer
des autres chaines de caractères) permettant de décrire les conditions d’utilisation d’une fonction aux
utilisateurs. La docstring d’une fonction est accessible (à condition que le code de cette fonction ait
été préalablement exécuté dans le shell) via la fonction help. Toutes les fonctions primitives (i.e.
intégrées) de Python contiennent toutes une docstring6 (souvent très complète) .
En TP, tout étudiant demandant de l’aide sur une fonction inconnue sans avoir pris la
peine de chercher sa docstring s’expose à une humiliation et des souffrances dont il n’a
même pas idée !

Remarque 1.4.5 (Docstrings et commentaires)


Il est important de distinguer :

• la docstring d’une fonction, propre à chaque fonction, qui explique comment utiliser la fonc-
tion, en décrivant les paramètres attendus, les valeurs retournées et les exceptions levées.

• des commentaires sur le code qui expliquent comment votre code fonctionne. Les commentaires
ne sont pas lus par l’interpréteur, ils sont uniquement destinés à faciliter la lecture du code (par
soi-même ou un tiers) ainsi que la maintenance du code. Un commentaire débute par le symbole
# et continue sur le reste de la ligne.

On se rappellera de cette citation :

“Les programmes doivent être écrits pour être lus par des gens et accidentellement
exécutés par des machines.”

Abelson & Sussman, Structure and Interpretation of Computer Programs.

1.4.2 Portée des variables


Une variable définie à l’intérieur du corps d’une fonction est appelée variable locale. Les variables
utilisées à l’intérieur d’une fonction ne sont plus définies quand la fonction se termine ; on dit qu’elles
sont hors de portée.
6
Encore faut-il savoir la lire !

Informatique MPSI/PCSI 15
1.4 Les fonctions Chapitre 1. Programmation impérative en Python

Exemple 1.4.6 (Variable locale)


On définit la fonction booléenne suivante :
def pair(nb):
flag = nb % 2 == 0
return flag
Même après avoir exécuté le code de la fonction dans un terminal, le contenu de la variable booléenne
flag est hors de portée :
>>> pair (4)
True
>>> pair (5)
False
>>> flag
Traceback (most recent call last ):
File "<console >", line 1, in <module >
NameError : name ’flag ’ is not defined

Remarque 1.4.7
Vous trouverez ci-dessous les noms des erreurs classiques que vous ferez en TP :

NameError IndexError TypeError SyntaxError ZeroDivisionError

Identifiez-les, sachez lire les messages d’erreur correctement (de bas en haut) pour débugger.

Une variable définie à l’extérieur du corps d’une fonction est appelée variable globale.
Une variable globale est accessible dans le corps d’une fonction. Le mot-clé global permet de ne pas
créer de nouvelle variable locale (dans l’espace des noms de la fonction) mais d’accéder au contenu de la
variable définie à l’extérieure du corps de la fonction.
Exemple 1.4.8

>>> x = 0
>>> def incremente ():
... """ a j o u t e 1 à l a v a r i a b l e g l o b a l e x """
... x += 1
...
>>> incremente ()
Traceback (most recent call last ):
File "<console >", line 1, in <module >
File "<console >", line 3, in incremente
UnboundLocalError : local variable ’x’ referenced before assignment
On recommence en déclarant la variable x comme globale.
>>> def incremente_bis ():
... """ a j o u t e 1 à l a v a r i a b l e g l o b a l e x """
... global x
... x += 1
...
>>> incremente_bis ()
>>> x
1
Remarquer que la fonction incremente ne renvoie rien. On dit qu’elle agit par effet de bord : elle
modifie l’état d’une variable dont la valeur n’est pas renvoyée.

16 Informatique MPSI/PCSI
Chapitre 1. Programmation impérative en Python 1.5 Les test conditionnels

Remarque 1.4.9 (Effet de bord )


On limitera au maximum l’utilisation de variables globales (et la programmation par effet de bord
d’une manière plus générale). On préférera plutôt considérer ces variables comme arguments d’entrée
des fonctions considérées. Cela simplifie la compréhension et la recherche d’erreurs dans le code.

1.5 Les test conditionnels


Un test conditionnel évalue une expression booléenne, et réalise ou non une série d’instructions selon
la valeur de vérité de cette expression booléenne.
La structure d’un test conditionnelle peut varier selon la présence ou non d’alternatives et/ou de tests
imbriqués. On présente ci-dessous trois squelettes de tests conditionnels :


if booleen :
# instructions

Si la valeur du booléen est True alors la série d’instructions indentée est réalisée. On n’effectue
pas la série d’instructions indentée dans le cas contraire.


if booleen :
# instructions
else:
# instructions
# alternatives

Si la valeur du booléen est True alors on réalise (uniquement) la première série d’instructions
indentée, sinon, on réalise la série d’instructions alternatives (elle aussi indentée).


if booleen0 :
# instructions
elif booleen1 :
# instructions
# alternatives 1
elif booleen2 :
# instructions
# alternatives 2
.
.
.
else:
# instructions
# alternatives n

Les booléens sont évalués dans l’ordre de lecture. Dès qu’un de ces booléens a pour valeur True,
la série d’instructions indentée associée (et uniquement celle-ci) est exécutée. Dans le cas où aucun
des booléens n’est vrai (else), la dernière instruction alternative est exécutée.

Informatique MPSI/PCSI 17
1.6 Les structure de boucles Chapitre 1. Programmation impérative en Python

Exemple 1.5.1
On (re-)définit ci-dessous la fonction valeur absolue.
def absolue (x):
if x >= 0:
return x
else:
return −x

1.6 Les structure de boucles


1.6.1 Les boucles itératives
Les boucles itératives (non conditionnelles) permettent de répéter une série d’instructions dépendant
(ou non) d’une variable décrivant une séquence déterminée.
On présente ci-dessous le squelette des boucles itératives :
for variable in sequence :
# instructions

La série indentée d’instructions est répétée autant de fois qu’il y a d’éléments dans la séquence.
Une boucle itérative ne nécessite pas d’initialisation : la variable est définie à l’entrée dans la boucle par
la syntaxe elle-même.
Il existe deux manières de parcourir une séquence :

• le parcours par élément

• le parcours par indice

Pour illustrer ces deux types de parcours, on définit dans les deux exemples suivants des fonctions calculant
la somme des éléments d’une liste de nombres.
Exemple 1.6.1 (Parcours par élément)

def somme(liste ):
""" r e n v o i e l a somme d e s é l é m e n t s de l i s t e ( de t y p e l i s t )
A t t e n t i o n : l e s é l é m e n t s de l i s t e d o i v e n t ê t r e de t y p e numérique """
# initialisation
s = 0
# p a r c o u r s par é l é m e n t
for nb in liste:
s = s + nb
return s

Pour le parcours par indice, on calcule la longueur de la séquence (via la fonction len) et on utilise
l’itérateur range.

• L’instruction range(n) génère successivement tous les entiers de 0 inclus à n exclu, i.e. de 0 à n-1.

• L’instruction range(a,b) génère successivement tous les entiers de a inclus à b exclu (!).

• L’instruction range(a, b, p) génère successivement les entiers a, a+p, a+2p jusqu’à b exclu (!).
Le nombre p est appelé pas.

18 Informatique MPSI/PCSI
Chapitre 1. Programmation impérative en Python 1.6 Les structure de boucles

Exemple 1.6.2 (Parcours par indice)

def sommeBis (liste ):


""" r e n v o i e l a somme d e s é l é m e n t s de l i s t e ( de t y p e l i s t )
A t t e n t i o n : l e s é l é m e n t s de l i s t e d o i v e n t ê t r e de t y p e numérique """
# initialisation
n = len(liste)
s = 0
# p a r c o u r s par i n d i c e
for k in range (0,n): # 0 <= k < n
s = s + liste[k]
return s

Remarque 1.6.3
On prendra l’habitude de noter en commentaire l’ensemble des valeurs prises par la variable parcourant
un itérateur.

1.6.2 Les boucles conditionnelles


Les boucles conditionnelles permettent de répéter une série d’instructions tant qu’une expression
booléenne est vraie.
On présente ci-dessous le squelette des boucles conditionnelles :
# initialisation
while booleen :
# instructions

On prendra soin de bien écrire une boucle while !

• Lorsque la valeur de l’expression booléenne n’est jamais modifiée par la série d’instructions à
l’intérieur de la boucle, on parle de boucle infinie : le code ne permet pas de sortir de la boucle
qui répétera (théoriquement) à l’infini la série d’instructions7 .
A moins que cela soit absolument nécessaire, on évitera d’écrire des boucles infinies. Pour cela, on
veillera à ce qu’il existe au moins une itération de la boucle qui modifie la valeur de l’expression
booléenne.

• Une boucle while devra toujours être initialisée (par la définition d’une variable par exemple) avant
l’entrée dans la boucle, de façon à en permettre l’entrée (ou à en forcer l’entrée).
Exemple 1.6.4 (Division euclidienne)

def division_euclidienne (a,b):


""" r e n v o i e l e c o u p l e ( q u o t i e n t , r e s t e )
de l a d i v i s i o n e u c l i d i e n n e de l ’ e n t i e r a par l ’ e n t i e r b """
# initialisation
r, q = a, 0
while r >= b:
r = r − b
q += 1
return q, r
Remarquons qu’à la sortie de la boucle while, la condition r >= b n’est plus vérifiée.

7
Il est important de pouvoir vérifier qu’une boucle conditionnelle termine ; nous aborderons cette question dans un prochain
chapitre.

Informatique MPSI/PCSI 19
1.6 Les structure de boucles Chapitre 1. Programmation impérative en Python

Remarque 1.6.5 (Boucles while vs boucles for)


Le nombre d’itérations d’une boucle while est indéterminé : il est conditionné par l’initialisation et
la série d’instructions du corps de la boucle.
Le nombre d’itérations d’une boucle for est déterminé : il est égal au nombre d’éléments de la
séquence.
On utilisera donc les boucles for lorsque le nombre d’itérations est connu (ou tout du moins déterminé),
tandis qu’on pourra utiliser les boucles while dans tous les cas (et en particulier lorsque le nombre
d’itérations est a priori non déterminé).

Exemple 1.6.6 (Parcours par indice)

def sommeTer (liste ):


""" r e n v o i e l a somme d e s é l é m e n t s de l i s t e ( de t y p e l i s t )
a t t e n t i o n : l e s é l é m e n t s de l i s t e d o i v e n t ê t r e de t y p e numérique """
# initialisation
n = len(liste)
s = 0
k = 0 # i n i t i a l i s a t i o n de l a v a r i a b l e i n d i c e
# boucle while
while k < n: # 0 <= k < n
s = s + liste[k]
k += 1
return s

20 Informatique MPSI/PCSI
Chapitre 2
Présentation de modules usuels

Notion de module
La distribution standard de Python contient un grand nombre de bibliothèques logicielles externes. Chaque
bibliothèque regroupe notamment un ensemble de fonctions et procédures autour d’un thème (calcul
numérique et scientifique, traitement d’images, graphisme, programmation internet et réseau, gestion de
base de données, etc).
Pour pouvoir accéder aux fonctionnalités d’une bibliothèque, il est nécessaire d’importer le fichier (au
format .py) qui la contient. La syntaxe standard est la suivante :
import <module >

Pour accéder à une fonction funct d’un module mod préalablement importé comme ci-dessus, il est néces-
saire de rappeler le nom du module via la syntaxe mod.funct.

2.1 Module random


Le module random est dédié à la génération de variables aléatoires. Il contient notamment les fonctions
suivantes.

• random() simule une variable aléatoire uniforme sur [0, 1[ et renvoie un nombre aléatoire de
l’intervalle [0, 1[.
>>> random . random () # r e n v o i e un nombre a l é a t o i r e c o m p r i s e n t r e 0 e t 1
0.49105308610499

• randint(a,b) simule une variable aléatoire uniforme sur Ja, bK et renvoie un nombre aléatoire de
l’ensemble Ja, bK.
>>> random . randint (1, 6) # s i m u l a t i o n d ’ un l a n c e r de dé
1

• choic(seq) renvoie un élément choisi (uniformément) aléatoirement dans une séquence seq.
>>> random . choice ("abcde") # r e n v o i e une l e t t r e c h o i s i e a l é a t o i r e m e n t
’b’
>>> random . choice ([k for k in range (1 ,7)]) # s i m u l a t i o n l a n c e r de dé
3

• shuffle(x) permute aléatoirement (sur place !) une liste x.


>>> liste = [1, 2, 3, 4, 5, 6] # p e r m u t a t i o n a l é a t o i r e d ’ une l i s t e
>>> random . shuffle (liste)
>>> liste # l a l i s t e a é t é m o d i f i é e ( o p é r a t i o n " s u r p l a c e ")
[6, 2, 3, 5, 4, 1]

21
2.2 Module time Chapitre 2. Présentation de modules usuels

Remarque 2.1.1
Le module random contient bien-sûr bien plus que les quelques fonctions présentées ici. Il permet
notamment de simuler des variables aléatoires suivant des lois exponentielle, normale, Gamma ou de
Weibull.

Remarque 2.1.2
On peut raccourcir la syntaxe d’appel d’une fonction d’un module externe en donnant un “petit nom”
au module importé à l’aide du mot-clé as. Le code ci-dessous est équivalent à ceux présentés en
exemple ci-dessus.
import random as rd
rd. random ()
rd. randint (1, 6)
rd. choice ("abcde")
rd. choice ([k for k in range (1 ,7)])
rd. shuffle ([1, 2, 3, 4, 5, 6])

Remarque 2.1.3 (Attention ! )


La syntaxe from module import * permet d’éviter d’appeler systématiquement le nom du module
lorsqu’on appelle une fonction de ce module. Cette syntaxe est largement utilisée aux écrits du
concours pour alléger la syntaxe (pour mieux se concentrer sur le fond).
from random import ∗
random ()
randint (1, 6)
choice ("abcde")
choice ([k for k in range (1 ,7)])
shuffle ([1, 2, 3, 4, 5, 6])
À moins qu’elle soit imposée, cette syntaxe est cependant à bannir !
En effet, from module import * ne référence pas le module lui-même (en le rajoutant dans l’espace
des noms) mais référence chacun des objets que définit le module (et les rajoute dans l’espace des
noms).
En agissant ainsi, on surcharge inutilement l’espace des noms mais surtout on peut implicitement
écraser des objets déjà définis : si ob désigne à la fois un objet préalablement défini dans l’espace des
noms et un objet de la bibliothèque mod, la syntaxe from mod import * écrase l’ancien objet dans
l’espace des noms.
Pour contourner le problème de référencement, on peut importer une sélection choisie d’objets depuis
un module. Par exemple :
from random import randint , choice
randint (1, 6)
choice ("abcde")
Cette syntaxe ne résout cependant pas le problème de l’écrasement d’un objet portant le même nom
dans l’espace des noms.

2.2 Module time


Le module time regroupe des fonctionnalités liées à la mesure du temps.
On utilisera la fonction clock() qui renvoie le temps processeur (en secondes), mais on se rappellera que
la définition du temps processeur dépend du système d’exploitation. Cette fonction permet notamment
d’évaluer le temps d’exécution d’un algorithme.

22 Informatique MPSI/PCSI
Chapitre 2. Présentation de modules usuels 2.3 Module matplotlib

import time
# mesure à l ’ i n s t a n t t 0
t0 = time.clock ()
...
# algorithme
...
# mesure à l ’ i n s t a n t t 1
t1 = time.clock ()
# a f f i c h e l e temps d ’ e x é c u t i o n e n t r e deux a p p e l s de t i m e . c l o c k ( )
print (t1 − t0)

Remarque 2.2.1
On utilisera cette fonction pour comparer des temps d’exécution d’algorithmes sur une même machine.
On fera notamment attention à ne pas lancer de programme pendant ou entre deux mesures.

2.3 Module matplotlib


Le module matlplotlib1 est une vaste bibliothèque destinée à la représentation graphique. Nous
n’utiliserons que le sous-module pyplot qui nous suffira amplement à réaliser des représentations
graphiques simples 2D.
Il convient de présenter quelques fonctions fondamentales de ce sous-module :

• plot(x, y) prend en argument deux listes x = [x0 , . . . , xn−1 ] et y = [y0 , . . . , yn−1 ] et place succes-
sivement - en les reliant - les points de coordonnées (x0 , y0 ), . . . , (xn−1 , yn−1 ).
La fonction plot accepte de nombreux arguments optionnels permettant de personnaliser la
représentation graphique (épaisseur et couleur du trait, marquage des points, etc).

• show() affiche la représentation graphique (dans une nouvelle fenêtre) construite à l’aide de la
fonction plot.
En mode non-interactif, l’affichage est bloqué jusqu’à la fermeture effective de la fenêtre.

Le code ci-dessous
import matplotlib . pyplot as plt
# c r é a t i o n de l a l i s t e d e s a b s c i s s e s d e s p o i n t s de l a p a r a b o l e
parabole_x = [k for k in range( −4,5)]
# c r é a t i o n de l a l i s t e d e s o r d o n n é e s d e s p o i n t s de l a p a r a b o l e
parabole_y = [k ∗∗ 2 for k in parabole_x ]
# t r a c e r d i s c o n t i n u d e s p o i n t s de l a p a r a b o l e en r o u g e
plt.plot(parabole_x , parabole_y , "r−−", linewidth = 2, label = " parabole ")
# t r a c e r en p o i n t i l l é s d ’ un r e c t a n g l e b l e u (+ marqueur " diamond ")
plt.plot([ − 3,3,3, − 3, − 3],[5,5,17,17,5], "b:", label=" rectangle ", marker ="D")
# b o r n e s [ xmin , xmax , ymin , ymax ] de l a f e n ê t r e ( à t e s t e r : " e q u a l ")
plt.axis([ −5, 5, −1, 24])
# p l a c e m e n t d e s l é g e n d e s ( à t e s t e r : l o c = " upper l e f t " , " c e n t e r " , e t c )
plt. legend (loc = "best")
# affichage
plt.show ()

1
La documentation complète de la bibliothèque matplotlib est disponible à l’adresse http://matplotlib.org/contents.
html

Informatique MPSI/PCSI 23
2.4 NumPy Chapitre 2. Présentation de modules usuels

permet d’afficher la figure ci-après.

2.4 NumPy
Le module numpy2 est destiné au calcul numérique, notamment à la manipulation de tableaux multidi-
mensionnels.

2.4.1 Le type array


NumPy définit un nouveau type : le type array (ou plus précisément ndarray). Il s’agit d’une implémen-
tation de la structure de tableau/matrice (uni/bi/multi-dimensionnels) dont tous les éléments sont du
même type.

Définition d’un array

• Un ndarray peut se définir par conversion d’une liste via la fonction array (le second argument -
optionnel - impose le type de tous les éléments du tableau).
>>> import numpy as np
>>> tab = np.array ([1, 2, 3, 4], float ) # t a b l e a u u n i d i m e n s i o n n e l
>>> tab
array ([ 1., 2., 3., 4.])
>>> type(tab)
<class ’numpy. ndarray ’>

>>> mat = np.array ([[1 , 2], [3 ,4]]) # t a b l e a u b i d i m e n s i o n n e l ( m a t r i c e )


>>> mat
array ([[1 , 2],
[3, 4]])

2
La documentation complète du projet NumPy est disponible à l’adresse http://docs.scipy.org/doc/numpy/reference/

24 Informatique MPSI/PCSI
Chapitre 2. Présentation de modules usuels 2.4 NumPy

>>> mat3D = np.array ([[[8 , 7],[6, 5]], [[4, 3], [2 ,1]]]) # m a t r i c e 3D


>>> mat3D
array ([[[8 , 7],
[6, 5]],
[[4, 3],
[2, 1]]])

• Les tableaux array sont séquentiels : on accède à un élément du tableau par les indices qui le
placent dans le tableau (l’ordre est important).
>>> tab [0], tab [1], tab[−1]
(1.0 , 2.0, 4.0)

>>> mat [0]


array ([1, 2])
>>> mat [0,0], mat [1,0], mat [0,1], mat [1 ,1]
(1, 3, 2, 4)

>>> mat3D [1 ,0 ,1]


3

On peut aussi définir un array par une fonction de construction.

• La fonction arange permet de générer un array de nombres équirépartis dont on précise le premier
terme, le dernier terme (exclu !) et le pas (par défaut égal à 1).
>>> np. arange (2, 3, 0.2)
array ([ 2. , 2.2, 2.4, 2.6, 2.8])

La fonction arange rappelle l’itérateur range.

• La fonction linspace permet de générer un array de nombres équirépartis dont on précise le premier
terme, le dernier (inclu !) et le nombre d’éléments du tableau.
>>> np. linspace (2, 2.8, 5)
array ([ 2. , 2.2, 2.4, 2.6, 2.8])

• La fonction zeros (resp. ones) permet de générer un array rempli de 0 (resp. 1). Si l’argument
d’entrée est un entier, le tableau est unidimensionnel, sinon l’argument est un tuple et dans ce cas
le tableau est au moins à deux dimensions.
>>> np.zeros (6)
array ([ 0., 0., 0., 0., 0., 0.])

>>> np.zeros ((2 ,3))


array ([[ 0., 0., 0.],
[ 0., 0., 0.]])

>>> np.ones (6)


array ([ 1., 1., 1., 1., 1., 1.])

>>> np.ones ((2 ,3))


array ([[ 1., 1., 1.],
[ 1., 1., 1.]])

• La fonction eye permet de générer une matrice identité de type array dont la taille est donnée en
entrée (sous la forme d’un entier).

Informatique MPSI/PCSI 25
2.4 NumPy Chapitre 2. Présentation de modules usuels

>>> np.eye (3)


array ([[ 1., 0., 0.],
[ 0., 1., 0.],
[ 0., 0., 1.]])

• La fonction diag permet de générer une matrice diagonale dont la diagonale est donnée en entrée
sous la forme d’une séquence. Un argument optionnel k permet de décaler la diagonale de |k| cases
vers la droite (si k > 0) ou vers le bas (si k < 0).
>>> vect = np.array ([1, 2, 3])
>>> np.diag(vect)
array ([[1 , 0, 0],
[0, 2, 0],
[0, 0, 3]])

>>> np.diag(vect , k=1)


array ([[0 , 1, 0, 0],
[0, 0, 2, 0],
[0, 0, 0, 3],
[0, 0, 0, 0]])

>>> np.diag(vect , −1)


array ([[0 , 0, 0, 0],
[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 0, 3, 0]])

• La fonction reshape permet de redimensionner un array. Le tableau originel n’est pas modifié : la
fonction reshape effectue d’abord une copie.
>>> tab = np. linspace (1 ,6 ,6)
>>> tab
array ([ 1., 2., 3., 4., 5., 6.])

>>> np. reshape (tab , (2 ,3))


array ([[ 1., 2., 3.],
[ 4., 5., 6.]])

>>> np. reshape (tab , (3 ,2))


array ([[ 1., 2.],
[ 3., 4.],
[ 5., 6.]])

• La fonction shape renvoie les dimensions d’un tableau array sous la forme d’un tuple.
>>> a = np.array ([[3 , 2], [4, 3]])
>>> np.shape(a)
(2, 2)

Opérations sur le type array

Les tableaux array ont été conçus dans le but de supporter les opérations vectorielles.

• Les opérateurs arithmétiques agissent comme des opérateurs terme-à-terme entre nombres et
tableaux ou sur des tableaux de même dimension.

26 Informatique MPSI/PCSI
Chapitre 2. Présentation de modules usuels 2.4 NumPy

>>> np.ones ((2 ,2)) + np.diag ([3 ,4])


array ([[ 4., 1.],
[ 1., 5.]])

>>> 2 + np.zeros ((2 ,2))


array ([[ 2., 2.],
[ 2., 2.]])

>>> np.ones ((2 ,2)) ∗ np.diag ([3 ,4])


array ([[ 3., 0.],
[ 0., 4.]])

>>> 2 ∗ np.ones ((2 ,2))


array ([[ 2., 2.],
[ 2., 2.]])

2.4.2 Quelques opérateurs d’algèbre linéaire


• Le produit matriciel s’écrit à l’aide de la fonction dot.
>>> a = np.array ([3, 2, 4, 3]). reshape ((2 ,2))
>>> b = np.array ([3, −2, −4, 3]). reshape ((2 ,2))
>>> np.dot(a,b)
array ([[1 , 0],
[0, 1]])

• La méthode transpose renvoie la transposée d’une matrice.


>>> a = np.array ([[1 ,2] ,[3 ,4]])
>>> a. transpose ()
array ([[1 , 3],
[2, 4]])

• Le rang d’une matrice se calcule par la fonction rank.


>>> a=np. arange (15). reshape ((3 ,5))
>>> a
array ([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
>>> np.rank(a)
2

• La fonction inv du sous-module linalg renvoie l’inverse d’une matrice carrée (inversible !).
>>> a = np.array ([[3 ,2] ,[4 ,3]])
>>> np. linalg .inv(a)
array ([[ 3., −2.],
[ −4., 3.]])

>>> np. linalg .inv(np.ones ((2 ,2)))


Traceback (most recent call last ):
File "<console >", line 1, in <module >
raise LinAlgError (" Singular matrix ")
numpy. linalg . linalg . LinAlgError : Singular matrix

Informatique MPSI/PCSI 27
2.5 SciPy Chapitre 2. Présentation de modules usuels

2.4.3 Fonctions universelles


Le module numpy définit aussi toutes les fonctions mathématiques usuelles (fonctions puissances,
trigonométriques (et leurs réciproques), hyperboliques (et leurs réciproques), etc.
Toutes ces fonctions sont vectorielles : on peut les appliquer terme-à-terme à des tableaux array.
>>> a = np.array ([[ np.pi , np.pi/2], [np.pi/3, np.pi /6]])
>>> np.cos(a)
array ([[ − 1.00000000e+00, 6.12323400e −17],
[ 5.00000000e −01, 8.66025404e −01]])

2.5 SciPy
Le module scipy3 est destiné à l’usage scientifique. Il contient de nombreux sous-modules spécialisés
dans les domaines mathématiques suivants (liste non exhaustive) :

• l’algèbre linéaire (scipy.linalg)

• l’intégration (scipy.integrate)

• l’optimisation (scipy.optimize)

• l’interpolation (scipy.interpolate)

• le traitement du signal (scipy.signal)

• les statistiques (scipy.stats).

3
La documentation complète du projet Scipy est disponible à l’adresse http://docs.scipy.org/doc/scipy/reference/

28 Informatique MPSI/PCSI
Partie II

Machines numériques

29
Chapitre 3
Machines numériques

3.1 Description d’une machine numérique


Il existe de nombreux types de machines numériques : l’ordinateur personnel (fixe ou portable), la tablette
et le téléphone portable (dont les technologies convergent de plus en plus), le serveur, etc.
Toute machine numérique se compose de deux parties distinctes : la partie matérielle ou hardware et la
partie logicielle ou software.

3.1.1 L’ordinateur personnel


Les principaux composants (hardware) d’un ordinateur personnel sont :

• le processeur ou CPU (Central Processing Unit)


Il agit en tant que véritable cerveau de la machine. Il est composé en particulier :

I d’une unité arithmétique et logique (ALU : Arithmetic and Logic Unit) capable d’effectuer
des opérations (arithmétiques et logiques) de base,
I d’une unité de contrôle, organisant les autres unités du processeur,
I de registres, mémoires de petites tailles (plusieurs octets), à accès très rapide, contenant
"l’état du système" à un instant (cycle) donné (adresse de l’instruction en cours, données et
résultats intermédiaires, etc),
I d’une unité de calculs en virgule flottante (Floating Point Unit) permettant de faire des
calculs (approchés) dans (un sous-ensemble de) R,
I de mémoire cache dont la principale utilité est de réduire considérablement le temps d’accès
à la mémoire de stockage. Il s’agit d’un exemple de mémoire tampon (buffer ).

Un processeur est caractérisé par :

I son jeu d’instruction, i.e. l’ensemble des opérations qu’il est capable d’effectuer,
I la taille de ses registres (entre 8 et 128 bits actuellement)
I sa cadence (quelques GHz actuellement)
I son architecture physique (taille, agencement, nombre de coeurs)

• la carte-mère (mainboard ou motherboard ),


Il s’agit d’un support permettant de relier les principaux composants (processeur, mémoires) et les
périphériques externes par un ensemble de circuits électriques appelés chipset.
La carte-mère contient en particulier une horloge interne, alimentée par une pile bouton, qui cadence
le système, un programme de bas niveau, le BIOS (Basic Input/Output System), en charge de la
gestion du matériel ainsi qu’une ROM (Read Only Memory ou mémoire morte) contenant le
BIOS.

31
3.1 Description d’une machine numérique Chapitre 3. Machines numériques

• la mémoire vive ou RAM (Random Access Memory)


Il s’agit d’une mémoire à accès rapide, mais volatile (détruite hors-tension). Ce type de mémoire
est utilisé pour les traitements d’instructions en cours (fonctionnement de l’OS, programmes en cours
d’exécution, etc).

• la mémoire de masse
Il s’agit d’une mémoire non volatile (on dit aussi rémanente), pouvant être à la fois lue et écrite,
souvent de grande capacité.
Le dispositif physique de stockage de cette mémoire de masse est appelé disque dur. Ce terme
est parfois employé à tort puisque de nouvelles générations de "disques durs" ne contiennent plus
de disque physique. On fera donc la distinction entre les disques durs HDD (Hard Drive Disc),
contenant un support magnétique à rotation, et les "disques" durs SSD (Solid-State Disc), composés
de semi-conducteurs mais dépourvus de disque physique.

• les bus qui forment l’ensemble des systèmes de communication entre les composants.
Chaque bus est caractérisé par une fréquence propre et un nombre de bits transférables simultané-
ment.

• l’alimentation qui transforme un courant alternatif (220V efficaces) en courant continu (12V).

• l’écran, caractérisé par sa définition (nombre total de pixels à l’écran), sa résolution (densité de
pixels) fournie en ppi (pixels per inch) ainsi que par son temps de réponse.

• le ventirad, composé lui-même :

I d’un radiateur (ou dissipateur), ayant pour rôle d’échanger de la chaleur entre le processeur
et du système,
I d’un ventilateur thermorégulé (dont la vitesse des pales varie selon les besoins du CPU).

• les cartes-filles (carte graphique, carte son, carte réseau, etc) qui permettent de compléter/améliorer
les performances de la carte-mère.
On pourra noter que les cartes graphiques actuelles contiennent un processeur, communément appelé
GPU (Graphics Processing Unit), soulageant le CPU en prenant par exemple en charge les calculs
de rendu de l’affichage, le rendu 3D, la mémoire vidéo,

• les périphériques externes tels que le clavier, la souris, les haut-parleurs, reliés au système par
différents ports.

3.1.2 La tablette
Comme l’ordinateur personnel (de bureau ou portable), une tablette contient : une carte-mère, un pro-
cesseur (soutenu généralement par un processeur graphique), de la mémoire-vive, de la mémoire de masse
sous la forme exclusive de «disque dur» SSD.
Certains composants de la tablette lui sont cependant plus spécifiques1 :

• la batterie rechargeable permettant la portabilité de l’appareil,

• l’écran tactile (à technologie capacitive principalement),

• le duo accéléromètre/gyroscope permettant de déterminer l’orientation et l’inclinaison de l’appareil


dans l’espace.

1
propos à nuancer en raison de la convergence technologique des ordinateurs portables, des tablettes et des smartphones.

32 Informatique MPSI/PCSI
Chapitre 3. Machines numériques 3.2 Le système d’exploitation

3.2 Le système d’exploitation


3.2.1 Description
Le système d’exploitation ou OS (Operating Systèm) est un ensemble de programmes (software) qui :

• assure la liaison entre les ressources matérielles d’une machine numérique et les applications infor-
matiques,

• contrôle la réalisation des programmes,

• assure la gestion l’espace mémoire en fonction des ressources et des demandes,

• fournit des points d’entrée génériques aux programmes pour les périphériques.

Il existe principalement deux familles d’OS :

I la famille Microsoft Windows, avec les systèmes d’exploitation Windows XP, Vista, Seven, etc,

I la famille UNIX, avec les systèmes d’exploitation de la firme Apple (OS X, iOS), les systèmes
d’exploitation contenant un noyau Linux (Ubuntu, Android).

3.2.2 Structure de fichiers


Chaque OS possède une structure (de gestion) de fichiers (file system), i.e. une structure arborescente
dans laquelle sont stockées des données (en grande quantité) qui peuvent être partagées entre plusieurs
programmes.
Cette structure arborescente permet à tout utilisateur de localiser un fichier à partir de son chemin d’accès
: un répertoire racine contient des fichiers et/ou des (sous-)répertoires qui contiennent récursivement des
fichers et/ou des sous-répertoires.
Chaque fichier possède un nom ainsi qu’une extension stipulant son format d’enregistrement.
Exemple 3.2.1

/Users/KH/Dropbox/Sup - Info pour tous/Cours-Exo/Machine numérique.pdf

désigne le chemin d’accès (absolu) du fichier nommé Machine numérique.


Ce fichier est placé dans le répertoire Cours-Exo, lui-même sous-répertoire du répertoire Sup - Info
pour tous.
Son extension est ".pdf" (Portable Document Format). Ce fichier peut être pris en charge par
l’application Acrobat Reader (ou l’application Aperçu sous Mac).

La structure de fichiers associe à chaque fichier des métadonnées contenant par exemple les droits d’accès
(lecture, écriture, exécution), la date du dernier accès, la taille du fichier, son propriétaire, le format du
fichier.

3.3 Compléments sur les différents types de mémoire


Tous les types de mémoire n’ont pas les mêmes caractéristiques :

• les registres, inclus dans le CPU, et la mémoire cache inhérente au processeur ont des temps
d’accès très faibles grâce à des technologies de pointe. Leur coût important est le facteur limitant
leur capacité.

• A l’inverse, la mémoire de masse (HDD et dans une moindre mesure SSD) est relativement peu
coûteuse et a une très grande capacité. Son temps d’accès est cependant bien plus important que
celui des registres.

Informatique MPSI/PCSI 33
3.3 Compléments sur les différents types de mémoire Chapitre 3. Machines numériques

Le tableau ci-dessous illustre le compromis temps/mémoire des différents types de mémoire 2 :

Type de mémoire Temps d’accès Capacité


registres 1 → 2 ns 32 → 128 bits (=b 4 → 16 octets)
mémoire cache (CPU) 4 → 30 ns 8Kio (L1) → 512Kio (L2)
mémoire vive (DRAM) 60 ns 1 → 16 Gio
mémoire de masse SSD 100 000 ns plusieurs centaines de Gio
mémoire de masse HDD 3 → 12 000 000 ns plusieurs Gio → quelques Tio

La figure ci-après synthétise les caractéristiques des différents types de mémoire.

e
iqu
registres
log
o
chn

Tem
t te

ps
mémoire cache
coû

d’a
u&

ccè
s
ten

&c
con

apa
mémoire vive
du

cité
lité
abi
ri
Va

mémoire de masse

Figure 3.1: Classification des différents types de mémoire

2
ces valeurs, données à titre indicatif, ne sont peut-être déjà plus d’actualité, selon les “lois” de Moore.

34 Informatique MPSI/PCSI
Chapitre 4
Représentation des nombres en machine

4.1 Représentation binaire en mathématiques


L’utilisation du système de représentation binaire en informatique découle directement des propriétés
physiques des composants utilisés.

4.1.1 Représentations binaire et décimale


Définition-Proposition 4.1.1 (Représentation en base 10 )
Pour tout entier naturel n ∈ N, il existe (a0 , . . . , ap ) ∈ J0, 9Kp+1 tel que :
p
X
ak × 10k = ap × 10p + ap−1 × 10p−1 + · · · + a1 × 101 + a0 × 100 .

n=
k=0

La notation ap ap−1 . . . a1 a0 10 constitue le développement décimal de l’entier n en base 10.

Définition-Proposition 4.1.2 (Représentation en base 2 )


Pour tout entier naturel n ∈ N, il existe (a0 , . . . , ap ) ∈ {0; 1}p+1 tel que :
p
X
ak × 2k = ap × 2p + ap−1 × 2p−1 + · · · + a1 × 21 + a0 × 20 .

n=
k=0

• La notation ap ap−1 . . . a1 a0 2 constitue le développement binaire de l’entier n en base 2.

• L’entier ap est appelé bit de poids fort (ou encore MSB = “Most significant bit”).

• L’entier a0 est appelé bit de poids faible (ou encore LSB = “Least significant bit”).

Méthode 4.1.3
Il existe un algorithme pour déterminer le développement binaire d’un nombre entier.
Il suffit d’étudier les restes des divisions successives par 2, comme on le montre ci-dessous :

• 23 = 11 × 2 + 1

• 11 = 5 × 2 + 1

• 5=2×2+ 1

• 2=1×2+ 0

• 1=0×2+ 1
2
On en déduit alors le développement binaire de 23 : 10111 .
Pour de “petits” nombres, on peut utiliser ses connaissances des puissances de 2.
Par exemple : 23 = 16 + 4 + 2 + 1 = 1 × 24 + 0 × 23 + 1 × 22 + 1 × 21 + 1 × 20 .

35
4.2 Représentation des nombres entiers Chapitre 4. Représentation des nombres en machine

Exercice 4.1.4
Déterminer la représentation binaire de 1234.

4.1.2 Bit, octet, byte


Définition 4.1.5

• Un bit (binary digit) est :

– un chiffre de l’écriture binaire (0 ou 1) en mathématiques


– la plus petite quantité d’information pouvant représentater deux valeurs distinctes en in-
formatique (0 ou 1, Vrai ou Faux, . . . ).

• Un octet correspond à la quantité d’information égale à 8 bits.

• Un byte correspond à la plus petite unité adressable en mémoire.

Remarque 4.1.6 (Attention : désinformation ! )

• Attention à ne pas confondre bit et byte : bien qu’un byte soit généralement stocké sur 8 bits
actuellement, ce n’est pas un règle : sur certains systèmes, un byte varie entre 5 et 9 bits.

• Il n’est pas rare de voir des confusions dans les unités de mesure de l’information : 1 Kio (lire
kibi-octets et non kilo-octets) ne désigne pas 1000 octets mais 210 = 1024 octets.
Les entreprises de télécommunication entretiennent d’ailleurs cette confusion.
Les notations Mi, Gi, Ti sont les préfixes respectifs des ordres de grandeur 220 , 230 et 240 .

4.2 Représentation des nombres entiers


4.2.1 Représentations des entiers non signés et signés - Limites
• En machine, tous les entiers naturels - on dit entiers non signés - sont représentables exactement
en mémoire, dans les limites de capacité.

• Les entiers signés (entiers relatifs) sont eux-aussi représentables exactement en mémoire :

– le bit de poids fort est dédié au signe de l’entier : 0 pour un entier positif, 1 pour un entier
strictement négatif,
– le reste des bits est dédié à la représentation binaire de la valeur absolue de l’entier.

Exemple 4.2.1
Sur 8 bits, l’entier −115 est enregistré en machine sous la forme de l’entier signé suivant :

1 111 0011

En effet :

(i) -115 est négatif d’où le bit de poids fort égal à 1


2
(ii) la représentation binaire de 115 est 111 0011 :

115 = 64 + 32 + 16 + 2 + 1 = 26 + 25 + 24 + 21 + 20 .

36 Informatique MPSI/PCSI
Chapitre 4. Représentation des nombres en machine 4.2 Représentation des nombres entiers

Exercice 4.2.2

(i) Sur 8 bits, quel est le plus grand entier non signé qu’on peut représenter ?

(ii) Sur 8 bits, quels sont les plus petit et plus grand entiers signés qu’on peut représenter ?

Remarque 4.2.3 (A retenir ! )

(i) Sur N bits, on peut représenter tous les entiers de 0 à 2N − 1 sous la forme d’entiers non signés.

(ii) Sur N bits, on peut représenter tous les entiers −2N −1 + 1 à 2N −1 − 1 sous la forme d’entiers
signés.

La représentation en entier signé pose deux problèmes majeurs :

i) l’unicité de zéro : la représentation de 0 et −0 n’est pas la même en machine.

ii) la compatibilité avec les opérations arithmétiques de base, illustrée par l’exemple ci-dessous.
Les représentations respectives de 7 et −5 sous forme d’entiers signés, codés sur 8 bits, sont :

0 000 0111 et 1 000 0101

Le calcul de la somme 7 + (−5) s’effectuant bit-à-bit en informatique (avec retenue), le résultat en


machine est alors :
1 000 1100
ce qui correspond à l’entier signé −12 et non 2.

4.2.2 Représentation des entiers en complément à deux


• Dans la représentation des entiers (relatifs) en complément à deux, les entiers positifs sont représentés
comme tels (en binaire).

• Pour déterminer la représentation d’un entier négatif x en complément à deux N bits :

(i) on inverse les bits de la représentation binaire de sa valeur absolue.


Cette notation est appelée représentation en complément à un.
(ii) On ajoute ensuite 1 en ignorant les dépassements de capacité.
Cette notation est appelée représentation en complément à deux.

Exemple 4.2.4

• La représentation de 7 en complément à deux sur 8 bits est : 0 000 0111

• (i) La représentation binaire de 5 sur 8 bits est : 0000 0101


(ii) La représentation en complément à un de −5 sur 8 bits est alors : 1111 1010
(iii) La représentation en complément à deux de −5 sur 8 bits est alors : 1 111 1011

Proposition 4.2.5 (Pour aller plus loin)

(i) La représentation en complément à un d’un entier négatif x sur N bits coïncide avec la représen-
tation sur N bits de l’entier positif 2N − 1 − |x| sous la forme d’entier (non signé).
(ii) La représentation en complément à deux d’un entier négatif x sur N bits coïncide avec la
représentation sur N bits de l’entier positif 2N − |x| sous la forme d’entier (non signé).

Informatique MPSI/PCSI 37
4.2 Représentation des nombres entiers Chapitre 4. Représentation des nombres en machine

Démonstration :

(i) Soit x un entier négatif. En sommant les représentations en complément à un des nombres x et |x|, on obtient
11 . . . 11 i.e. la représentation binaire de l’entier 2N − 1.
| {z }
N bits

(ii) Triviale.

Cette notation résout :

• le problème de l’unicité de 0 puisque la notation en complément à deux de −0 est la même que


celle de +0 (0 est considéré comme positif).
En effet, la représentation en complément à un de −0 sur 8 bits (par exemple) est 1111 1111 .
Celle en complément à deux est donc 0 000 0000 .

• La compatibilité avec les opérations arithmétiques de base, comme le montre l’exemple


ci-après.

Exemple 4.2.6
Additionnons les nombres 7 et −5, codés sur 8 bits, à l’aide de leur représentation en complément à
deux.

(i) Les représentations de 7 et −5 en complément à deux sur 8 bits sont respectivement :

0 000 0111 et 1 111 1011 .

(ii) Puisque :
0000 011112 + 1111 10112 = 1 0000 00102 ,
la somme 7 + (−5) est alors représentée en machine sous la forme :

0 000 0010

correspondant à l’entier égal à 2 (le dépassement de capacité est ignoré).

Proposition 4.2.7
Sur N bits, on peut représenter en complément à deux tous les entiers entre −2N −1 et 2N −1 − 1.

Démonstration :

• On dispose de N − 1 bits pour les entiers positifs (le bit de poids fort est nul), ce qui permet de représenter tous les
entiers entre 0 et 2N −1 − 1.
• Tout nombre négatif x représentable possède une représentation en complément à deux dont le bit de poids fort est
1. Cette représentation coïncide avec la représentation binaire de l’entier positif (non signé) 2N + x compris entre :
2 2
2N −1 = 1 00
| .{z
. . 00} et 2N − 1 = 1 11
| .{z
. . 11}
N −1 bits N −1 bits

Ainsi, en soustrayant 2N pour obtenir l’entier négatif représenté, on trouve :

2N −1 − 2N ≤ x ≤ −1 i.e. − 2N −1 ≤ x ≤ −1.

• On en déduit alors que l’ensemble des entiers représentables sur N bits en complément de deux est :

J−2N −1 , 2N −1 − 1K.

38 Informatique MPSI/PCSI
Chapitre 4. Représentation des nombres en machine 4.3 Représentation de nombres réels

4.3 Représentation de nombres réels


4.3.1 Représentation des nombres binaires à virgule
Notation 4.3.1
2
On note bn . . . b0 , b−1 . . . b−p le réel :
n
X
bk × 2k .
k=−p

Cette notation constitue une représentation d’un nombre décimal (positif ) en binaire.

Remarque 4.3.2
Les nombres réels admettant une représentation binaire à virgule sont nécessairement décimaux, mais
cette condition n’est cependant pas suffisante : 0,1 n’admet pas de représentation binaire à virgule.

Exemple 4.3.3
Le réel 3, 8125 est codé (en virgule fixe), sur 2 × 8 bits (8 pour la partie entière et 8 pour la partie
fractionnaire ) sous la forme :
2
0000 0011, 1101 0000
10 2
En effet : 3 = 0011 .
De plus, on pourra remarquer que :

0, 8125 = 0.5 + 0, 25 + 0, 0625 = 1 × 2−1 + 1 × 2−2 + 0 × 2−3 + 1 × 2−4 .

D’où la représentation en virgule fixe de 0, 8125 sur 8 bits (+1 pour le zéro avant la virgule) :
2
0, 1101 0000 .

La représentation en virgule fixe était utilisée dans les années 1970, avant l’apparition de la représentation
en virgule flottante. Elle est parfois utilisée dans les systèmes ne disposant pas d’une unité de calcul
en virgule flottante. On l’utilise actuellement pour améliorer la précision de certains calculs ou d’en
augmenter la vitesse.
Cette représentation pose cependant quelques problèmes dont :

• le gaspillage des bits à gauche de la virgule pour les petits nombres en valeur absolue,

• le gaspillage des bits à droite de la virgule pour les nombres dont la partie décimale est petite,
Exercice 4.3.4

• Déterminer la représentation binaire du nombre 21, 140625.

• En déduire un algorithme permettant de déterminer (lorsqu’elle existe) l’écriture binaire d’un


nombre réel.

4.3.2 Représentation en virgule flottante - Norme IEEE 754


La plupart des ordinateurs personnels actuels utilisent la norme IEEE 754 offrant (au moins) deux préci-
sions de codage des nombres à virgule flottante : une simple précision sur 32 bits et une double précision
sur 64 bits.

Informatique MPSI/PCSI 39
4.3 Représentation de nombres réels Chapitre 4. Représentation des nombres en machine

Définition 4.3.5
On dit qu’un réel x non nul est représentable en virgule flottante suivant la norme IEEE 754 par le
triplet (s, e, m) s’il admet l’écriture scientifique en base 2 suivante :

x = (−1)s × m × 2e .

• s, représenté sur un bit, code le signe de x : 0 pour positif, 1 pour négatif.

• m, appelé mantisse de x, vérifie 1 ≤ m < 2.


Son bit de poids fort étant égal à 1, il est implicite : seule la partie fractionnaire f de m est
enregistrée en mémoire (m = 1, f ), codé sur 23 bits en simple précision et sur 52 en double
précision,

• e, codé sur 8 bits en simple précision et sur 11 en double précision est appelé l’exposant de x.
Afin d’éviter la comparaison d’entiers signés représentés en compléments de deux, on ne stocke
pas directement l’exposant e en machine : on le décale d’un entier d de façon à ce que l’entier
e + d soit strictement positif.

– en simple précision −126 ≤ e ≤ 127, d = 28−1 − 1 = 127 et 1 ≤ e + d ≤ 254,


– en double précision −1022 ≤ e ≤ 1023, d = 211−1 − 1 = 1023 et 1 ≤ e + d ≤ 2046.

Définition 4.3.6 (Conventions des nombres non normalisés)


Certaines valeurs d’exposant et de mantisse sont réservées par convention à des valeurs particulières.

(i) les deux zéros (+0 et −0 selon le bit de signe)

(ii) les deux infinis (+∞ et −∞ selon le bit de signe)

(iii) les NaNs (“Not a Number ”).

Les tableaux suivants présentent les principales conventions en simple et double précisions :

Simple précision : 32 bits


. .... .... ... .... .... .... .... ....
|{z} | {z } | {z }
signe exposant décalé mantisse
1bit 8bits 23bits

Type Exposant Exposant décalé Mantisse


Zéros −127 0 0
Nombres normalisés −126 → 127 1 → 254 (= 28 − 2) quelconque
Infinis 128 255 (= 28 − 1) 0
NaN 128 255 (= 28 − 1) non nulle

Double précision : 64 bits


. ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... ....
|{z} | {z } | {z }
signe exposant décalé mantisse
1bit 11bits 52bits

Type Exposant Exposant décalé Mantisse


Zéros −1023 0 0
Nombres normalisés −1022 → 1023 1 → 2046 (= 211 − 2) quelconque
Infinis 1024 2047 (= 211 − 1) 0
NaN 1024 2047 (= 211 − 1) non nulle

40 Informatique MPSI/PCSI
Chapitre 4. Représentation des nombres en machine 4.3 Représentation de nombres réels

Remarque 4.3.7
En plus de ces conventions, la norme IEE 754 définit quatre modes d’arrondi pour les calculs en virgule
flottante:

• l’arrondi vers le flottant le plus proche (par défaut),


en cas d’équidistance, on choisit le flottant dont le bit de poids faible est nul,
• l’arrondi par excès, aussi appelé arrondi vers +∞ ,

• l’arrondi par défaut, aussi appelé arrondi vers −∞,

• l’arrondi vers 0 : arrondi par excès (resp. par défaut) pour les flottants négatifs (resp. positifs)

Exemple 4.3.8
Représentons le nombre −6, 625 en simple précision selon la norme IEEE 754 :
2
(i) La représentation binaire de 6, 625 est 110, 101 .
2 2
(ii) On procède alors à la normalisation de la mantisse : 110, 101 = 1, 10101 × 22 .

(iii) On occupe les 23 bits (+ le bit implicite) de la mantisse :


2 2
110, 101 = 1, 1010 1000 0000 0000 0000 000 × 22 .

2
(iv) On décale l’exposant et on le stocke sur 8 bits : 2 + d = 1000 0001 .

(v) Conclusion : le nombre −6, 625 est stocké en machine en simple précision sous la forme :

1 1000 0001 1010 1000 0000 0000 0000 000 .


|{z} | {z } | {z }
signe exposante décalé mantisse

4.3.3 Approximations et erreurs dans la représentation en virgule flottante


• En double précision, 2.01000 + 1 n’est pas représentable. Il est approximé par 2.01000 .
Problème : (2.01000 + 1) − 2.01000 est considéré comme nul en machine.

• La densité des flottants n’est pas uniforme !

• L’erreur relative commise en approximant un réel par un flottant est :

– majorée par 2−23 ≈ 1, 1921 × 10−7 en simple précision


– majorée par 2−52 ≈ 2, 2204 × 10−16 en double précision (utilisée en Python).

L’erreur absolue est définie comme le produit de l’erreur relative (définie ci-dessus) par 2e où e
désigne l’exposant dans l’écriture en virgule flottante.

Informatique MPSI/PCSI 41
4.3 Représentation de nombres réels Chapitre 4. Représentation des nombres en machine

Valeurs simple précision (sur 32 bits) double précision (sur 64 bits)

fmin 2−126 ≈ 1, 1755 × 10−38 2−1022 ≈ 2, 2251 × 10−308


   
1 1 1 1
fmax 1 + + · · · + 23 × 2127 ≈ 3, 4028 × 1038 1 + + · · · + 52 × 21023 ≈ 1, 977 × 10308
2 2 2 2
machine 2−23 ≈ 1, 1921 × 10−7 2−52 ≈ 2, 2204 × 10−16
dmin 2−23 × 2−126 ≈ 1, 4013 × 10−45 2−52 × 2−1022 ≈ 4, 9407 × 10−324
dmax 2−23 × 2127 ≈ 2, 0282 × 1031 2−52 × 21023 ≈ 1, 9858 × 10292

où :

• fmin désigne le plus petit flottant strictement positif,

• fmax désigne le plus grand flottant strictement positif,

• machine désigne l’epsilon machine, i.e. la distance entre 1. et le flottant qui le suit.

• dmin désigne la plus petite distance entre deux flottants consécutifs strictement positifs.

• dmax désigne la plus grande distance entre deux flottants consécutifs strictement positifs.
Exercice 4.3.9

1. Déterminer les représentations en machine (en simple précision) des nombres suivants : 0, 1, le
successeur de 1, le prédécesseur de 1, le plus petit flottant strictement positif, le plus grand flottant
positif et les infinis.

2. Retrouver par le calcul tous les résultats en simple précision du paragraphe ci-dessus.

42 Informatique MPSI/PCSI
Chapitre 4. Représentation des nombres en machine 4.4 Exercices

4.4 Exercices

Exercice 4.4.1

(i) Ecrire le developpement binaire des entiers de 16 à 33.


2 2 2
(ii) Déterminer la représentation en base 10 des entiers 11111111 , 101010101 et 11101110 .

(iii) Déterminer la représentation binaire des nombres suivants : 12, 24, 48, 37, 142 et 1000.

Exercice 4.4.2
Effectuer les calculs suivants dans N :
2 2 2 2 2 2 2 2
a. 110101001 + 1111 . b. 1111111 + 101010 . c. 1111 × 1111 . d. 110101001 × 1010 .

Les résultats seront donnés sous leur représentation binaire.

Exercice 4.4.3
Quels sont tous les entiers relatifs représentables en compléments à deux sur 16 bits ? Quel est leur
nombre ?

Exercice 4.4.4

1. Quelle structure algébrique (groupe, anneau, corps) l’ensemble des entiers naturels écrits sur 8 bits
forme-t-il ?

2. Même question pour l’ensemble des entiers relatifs écrits sur 8 bits en complément à deux.

Exercice 4.4.5

1. Ecrire en Python une fonction decimal_to_binary prenant en entrée un entier naturel n (en base
10, de type int) qui renvoie la représentation binaire de n sous la forme d’une chaine de caractères
(type str).

2. Ecrire en Python une fonction binary_to_decimal prenant en entrée une chaine de caractères
représentant un entier naturel en base 2 et renvoyant sa représentation en base 10 (type int).

Exercice 4.4.6
Ecrire un script Python (pas une fonction) permettant de déterminer quel est le plus grand entier
relatif (type int) représentable en machine.
Attention : ce script ne fonctionnera que sur les versions antérieures à la version 3.0 de Python. En effet, la nouvelle
version de Python permet de représenter des entiers de taille illimitée, ou plus précisément, limitée uniquement par
l’espace mémoire disponible.

Exercice 4.4.7

1. Déterminer le flottant représenté selon la norme IEEE 754 par le mot binaire :
1100010001101001001111000011100000000000000000000000000000000000

2. Déterminer la représentation en double précision des réels suivants selon la norme IEEE 754 :
7, 0 121, 125 98, 625 1, 046875.

Informatique MPSI/PCSI 43
4.4 Exercices Chapitre 4. Représentation des nombres en machine

Exercice 4.4.8
Déterminer un nombre réel qui ne puisse pas être représentable en virgule flottante, quelle que soit la
précision.

Exercice 4.4.9

1. Quel est le nombre de décimales après la virgule affichées en Python ? Justifier.

2. Déterminer l’erreur relative obtenue d’un calcul formé d’un million de multiplications.

3. Déterminer les plus grande et plus petite erreurs absolues (strictement positives) obtenues en som-
mant ou multipliant deux flottants (sans dépassement de capacité).

Exercice 4.4.10

1. Prévoir les résultats des trois derniers calculs suivants effectués dans un shell Python :

>>> x, y, z = 1, -1, 2**(-55)


>>> x + (y + z)
>>> (x + y) + z
>>> x + (y + z) == (x + y) + z

Quelle est la morale de cette histoire ?

2. Proposer une fonction permettant de décider si deux flottants sont suffisamment proches pour être
considérés comme égaux.

Exercice 4.4.11 (Illustration de phénomènes pathologiques sur une petite base de flottants)
On désigne par la notation F(2, 3, −2, 1) l’ensemble des flottants écrits en base 2, dont le signe est
codé sur 1 bit, la mantisse (normalisée) est codée sur 2 bits et dont l’exposant minimal est −2 et
l’exposant maximal est 1.

1. Représenter sur l’axe des réels l’ensemble des valeurs de F(2, 3, −2, 1).
On précisera :
• Le cardinal de cet ensemble (dont le nombre de flottants strictement positifs),
• le plus petit strictement positif, le plus grand flottant strictement positif,
• l’epsilon machine (i.e. la distance entre 1 et son successeur),
• la plus petite et la plus grande distance entre deux flottants consécutifs strictement positifs.
2. On note ⊕ et ⊗ les opérations de somme et produit en machine. On fixe le mode d’arrondi des
résultats sur F(2, 3, −2, 1) comme celui par défaut pour la norme IEEE 754 : l’arrondi au flottant
le plus proche.
Effectuer les opérations suivantes dans F(2, 3, −2, 1) illustrant les principaux écueils des calculs en
virgule flottante (notés entre parenthèses) :
5 5
(i) ⊕ (overflow)
2 2
1 3
(ii) ⊗ (underflow)
4 8
1
(iii) 2 ⊕ (absorption)
 4      
5 3 5 3
(iv) 3 ⊕ ⊕ − puis 3 ⊕ ⊕ − (non associativité des opérations de base).
16 2 16 2

44 Informatique MPSI/PCSI
Partie III

Algorithmique

45
Chapitre 5
Algorithmes classiques
5.1 Statistiques élémentaires
5.1.1 Moyenne d’une série statistique
n
1X
La moyenne arithmétique des réels x1 , x2 , . . . , xn , notée x, est définie par x = xk .
n
k=1
Lorsque la série statistique est donnée sous la forme d’un tableau, on peut calculer la moyenne de cette
série en n’effectuant un seul parcours, comme illustrer ci-dessous.
def moyenne (tab ):
""" r e n v o i e l a moyenne d e s v a l e u r s du t a b l e a u de nombres t a b """
sigma = 0
for nb in tab: # l a v a r i a b l e nb d é c r i t t o u s l e s é l é m e n t s de t a b
sigma += nb
return sigma/len(tab)
Moyenne des valeurs d’un tableau de nombres

On peut remarquer que l’opération de division (par la taille du tableau) n’est effectuée qu’après le calcul de la somme
des termes du tableau de façon à réduire le nombre d’opérations dites élémentaires.

5.1.2 Variance d’une série statistique


On appelle variance des réels x1 , x2 , . . . , xn le réel noté σ 2 défini par :
n
1X
2
σ = (xk − x)2 .
n
k=1
On remarque que chacune des valeurs de la série est appelée deux fois dans cette formule : une fois
explicitement et une seconde fois dans le calcul de la moyenne x de la série. Or, le théorème de König-
Huygens assure que :
n
!
2 1X 2
σ = xk − x2 .
n
k=1

On peut alors optimiser le calcul de la variance d’une série statistique en couplant "à la volée" le calcul de
la moyenne à celui de la variance, i.e. en un seul parcours de tableau, comme l’illustre le code ci-suivant.
def variance (tab ):
""" r e n v o i e l a v a r i a n c e d e s v a l e u r s du t a b l e a u de nombres t a b """
n = len(tab)
sigma , sigma_carre = 0, 0
for nb in tab: # l a v a r i a b l e nb d é c r i t t o u s l e s é l é m e n t s de t a b
sigma += nb
sigma_carre += nb ∗∗ 2
return ( sigma_carre /n) − (sigma/n ) ∗∗ 2
Variance des valeurs d’un tableau de nombres

Remarque : l’écart-type σ d’une série statistique est défini comme la racine de la variance.

47
5.2 Algorithmes de recherche séquentielle Chapitre 5. Algorithmes classiques

5.2 Algorithmes de recherche séquentielle


5.2.1 Recherche du maximum dans un tableau de nombres
L’algorithme de recherche du maximum d’un tableau de nombres noté tab n’effectue qu’un seul parcours,
dans sa totalité du tableau.
Le principe est qu’à chaque itération, on stocke le maximum des valeurs parcourues jusque là dans une
variable qu’on appellera max_actuel. Pour cela, on compare la valeur de la case en cours avec le maximum
des valeurs précédemment obtenu :

• si cette valeur est strictement supérieure à celle de max_actuel, elle devient la nouvelle valeur de la
variable max_actuel,

• sinon, le maximum des valeurs parcourues reste inchangé (on ne modifie pas la variable max_actuel),

et on passe à la case suivante.


if tab[k] > max_actuel :
max_actuel = tab[k]
On commence par affecter la première valeur du tableau à la variable max_actuel puis on parcourt le
reste du tableau selon la portion d’algorithme décrite plus haut.
# Initialisation
max_actuel = tab [0]

# Pa rc our s du t a b l e a u
for k in range (1,n):
if tab[k] > max_actuel :
max_actuel = tab[k]
Une fois le tableau entièrement parcouru, on renvoie la valeur de max_actuel : cette variable contient le
maximum des valeurs du tableau.
La fonction ci-dessous illustre l’algorithme de recherche du maximum d’un tableau de nombres. On
pourra remarquer que ce code "lève" une erreur (à l’aide de l’instruction raise) lorsque le tableau est
vide : l’erreur très (trop !) classique IndexError.

def max_tableau (tab ):


""" r e n v o i e l ’ é l é m e n t maximal d ’ un t a b l e a u de nombres """
n = len(tab)
# Levée d ’ une e r r e u r l o r s q u e l e t a b l e a u e s t v i d e
if n == 0:
raise IndexError ("Le tableau est vide !")

# Initialisation
max_actuel = tab [0]

# P a r c o u r s du t a b l e a u
for k in range (1,n):
if tab[k] > max_actuel :
max_actuel = tab[k]
return max_actuel

Algorithme de recherche du maximum dans un tableau de nombres

48 Informatique MPSI/PCSI
Chapitre 5. Algorithmes classiques 5.2 Algorithmes de recherche séquentielle

5.2.2 Recherche d’un élément dans un tableau


Le but de cet algorithme est de rechercher un élément x dans un tableau tab.
Le principe de l’algorithme est de parcourir le tableau (au plus une seule fois et pas nécessairement en
totalité) jusqu’à trouver l’élément recherché.

• À chaque étape, on compare l’élément recherché x avec un élément d’indice k du tableau tab.

if tab[k] == x: S’il y a correspondance, on lève un drapeau (variable booléenne)


flag = True pour signaler que l’élément a bien été trouvé.
else: Sinon, on passe à l’élément “suivant” dans le tableau, en incrémentant
k += 1 la variable d’indexation k.

• Ne connaissant pas le nombre d’itérations nécessaires à trouver l’élément, on utilise une boucle
conditionnelle while.
while flag == False and k < n:
if tab[k] == x: La recherche continue tant que l’élément x n’a pas
flag = True été trouvé et qu’il reste à "tester" des éléments du
else: tableau tab.
k += 1

• Il est nécessaire d’initialiser convenablement la boucle while, notamment de définir la variable


drapeau flag. On force l’entrée dans la boucle en affectant à cette variable booléenne la valeur
False (puisque l’élément n’a pas encore été trouvé !).
La condition de sortie de boucle est alors : l’élément x a été trouvé ou bien tous les éléments du
tableau ont été testés (et aucun ne correspond)
On a construit la variable flag de façon à ce qu’elle indique si l’élément x appartient ou non au
tableau tab. On renvoie alors la valeur de cette variable booléenne.
On donne ci-dessous un exemple de fonction codant l’algorithme de recherche d’un élément dans un
tableau à l’aide de la notion de drapeau et une variante ne l’utilisant pas.

def recherche (x,tab ): def recherche_bis (x,tab ):


""" f o n c t i o n b o o l é e n n e q u i """ f o n c t i o n b o o l é e n n e q u i
recherche l ’ élément x recherche l ’ élément x
dans l e t a b l e a u t a b """ dans l e t a b l e a u t a b """
# Initialisation # Initialisation
n, k = len(tab), 0 n = len(tab)
flag = False k = 0

# Pa rc o u r s du t a b l e a u # P a r c o u r s du t a b l e a u
while flag == False and k < n: while k < n:
if tab[k] == x: if tab[k] == x:
flag = True return True
else: else:
k += 1 k += 1
return flag return False

Algorithme de recherche d’un élément Variante de l’algorithme de recherche

La variante de l’algorithme utilise une propriété inhérente au langage Python voulant qu’une fonction ter-
mine après l’exécution de l’instruction return, ce qui a pour conséquence que les lignes suivant l’instruction
return ne sont pas lues par l’interpréteur. Cela permet en particulier de pouvoir sortir d’une boucle
(qu’elle soit conditionnelle ou non).

Informatique MPSI/PCSI 49
5.2 Algorithmes de recherche séquentielle Chapitre 5. Algorithmes classiques

5.2.3 Recherche dichotomique dans un tableau trié


Le but de cet algorithme est de rechercher un élément x dans un tableau tab (préalablement) trié (selon
l’ordre des réels ou l’ordre lexicographique par exemple).
Le principe de cet algorithme est de rechercher l’élément selon le paradigme "diviser pour mieux régner "
: à chaque étape, on divise par deux (environ) la taille du tableau dans lequel on effectue la recherche de
l’élément x (d’où le nombre de recherche dichotomique).
• A chaque étape, si on cherche dans le tableau parmi les éléments d’indice get d, oncompare l’élément
g+d
recherché x à l’élément médian du sous-tableau tab[g:d], d’indice m = .
2

m = (g+d)//2
if x == tab[m]: S’il y a égalité, on renvoie True pour signifier que l’élément x appartient
return True bien au tableau tab.
elif x < tab[m]: Sinon, on réduit "l’intervalle de recherche" au sous-tableau tab[g:m] ou
d = m − 1 tab[m+1:d+1], en modifiant la valeur des variables g et d (indices des
else: bornes gauche et droite de l’intervalle de recherche).
g = m + 1

• Cette recherche continue tant que l’élément recherché n’a pas été trouvé et qu’il reste des éléments
à comparer, d’où la nécessité d’utiliser une boucle conditionnelle.
Dans le cas où g=d, il ne reste plus qu’un élément à tester et on a m=g=d. S’il ne correspond pas
à celui recherché, la recherche doit terminer et l’une des alternatives du test conditionnel entraine
g>d.
Puisqu’on peut sortir d’une boucle à l’aide de l’instruction return, la condition (suffisante) de
sortie de boucle correspond au cas où il ne reste plus d’élément à comparer. On continue donc la
recherche tant que g<d (ce qui peut paraitre anecdotique voire paradoxal au premier abord, mais
qui se comprend parfaitement après réflexion).
• A la sortie de boucle, on renvoie False : en effet, si l’algorithme n’a pas terminé mais qu’on est
sorti de la boucle, c’est que l’élément recherché n’a pas été trouvé.
Le code ci-dessus présente une fonction booléenne de recherche dichotomique d’un élément x dans un
tableau trié tab.

def recherche_dichotomique (x,tab ):


""" f o n c t i o n b o o l é e n n e de r e c h e r c h e d i c h o t o m i q u e
de l ’ é l é m e n t x dans l e t a b l e a u t r i é t a b """

# Initialisation
g, d = 0, len(tab) − 1

# Parcours dichotomique
while g <= d:
m = (g+d)//2
if x == tab[m]:
return True
elif x < tab[m]:
d = m − 1
else:
g = m + 1
return False
Algorithme de recherche dichotomique

50 Informatique MPSI/PCSI
Chapitre 5. Algorithmes classiques 5.2 Algorithmes de recherche séquentielle

5.2.4 Recherche d’un mot dans une chaine de caractères


La finalité de cet algorithme est de vérifier si une chaine de caractères, qu’on notera chaine, contient une
sous-chaine donnée qu’on notera mot.
Le principe de l’algorithme est de gérer en permanence deux "curseurs" :

• l’un, noté i, indiquant l’indice de la première lettre du mot dans la chaine,

• l’autre, noté j, indiquant l’indice du caractère du mot actuellement testé dans la chaine.

L’idée est de comparer, pour une valeur de i donnée, les caractères mot[j] et chaine[i+j], dans
l’hypothèse que le mot "commence" à l’indice i de la chaine, pour tout j variant entre 0 et
long_mot - 1 ( = len(mot) - 1).
Cette comparaison est effectuée tant :

• qu’il reste des caractères à comparer, i.e. j < long_mot,

• et que les caractères coïncident, i.e. chaine[i+j] == mot[j].

while j < long_mot and chaine [i+j] == mot[j]:


j += 1
En sortie de boucle, il y a deux possibilités :

• ou bien deux caractères sont distincts (et dans ce cas, on incrémente la variable i de façon à décaler
l’indice de départ du mot dans la chaine),

• ou bien tous les caractères testés correspondent deux-à-deux. Dans ce cas, puisque la variable j a
été incrémentée à chaque test "réussi", sa valeur en sortie de boucle est long_mot.

while j < long_mot and chaine [i+j] == mot[j]:


j += 1
if j == long_mot :
return True
Il ne faut pas oublier de réinitialiser la valeur du curseur j (à zéro) à chaque fois que le curseur i est
déplacé : on recommence à tester chacun des caractères du mot, à partir du premier (j = 0).
En prenant en compte la longueur du mot dans la chaine, on remarque que les valeurs de i varient entre
0 et len(chaine) - len(mot).
Enfin, si la fonction n’a pas renvoyé True en sortie de la boucle for, c’est que la chaine ne contient pas
le mot; on renvoie alors False.

def recherche_mot_dans_chaine (mot , chaine ):


""" f o n c t i o n b o o l é e n n e q u i r e n v o i e s i c h a i n e c o n t i e n t mot """

long_chaine , long_mot = len( chaine ), len(mot)


for i in range( long_chaine − long_mot + 1):
j = 0
while j < long_mot and chaine [i+j] == mot[j]:
j += 1
if j == long_mot :
return True
return False

Algorithme de recherche d’un mot dans une chaine

Informatique MPSI/PCSI 51
5.2 Algorithmes de recherche séquentielle Chapitre 5. Algorithmes classiques

52 Informatique MPSI/PCSI
Chapitre 6
Analyse algorithmique
6.1 Terminaison et correction d’algorithmes
6.1.1 Le problème de l’arrêt
Le problème de l’arrêt, qui consiste à savoir si un algorithme (un calcul de fonction) termine ou non,
est un problème non trivial : Alan Turing a prouvé en 1936 qu’il s’agit d’un problème algorithmique-
ment indécidable. En d’autres termes, il n’existe aucun moyen algorithmique de déterminer si un calcul
quelconque termine.

6.1.2 Terminaison
Définition 6.1.1
Prouver la terminaison d’un (segment d’) algorithme consiste à montrer qu’il s’exécute en temps
fini, i.e. qu’il réalise un nombre fini d’opérations.

Remarque 6.1.2
La seule structure itérative nécessitant une preuve de terminaison est la boucle conditionnelle (boucle
while).
Pour prouver la terminaison d’une telle boucle, on utilise la propriété mathématique suivante : “toute
suite strictement décroissante et minorée d’entiers est finie”.

Exemple 6.1.3
Montrons la terminaison de mystere(a,b) pour tout (a, b) ∈ N × N∗ .
def mystere (a,b):
r, q = a, 0
while r >= b:
r = r − b
q += 1
return q, r

La suite des valeurs de r est strictement décroissante (car b > 0), à valeurs entières et minorée par b.
La suite des valeurs de r est donc finie, ce qui assure la terminaison de l’algorithme.

Exemple 6.1.4 (Le problème de l’arrêt d’un algorithme est non trivial )
On ne sait pas prouver à l’heure actuelle la terminaison de syracuse(a) pour tout a ∈ N∗ .
def syracuse (x0):
x = x0
while x != 1:
if x%2 == 0:
x = x//2
else:
x = 3 ∗ x+1
return x

53
6.2 Complexité Chapitre 6. Analyse algorithmique

6.1.3 Correction
Définition 6.1.5
Prouver la correction d’un (segment d’) algorithme consiste à prouver qu’il fournit bien une solution
valide pour répondre au problème donné.

Pour prouver la correction d’un algorithme itératif, on utilise la notion d’invariant de boucle.
Définition 6.1.6
Un invariant de boucle est une assertion vraie quelque soit l’itération d’une boucle.
Elle est en particulier vraie à l’entrée et à la sortie d’une boucle.

Exemple 6.1.7
Montrons la correction de l’algorithme de l’exemple 6.1.3 en exhibant un invariant de boucle.

1. Vérifier que l’assertion a = bq + r est un invariant de boucle.

2. En déduire la fonction mystere.

1. • Considérons l’assertion a = bq + r. Elle est vraie avant l’entrée dans la boucle :


bq + r = b × 0 + a = a.

• Notons r0 et q 0 les nouvelles valeurs, après une itération de boucle, de r et q. Alors r0 = r − b,


q 0 = q + 1 et :
bq 0 + r0 = b(q + 1) + (r − b) = bq + r = a.

L’assertion a = bq + r est donc bien un invariant de boucle.


2. En sortie de boucle, on a donc r < b. On a aussi r + b ≥ b, i.e. r ≥ 0, car sinon il aurait fallu sortir de
la boucle une itération plus tôt. Pour résumer :
a = bq + r et 0 ≤ r < b,
ce qui assure que q et r sont respectivement le quotient et le reste de la division euclidienne de a par b.
Remarque 6.1.8 (A retenir ! )
Les preuves correction et la terminaison de tout algorithme peuvent être exigées au
concours !

6.2 Complexité
6.2.1 Introduction
Pour comparer l’efficacité d’algorithmes associés à un même problème, on définit des outils de mesure de
leurs performances qui ne dépendent pas :
• de la machine sur lequel il est implémenté,
• des performances du processeur,
• des accès à la mémoire vive et de stockage,
• du langage de programmation dans lequel il est écrit,
• du compilateur/interpréteur utilisé.
On se place pour cela sur une machine idéalisée dont la mémoire est considérée comme infinie et l’accès
aux données se fait en temps constant. Les algorithmes seront souvent écrits en pseudo-code.

54 Informatique MPSI/PCSI
Chapitre 6. Analyse algorithmique 6.2 Complexité

Définition 6.2.1
On définit plusieurs notions de complexité algorithmique :

• la complexité en mémoire évaluant l’espace mémoire nécessaire à l’exécution de l’algorithme,

• la complexité en temps qui se divise elle-même en trois notions :

– la complexité dans le pire des cas,


– la complexité dans le meilleur des cas,
– la complexité en moyenne (moyenne probabiliste des complexités).

Pour évaluer la complexité temporelle d’un algorithme, on compte le nombre “d’opérations élémentaires”
effectuées par l’algorithme. C’est la nature du problème qui rend certaines opérations fonda-
mentales.
Exemple 6.2.2
Voici quelques exemples d’opérations considérées comme élémentaires :

• l’addition bit-à-bit lorsqu’on additionne deux nombres binaires,

• l’ensemble des opérations algébriques lors de l’évaluation d’un polynôme en un réel,

• la multiplication de deux réels lorsqu’on multiplie deux matrices,

• la comparaison de deux éléments d’un même tableau.

Le temps d’exécution d’un algorithme dépend essentiellement de la taille de l’entrée, qu’on assimile à un
entier naturel.
Exemple 6.2.3
Il s’agit par exemple :

• du nombre n ou du nombre de bits nécessaire à écrire n dans le cas où l’entrée est un entier n,

• du degré d’un polynôme,

• de la longueur d’un chaîne de caractères,

• du nombre d’éléments d’un tableau, d’une liste, d’un fichier,

• de la taille n d’une matrice carrée.

Remarque 6.2.4
Le choix d’une structure de données est fondamental dans la conception d’un algorithme quant à son
efficacité.

6.2.2 Complexité asymptotique


Rappels mathématiques
Définition 6.2.5 (Notations de Landau)
On dit qu’une suite réelle (un )n∈N est dominée par la suite (vn )n∈N et on note :
un = O(vn ),
n→+∞

s’il existe n0 ∈ N et K > 0 tel que :


∀n ≥ n0 , |un | ≤ K|vn |.

Informatique MPSI/PCSI 55
6.2 Complexité Chapitre 6. Analyse algorithmique

Définition 6.2.6 (Notations de Landau)


On dit qu’une suite réelle (un )n∈N est du même ordre de grandeur que la suite (vn )n∈N et on note

un = Θ(vn ),
n→+∞

s’il existe n0 ∈ N, K1 > 0 et K2 > 0 tels que :

∀n ≥ n0 , K1 |vn | ≤ |un | ≤ K2 |vn |.

Remarque 6.2.7

• Nous n’utiliserons en informatique que des suites à valeurs positives, nous pouvons donc nous
passer des valeurs absolues dans la définition précédente.

• Si la suite (vn )n∈N ne s’annule pas, la suite u est dominée par v si la suite u
v est majorée.

Proposition 6.2.8

1 = O(log n) log n = O(n1/2 )


n→+∞ n→+∞

n1/2 = O(n) n = O(n log n)


n→+∞ n→+∞

n log n = O(n2 ) n2 = O(n3 )


n→+∞ n→+∞

n3 = O(2n ) 2n = O(n!)
n→+∞ n→+∞

6.2.3 Classe de complexité asymptotique d’un algorithme


Définition 6.2.9
La complexité asymptotique est le comportement de la complexité d’un algorithme lorsque la taille
(souvent notée n) de son entrée est asymptotiquement grande.

Pour évaluer la complexité asymptotique d’un algorithme, on compare sa complexité aux classes de com-
plexité asymptotique usuelles, de façon à en avoir un ordre de grandeur.
Définition 6.2.10
On dit qu’un algorithme de complexité T (n) s’exécute :

• en temps constant si T (n) = Θ(1),

• en temps logarithmique si T (n) = Θ(log n),

• en temps linéaire si T (n) = Θ(n),

• en temps quasi-linéaire si T (n) = Θ(n log n),

• en temps quadratique si T (n) = Θ(n2 ),

• en temps cubique si T (n) = Θ(n3 ),

• en temps polynomial s’il existe p ∈ N tel que T (n) = Θ(np ),

• en temps exponentiel s’il existe a > 1 tel que T (n) = Θ(an ).

56 Informatique MPSI/PCSI
Chapitre 6. Analyse algorithmique 6.2 Complexité

Pour se donner une idée des différentes classes de complexité, calculons les temps d’exécutions
d’algorithmes ne réalisant que des opérations numériques sur un ordinateur personnel standard, capa-
ble de réaliser 1011 FLOPS 1 .

T (n)
Temps
log n n n log n n2 n3 2n
102 0.7 ns 1 ns 5 ns 0.1 µs 10 µs 4 × 1011
années
103 1 ns 10 ns 0.7 µs 10 µs 10 ms 10290
années
n

104 1.3 ns 0.1 µs 0.9 µs 1 ms 10 s


105 1.7 ns 1 µs 12 µs 0.1 s 2 h 46
min
106 2 ns 10 µs 0.71 ms 10 s 116 jours

On présente ci-dessous (un ordre de grandeur de) la valeur de n atteinte en une minute par des algorithmes
de chacune des classes de complexité.

T (n) log n n n log n n2 n3 2n


12
n 101,8×10 6 × 1012 1, 6 × 1011 2, 4 × 106 18171 42

Exemple 6.2.11
Pour répondre à un problème donné (un problème de tri par exemple), on implémente deux algorithmes
distincts :

• un algorithme en n log2 n opérations (complexité quasi-linéaire) sur une première machine, ca-
pable d’effectuer 229 opérations par secondes (typiquement Pentium III 450),

• un algorithme en n2 opérations (complexité quadratique) sur une seconde machine, capable


d’effectuer 225 opérations par secondes (typiquement 486 DX 33).

Comparons les temps d’exécution de deux algorithmes pour n = 220 :

• Le temps d’exécution de l’algorithme en n2 opérations, sur la machine la plus rapide, est


d’environ 34 minutes.

• Le temps d’exécution de l’algorithme en n log2 n opérations, sur la machine la plus lente est de
0,625 secondes.

Remarque 6.2.12
Il ne suffit donc pas de travailler sur une machine puissante, encore faut-il développer de bons algo-
rithmes !

1
FLOPS est l’acronyme de floating point operations per second.

Informatique MPSI/PCSI 57
6.2 Complexité Chapitre 6. Analyse algorithmique

Exemple 6.2.13
On considère l’algorithme ci-dessous, écrit en pseudo-code, permettant de déterminer le plus grand
élément d’un tableau de nombres :
ALGORITHME : MAX_TAB
ENTREE : Tableau T
m <- T[0]
n <- nb d’élément de T
POUR k de 1 à n-1 FAIRE :
SI T[k] > m FAIRE :
m <- T[k]
FIN SI
k <- k+1
FIN POUR
SORTIE : Élément maximal m de T
• Déterminons la complexité (en temps) de cet algorithme.
Ici, l’opération fondamentale est la comparaison d’éléments. A chaque itération, on effectue une
comparaison. On effectue ainsi n − 1 comparaisons.
Cet algorithme s’exécute donc en Θ(n) opérations, i.e. sa complexité est linéaire.

• Montrons que cet algorithme est optimal :


Supposons qu’il existe un algorithme réalisant au plus de n − 2 comparaisons. Cela implique
qu’un élément - potentiellement maximal - n’a pas été comparé aux autres, ce qui est absurde.
Notre algorithme est donc optimal en nombre de comparaisons.

58 Informatique MPSI/PCSI
Chapitre 6. Analyse algorithmique 6.3 Exercices

6.3 Exercices
Exercice 6.3.1

1. Écrire en Python, à l’aide d’une boucle while une fonction somme qui a pour argument d’entrée un
entier n et qui renvoie en sortie la somme des entiers de 1 à n.

2. Prouver la terminaison et la correction de ce programme.

Exercice 6.3.2
On considère l’algorithme suivant (où les arguments d’entrée x et n sont de type entier positifs) :
def square_and_multiply (x,n):
y, p = x, n
r = 1
while p > 0:
if p%2 == 0:
y = y ∗∗ 2
p = p//2
else:
r ∗= y
p −= 1
return r

1. Montrer que l’algorithme termine.

2. Que renvoie square_and_multiply(2,11) ?


Indication : on dressera un tableau suivant l’évolution du contenu des variables y, p et r.

3. Que peut-on conjecturer ?

4. Que fait la fonction square_and_multiply ? Une preuve de correction est attendue.

Exercice 6.3.3
On considère l’algorithme suivant (où l’argument d’entrée x est de type entier) :
def floored_root (x):
a = 0
b = 1
c = 1
while c <= x:
a += 1
b += 2
c += b
return a

1. Montrer que l’algorithme termine.


2. Dresser un tableau des valeurs de a, b et c lors du calcul de floored_root(68).
3. Conjecturer une expression de b et c en fonction de a. Démontrer cette conjecture
4. Montrer que P : “a2 ≤ x” est invariant de boucle.
5. En déduire la preuve du programme.

Informatique MPSI/PCSI 59
6.3 Exercices Chapitre 6. Analyse algorithmique

Exercice 6.3.4 (Analyse des algorithmes du programme ♥)


Pour chacun des algorithmes suivant, déterminer sa complexité dans le meilleur puis le pire des cas :

a. recherche du maximum d’un tableau de nombres

b. calcul de la moyenne, de la variance d’un tableau de nombre

c. approximation d’une intégrale par la méthode des rectangles, des trapèzes,

d. recherche d’un mot dans une chaîne de caractères,

e. recherche du zéro d’une fonction continue monotone sur un intervalle,

f. recherche séquentielle dans un tableau (cf. exercice 5),

g. recherche dichotomique dans un tableau trié (cf. exercice 5).

Exercice 6.3.5 (Recherche séquentielle et dichotomique dans un tableau trié ♥)


On cherche à trouver un algorithme permettant de décider si un tableau trié T (par ordre croissant)
continent un élément x.

1. Évaluer la complexité, dans le meilleur et le pire des cas, de l’algorithme de recherche séquentielle
d’un élément dans un tableau.

2. On note C(n) le nombre de comparaisons de l’algorithme de recherche dichotomique dans le pire


des cas.

a. Établir une relation de récurrence.


b. Calculer exactement C(2p ) où p ∈ N.
c. En déduire la classe de complexité de l’algorithme de recherche dichotomique.

Exercice 6.3.6 (Tri à bulle - bubble sort)


On s’intéresse ici à un algorithme de tri d’un tableau de nombres : l’algorithme du tri à bulle.
Son principe est le suivant :

• à la première itération, on parcourt le tableau dans sa totalité, et si deux éléments consécutifs


ne sont pas ordonnés correctement, on les permute,

• à la seconde itération, on réitère le procédé précédent en ne parcourant que les n − 1 premiers


éléments du tableau (de taille n),

• la dernière itération se limite à comparer les deux premiers éléments du tableau.

1. Écrire en Python une fonction tri_bulle triant sur place, i.e. en modifiant le tableau fourni en
entrée, à l’aide de l’algorithme du tri à bulle.

2. Prouver la terminaison et la correction de l’algorithme du tri à bulle.


Bonus : d’où vient le nom de cet algorithme de tri ?

3. Déterminer, dans le meilleur et le pire des cas, le nombre de comparaisons du tri à bulle.

4. Optimiser le code écrit à la question 1 de manière à ce que l’algorithme s’arrête dès lors que le
tableau est trié.

60 Informatique MPSI/PCSI
Chapitre 6. Analyse algorithmique 6.3 Exercices

Exercice 6.3.7
Le problème est de déterminer à partir de quel étage d’un immeuble, sauter par une fenêtre est fatal.
Vous êtes dans un immeuble à n étages (numérotés de 1 à n) et on dispose de k étudiants. Il n’y a
qu’une opération possible pour tester si la hauteur d’un étage est fatal : faire sauter un étudiant par
la fenêtre. S’il survit, vous pouvez le réutiliser ensuite.
Le but du problème est de proposer un algorithme pour trouver la hauteur à partir de laquelle un saut
est fatal (on renverra n + 1 si on survit encore en sautant du n-ème étage) en faisant le minimum de
sauts.

1. Si k ≥ dlog2 (n)e, proposer un algorithme en O(log2 (n)) sauts.


 n 
2. Si k ≤ dlog2 (n)e, proposer un algorithme en O k + k−1 sauts.
2

3. Si k = 2, proposer un algorithme en O( n) sauts.

Exercice 6.3.8 (Qui est la star ? )


Dans un groupe de n personnes (numérotées de 1 à n pour les distinguer), une star est quelqu’un que
tout le monde connait mais qui ne connait personne.
Pour trouver une star, s’il en existe une, on ne peut poser qu’une seule question, à n’importe quels
individus i du groupe : “est-ce que vous connaissez j ?”. On notera ”i → j” l’assertion “l’individu i
connait l’individu j”.
On suppose que toutes les personnes sont honnêtes. On cherche un algorithme qui trouve une star, s’il
en existe une, et qui, dans le cas contraire, garantit qu’il n’y en a pas, en posant le moins de questions
possibles.

1. Combien un groupe peut-il avoir de star ?

2. Écrire le meilleur algorithme que vous pouvez en déterminer sa complexité (en nombre de questions).
On peut y arriver en O(n) questions.

Informatique MPSI/PCSI 61
6.3 Exercices Chapitre 6. Analyse algorithmique

62 Informatique MPSI/PCSI
Partie IV

Bases de données relationnelles

63
Chapitre 7
Bases de données relationnelles

7.1 Introduction
Le développement et la démocratisation rapide des ordinateurs ont très vite posé la problématique du
stockage, de la consultation (lecture) et de la modification de données.

7.1.1 Inadéquation des structures de données “plates”


Pour illustrer l’inadéquation des structures de données “plates” dans certaines situations, on considère
l’exemple de la gestion des livres d’une bibliothèque.
Tous les livres possèdent un numéro d’exemplaire, un titre, un ou plusieurs auteurs et éditeurs. Le
numéro d’exemplaire est un identifiant unique permettant de différencier les exemplaires d’un même livre.
Lorsqu’une personne emprunte un livre, il faut mémoriser son nom, son prénom, son numéro de téléphone,
son adresse, la date de l’emprunt et la date de retour, une fois ce dernier réalisé. Toutes ces informations
doivent être conservées pour garder un historique des emprunts.
On peut alors développer une application de gestion d’une bibliothèque qui choisit de stocker les infor-
mations précédentes de manière persistante dans un fichier texte de la façon suivante :

• le fichier texte contient à l’origine une ligne par livre,

• sur chaque ligne, on renseigne les informations idEx, titre, auteur, éditeur séparés par des tabula-
tions,

• lorsqu’un livre est emprunté pour la première fois, on complète la ligne du livre en question par les
champs nom, prénom, téléphone, adresse, date_emprunt,

• lorsqu’un livre est retourné, on ajoute, après tabulation, un dernier champ date_retour au bout de
la ligne correspondante,

• lorsqu’un livre est emprunté une nouvelle fois, on ajoute une nouvelle ligne en copiant toutes les
informations sur le livre qu’on complète par les informations de l’empruntant.

On suppose que l’application fonctionne “correctement” depuis 20 ans, que le nombre de personnes inscrites
à la bibliothèque est de 5000 personnes par an en moyenne et qu’un abonné emprunte en moyenne 2
ouvrages par mois.

1. Quel est environ le nombre de lignes du fichier de données ?


réponse :

2. On suppose que chaque caractère occupe 1 octet et qu’une ligne contient, en moyenne, 200 caractères.
Quelle est la taille approximative du fichier ?
réponse :

3. On suppose qu’un accès au fichier coûte 8 ms (temps moyen d’accès au disque dur), qu’une lecture
de ligne coûte 0,1 ms (temps mis pour lire les 200 caractères), et qu’une recherche sur la ligne pour
trouver le numéro de l’exemplaire ou le nom et prénom de l’abonné coûte 0,01ms.

65
7.2 Modèle de données relationnelle - vocabulaire Chapitre 7. Bases de données relationnelles

Lorsqu’un abonné emprunte un livre, le bibliothécaire saisit simplement le numéro de l’exemplaire, le


nom et le prénom de l’abonné. L’application se charge alors de parcourir le fichier pour rechercher les
informations manquantes (concernant le livre et l’abonné).
Quel est, dans le pire des cas, le temps mis par l’application pour compléter les informations saisies
par le bibliothécaire ?
réponse :

4. On suppose qu’une personne est abonnée depuis l’origine de l’application. Elle prévient le bibliothécaire
que son nom est mal orthographié. Quel est le nombre moyen de lignes à modifier dans tout le fichier
pour corriger cette erreur.
réponse :

5. Cette “base de données” permet-elle réellement de retrouver des informations ?

7.1.2 Représentation de données


Les représentations de données selon les modèles actuels permettent de répondre aux problèmes précédents
liés à la cohérence et la redondance des données :

• recherche d’information,

• mise à jour des données

• taille du fichier

• non dégradation des performances des requêtes avec le temps


Définition 7.1.1
Une base de données est un ensemble structuré de données :

• stocké sur un support accessible à une ou plusieurs machines,

• modélisant des informations réelles,

• pouvant être interrogé (par des requêtes),

• pouvant être mis à jour par une “communauté” d’utilisateurs.

Remarque 7.1.2 (À retenir ! )


La cohérence et la non-redondance des données constituent la problématique fondamentale des
bases de données.

7.2 Modèle de données relationnelle - vocabulaire


Le modèle de données relationnelle représente une base de données comme un ensemble de tables
(sans se préoccuper de la façon dont ces données sont stockées en mémoire).
Attention : ce modèle ne constitue qu’une abstraction de l’enregistrement physique des informations.

• Les données sont manipulées par les opérateurs de l’algèbre relationnelle (cf. paragraphe 7.3).

• La cohérence des données de la base est définie par des contraintes d’intégrité.
Définition 7.2.1
Un attribut est un identifiant (un nom) décrivant une information stockée dans une base.

66 Informatique MPSI/PCSI
Chapitre 7. Bases de données relationnelles 7.2 Modèle de données relationnelle - vocabulaire

Exemple 7.2.2
Le nom de famille d’une personne, son âge, le code postal d’une ville sont des attributs.

Définition 7.2.3
Le domaine d’un attribut est l’ensemble (fini ou non) de ses valeurs possibles.

Exemple 7.2.4
Le domaine de l’attribut code postal est l’ensemble des nombres à 5 chiffres dont les deux premiers
varient entre 01 et 99.

Définition 7.2.5
Une relation est un sous-ensemble du produit cartésien de n domaines d’attributs.

• Elle est représentée sous forme de table à deux dimensions dans laquelle les n attributs corre-
spondent aux titres des n colonnes.

Un schéma de relation précise le nom de la relation ainsi que la liste des attributs avec leurs
domaines respectifs.

Exemple 7.2.6
La table suivante
personne
idSecu nom prenom
1 62 05 99 152 424 31 Banner Bruce
1 62 08 99 268 011 22 Parker Peter
2 65 12 99 280 042 18 Stacy Gwen

illustre un exemple de schéma de relation de la relation :


personne (idSecu, nom, Prénom)

Définition 7.2.7
On appelle degré d’une relation son nombre d’attributs.

Définition 7.2.8
On appelle occurrence (ou encore n-uplet ou tuple) un élément de l’ensemble défini par une relation.

• Une occurrence est représentée par une ligne de la table représentant la relation.

Définition 7.2.9
On appelle cardinalité d’une relation son nombre d’occurrences.

• La cardinalité d’une relation est alors le nombre de lignes de la table représentant la relation.

Définition 7.2.10
On appelle clé candidate d’une relation un ensemble (minimal) d’attributs de la relation permettant
de distinguer deux occurrences.

• À deux occurrences distinctes d’une relation correspondent deux valeurs distinctes de clé candi-
date.

Remarque 7.2.11 (Contrainte d’intégrité 1 )


Toute relation doit avoir au moins une clé candidate.

Informatique MPSI/PCSI 67
7.3 Algèbre relationnelle et instructions MySQL Chapitre 7. Bases de données relationnelles

Définition 7.2.12
La clé primaire d’une relation est une clé candidate choisie pour caractériser chaque occurrence
d’une relation.

• Pour signaler la clé primaire d’une relation, on soulignera ses attributs dans le schéma de relation.

Remarque 7.2.13 (Création d’une clé primaire)


Il est parfois nécessaire de créer artificiellement une clé primaire en ajoutant un attribut à une relation
de façon à caractériser ses occurrences.
Par exemple, la relation personne(nom, prénom) n’a pas de clé primaire, ce qu’on s’interdit par
construction (contrainte d’intégrité). En effet : deux personnes distinctes peuvent avoir mêmes noms
et mêmes prénoms (problème d’homonymie), rendant impossible leur distinction dans la relation
personne.

Définition 7.2.14
Dans une relation, une clé étrangère est un ensemble d’attributs (clé) qui constitue une clé candidate
pour une autre relation.

Remarque 7.2.15 (Contrainte d’intégrité 2 )


Une clé étrangère dans une relation ne peut constituer une clé candidate pour cette même relation.

Définition 7.2.16
On appelle schéma relationnel l’ensemble des schémas de relation (en précisant les clés étrangères)
et base de données relationnelle l’ensemble des occurrences des différentes relations du schéma
relationnel.

Exercice 7.2.17
Définir un schéma relationnel à partir la figure suivante (modèle entité-association) :

ETUDIANT
COURS
idEtudiant 1,n SUIVRE 0,n
idCours
nom note
intitule
prenom

7.3 Algèbre relationnelle et instructions MySQL


7.3.1 La sélection
Définition 7.3.1
La sélection est une opération qui, à une relation R et un prédicat E, associe (génère) une relation,
notée σE R, regroupant exclusivement l’ensemble des occurrences de R vérifiant l’assertion E.
La requête MySQL associée est :
SELECT ∗ FROM R WHERE E

68 Informatique MPSI/PCSI
Chapitre 7. Bases de données relationnelles 7.3 Algèbre relationnelle et instructions MySQL

Exemple 7.3.2
On considère la relation suivante :
personne
idPers nom prenom
1 Euler Leonhard
2 Gauss Carl Friedrich
3 Serre Jean-Pierre
4 Grothendieck Alexander
5 Curie Marie
6 Curie Pierre

On obtient la relation suivante, après sélection de toutes les occurrences ayant un identifiant (idPers)
strictement supérieur à 3 :

σidPers>3 personne
idPers nom prenom
4 Grothendieck Alexander
5 Curie Marie
6 Curie Pierre

La requête MySQL associée est :


SELECT ∗
FROM personne
WHERE idPers > 3

7.3.2 La projection
Définition 7.3.3
La projection est une opération qui, à une relation R et une liste d’attributs A1 , . . . , An , associe
(génère) une relation, notée ΠA1 ,...,An R, regroupant toutes les occurrences de R mais où tous les
attributs de R autres que A1 , . . . , An ont été supprimés.
La requête MySQL associée est :
SELECT A_1 , ..., A_n FROM R

Pour supprimer les doublons dans la nouvelle relation, on place le mot-clé DISTINCT avant la liste
d’attributs sur laquelle s’effectue la projection.

Exemple 7.3.4
Les projections respectivement sur les attributs nom et prenom, et sur l’attribut nom de la relation
personne fournissent les relation suivantes :

Πnom, prenom personne Πnom personne


nom prenom nom
Euler Leonhard Euler
Gauss Carl Friedrich Gauss
Serre Jean-Pierre Serre
Curie Marie Curie
Curie Pierre

La requête MySQL associée est : La requête MySQL associée est :

SELECT nom , prenom FROM personne SELECT DISTINCT nom FROM personne

Informatique MPSI/PCSI 69
7.3 Algèbre relationnelle et instructions MySQL Chapitre 7. Bases de données relationnelles

7.3.3 Le produit cartésien


Définition 7.3.5
Le produit cartésien est une opération qui, à une relation R1 et une relation R2 , associe (génère)
une relation, notée R1 × R2 , dont les attributs sont formés par la réunion de ceux de R1 et R2 , et
dont les occurrences sont formées par toutes les combinaisons possibles des occurrences de R1 et R2 .
La requête MySQL associée est :
SELECT ∗ FROM R_1 , R_2

Exemple 7.3.6
On considère les relations suivantes :
personne
nom peuple
Gandalf Maiar
Elendil Hommes

cadeau
artefact type créateur
Narsil épée Telchar
Narya anneau Celebrimbor

personne × cadeau
nom peuple artefact type créateur
Gandalf Maiar Narsil épée Telchar
Gandalf Maiar Narya anneau Celebrimbor
Elendil Hommes Narsil épée Telchar
Elendil Hommes Narya anneau Celebrimbor

La requête MySQL associée est :


SELECT ∗ FROM personne , cadeau

7.3.4 La réunion, l’intersection et la différence


Définition 7.3.7
La réunion est une opération qui, à une relation R1 et une relation R2 , associe (génère) une relation,
notée R1 ∪ R2 , dont l’ensemble des occurrences correspond à la réunion (au sens mathématique) des
occurrences de R1 et R2 .
On définit de la même façon les notions d’intersection et de différence, notées R1 ∩ R2 et R1 − R2 .
La requête MySQL associée à la réunion de deux relations est :
SELECT ∗ FROM R_1
UNION
SELECT ∗ FROM R_2

Remarque 7.3.8 (Attention ! )


La réunion, l’intersection ou la différence de deux relations ne peuvent porter que sur la même liste
d’attributs.

70 Informatique MPSI/PCSI
Chapitre 7. Bases de données relationnelles 7.3 Algèbre relationnelle et instructions MySQL

Exemple 7.3.9
On considère les relations suivantes :

mathematicien physicien
nom prenom nom prenom
Newton Isaac Newton Isaac
Leibnitz Gottfried Hawking Stephen
Abel Niels Leibnitz Gottfried

On obtient la relation suivante après union des deux relations précédentes :

mathematicien ∪ physicien
nom prenom
Newton Isaac
Leibnitz Gottfried
Abel Niels
Hawking Stephen

La requête MySQL associée est :


SELECT ∗ FROM mathematicien
UNION
SELECT ∗ FROM physicien

Remarque 7.3.10 (Attention ! )


Il n’existe pas d’instructions en MySQL permettant de réaliser une intersection ou une réunion. On peut
cependant contourner le problème en construisant des requêtes (non optimisées) à l’aide d’instructions
alternatives.
En reprenant l’exemple précédent, la requête pour déterminer les scientifiques qui sont à la fois mathé-
maticiens et physiciens, et celle pour déterminer les mathématiciens qui ne sont pas physiciens peuvent
s’écrire :

SELECT ∗ FROM mathematicien SELECT ∗ FROM mathematicien


WHERE nom , prenom IN ( WHERE nom , prenom NOT IN (
SELECT ∗ FROM physicien SELECT ∗ FROM physicien
) )

7.3.5 La jointure
Définition 7.3.11
La jointure est une opération qui, à deux relations R1 et R2 et un prédicat E, associe une relation,
notée R1 ./ R2 , dont les occurrences correspondent à celles de R1 × R2 vérifiant le prédicat E.
E
La requête MySQL associée est :
SELECT ∗ FROM
R_1 JOIN R_2 ON E

Remarque 7.3.12
La jointure correspond simplement à un produit cartésien suivi d’une sélection :

R1 ./ R2 = σE (R1 × R2 ) .
E

Cette opération de jointure est cependant optimisée en machine, en comparaison avec la requête
effectuant une sélection sur un produit cartésien.

Informatique MPSI/PCSI 71
7.3 Algèbre relationnelle et instructions MySQL Chapitre 7. Bases de données relationnelles

Exemple 7.3.13
On considère les relations suivantes :
personne cadeau
nom prenom age article age prix
Amaury Olympe 113 montre 24 239
Le Goff Maureen 24 peluche 2 39
El Rez William 2 livre 113 28
Highmore Freddie 24

On obtient la relation suivante après jointure entre les relations précédentes sur l’expression logique
“personne.age = cadeau.age”.

personne ./ cadeau
personne.age = cadeau.age
nom prenom age article prix
Amaury Olympe 113 livre 28
Le Goff Maureen 23 montre 239
El Rez William 1 peluche 39
Highmore Freddie 23 montre 239
La requête MySQL associée est :
SELECT ∗ FROM
personne JOIN cadeau ON personne .age = cadeau .age
L’erreur à éviter serait d’écrire la requête suivante (non optimisée) :
SELECT ∗ FROM personne , cadeau
WHERE personne .age = cadeau .age

7.3.6 La division
Définition 7.3.14
La division est une opération qui, à une relation R1 et une relation R2 tel que le schéma de R2
soit strictement inclus dans celui de R1 , associe (génère) une relation, notée R1 /R2 , dont les occur-
rences sont formées des parties d’occurrences de R1 qui, associées à toutes les occurrences de R2 ,
appartiennent dans R1 .

Exemple 7.3.15
On considère les relations suivantes :
enseignement etudiant
enseignant nom nom
Euler Laplace Laplace
d’Alembert Galois Galois
Gauss Laplace
Euler Galois
d’Alembert Laplace
Euler Poincarré
Gauss Poincarré
Les occurrences de la relation “enseignement/etudiant” correspondent aux enseignants ayant enseigné
à tous les étudiants de la relation etudiant :
enseignement/etudiant
enseignant
Euler
d’Alembert

72 Informatique MPSI/PCSI
Chapitre 7. Bases de données relationnelles 7.3 Algèbre relationnelle et instructions MySQL

Proposition 7.3.16
La division est caractérisée par la propriété suivante : R1 /R2 est la plus grande relation R (au sens
de l’inclusion) vérifiant R × R2 ⊂ R1 .

Remarque 7.3.17
Il n’existe pas de traduction directe en SQL ou MySQL de l’opérateur de division. Il est alors nécessaire
de reformuler grâce à l’équivalence suivante :
 
∀x, P(x) ⇔ ¬ ∃x, ¬P(x) .

Pour reprendre l’exemple précédent, on peut reformuler la requête

“quels sont les enseignants qui ont enseigné à tous les étudiants ?”

en la requête :

“quels sont les enseignants qui ne vérifient pas qu’il existe un étudiant qui n’ait pas suivi leur cours ?”.

La requête MySQL associée est :


SELECT DISTINCT enseignant
FROM enseignement AS prof_pour_tous
WHERE NOT EXISTS (
SELECT ∗ FROM etudiant AS eleve
WHERE NOT EXISTS (
SELECT ∗
FROM etudiant JOIN enseignement ON enseignement .nom = etudiant .nom
WHERE (
eleve.nom = etudiant .nom
AND enseignement . enseignant = prof_pour_tous . enseignant
)
)
)

7.3.7 Les fonctions d’agrégation


Définition 7.3.18
Une fonction d’agrégation est une fonction qui renvoie une valeur (unique) à partir d’un ensemble
de valeurs, par exemple une colonne.

Les fonctions d’agrégations au programme sont :

• MIN, calculant le minimum d’une série de valeurs.

• MAX, calculant le maximum d’une série de valeurs

• SUM, calculant la somme d’une série de valeurs,

• COUNT, comptant le nombre d’occurrences

• AVG, calculant la moyenne d’une série de valeurs.

Informatique MPSI/PCSI 73
7.4 Systèmes de gestion de base de données (SGBD) Chapitre 7. Bases de données relationnelles

Exemple 7.3.19

• La requête MySQL suivante :


SELECT COUNT ( ∗ ) FROM personne

permet de déterminer le nombre de lignes de la relation personne.

• La requête MySQL suivante :


SELECT COUNT ( DISTINCT nom) FROM personne

permet de déterminer le nombre de noms distincts de la relation personne.

• La requête MySQL suivante :


SELECT MAX(age) FROM personne

permet de déterminer l’âge de la personne la plus âgée de la relation personne.

• La clause GROUP BY permet de filtrer les lignes sur lesquelles s’effectuent le calcul.
Par exemple, si on considère la relation suivante :

devoir
nom prenom n_devoir note
Cooper Sheldon 1 20
Wolowitz Howard 1 12
Cooper Sheldon 2 20
Wolowitz Howard 2 14

la requête MySQL ci-dessous :


SELECT nom , prenom , AVG(note) AS moyenne
FROM devoir
GROUP BY nom , prenom

permet alors de créer la table suivante :

nom prenom moyenne


Cooper Sheldon 20
Wolowitz Howard 13

• Remarque : une sélection sur des agrégats ne s’effectue pas avec le mot-clé WHERE mais avec
HAVING, qu’on placera, lorsqu’un regroupement est nécessaire, après le mot-clé GROUP BY.

7.4 Systèmes de gestion de base de données (SGBD)


7.4.1 L’architecture client/serveur
L’architecture client/serveur permet à de nombreux utilisateurs de travailler sur une même base de données
depuis différentes machines.

• La base de données est stockée sur une machine appelée serveur.

• Les clients n’ont pas un accès direct à la base de données ; ils la consultent par des requêtes.

74 Informatique MPSI/PCSI
Chapitre 7. Bases de données relationnelles 7.5 Exercices

• Le serveur traite la base de données en la modifiant éventuellement et renvoie les résultats des
requêtes au client.

• plusieurs clients peuvent effectuer simultanément des requêtes ; le système de gestion de base de
données (SGBD) gère la cohérence des données (le client n’a pas accès aux fichiers contenant les
données).

requêtes

client réseau serveur

réponse

Dans une architecture client/serveur, le client est dit lourd : il partage du calcul (processing)
avec le serveur.

7.4.2 L’architecture 3-tiers (ou trois couches)


L’architecture trois-tiers est basée sur le principe que tout système d’information nécessite la réalisation
de trois fonctionnalités : le stockage des données, la logique applicative et la présentation.
Les éléments permettant la réalisation classique d’un système en architecture trois tiers sont les suivants:

• un système de base de donnée relationnel (SGBDR) pour le stockage des données,

• un serveur d’applications pour la logique applicative,

• et un navigateur web pour la présentation.

requêtes
système de gestion
client serveur SQL de base de données
navigateur applicatif relationnelles
(SGBDR)
réponse

7.5 Exercices
Exercice 7.5.1

1. Définir un schéma relationnel à partir la figure suivante (modèle entité-association) :

ENSEIGNANT
COURS
idEnseignant 0,n DISPENSER 0,n
idCours
nom
intitule
prenom

2. Proposer un exemple de (petite) base de données associée au schéma relationnel précédent.


3. (a) Reprendre l’exemple précédent lorsque la cardinalité du côté de cours est 1, 1.
(b) Justifier pourquoi le schéma relationnel doit être modifié dans ce cas précis. Donner le schéma
relationnel correct.

Informatique MPSI/PCSI 75
7.5 Exercices Chapitre 7. Bases de données relationnelles

Exercice 7.5.2
On considère la base de donnée AirlineDB dont on présente le schéma relationnel (incomplet) :
pilot (numP, nameP, address, salary)
airplane (numAP, nameAP, capacity, localisation)
flight (numF, numP, numAP, depT, arrT, depH, arrH)

1. Identifier les clés étrangères de manière à définir correctement le schéma relationnel.

2. Écrire une requête MySQL permettant de répondre aux questions suivantes.

a. Où se trouvent actuellement les avions pouvant contenir plus de 300 passagers ?


b. Déterminer les pilotes domiciliés à Tokyo dont le salaire est compris entre 100000 et 200000
dollars.
On considérera que tous les salaires sont calculés en dollars.
c. Quels sont les noms des pilotes qui ne sont pas en service ?
d. Quels sont les vols (numéro, ville de départ, ville d’arrivée) effectués par les pilotes de numéro
145 et 206 ?
e. Classer les pilotes dans l’ordre croissant de leur salaire et déterminer leur salaire moyen
f. Quels sont les pilotes en service qui vont atterrir dans la ville dans laquelle ils sont domiciliés ?
g. Quel est le nombre d’avions au départ de chacune des villes ?
h. Quel est le nom du pilote qui réalise le vol le plus long ?

Exercice 7.5.3
On considère la base de donnée CarRentalDB dont on présente le schéma relationnel (incomplet) :
voiture (id_voiture, marque, modele, état)
client (id_client, nom, prenom, adresse, num_permis)
location (id_loc, id_voiture, id_client, date_debut, date_fin)

1. Identifier les clés étrangères de manière à définir correctement le schéma relationnel.

2. Écrire une requête MySQL permettant de répondre aux questions suivantes.

a. Déterminer le nombre de voitures louées par l’entreprise depuis la création de la base de données.
b. Déterminer les modèles qui n’ont jamais été loués.
c. Déterminer la marque de voiture la plus louée.
d. Déterminer le nombre de locations de voitures par marque. On triera le résultat par ordre
décroissant.
e. Déterminer le nombre de voitures louées par client.
f. Déterminer les clients (nom, prénom) qui n’ont pas encore rendu leur voiture de location.
La vacuité d’un champ se teste avec la syntaxe IS NULL.
g. Quels sont les véhicules qui ont été loués sur la durée la plus longue. On classera le résultat par
ordre décroissant de l’état.

76 Informatique MPSI/PCSI
Partie V

Analyse numérique

77
Chapitre 8
Résolution d’équations
8.1 Algorithme de recherche du zéro d’une fonction monotone
Le but de cet algorithme est de rechercher une valeur approchée du zéro d’une fonction continue
monotone sur un intervalle donné, lorsqu’il existe. La fonction f, les bornes a et b de l’intervalle ainsi
que l’erreur d’approximation epsilon sont fournis par l’utilisateur comme arguments d’entrée.
Le principe est qu’à chaque itération, on divise par deux la longueur de l’intervalle dans lequel on recherche
u+v
le zéro éventuel : si on le recherche dans l’intervalle [u, v] et si on note m = , il se situe dans l’intervalle
2
[u, m] ou dans l’intervalle [m, v], par monotonie de la fonction f. Il suffit donc, selon les cas, d’affecter à
la variable u ou la variable v, la valeur de m de façon à réduire l’intervalle de recherche à l’étape suivante.
m = (u+v)/2
if f(u) ∗ f(m) <= 0:
v = m
else:
u = m
La recherche dichotomique continue tant que la longueur de l’intervalle de recherche est supérieure au
double de l’erreur d’approximation epsilon. Une fois cette condition fausse, on renvoie en sortie de boucle
la valeur médiane du dernier intervalle [u, v] : la distance de ce réel à tout entier de l’intervalle [u, v] est
bien inférieure à epsilon.
Enfin, à l’aide de l’instruction assert, on s’assure de se trouver bien dans les conditions de réalisation de
l’algorithme :

• les valeurs données définissent bien un intervalle (a < b),

• le zéro existe bien (et est unique) par le théorème de la bijection (f(a)*f(b) <= 0),

• et l’erreur d’approximation est bien strictement positive (epsilon > 0).

Les hypothèses de continuité et de monotonie ne sont quant à elles pas vérifiables : l’ensemble des flottants
(quelque soit la norme et la précision) est fini, il ne peut avoir la puissance du continu.
def recherche_zero_dichotomie (f,a,b, epsilon ):
""" Détermine , s ’ i l e x i s t e , une v a l e u r a p p r o c h é e à e p s i l o n p r è s
du z é r o d ’ une f o n c t i o n monotone s u r [ a , b ] , par d i c h o t o m i e """
assert a < b and f(a) ∗ f(b) <= 0 and epsilon > 0, \
"On ne peut pas conclure si f s’annule sur [a,b]"
u, v = a, b
while abs(v−u) > 2 ∗ epsilon :
m = (u+v)/2
if f(u) ∗ f(m) <=0:
v = m
else:
u = m
return (u+v)/2

Méthode de recherche dichotomique du zéro d’une fonction monotone

79
8.2 Méthode de Newton Chapitre 8. Résolution d’équations

8.2 Méthode de Newton


La méthode de Newton consiste à approcher un zéro d’une fonction à l’aide d’une suite (xn )n∈N définie
par récurrence :

(i) on choisit x0 qu’on suppose proche du zéro recherché ;

(ii) xn étant construit, on construit xn+1 comme étant l’abscisse du point d’intersection de l’axe des
abscisses avec la tangente à la courbe Cf (qu’on espère non verticale !) au point d’abscisse xn .

On illustre ci-dessous un cas où la suite définie par méthode de Newton converge un zéro de fonction.

x3 x2 x1 x0

La suite des approximations successives est définie par la relation de récurrence suivante :

f (xn )
xn+1 = xn − .
f 0 (xn )

En effet, la tangente à Cf au point d’abscisse xn a pour équation y = f 0 (xn )(x−xn )+f (xn ). L’abscisse du
point d’intersection de cette tangente avec l’axe des abscisses vérifie l’équation f 0 (xn )(x−xn )+f (xn ) = 0.
On obtient alors la relation de récurrence en résolvant l’équation.
On peut alors implémenter la méthode de Newton. Il faut noter que cette méthode nécessite la con-
naissance de la dérivée de la fonction dont on cherche à approximer un zéro (ce qui n’est pas toujours
possible).
Au lieu d’écrire un programme qui calculerait le n-ème terme de la suite, on propose ci-dessous la condition
d’arrêt suivante : lorsque la différence entre deux termes consécutifs est inférieure à une valeur ε fixée en
entrée, la méthode s’arrête et renvoie le dernier terme calculé.
def newton (f, fprime , x_0 , epsilon ):
""" Méthode de Newton : r e n v o i e une a p p r o x i m a t i o n , p r o c h e de x_0 ,
d ’ un z é r o de l a f o n c t i o n f , en c o n n a i s s a n t sa d é r i v é e f p r i m e """
x = x_0
while abs(f(x) / fprime (x)) > epsilon :
x −= (f(x) / fprime (x))
return x

Méthode de Newton

Remarque 8.2.1 (Attention ! )

• La méthode de Newton ne converge que sous certaines conditions, comme par exemple que
l’abscisse du point de départ soit suffisamment proche du zéro recherché.
• La condition d’arrêt ne garantit pas une bonne précision mais fonctionne assez bien en pratique.

80 Informatique MPSI/PCSI
Chapitre 9
Méthodes d’intégration numérique
9.1 Méthode des rectangles et sommes de Riemann
La méthode des rectangles est une méthode d’analyse numérique permettant d’approximer l’intégrale
Z b
f (x) dx
a
d’une fonction f sur le segment [a, b] par la somme de Riemann
n−1
X
(xk+1 − xk )f (xk ),
k=0
où (xk )0≤k≤n est une subdivision de [a, b], i.e. a = x0 < x1 < · · · < xn = b.

a = x0 x1 x2 xn−2 xn−1 xn = b
On illustre ci-dessous la méthode des rectangles dans le cas particulier où le pas h est constant (on dit
b−a
aussi que la subdivision est équirépartie) : h = et pour tout k ∈ J0, n − 1K, xk = a + kh.
n
La somme de Riemann devient alors :
n−1  
b−aX b−a
Sn = f a+k .
n n
k=0

def integration_rectangle (f,a,b,n):


""" a p p r o x i m a t i o n de l ’ i n t é g r a l e de f e n t r e a e t b par
l a méthode d e s r e c t a n g l e s ( n r e c t a n g l e s ) à pas c o n s t a n t """
h = (b−a)/n
somme = 0
for k in range(n):
somme += f(a + k ∗ h)
return h ∗ somme
Méthode des rectangles

81
9.2 Méthode des trapèzes Chapitre 9. Méthodes d’intégration numérique

9.2 Méthode des trapèzes


La méthode des trapèzes (à pas constant) consiste à approximer l’intégrale I par la somme des aires des
trapèzes définis par la subdivision équirépartie (xk )0≤k≤n :

n−1
b − a X f (xk ) + f (xk+1 )
Tn =
n 2
k=0

a = x0 x1 x2 xn−2 xn−1 xn = b

On peut remarquer que chacun des termes de cette somme est appelé deux fois, à l’exception de f (x0 ) et
f (xn ). En réindexant cette somme, on obtient une formule qui nous permet de ne calculer qu’une seule
fois chaque image :

n−1
" #
b − a f (x0 ) + f (xn ) X
Tn = + f (xk ) .
n 2
k=1

On en déduit alors le code (avec l’optimisation ci-dessus) de la méthode des trapèzes.


def integration_trapeze (f,a,b,n):
""" a p p r o x i m a t i o n de l ’ i n t é g r a l e de f e n t r e a e t b
par l a méthode d e s t r a p è z e s ( n t r a p è z e s ) """
h = (b−a)/n
somme = (f(a) + f(b))/2
for k in range (1,n):
somme += f(a + k ∗ h)
return h ∗ somme

Méthode des trapèzes

82 Informatique MPSI/PCSI
Chapitre 10
Équations différentielles - Méthode d’Euler

10.1 Équations différentielles ordinaires d’ordre 1


Définition 10.1.1
On appelle équation différentielle ordinaire d’ordre 1 toute équation différentielle de la forme :

y 0 (t) = F (t, y(t)).

On rappelle que le théorème de Cauchy-Lipschitz assure que, sous certaines conditions (vérifiées par
F ), pour tout y0 ∈ R, il existe une seule et unique application y de classe C 1 sur [a, b] vérifiant :
(
y(a) = y0
∀t ∈ [a, b], y 0 (t) = F (t, y(t)).
La preuve de ce théorème n’étant pas constructiviste, nombreux sont les exemples d’équations différen-
tielles ordinaires qu’on ne sait pas résoudre de façon exacte.
Le but des schémas numériques est alors de fournir une méthode d’approximation de ces solutions
(dont l’existence est assurée par le théorème de Cauchy-Lipschitz). Les calculs étant destinés au machines
numériques, on tentera d’approximer une solution y (donnée avec condition initiale) en un nombre fini de
points.

10.2 Présentation de la méthode d’Euler


Pour résoudre de manière approchée par la méthode d’Euler (explicite) l’équation différentielle ordinaire
(avec condition initiale) : (
y(a) = y0
∀t ∈ [a, b], y 0 (t) = F (t, y(t)).
sur l’intervalle [a, b], on construit une suite finie de n + 1 points (yk )0≤k≤n pour tenter d’approcher les
valeurs exactes y(tk )0≤k≤n où :
b−a
∀k ∈ J0, nK, tk = a + kh en notant h = .
n
La construction des valeurs approchées se base sur l’approximation (affine) d’une fonction par son le terme
régulier de son développement limité à l’ordre 1, y(t + h) ≈ y(t) + hy 0 (t), qu’on peut réécrire de la manière
suivante lorsque y est solution de l’équation différentielle :

y(t + h) ≈ y(t) + hF (t, y(t)).

On construit ainsi la suite (yk )0≤k≤n des approximations de (y(tk ))0≤k≤n grâce à la formule de récurrence
suivante : (
y0 = y(t0 ) = y(a) seule valeur exacte connue (condition initiale)

∀k ∈ J0, n − 1K, yk+1 = yk + hF (tk , yk ).

83
10.3 Implémentation de la méthode scalaire Chapitre 10. Équations différentielles - Méthode d’Euler

Graphiquement, cette méthode consiste à approximer y(tk+1 ) par l’ordonnée du point d’abscisse tk+1 de
la tangente à la courbe intégrale passant par le point de coordonnées (tk , y(tk )), comme l’illustre la figure
10.1 ci-dessous.

tangente

courbe intégrale

yk+1

y(tk+1 )

hF (tk , y(tk ))

y(tk )

tk tk+1

Figure 10.1: Illustration de l’approximation affine de la méthode d’Euler

10.3 Implémentation de la méthode scalaire


On présente ci-dessous le code en Python de l’algorithme de résolution approchée d’une équation différen-
tielle ordinaire scalaire (i.e. lorsque la fonction F est à valeurs dans R) par la méthode d’Euler.
1 def euler(F, a, b, y0 , h):
2 """ r e n v o i e l e s l i s t e s d e s temps [ t0 , . . . , t n ] e t d e s v a l e u r s
3 [ y0 , y1 , . . . , yn ] a p p r o c h a n t s u r l ’ i n t e r v a l l e [ a , b ] , a v e c un
4 pas de temps d i s c r é t i s é h , l ’ é q u a t i o n d i f f é r e n t i e l l e o r d i n a i r e
5 y ’ ( t ) = F( t , y ( t ) ) v é r i f i a n t l a c o n d i t i o n i n i t i a l e y ( a ) = y0 """
6

7 t_list , y_list = [a], [y0] # l i s t e s d e s temps e t d e s v a l e u r s


8 t, y = a, y0 # d e r n i e r s temps e t v a l e u r c a l c u l é s
9 while t+h <= b:
10 y = y + h ∗ F(t,y) # nouvelle valeur
11 y_list . append (y)
12 t += h # nouveau temps
13 t_list . append (t)
14 return t_list , y_list

Méthode d’Euler pour la résolution approchée d’équations différentielles scalaires

84 Informatique MPSI/PCSI
Chapitre 10. Équations différentielles - Méthode d’Euler 10.3 Implémentation de la méthode scalaire

Exemple 10.3.1
On peut par exemple l’utiliser pour résoudre sur [0, 1] par la méthode d’Euler l’équation différentielle
avec condition initiale : (
y(0) = 1
∀t ∈ [0, 1], y 0 (t) = y(t).
On commence par définir la fonction F : (t, y) 7→ y à l’aide de l’instruction lambda :
F = lambda t, y : y
On peut alors récupérer les listes des temps et des valeurs et comparer le second avec les valeurs
“exactes” :
>>> t_list , y_list = euler(F, 0., 1., 1., 0.25)
>>> t_list
[0, 0.25 , 0.5, 0.75 , 1.0]
>>> y_list
[1, 1.25 , 1.5625 , 1.953125 , 2.44140625]
>>> np.exp( t_list )
array ([ 1., 1.28402542 , 1.64872127 , 2.11700002 , 2.71828183])
Remarque : les fonctions du module numpy sont vectorielles, i.e. on peut leur appliquer un tableau de
valeur (ou une liste), elles renverront le tableau des images. Cela rend souvent service !

On peut enfin tracer la courbe représentative à l’aide de la bibliothèque matplotlib :


import matplotlib . pyplot as plt

# préparatifs
plt.clf () # clear figure
plt.grid () # t r a c e r des axes

# r e p r é s e n t a t i o n de y _ l i s t en f o n c t i o n de t _ l i s t
plt.plot(t_list , y_list )
plt.show () # affichage

Remarque 10.3.2 (Bibliothèque SciPy)


On peut utiliser ici la fonction odeint du module integrate de la bibliothèque SciPy.
En exécutant le code ci-dessous,
# méthode de r é s o l u t i o n i m p l é m e n t é e en Python :
from scipy. integrate import odeint

print (" solutions approchées par la bibliothèque scipy : ")

# remarquer l ’ i n t e r v e r s i o n de l ’ o r d r e d e s v a r i a b l e s dans F :
sol_scipy = odeint ( lambda y, t : y, 1., np. arange (0. ,1.25 ,0.25))
print ( sol_scipy )
on obtient :
solutions approchées par la bibliothèque scipy :
[[ 1. ]
[ 1.28402541]
[ 1.64872127]
[ 2.11700009]
[ 2.7182819 ]]

Informatique MPSI/PCSI 85
10.4 Implémentation de la méthode vectorielle Chapitre 10. Équations différentielles - Méthode d’Euler

10.4 Implémentation de la méthode vectorielle


Exemple 10.4.1 (Modèle “proie-prédateur” 1/2 )
Le modèle de Lotka-Volterra (encore appelé modèle proie-prédateur) permet de modéliser la dy-
namique de deux populations (les proies et les prédateurs) en interaction.
On note x(t) (resp. y(t)) l’effectif de la population des proies (resp. prédateurs) à l’instant t.
L’étude d’un système particulier nous mène à résoudre le système différentiel suivant :

x0 (t) = 2 x(t) (1 − 2y(t))
(S) 3
y 0 (t) = −y(t) (1 − x(t))

avec les conditions initiales suivantes :

0 ≤ t ≤ 50, x(0) = 1 et y(0) = 2.


 
x(t)
En posant U (t) = , on transforme un système différentiel (non linéaire) en une équation
y(t)
différentielle ordinaire (vectorielle) d’ordre 1 :

 0  2 !
x (t) x(t) (1 − 2y(t))
(S) ⇔ = 3
y 0 (t) −y(t) (1 − x(t))
⇔ U 0 (t) = F (t, U (t))

où F est la fonction :
   2 !
x x (1 − 2y)
F : t, U = 7→ 3
y −y (1 − x)

Vient maintenant la question de l’implémentation de la fonction F en machine...


Telle que présentée au paragraphe 10.3, la méthode d’Euler ne peut s’appliquer aux équations différentielles
ordinaires (d’ordre 1) vectorielles, i.e. lorsque la fonction F est à valeurs dans RN où N ≥ 2, si on n’utilise
pas le type array.
En effet, à la ligne 9 de la fonction euler, il est impossible de multiplier (terme-à-terme) l’objet multidi-
mensionnel F(t,y) (type list ou tuple par exemple) par le flottant h.
On fait alors le choix d’implémenter la fonction F de manière à ce que son type de sortie soit du type
array.
1 def euler_vect (F, a, b, y0 , h):
2 """ r e n v o i e l e t a b l e a u a r r a y d e s temps [ t0 , . . . , t n ] e t d e s v a l e u r s
3 [ y0 , y1 , . . . , yn ] a p p r o c h a n t s u r l ’ i n t e r v a l l e [ a , b ] , a v e c un
4 pas de temps d i s c r é t i s é h , l ’ é q u a t i o n d i f f é r e n t i e l l e o r d i n a i r e
5 y ’ ( t ) = F( t , y ( t ) ) v é r i f i a n t l a c o n d i t i o n i n i t i a l e y ( a ) = y0 """
6 t_list , y_list = [a], [y0] # l i s t e s d e s temps e t d e s v a l e u r s
7 t, y = a, y0 # d e r n i e r s temps e t v a l e u r c a l c u l é s
8 while t+h <= b:
9 y = y + h ∗ F(t,y) # nouvelle valeur
10 y_list . append (y)
11 t += h # nouveau temps
12 t_list . append (t)
13 return t_list , np.array( y_list ) # c o n v e r s i o n l i s t −> a r r a y

Méthode d’Euler pour la résolution approchée d’équations différentielles vectorielles

86 Informatique MPSI/PCSI
Chapitre 10. Équations différentielles - Méthode d’Euler 10.4 Implémentation de la méthode vectorielle

Exemple 10.4.2 (Modèle “proie-prédateur” 2/2 )


On peut alors résoudre le système différentiel donné à l’exemple 10.4.1

x0 (t) = 2 x(t) (1 − 2y(t))
(S) 3
y 0 (t) = −y(t) (1 − x(t))

avec les conditions initiales suivantes 0 ≤ t ≤ 50, x(0) = 1 et y(0) = 2


On commence par définir la fonction F :
def F_LV(t, u):
""" f o n c t i o n d é f i n i s s a n t l e s y s t è m e d i f f é r e n t i e l ( S ) """
[x, y] = u # affectation parallèle
return np.array ([2 ∗ x ∗ (1 − 2 ∗ y)/3, −y ∗(1− x)])
Après avoir choisi le pas (ici h = 0, 01), on résout le système différentiel avec conditions initiales :
t_list , u_mat = euler_vect (F_LV , 0, 50, np.array ([1 ,2]) , 0.01)
Tandis que chaque ligne de l’array u_mat contient les effectifs des proies et des prédateurs à un instant
donné, chaque colonne de l’array u_mat contient l’ensemble des effectifs d’une même population. On
peut donc suivre l’évolution de chaque population en extrayant les colonnes de u_mat :
x_array = u_mat [:, 0]
y_array = u_mat [:, 1]
Tout est alors prêt pour la représentation graphique :
import matplotlib . pyplot as plt
plt.clf ()
plt.plot(t_list , x_array , "b−−", label = "proie")
plt.plot(t_list , y_array , "r", label = " prédateur ")
plt. legend (loc = "best")
plt.title(" Modèle de Lotka−Volterra ")
plt.show ()

Informatique MPSI/PCSI 87
10.5 Propriétés et limites Chapitre 10. Équations différentielles - Méthode d’Euler

10.5 Propriétés et limites


Définition-Proposition 10.5.1
La méthode d’Euler est dite convergente : lorsque le pas h tend vers 0, la solution numérique
converge vers la solution exacte au sens suivant :
 
(h) (h)
lim max y tk − yk = 0,

h→0 0≤k≤nh
    
(h) (h)
où yk désignent les valeurs approchées de y tk obtenues avec un pas égal à h.
0≤k≤nh 0≤k≤nh

Définition 10.5.2

• On appelle erreur de consistance du schéma numérique le réel défini par :


h −1
nX
 
e(h) = y(tk+1 ) − y(tk ) + hF (tk , y(tk )) .

k=0

• On dit qu’un schéma numérique est d’ordre p si e(h) = O (hp ).


h→0

Définition-Proposition 10.5.3

• La méthode d’Euler est dite consistante : lim e(h) = 0.


h→0

• La méthode d’Euler est d’ordre 1.

Remarque 10.5.4
Le choix du pas doit être une décision réfléchie et non juste un travail d’ajustement jusqu’à obtention
d’une approximation “acceptable” :

• Si le pas est trop grand, l’erreur de consistance sera trop grande.

• S’il est trop petit, cela peut générer des erreurs d’approximation liées à la manipulation de
flottants.

Comme nous le verrons en TP, le choix du pas doit être réalisé en établissant un compromis entre la
précision souhaitée et les erreurs d’approximations liées à l’utilisation d’une machine numérique.
Si on ne trouve pas de compromis acceptable, on choisit une méthode d’ordre supérieur.

88 Informatique MPSI/PCSI
Chapitre 10. Équations différentielles - Méthode d’Euler 10.6 Exercices

10.6 Exercices
En général, il ne suffit pas qu’un schéma numérique soit convergent pour qu’il donne de bons résultats
sur n’importe quelle équation différentielle ordinaire.
Exercice 10.6.1 (Problème mal posé)
On considère le problème de Cauchy suivant, qu’on cherche à résoudre numériquement sur [1, 5] :

y(1) = 1
3 5
y 0 (t) = y(t) − .
t t3
1. Quelle est la forme des solutions de l’équation différentielle associée (sans la condition initiale
y(1) = 1) ?

2. Déterminer par le calcul l’unique solution sur [1, 5] du problème de Cauchy.

3. Implémenter la méthode d’Euler pour résoudre cette équation différentielle de manière à représenter
graphiquement la solution exacte et la solution approchée comme illustré ci-après.

4. Déduire des questions 1 et 2 que le problème est mal posé pour être résolu par la méthode d’Euler.

Informatique MPSI/PCSI 89
10.6 Exercices Chapitre 10. Équations différentielles - Méthode d’Euler

Exercice 10.6.2 (Problème de stabilité)


On considère le problème de Cauchy suivant :
(
y(0) = 0
y 0 (t) = 100(sin t − y(t)).

1
1. Vérifier que la fonction φ : t 7→ −100 cos t + 10000 sin t + 100e−100t est l’unique solution

10001
du problème. Remarquer que cette fonction est bornée sur [1, +∞[.

2. Écrire la formule de récurrence donnée par le schéma numérique de la méthode d’Euler.

3. Pour tout k ∈ J0, nK, k , on note l’erreur commise sur le calcul de yk . Exprimer k+1 en fonction
de k .

4. En déduire que pour h > 0, 02, la solution approchée oscille en “s’éloignant” de plus en plus de la
solution exacte.

Exercice 10.6.3 (Résolution d’équations différentielles scalaires d’ordre supérieur )

1. Montrer qu’on peut appliquer la méthode d’Euler aux équations différentielles d’ordre 2 de la forme
y 00 (t) + a(t)y 0 (t) + b(t)y(t) = c(t).

2. On chercher à résoudre de manière approchée par la méthode d’Euler sur l’intervalle [1; 10] de
l’équation de Bessel ci-dessous. Écrire deux scripts Python afin de représenter graphiquement la
solution approchée et son portrait de phase.

y(1) = 1, y 0 (1) = 
0 
2 00 0 2 1
t y (t) + ty (t) + t − y(t) = 0.
4

90 Informatique MPSI/PCSI
Chapitre 11
Résolution de systèmes linéaires - Pivot de Gauss
11.1 Opérations élémentaires
Les opérations élémentaires réalisables sur une matrice a (représentée par un array) associée à un
système linéaire sont :

• la multiplication d’une ligne par un scalaire non nul, Li ← tLi :


a[i] = t ∗ a[i]

• l’ajout à une ligne d’un multiple d’une autre ligne, Li ← Li + tLj :


a[i] = a[j] + t ∗ a[j]

• la permutation de deux lignes, Li ↔ Lj .


L’affectation parallèle a[i], a[j] = a[j], a[i] ne modifie pas l’array a. Cela est dû au fait que
le slicing sur le type array ne réalise pas de copie mais une vue, contrairement au sclicing sur les
listes.
On définit ci-dessous une fonction qui permute deux lignes en parcourant toutes les colonnes, et qui,
pour chaque colonne, permute les coefficients des deux lignes.
def permute_lignes (a, i, j):
for k in range(a.shape [1]):
a[i,k], a[j,k] = a[j,k], a[i,k]

Remarque 11.1.1

• Cette fonction renvoie None ; la fonction modifie physiquement l’array a.

• Chacune des ces opérations se traduit matriciellement par la multiplication à gauche par une
matrice inversible (matrice de permutation, de dilatation, de transvection).

11.2 Algorithme du pivot de Gauss


La méthode du pivot de Gauss permet de résoudre des systèmes linéaires de la forme AX = Y où A
est supposée une matrice inversible.
Cette méthode se décompose en deux étapes :

• la première étape, dite phase de descente, consiste à transformer la matrice A en une matrice
triangulaire supérieure, en appliquant les mêmes opérations au vecteur Y qu’à la matrice A.

• la deuxième étape, dite phase de remontée1 , consiste à résoudre le système linéaire en déterminant
la valeur de chaque inconnue, de la dernière à la première.
1
La phase de remontée est parfois divisée en deux : une première étape qui transforme la matrice triangulaire en une matrice
diagonale, puis une seconde qui résout le système devenu trivial.

91
11.2 Algorithme du pivot de Gauss Chapitre 11. Résolution de systèmes linéaires - Pivot de Gauss

11.2.1 Phase de descente


Le principe est de transformer la matrice A en une matrice triangulaire à l’aide d’opérations élémentaires
sur les lignes, en conservant l’invariant :
 
a1,1 a1,2 . . . . . . . . . a1,n
 0 a2,2 . . . . . . . . . a2,n 
 .. .. .. .. 
 
 . . . . 
Avant la j-ème itération, la matrice est de la forme : 
 0

. . . 0 aj,j . . . aj,n 
 .. .. .. .. 
 
 . . . . 
0 . . . 0 an,j . . . an,n

À l’entrée dans la j-itération, on commence par déterminer un pivot non nul2 ai,j parmi les coefficients
aj,j , aj+1,j , . . . , an,j . On permute alors la ligne j avec la ligne i. On élimine ensuite tous les coefficients
aj+1,j , . . . , an,j de la colonne j, à partir de la ligne j + 1.

Choix du pivot : méthode du pivot partiel

D’un point de vue mathématique, tous les pivots potentiels se valent. D’un point de vue informatique, ce
n’est pas le cas : les calculs s’effectuant sur des flottants (même si les coefficients sont complexes), il faut
limiter les erreurs générées par ces calculs. c’est pourquoi on utilise la méthode du pivot partiel : on
choisit, parmi aj,j , aj+1,j , . . . , an,j , le plus grand coefficient en valeur absolue (ou module).
On propose alors une fonction qui détermine le pivot pivot partiel à la j-ème itération3 , puis qui permute
la ligne j avec la ligne où l’on a trouvé le pivot. On réalise bien-sûr la permutation des mêmes lignes sur
le vecteur colonne Y .
def choix_pivot (a, y, j):
i_pivot = j
for i in range(j+1, a.shape [0]):
if abs(a[i,j]) > abs(a[i_pivot , j]):
i_pivot = i
if i_pivot != j:
permute_lignes (y, j, i_pivot )
permute_lignes (a, j, i_pivot )

Élimination

Une fois le pivot partiel placé à la ligne j - on conserve la notation aj,j - on retrouve l’invariant en
ai,j
appliquant, pour tout i ∈ Jj + 1, nK, l’opération élémentaire Li ← Li − Lj .
aj,j
On propose alors une fonction qui élimine tous les coefficients de la colonne j situés sous la ligne j. Les
opérations élémentaires décrites ci-dessus sont aussi appliquées au vecteur colonne Y .
def elimination (a, y, j):
for i in range(j+1, a.shape [0]):
y[i] = y[i] − (a[i,j] / a[j,j ]) ∗ y[j]
a[i] = a[i] − (a[i,j] / a[j,j ]) ∗ a[j]

Remarque 11.2.1
On fera attention à ne pas intervertir les deux dernières lignes de la fonction précédente. En effet, en
permutant ces deux lignes, on modifie la ligne (i + 1) de la matrice, et ainsi le coefficient a[i,j]. On
ne réaliserait alors pas les mêmes opérations sur les deux membres de la (i + 1)-ème équation.

2
L’inversibilité supposée de la matrice A nous assure de l’existence d’un pivot non nul à chaque itération.
3
Attention au décalage de numération en mathématique et en informatique : ce pivot est sur la (j + 1)-ème colonne.

92 Informatique MPSI/PCSI
Chapitre 11. Résolution de systèmes linéaires - Pivot de Gauss 11.2 Algorithme du pivot de Gauss

Phase de descente

Après n − 1 itérations, la matrice A - supposée inversible - est transformée en une matrice triangulaire
(dont les coefficients de la diagonale sont tous non nuls).
def descente (a, y):
for j in range(a.shape [0] − 1):
choix_pivot (a, y, j)
elimination (a, y, j)

11.2.2 Phase de remontée


Considérons un système AX = Y lorsque A est une matrice triangulaire, soit un système de la forme :


 a1,1 x1 + a1,2 x2 + . . . + a1,n−1 xn−1 + a1,n xn = y1
a2,2 x2 + . . . + a2,n−1 xn−1 + a2,n xn = y2



.. .. ..

 . . .
an−1,n−1 xn−1 + an−1,n xn = yn−1




an,n xn = yn

Il est aisé de résoudre le système résolvant successivement chacune des équations de la dernière ligne à la
première. Il suffit de remarquer que, si les valeurs de xi+1 , . . . , xn sont connues, alors on peut trouver xi :
 
n
1  X
xi = yi − ai,j xj  .
ai,i
j=i+1

On implémente cette idée dans la fonction suivante qui résout un système linéaire dans le cas où la
matrice est triangulaire inversible. Remarquez qu’au lieu de construire un tableau des solutions, on utilise
le tableau des membres de droite : une fois la valeur xi calculée, on la stocke à la place de y[i].
def remontee (a, y):
n = a.shape [0]
for i in range(n −1, −1, −1):
# c a l c u l de x_i
for j in range(i+1, n):
y[i] = y[i] − a[i,j] ∗ y[j]
y[i] = y[i] / a[i,i]
return y

11.2.3 Algorithme du pivot de Gauss


Toutes les fonctions implémentées agissent par effet de bord : elles modifient certains objets (mutables !)
passés en argument.
Pour résoudre le système, on commence par réaliser une copie profonde de la matrice a et du vecteur
colonne y, de manière à ne modifier successivement que les versions copiées de ces deux objets.
def pivot_Gauss (a0 , y0):
a = np.copy(a0)
y = np.copy(y0)
descente (a,y)
return remontee (a,y)

Informatique MPSI/PCSI 93
11.3 Complexité asymptotique Chapitre 11. Résolution de systèmes linéaires - Pivot de Gauss

11.3 Complexité asymptotique


Déterminer la complexité asymptotique des fonctions préalablement implémentées lorsque a désigne un
array de taille n × n et y un array de taille n × 1.

fonction complexité

permute_lignes

choix_pivot

elimination

descente

remontee

pivot_Gauss

94 Informatique MPSI/PCSI
Chapitre 11. Résolution de systèmes linéaires - Pivot de Gauss 11.4 Annexes

11.4 Annexes

import numpy as np

def permute_lignes (a, i, j):


for k in range(a.shape [1]):
a[i,k], a[j,k] = a[j,k], a[i,k]

def choix_pivot (a, y, j):


i_pivot = j
for i in range(j+1, a.shape [0]):
if abs(a[i,j]) > abs(a[i_pivot , j]):
i_pivot = i
if i_pivot != j:
permute_lignes (y, j, i_pivot )
permute_lignes (a, j, i_pivot )

def elimination (a, y, j):


for i in range(j+1, a.shape [0]):
y[i] = y[i] − (a[i,j] / a[j,j ]) ∗ y[j]
a[i] = a[i] − (a[i,j] / a[j,j ]) ∗ a[j]

def descente (a, y):


for j in range(a.shape [0] − 1):
choix_pivot (a, y, j)
elimination (a, y, j)

def remontee (a, y):


n = a.shape [0]
for i in range(n −1, −1, −1):
# c a l c u l de x_i
for j in range(i+1, n):
y[i] = y[i] − a[i,j] ∗ y[j]
y[i] = y[i] / a[i,i]
return y

def pivot_Gauss (a0 , y0):


a = np.copy(a0)
y = np.copy(y0)
descente (a,y)
return remontee (a,y)

Informatique MPSI/PCSI 95

Vous aimerez peut-être aussi