Vous êtes sur la page 1sur 15

III .

Programmation
modulaire

La description du problème, sa spécification, est souvent compliquée. Il est donc


nécessaire de décomposer le problème en sous-problèmes plus simples en précisant
l’enchaînement des actions entre sous-problèmes. C’est ce qui est appelé faire
l’analyse descendante d’un problème. Il s’agit donc de décomposer le problème en
plus petits problèmes, la décomposition s’arrête lorsque l’on sait résoudre le petit
problème, soit parce que il a déjà été résolu dans le cadre de la résolution d’un autre
problème, soit parce qu’il est suffisamment simple pour être expliqué en quelques
enchaînements.

L’analyse descendante a donc pour objet de spécifier les fonctions qu'un système
remplit, indépendamment de la manière dont ces fonctions sont réalisées. elle
commence par la description la plus générale du système et se poursuit par la
décomposition de cette fonction globale en plusieurs fonctions moins complexes et
ainsi de suite. On procède par affinage successif des ces fonctions.

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
31 32
III.1 Introduction BEGIN
Un problème complexe est la somme de problèmes un peu moins complexes. Cette vérité INT i, nf, pf, npf;
s'applique sur tous les problèmes moins complexes jusqu'à aboutir à des problèmes simples
nf ← 1;
à résoudre ou déjà résolus.
pf ← 1;
La modularité est le fait, pour un programme, d'être écrit en plusieurs morceaux npf ← 1;
relativement indépendants les uns des autres. La modularité se manifeste de deux manières
write ("Donner la valeur de n : ");
différentes. Dans la première façon de faire de la modularité, un programme est décomposé read (n);
en petits modules appelés sous programmes qui font chacun une tâche bien précise et qui
peuvent être exécutés plusieurs fois. Parmi ces modules il y a le programme principal qui write ("Donner la valeur de p : ");
appelle des sous programme chaque fois que cela est nécessaire. Les sous programmes read (p);
peuvent s'appeler entre eux. La deuxième façon de faire de la modularité et qui est
FOR i ← 1 TO n DO
complémentaire de la première est d'écrire un programme dans plusieurs fichiers séparés. La
nf ← nf * i ;
modularité a d'énormes avantages lors du développement d'un programme et surtout sur le END FOR
coût du développement.
FOR i ← 1 TO p DO
III.2 Procédures et Fonctions pf ← pf * i ;
END FOR
écrire un programme qui résout un problème revient toujours à écrire des sous-programmes
qui résolvent des sous parties du problème initial. L'un des objectifs de cette structuration FOR i ← 1 TO (n - p) DO
hiérarchique est la maîtrise du développement logiciel. En effet, il est plus facile de conduire npf ← npf * i ;
une réflexion sur des problèmes simples que sur un problème complexe. END FOR

L'usage des procédures et des fonctions su justifie aussi par l'usage répété d'un même cnp ← nf / (pf * npf);
procédé de calcul plusieurs fois. Par exemple pour calculer : END

C np =
n! Algo 15. Calcul de C np
p! (n − p)!
Les trois structures itératives FOR sont quasiment les même, il n'y a que le noms de certaines
on va appliquer plusieurs fois le processus de calcul de la factorielle d'une valeur. Ceci variables qui change.
revient, dans un algorithme à réécrire plusieurs fois le même code ou presque. L'idée serait
Pour mieux concevoir les algorithmes, il faut opter pour une stratégie permettant de séparer
donc d'écrire une seule fois le code d'un processus récurrent et de le paramétrer avec les
le traitement dupliqué du corps du programme. Ainsi il ne figure qu’une seule fois dans le
bonnes valeurs.
programme et y faire appel à chaque fois que nécessaire.
Découper l’algorithme en sous-algorithmes plus simples, jusqu’à des opérations considérées
Un sous programme est un algorithme à part entière faisant partie d’un programme. On
primitives permet :
distingue généralement deux types de sous-programmes : les procédures et les fonctions.
• Simplification : l'approche top-down permet de raisonner plus simplement sur un
Une procédure est un sous-programme qui effectue un traitement et redonne la main au
problème complexe
programme. Une procédure peut modifier ou initialiser des variables de l’algorithme.
• Abstraction (ignorer les détails) : les sous problèmes sont eux même des problème à
résoudre de manière globale. Une fonction est un sous programme qui effectue un traitement et redonne la main au
• Structuration : par la décomposition en sous fonctionnalités indépendantes mais programme en retournant une valeur unique. La valeur retournée doit toujours être du
complémentaires même type (type simple, structuré, …). On dit que le type retourné est le type de la fonction.
• Réutilisation : des fonctionnalités implémentées pouvant servir pour résoudre d'autres
Une procédure, contrairement à une fonction, n’a pas de type. Une fonction peut modifier ou
problèmes.
initialiser des variables de l’algorithme mais c’est généralement déconseillé.
p
L'algorithme de calcul de C n sans décomposition serait le suivant : Les fonctions et les procédures permettent de décomposer un programme complexe en une
série de sous-programmes plus simples, lesquels peuvent à leur tour être décomposés eux-
mêmes en fragments plus petits, et ainsi de suite.
ALGORITHM combinaison
INPUT INT n, p;
OUTPUT INT cnp ;

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
33 34
REMARQUE : dans le langage de type C, il n'y a pas la notion de procédure mais EXEMPLE : écrire une fonction qui calcule la factorielle d'une valeur entière v.
uniquement des fonction. Lorsqu'une fonction ne doit pas retourner une valeur, elle
retourne une valeur vide (void). FUNCTION INT factorielle (INPUT INT v)
BEGIN
III.2.1 Définition d'une procédure INT i, f;

Une procédure est défini par le mot PROCEDURE. Elle est caractérisée par un nom qui doit f ← 1;
être explicite et peut avoir un certain nombre de paramètres. Les paramètres peuvent être
des paramètres d'entrée (que la procédure ne modifie pas) ou des paramètres de sortie qui FOR i ← 1 TO v DO
sont soit initialisés, soit modifiés par la procédure. Le corps d'une procédure est un bloc f ← f * i;
END FOR
d'instructions délimité par les BEGIN et END et contient des instructions et des structures
de contrôles telles que présentées dans le chapitre I. return f;
END
PROCEDURE nom_proc ([param [, param]*]) Algo 17. - Fonction pour le calcul de la factorielle d'une valeur v
BEGIN
actions III.2.2 Passage de paramètres
END
Les procédures et fonctions reçoivent des paramètres leurs permettant d’effectuer les
Chaque paramètre est soit un paramètre d'entrée soit de sortie. Un paramètre d'entrée est traitements appropriés. Un algorithme principal peut lui aussi recevoir des paramètres. Pour
défini par le mot INPUT, un type et le nom du paramètre. Un paramètre d'entrée doit calculer la factorielle d’un entier n, il faut indiquer à la fonction le nombre n en question.
obligatoirement avoir une valeur. Un paramètres de sortie est défini par le mot clé OUTPUT, C’est le paramètre de la fonction.
un type et un nom. Un paramètre de sortie peut ne pas avoir une valeur initiale, s'il en a une
c'est un paramètre d'entrée sortie. Dans les deux cas sa valeur est soit calculée soit mise à Le programme de calcul de C np utilisant la fonction factorielle serait le suivant :
jour par la procédure.
ALGORITHM combinaison
EXEMPLE : écrire une procédure qui calcule la valeur minimale et la valeur maximle d'un INPUT INT n, p;
tableau. OUTPUT INT cnp ;

BEGIN
PROCEDURE minmax (INPUT INT t[], INPUT INT n,
INT nf, pf, npf;
OUTPUT INT min, OUTPUT INT max)
BEGIN
write ("Donner la valeur de n : ");
INT i;
read (n);
write ("Donner la valeur de p : ");
min ← t[0]; read (p);
max ← t[0];
nf ← factorielle (n);
FOR i ← 0 TO n - 1 DO pf ← factorielle (p);
IF t[i] < min THEN
npf ← factorielle (n - p);
min ← t[i];
cnp ← nf / (pf * npf);
ELSE IF t[i] > max THEN
END
max ← t[i];
END IF Algo 18. Calcul de C np - usage de la fonction factorielle
END FOR
END
Les paramètres d'une procédure ou une fonction sont des paramètres formels. La valeur de
Algo 16. - Procédure pour le calcul du minimum et du maximum d'un tableau
ces paramètres sont transmises par le programme principal. Les modes de passage sont par
III.2.2 Définition d'une fonction valeur (les paramètres d'entrée) ou par référence (les paramètres de sortie).

Une fonction est défini par le mot FUNCTION. Elle est caractérisée par un type, un nom qui III.2.2.1 Passage de paramètres par valeur
doit être explicite et peut avoir un certain nombre de paramètres. Les paramètres d'une
fonction sont généralement des paramètres d'entrée (que la fonction ne modifie pas). Les Lorsque le passage de paramètres se fait par valeur, la procédure ou la fonction reçoit une
paramètres de sortie ne sont pas conseillés pour les fonctions. Le corps d'une procédure est copie des valeurs effectives des données de l'algorithme appelant (fig. 7). Les copies sont
un bloc d'instructions délimité par les BEGIN et END et contient des instructions et des gérées dans l'espace mémoire réservé à la procédure / fonction. Si ces copies sont modifiées,
structures de contrôles telles que présentées dans le chapitre I. La fonction doit retourner un les valeurs réelles ne sont pas touchées.
résultat dont le type est le même que celui attribué à la fonction.
Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
35 36
PROG : avant échange a = 5, b = 8
Nom de la variable Adresse mémoire valeur PROC : avant échange a = 5, b = 8
PROC : après échange a = 8, b = 5
PROC : après échange a = 5, b = 8
x 0x12AA09 6
PROCEDURE f (ENTREE ENTIER a, ENTREE ENTIER b) ? 0x12AA0A ? Les valeurs du programme appelant n'ont pas été modifiées.
DEBUT

? 0x12AA0B ? III.2.2.1 Passage de paramètres par référence
FIN y 0x12AA0C 4
ALGORITHME A
Lorsque le passage de paramètres se fait par valeur, la procédure ou la fonction reçoit les
DEBUT adresses mémoires des valeurs effectives des données de l'algorithme appelant (fig. 8). Le
ENTIER x ; sous programme peut ainsi travailler directement sur le contenu des adresses mémoire et
ENTIER y;
… modifier les valeurs effectives.
f (x, y)
… Nom de la variable Adresse mémoire valeur
FIN
a 0xAF00CD 6
b 0xAF00CE 4 x 0x12AA09 6
PROCEDURE f (SORTIE ENTIER a, SORTIE ENTIER b) ? 0x12AA0A ?
Les valeurs des paramètres formels sont des copies des DEBUT
valeurs effectives …
? 0x12AA0B ?
Fig. 7 - Passage de paramètres par valeur
FIN y 0x12AA0C 4
EXEMPLE : écrire une procédure qui échange deux valeurs entre elles. Cette procédure ALGORITHME A
reçoit en paramètre deux valeurs identifiées par les paramètres formels a et b. Elle utilise une DEBUT
variable intermédiaire : tmp. La valeur de a est mémorisée dans tmp, la valeur de a est ENTIER x ;
ENTIER y;
remplacée par la valeur de b et enfin la valeur de b est remplacée par la valeur de tmp. …
f (x, y)
La procédure affiche les valeurs de ses paramètres formels avant et après le traitement. Elle …
FIN
est appelée par le programme prog qui lui passe deux valeurs qui lui aussi affiche avant a 0xAF00CD 0x12AA09
l'appel de la procédure et après l'appel de la procédure. b 0xAF00CE 0x12AA0C

PROCEDURE echange (INPUT INT a, INPUT INT b) Les valeurs des paramètres formels sont les adresses
BEGIN mémoire des valeurs effectives
INT tmp;
Fig. 8 - Passage de paramètres par référence
write ("PROC : avant échange a = ", a, ", b = ", b);
tmp ← a; EXEMPLE : Nous allons réécrire la même procédure echange en utilisant cette fois-ci un
a ← b; passage par référence des paramètres. La procédure affiche les valeurs de ses paramètres
b ← tmp; formels avant et après le traitement. Elle est appelée par le programme prog qui lui passe
write ("PROC : après échange a = ", a, ", b = ", b); deux valeurs qui lui aussi affiche avant l'appel de la procédure et après l'appel de la
END procédure.
ALGORITHM prog
INPUT INT x, y;
BEGIN
write ("Donner x : ");
PROCEDURE echange (INPUT/OUTPUT INT a, INPUT/OUTPUT INT b)
read (x);
BEGIN
write ("Donner y : ");
INT tmp;
read (y);
write ("PROG : avant échange a = ", a, ", b = ", b);
write ("PROC : avant échange a = ", a, ", b = ", b);
echange (x, y);
write ("PROG : après échange a = ", a, ", b = ", b); tmp ← a;
END a ← b;
b ← tmp;
Algo 19. - Résultat de traitement lors d'un passage de paramètres par valeur write ("PROC : après échange a = ", a, ", b = ", b);
END
Le résultat de l'exécution d'un programme correspondant aux algorithmes (algo 19), si
l'utilisateur donnait a = 5 et b = 8, serait :

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
37 38
ALGORITHM prog Une variable globale est une variable dont la visibilité s’étend à tout le programme et ses
INPUT INT x, y; sous programmes. Les variables globales sont déclarées avant le programme principal.
BEGIN
write ("Donner x : "); Lorsqu'une variable globale porte le même nom qu'une variable locale à un bloc
read (x); d'instructions, la variable globale est masquée par la variable locale.
write ("Donner y : ");
read (y); EXEMPLE
write ("PROG : avant échange a = ", a, ", b = ", b);
echange (x, y); INT k ;
write ("PROG : après échange a = ", a, ", b = ", b);
END PROCEDURE proc (INPUT INT val)
Algo 20. - Résultat de traitement lors d'un passage de paramètres par référence BEGIN
INT k;
Le résultat de l'exécution d'un programme correspondant aux algorithmes (algo 20), si
l'utilisateur donnait a = 5 et b = 8, serait : k ← val + 2;
PROG : avant échange a = 5, b = 8 write ("PROC - la valeur de k = ", k);
PROC : avant échange a = 5, b = 8 END
PROC : après échange a = 8, b = 5
PROC : après échange a = 8, b = 5 ALGORITHM prog
Les valeurs des variables du programme appelant ont bel et bien été modifiées. BEGIN
INT val;
III.3 Portée des variables
write ("Donner une valeur : ");
Les variables utilisées par les programmes et les sous programmes ont une portée (ou en read (val);
d’autres termes durée de vie) limitée. Elle commence au moment où la variable est déclarée write ("PROG - la valeur de val est : ", val);
et se termine par la terminaison du bloc d'instructions où elle a été déclarée. k ← val + 2;
write ("PROG - la valeur de k est : ", k);
La nature du bloc dépendent du langage de programmation. Dans la majorité des langages proc (k);
de programmation, un bloc est un programme ou un sous programme. cela veut dire qu'une write ("PROG - la valeur de k est : ", k);
END
variable déclarée dans un programme est accessible tout le long du programme et il en est de
même lorsqu'il s'agit d'une procédure ou d'une fonction (fig. 9). Dans d'autres langages, le Algo 21. - Portée des variables
langage C en particulier, la portée d'une variable peut se limiter à n'importe quel bloc
d'instruction. Une variable peut être déclarée dans une structure de contrôle quelconque, un Le résultat de l'exécution d'un programme correspondant aux algorithmes (algo 21), si
if (IF) par exemple, et sa portée se limite à ce bloc d'instruction. Il est même possible l'utilisateur donnait val = 5 serait :
d'introduire un bloc sans structure de contrôle et y déclarer des variables qui lui sont locales.
PROG - la valeur de val est : 5
Ces variables n’existent que dans ces blocs et disparaissent dès lors que l’exécution du bloc PROG - la valeur de k est : 7
concerné se termine. PROC - la valeur de k est : 9
PROG - la valeur de k est : 7
Variables globales
Dans l'exemple précédent (Algo. 21), la première variable k est globale, et sa portée s'étend
Variables locales
sur tout le code alors que la deuxième variable k, déclarée dans la procédure proc est locale à
cette procédure. Dans la procédure proc, val est la paramètre formel et la variable globale k
PROGRAMME PRINCIPAL
est masquée par la variable locale du même nom. Dans le programme principal prog, la
variable val est locale et la variable k utilisée par ce programme est la variable globale. En
effet puisqu'il n'y a pas de variable locale au programme principal dont le nom et k et
Variables locales puisqu'il y a une variable globale nommée k, alors elle est directement accessible puisqu'elle
est dans sa portée.
SOUS PROGRAMME
III.4 Récursivité
Le principe de la récursivité est directement issu de la récurrence en mathématique. Un
problème récurrent est un problème qui se répète. Une définition récurrente d’un ensemble
consiste en la description des éléments de base et des règles de construction récursive. Les
Fig. 9 - Portée des variables
règles de construction récursives permettent de caractériser les éléments d'un ensemble à

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
39 40
partir d'éléments plus simples du même ensemble (en général obtenus ou construits à l'étape EXEMPLE : Calcul C n .
p

précédente).
Nous avons déjà calculé cette valeur d'une manière itérative : C n = n! p!( n − p )!
p

Une définition récursive est, par conséquent, une définition dans laquelle intervient ce que
l’on veut définir. Un algorithme est dit récursif lorsqu’il est défini en fonction de lui-même, On peut calculer cette valeur autrement :
c'est à dire qui s'appelle lui même. Un algorithme récursif doit contenir au moins une base
1 si p = 0 ou n = p
qui permet de définir sa valeur sans appel récursif – c’est la condition d’arrêt de l’algorithme. C np =  p p −1
 C n −1 + C n −1
Concevoir un algorithme récursif consiste donc à définir un certain nombre de cas dont la
résolution est connue, ces « cas simples » formeront les cas d’arrêt de la récursion (la base); FUNCTION INT combinaison (INPUT INT n, INPUT INT p)
ainsi qu'un moyen de se ramener d’un cas « compliqué » à un cas « plus simple ». BEGIN
IF p = 0 OR p = n THEN
return 1;
Lors de la conception d'un algorithme récursif, il faut toujours commencer
ELSE
par élaborer les bases c'est à dire les conditions d'arrêt. Un algorithme return combinaison (n - 1, p) + combinaison (n - 1, p - 1);
récursif qui ne commence pas par tester s'il a atteint une condition d'arrêt END IF
tournera indéfiniment. END

Algo 23. - Calcul de C np - algorithme récursif


La récursivité peut être simple, multiple, imbriquée ou mutuelle. La récursivité est simple
lorsqu'un seul appel récursif est exécuté à chaque fois. La récursivité est multiple lorsque CONTRE EXEMPLE
plusieurs appels récursifs sont exécutés. La récursivité est imbriquée lorsque le paramètre
formel d'un appel récursif est lui même un appel récursif. Finalement, la récursivité mutuelle Il ne suffit pas qu'il y ait plusieurs appels récursifs dans le code pour que la récursivité soit
est une récursivité indirecte (ou cachée c'est lorsqu'un algorithme A1 appelle un algorithme multiple. Dans le code suivant et bien qu'il y ait plusieurs appels récursifs de la procédure f,
A2 et que ce dernier appelle A1. la récursivité reste une récursivité simple car à chaque exécution, un seul appel récursif est
exécuté selon les condition et l'état du programme.
III.4.1 Récursivité simple
PROCEDURE f (p)
Lorsque un algorithme récursif ne fait appel à lui même qu'une seule fois à chaque appel, on BEGIN
dit que la récursivité est simple. C'est la récursivité la plus commune. IF condition 1 THEN
f (p1)
EXEMPLE : Si on veut élever une valeur x à la puissance n, il suffit que n soit positif ou nul, ELSE IF condition 2 THEN
ensuite d'élever x à la puissance n - 1 et de multiplier ce résultat par x. Commençons par la f (p2)
base de cette récursion : si n = 0, ∀ x, xn = 1. si n > 0 xn = x × xn-1. ELSE
f (p3)
 1 si n = 0 END IF
xn :  n −1
x × x END
III.4.3 Récursivité imbriquée
FUNCTION REAL puissance (INPUT REAL x, INPUT INT n)
BEGIN Lorsqu'un paramètre passé à un sous programme récursif est lui même un appel récursif au
IF n = 0 THEN
return 1; même sous programme, on dit que la récursivité est imbriquée. Cela est totalement différent
ELSE de la récursivité multiple.
return x * puissance (x, n - 1);
END IF EXEMPLE : La fonction d'Ackermann qui a besoin de deux paramètres, m et n, et est définie
END de la manière suivante :
Algo 22. - Calcul de puissance, algorithme récursif
n + 1 si m = 0

ack (m , n ) =  ack (m - 1, 1) si m > 0 et n = 0
III.4.2 Récursivité multiple
lorsqu'à chaque exécution d'un algorithme, plusieurs appels récursifs sont exécutés, la  ack (m - 1, ack (m , n - 1))

récursivité est alors multiple. il faut que tous les appels soient exécutés pour qu'on qualifie la
récursivité de multiple.

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
41 42
FUNCTION INT ack (INPUT INT m, INPUT INT n)
BEGIN
IF m = 0 THEN
return n + 1;
ELSE IF m > 0 AND n = 0 THEN
return ack (m - 1, 1);
ELSE
return ack (m - 1, ack (m, n - 1));
END IF
END
Algo 24. - Calcul de la fonction d'Ackermann : récursivité imbriquée

III.4.4 Récursivité mutuelle


La récursivité mutuelle n'est pas une récursivité directe. Deux algorithmes sont
mutuellement récursifs si chacun d'eux appelle l'autre. Chacun des deux algorithmes est
appelé indirectement à travers l'autre, c'est pour cette raison qu'on parle de récursivité
cachée.

EXEMPLE : calcul de parité d'un entier naturel : 0 est un nombre pair et ∀ n, n est pair si et
seulement si n - 1 est impair. Comme nous venons de le voir, si on veut écrire une fonction
pair, il faut écrire aussi une fonction impair. 0 n'est pas impair ; ∀ n > 0, n est impair si et Fig. 9 - Le problème des tours de Hanoï
seulement si n - 1 est pair.
En déplaçant les disques, il faut respecter les règles suivantes :
 VRAI si n = 0
pair (n) :  • on ne peut déplacer plus d'un disque à la fois,
VRAI si impair (n - 1) • on ne peut placer un disque que sur un autre disque plus grand que lui ou sur un
emplacement vide.
FAUX si n = 0
impair (n) :  On suppose que cette dernière règle est également respectée dans la configuration de départ.
 VRAI si pair (n - 1)
Il s'agit d'écrire un algorithme capable de résoudre le problème des tours de Hanoï pour un
FUNCTION BOOL pair (INPUT INT n) nombre quelconque de disques sur la tour de départ. Considérons le cas de trois disques :
BEGIN
IF n = 0 THEN
return TRUE;
ELSE
return impair (n - 1);
END IF
END

FUNCTION BOOL impair (INPUT INT n)


BEGIN
IF n = 0 THEN
return FALSE; Fig. 10 (a) Fig. 10 (b)
ELSE
return pair (n - 1);
END IF
END
Algo 25. - Calcul de parité : récursivité mutuelle

EXERCICE : Les tours de Hanoï


Le problème des tours de Hanoï (fig. 10) est un jeu de réflexion imaginé par le mathématicien
français Édouard Lucas en 1883, et consistant à déplacer des disques de diamètres différents
d'une tour de « départ » (D) à une tour d'« arrivée » (A) en passant par une tour « Fig. 10 (c) Fig. 10 (d)
intermédiaire » (I), et ceci en un minimum de coups.

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
43 44
Un problème peut être subdivisé en un certain nombre de problèmes de même nature que le
problème global. Dans ce cas, il est plus facile et plus rapide de traiter les sous problèmes
chacun à part que de traiter le problème global. Du moment où un sous problème est de la
même nature que le problème global, la récursivité permet à un algorithme de s'appeler lui
même et de traiter chaque sous problème.

DEFINITION : un algorithme de type diviser pour régner est un algorithme récursif qui
fonctionne selon le principe suivant :
Fig. 10 (e) Fig. 10 (f)
Si la taille du problème est suffisamment petite, il est résolu sans faire d'appel récursif, c'est
ce qu'on appelle la base ou la condition d'arrêt.
Si la taille du problème est importante, on procède comme suit :

• Diviser : le problème en un certain nombre de sous-problèmes ;


• Régner : sur les sous-problèmes en les résolvant récursivement ou, si la taille d’un sous-
problème est assez réduite, le résoudre directement ;
• Combiner : les solutions des sous-problèmes en une solution complète du problème
initial.

Fig. 10 (g) Fig. 10 (h) Le schéma général d'un algorithme fonctionnant sur le principe de diviser pour régner est le
suivant :
Fig. 10 - Solution du problème des tours de Hanoï avec trois plateaux
PROCEDURE regner (INPUT PROBLEME p)
Si on observe le déroulement du déplacement des différents plateaux, on peut constater OUTPUT SOLUTION s;
qu'au niveau de la fig. 10 (d) nous avons réussi à déplacer deux plateaux de la tour de D vers BEGIN
la tour I en utilisant la tour A comme intermédiaire. Ensuite on effectue le déplacement du IF || p || est petite THEN
plus grand plateau de D vers A (fig. 10 (e)). Finalement on déplace les deux plateaux de I s ← base (p);
vers A en utilisant D comme tour intermédiaire. ELSE
Pour généraliser le principe du problème des tours de Hanoï : (p1, p2, ..., pn) ← diviser (p);
FOR i ← 1 TO n DO
• On suppose que l’on sait résoudre le problème pour (n - 1) disques. si ← regner (pi);
• Pour déplacer n disques de la tige D vers la tige A, on déplace les END FOR
(n - 1) plus petits disques de la tige D vers la tige I. s ← combiner (s1, s2, ..., sn);
• Ensuite on déplace le plus gros disque de la tige D vers la tige A END IF
• Enfin on déplace les (n - 1) plus petits disques de la tige I vers la tige A. END
On constate que le problème est récursif et on peut donc construire l'algorithme suivant (on
suppose qu'il existe un type TOUR :
EXEMPLE
PROCEDURE hanoi (INPUT INT n, OUTPUT TOUR D,
Recherche de la valeur maximale dans un tableau non trié. Le tableau débute à l'indice p et
OUTPUT TOUR A,
OUTPUT TOUR I,) se termine à l'indice r.
BEGIN
IF n = 1 THEN Si p = r alors le tableau ne contient qu'un seul élément et c'est le maximum (cas de base);
deplacer (D, A); Sinon,
ELSE
hanoi (n - 1, D, I, A); 1. On divise le tableau en deux sous tableaux de taille égale en calculant l'indice du milieu :
deplacer (D, A); q = (p + r) / 2 (DIVISER)
hanoi (n - 1, I, A, D); 2. On calcule le maximum du tableau entre l'indice p et q. Ensuite on calcule le maximum
END IF du tableau entre l'indice q + 1 et r (REGNER)
END
3. On retourne le maximum des deux valeurs (COMBINER)
Algo 25. - Algorithme pour le problème des tours de Hanoï

III.4.5 Diviser pour régner


Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
45 46
FUNCTION INT max (INPUT INT T[], INPUT INT p, INPUT INT r)
BEGIN
INT m1, m2;
INT q;

IF p = r THEN
return p;
ELSE
q ← (p + r) / 2;
m1 ← max (T, p, q);
m2 ← max (T, q + 1, r);
IF T[m1] > T[m2] THEN
return m1;
ELSE
return m2;
END IF
END IF
END
Algo 26. - Diviser pour régner : recherche du maximum d'un tableau non trié

EXEMPLE
Recherche d'une valeur dans un tableau trié. Le tableau débute à l'indice p et se termine à
l'indice r. Si le tableau ne contient qu'un seul élément (p = r) et si cet élément est égal à la
valeur recherchée alors on retourne TRUE. Sinon, on divise le tableau en deux en calculant
l'indice q = (p + r) / 2. Si la valeur de l'élément se trouvant à l'indice q est égale à la valeur
recherchée alors on retourne TRUE. Si la valeur de l'élément se trouvant à l'indice q est
supérieure à la valeur recherchée, on recherche dans le tableau entre les indices p et q. Si la
valeur de l'élément se trouvant à l'indice q est inférieure à la valeur recherchée, on recherche
dans le tableau entre les indices q + 1 et r.

FUNCTION BOOL recherche (INPUT INT v, INPUT INT T[],


INPUT INT p, INPUT INT r)
OUTPUT INT val;
BEGIN
INT q;

IF p = r THEN
IF T[p] = v THEN
return TRUE;
ELSE
return FALSE;
ELSE
q ← (p + r) / 2;
IF T[q] = v THEN
return TRUE;
ELSE IF T[q] > v THEN
return recherche (v, T, p, q);
ELSE
return recherche (v, T, q + 1, r);
END IF
END IF
END
Algo 27. - Diviser pour régner : recherche d'une valeur dans un tableau trié

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
47 48
IV .
Gestion de la
mémoire

La mémoire se définit comme la capacité à stocker et à récupérer l'information.


En psychologie cognitive la mémoire est la faculté de l'esprit ayant pour fonction
d'enregistrer, conserver et rappeler des informations. Dans les sciences humaines,
on parle par extension de mémoire collective pour désigner les souvenirs
partagés au sein d’une collectivité. En intelligence économique, la mémoire d'un
organisme est sa capacité à transformer des informations brutes en connaissances
partagées par le personnel de cet organisme et ses partenaires. En informatique et
électronique, la mémoire est un dispositif physique permettant la conservation et
la restitution d'information ou de données. En physique, un alliage à mémoire de
forme est un alliage capable de retourner à sa forme initiale après une
déformation. En biologie, la mémoire immunitaire est la capacité du système
immunitaire adaptatif à reconnaître des antigènes alors que la mémoire nucléaire
est la conservation de l'état d'expression de la chromatine dans une lignée
cellulaire.

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
49 50
VI.1 Introduction
Adresse max
Automatique PILE
Les données manipulées par un programme occupent de la mémoire. La taille de chaque Automatique
donnée dépend de son type et de l'architecture de la machine sur laquelle tourne le Automatique
programme. Il existe trois type d'occupation mémoire des variables, nous en avons déjà
rencontré deux : les données statiques, les données automatiques et les données dynamiques
dont l'occupation mémoire est à la demande.
Dynamique
Dans la fig. 9, les données schématisées sont les statiques (variables et constantes globales) et Dynamique
Dynamique TAS
les données automatiques (données locales).
Statique
Statique
Une variable statique est une variable à laquelle est assigné un emplacement mémoire fixe, et Statique
dont la durée de vie est celle du programme en cours d’exécution (contexte de programme Adresse min
Code exécutable
par opposition à un contexte de fonction). Une déclaration globale (effectuée en dehors du
corps d'une fonction crée nécessairement une variable statique, puisque non associée à un
contexte particulier hormis celui du programme. Fig. 10 - Organisation de la mémoire pour un processus donné

Une variable automatique est une variable créée dans le contexte d’exécution d’une fonction, IV.2.1 Primitives de gestion de mémoire
au moment de l’appel (allouée dans la pile). Un contexte d’exécution étant détruit lorsque
l’on quitte la fonction, les variables automatiques disparaissent. Entre autres conséquences, Pour pouvoir allouer dynamiquement la mémoire, on dispose de certaines primitives
cela signifie que lors d’appels successifs à une même fonction, on ne retrouve jamais les permettant de réserver de l'espace mémoire et retourne l'adresse de la zone mémoire
variables locales avec la valeur qu'elles avaient lors de l'appel précédent. Par défaut, toute réservée. De même, il existe une primitive pour libérer explicitement la mémoire lorsque cela
déclaration locale crée une variable automatique. est nécessaire.

Une variable dynamique est une variable qui est créée et détruite explicitement (allouée dans Une adresse mémoire est référencée par un pointeur. On considère le type générique
le tas). Nous n'avons pas encore évoqué dans ce cours ce type de variables combien pointeur qui permet de référencer une adresse mémoire quelconque : POINTER. Gérer la
important. Pour le maîtriser, il faut comprendre le fonctionnement de la mémoire lors de mémoire revient donc à gérer des pointeur. Les pointeur peuvent être typés. PINT
l'exécution d'un programme. représente un pointeur sur une donnée entière, PREAL est un pointeur sur une donnée de
type réel et PCAR représente un pointeur sur une donnée de type caractère.
REMARQUE : une variable statique peut être déclarée à l'intérieur d'un contexte d'un sous
programme. Dans ce cas, il faut utiliser le mot STATIC et la valeur des variables statiques IV.2.1 La primitive sizeof
sont conservées d'un appel à l'autre du sous programme. L'exemple suivant montre
comment déclarer une variable statique : La fonction sizeof permet de calculer la taille mémoire en octets qu'occupe un type de
données. Le type de données peut être un type de base ou un type crée par le programmeur.
STATIC INT val ; Le paramètre de la fonction sizeof est le nom du type dont on veut connaitre la taille
mémoire.
VI.2 Allocation dynamique de la mémoire
SYNTAXE : INT sizeof (TYPE nom_type);
L'allocation dynamique de la mémoire permet une meilleure gestion de cette ressource. Une
allocation statique de la mémoire impose de connaître la taille de la donnée, c'est le cas des EXEMPLE : sizeof (CHAR) = 1, puisqu'un caractère occupe un octet.
tableaux statiques par exemple. En effet, la mémoire statique est allouée dès la compilation.
Hormis la taille de données primitives simples (caractère, entier, réel, ...), l'allocation IV.2.2 La primitive malloc
dynamique, quant à elle, permet de retarder la réservation de l'espace mémoire jusqu'à la
La fonction malloc est utilisée pour allouer une certaine quantité de mémoire (valeur reçue
maîtrise de la taille exacte de l'espace mémoire nécessaire. Les données statiques sont créées
en paramètre) pendant l'exécution d'un programme. La fonction malloc demande à réserver
lors de leurs déclaration et existent tant que le programme est chargé en mémoire. Une
un bloc de mémoire dans le tas. S'il y a un bloc mémoire libre de taille supérieure ou égale à
variable dynamique peut être créée et détruite quand on en a besoin. La fig. 11 montre
la valeur demandée, la demande est acceptée, le système d'exploitation réserve quantité
l'organisation de la mémoire entre le code d'un programmes et les différents types de
demandée de mémoire et retourne l'adresse du début du bloc mémoire réservé. Lorsque la
données.
quantité de mémoire est pas plus nécessaire, il faut la libérer en invoquant la fonction free.

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
51 52
SYNTAXE : POINTER malloc (INPUT INT taille); SYNTAXE : POINTER realloc (INPUT POINTER ptr, INPUT INT taille);

EXEMPLE : allocation dynamique d'un tableau d'entier. L'algorithme demande le nombre


EXEMPLE : tableaux dynamiques : changer la taille d'un tableau.
d'élément que doit contenir un tableau d'entier et réserve la place mémoire nécessaire et
retourne l'adresse du début de bloc mémoire.
ALGORITHM tableau_dynamique
ALGORITHM creat_tab
INPUT INT taille; INPUT INT taille;
OUTPUT PINT tab; OUTPUT PINT tab;
BEGIN
write ("donner la taille du tableau à créer : "); BEGIN
read (taille); INT i;
CHAR rep;
tab ← (PINT) malloc (taille * sizeof (INT));
BOOL err;
IF tab = NULL THEN
write ("Erreur, pas assez de mémoire libre");
END IF i ← 0;
END err ← FALSE;

Algo 28. - Allocation dynamique de la mémoire pour un tableau : malloc write ("donner la taille du tableau à créer : ");
read (taille);
IV.2.3 La primitive calloc
tab ← (PINT) calloc (taille, sizeof (INT));
La fonction calloc est utilisée pour allouer une certaine quantité de mémoire pendant IF tab = NULL THEN
l'exécution d'un programme. La fonction calloc demande à réserver un bloc de mémoire write ("Erreur, pas assez de mémoire libre");
dans le tas. S'il y a un bloc mémoire libre de taille supérieure ou égale à la valeur demandée, ELSE
la demande est acceptée, le système d'exploitation réserve la quantité demandée de mémoire REPEAT
IF i >= taille THEN
et retourne l'adresse du début du bloc mémoire réservé. La différence entre malloc et calloc
taille ← taille + 10;
c'est les paramètres reçus en entrée et le fonctionnement. En effet, calloc reçoit deux
tab ← realloc (tab, taille * sizeof (INT));
paramètres : le nombre de bloc mémoire à réserver et la taille de chaque bloc. La primitive
calloc initialise la mémoire réservée à 0 alors que malloc n'y touche pas. Lorsque la quantité IF tab = NULL THEN
de mémoire n'est plus nécessaire, il faut la libérer en invoquant la fonction free. write ("Erreur - pas assez de mémoire !");
err ← TRUE;
SYNTAXE : POINTER calloc (INPUT INT nb_bloc, INPUT INT taille); END IF
END IF
EXEMPLE : allocation dynamique d'un tableau d'entier. L'algorithme demande le nombre IF err = FALSE THEN
d'élément que doit contenir un tableau d'entier et réserve la place mémoire nécessaire et write ("Donner une valeur entière : ");
retourne l'adresse du début de bloc mémoire. read (tab[i]);
i ← i + 1;
ALGORITHM creat_tab
INPUT INT taille; write ("Saisir une autre valeur ? (O/N) : ");
OUTPUT PINT tab; read (rep);
BEGIN END IF
write ("donner la taille du tableau à créer : ");
read (taille); UNTIL (err = TRUE OR rep = 'N');
tab ← (PINT) calloc (taille, sizeof (INT)); END IF
IF tab = NULL THEN END
write ("Erreur, pas assez de mémoire libre");
Algo 30. - Allocation dynamique de la mémoire pour un tableau : realloc
END IF
END
VI.3 Les types structurés
Algo 29. - Allocation dynamique de la mémoire pour un tableau : calloc
Lors de la gestion de l'information, certaines données peuvent être interdépendantes. Par
IV.2.3 La primitive realloc exemple, un répertoire téléphonique contient des données diverses se rapportant à
différentes personnes répertoriées. Ces données sont généralement récurrentes. Un
La fonction realloc s'utilise suite à l'utilisation de malloc ou de calloc (jamais seule). Elle répertoire réaliste comprendrait le nom, le prénom, le numéro de téléphone fixe, le numéro
peut être rappelée plusieurs fois de suite (dans une boucle FOR ou WHILE par exemple). du mobile, le numéro du bureau, l’adresse e-mail etc. Un autre exemple, lors de la gestion de
Elle sert à réattribuer de la mémoire à un pointeur mais pour une taille mémoire différente.
Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
53 54
données relatives à des étudiants, il faudrait mémoriser pour chaque étudiant, son nom, son Lorsqu'une variable de type structure est déclarée, la mémoire correspondante est allouée et
prénom, sa date de naissance, sa classe etc. il est possible d'accéder à chacun des membres un à un en utilisant la syntaxe suivante :

Pour une gestion cohérente de ce genre de données, il faut les lier logiquement pour lorsque nom_variable.nom_membre
l'on accède à un nom par exemple, on est sûr d'accéder au prénom correspondant, au bon
numéro de téléphone, etc. En algorithmique, on utilise les enregistrement ou encore des EXEMPLE :
structures.
voiture ma_voiture;
Un enregistrement est une manière d'organiser la mémoire permettant de regrouper des
ma_voiture.marque ← "Mercedes";
données de différentes natures, liées entre elles, afin de rendre l'exploitation pratique pour le
ma_voiture.type ← "SLR";
programmeur et efficace lors de l'exécution. Les tableaux sont un type structuré de données ma_voiture.vin ← "WDB2009382F063843";
mais qui reste, à ce stade, assez simple et ne permet pas d'organiser les données tel que nous ma_voiture.cylindres ← 8;
l'avons exprimé ci-dessus puisqu'il ne regroupe que des données de même type. ma_voiture.puissance ← 626;
ma_voiture.ccode ← 744;
IV.3.1 Définition d'une structure ma_voiture.immat ← "GE109007";
ma_voiture.date_circulation ← "01/09/2009";
EXEMPLE : on voudrait représenter des données permettant d'identifier une voiture de
tourisme. Une voiture est caractérisée par sa marque, son type, sa motorisation (nombre de Les données membres d'une structure sont stockées en mémoire dans le même ordre que
cylindres), sa puissance, sa couleur, son VIN (numéro de châssis), son numéro dans la définition de la structure (fig. 11)
d'immatriculation, sa date de mise en circulation et sa couleur. Pour pouvoir regrouper. La
création d'une structure de données spécifique permet de regrouper toutes ces données
hétérogènes au sein d'un même espace mémoire.

TYPEDEF STRUCT voiture


BEGIN voiture.marque « Mercedes »
STRING marque;
voiture.marque « SLR »
STRING type;
STRING vin; // Numéro du châssis voiture.vin «WDB2009382F063843 »

INT cylindres; voiture.cylindres 8


INT puissance; voiture.puissance 626
INT ccode; // Code de la couleur voiture.ccode 744
STRING immat; // Numéro d'immatriculation voiture.immat « GE109007 »
STRING date_circulation; // Première mise en circulation
voiture.date_circulation « 01/09/2009 »
END

Cette définition ne permet pas d'allouer de l'espace mémoire, elle permet juste de calculer la
taille mémoire nécessaire pour créer un objet de type voiture. Elle permet aussi d'indiquer
comment les différentes données composant la structure sont agencées. Les données à
l'intérieur d'une structure sont les membres et peuvent être de n'importe quel type de
données y compris un pointeur sur la structure elle même.
Fig. 11 - Organisation mémoire d'une structure
IV.3.2 Déclaration et usage d'une structure
La définition d'une structure crée un nouveau type de donnée. Il est possible de définir des
Pour allouer de l'espace mémoire pour un type STRUCT, il faut déclarer une variable de ce tableau pouvant contenir des éléments de type structure. Il est possible de déclarer des
type : pointeurs sur un type structure, d'allouer de l'espace mémoire pour des éléments de type
structure en utilisant les primitives malloc, calloc et realloc, etc.
EXEMPLE :
Il est possible d'effectuer une affectation globale d'unen variable de type structure, c'est à
voiture ma_voiture; dire d'attribuer, en une seule instruction, la valeur de tous ses membres. Là aussi il faut
respecter l'ordre de la déclaration des membre.

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
55 56
voiture ma_voiture = { Un objet de type structure est toujours passé par valeur lorsqu'il est paramètre d'un sous
"Mercedes", // Marque programme. Par conséquent, les modifications sur le paramètre qui sont opérées dans le sous
"SLR", // Type
"WDB2009382F063843", // VIN programme n'affectent pas l'objet réel. il est donc indispensable, si on souhaite initialiser ou
8, // Motorisation modifier la valeur des membres d'un objet structure dans un sous programme, de passer un
626, // Puissance pointeur sur cet objet.
744, // Code de la couleur
"GE109007", // Immatriculation On considère la fonction ADRESS qui donne l'adresse mémoire d'une variable quelconque.
"01/09/2009" // Date de mise en circulation
Cette fonction peut être utilisée pour passer un pointeur sur un objet structure comme
}
paramètre d'un sous programme.
IV.3.2 Pointeurs de structure
EXEMPLE : on veut écrire une procédure qui initialise les caractéristiques d'une voiture.
Comme tout type de données, il est possible de déclarer un pointeur sur une structure et lui Cette procédure est ensuite appelé par un autre programme pour initialiser les
allouer de l'espace mémoire. La syntaxe de la déclaration d'un pointeur sur un type de caractéristiques d'un objet de type voiture. On utilisera par la suite le même sous programme
donnée structure est la suivante : pour initialiser un tableau contenant des objets de type voiture.

POINTER(nom_structure) ptr; PROCEDURE init (OUTPUT POINTER(voiture) p)


BEGIN
Il faut ensuite allouer de l'espace mémoire pour pouvoir accéder aux membres. L'allocation
write ("Marque de la voiture : ");
se fait à l'aide des primitives d'allocation déjà étudiées :
read (pv→marque);
ptr ← POINTER(nom_structure) malloc (sizeof (nom_structure)); write ("Type de la voiture : ");
read (pv→type);
La primitive sizeof détermine la taille mémoire nécessaire pour un élément de la structure et write ("Numéro de châssis de la voiture : ");
malloc réserve cet espace mémoire et retourne son adresse dans ptr. L"accès aux membres read (pv→vin);
d'un pointeur sur une structure se fait par l'opérateur → : write ("Nombre de cylindres de la voiture : ");
read (pv→cylindres);
ptr → nom_membre;
write ("Puissance (DIN) de la voiture : ");
EXEMPLE : read (pv→puissance);
write ("Code de la couleur de la voiture : ");
POINTER(voiture) pvoiture; read (pv→ccode);
pvoiture ← POINTER(voiture) malloc (sizeof (voiture)); write ("Matricule de la voiture : ");
pvoiture→marque ← "Mercedes"; read (pv→immat);
pvoiture→type ← "SLR"; write ("Date de mise en circulation : ");
pvoiture→vin ← "WDB2009382F063843"; read (pv→date_circulation);
pvoiture→cylindres ← 8; END
pvoiture→puissance ← 626; Algo 31. - Passage d'un objet de type structure comme paramètre d'un sous programme
pvoiture→ccode ← 744;
pvoiture→immat ← "GE109007"; Le programme qui appelle cette procédure crée un objet voiture et passe son adresse comme
pvoiture→date_circulation ← "01/09/2009"; paramètre de la procédure :

Une donnée de type structure peut avoir un membre qui est un pointeur sur elle même. Cela
ALGORITHM concessionnaire
est très utiles pour définir des types de données élaborés tels que les listes et les arbres : BEGIN
voiture ma_voiture;
TYPEDEF STRUCT ma_struct init (ADRESS (ma_voiture));
BEGIN END
STRING info; Algo 32. - Passage de l'adresse d'un objet structure à un sous programme
POINTER (ma_struct) ptr;
END L'algorithme suivant permet la saisie d'un tableau contenant des objets de type structure. Le
nombre maximal de voiture à saisir est MAX.
IV.3.2 Passage de structure en paramètre d'un sous programme

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données
57 58
ALGORITHM concessionnaire2
BEGIN
voiture liste [MAX];
INT nb_voit;
CHAR rep;

nb_voit ← 0;

write ("Saisie des caractéristiques des voitures");


REPEAT
init (ADRESS (liste [nb_voit]));
nb_voit ← nb_voit + 1;
write ("Saisir une autre voiture (O/N)");
read (rep);
UNTIL rep = 'N' OR nb_voit >= MAX
END
Algo 33. - Passage de l'adresse d'un objet structure à un sous programme - usage de tableaux

Ecole Polytechnique de Tunisie | Cours Algorithmique et Structures de Données


59

Vous aimerez peut-être aussi