Vous êtes sur la page 1sur 83

Chapitre : Récursivité

Hager KAMMOUN BOUZAIENE


LSI1
Emna JAMMOUCI FENDRI
2020/2021
2

LSI1

Plan
• Intérêt de la récursion
• Propriétés des algorithmes récursifs
 Démarche à suivre
 Exemples (Factorielle et Fibonacci)
 Utilisation de l’itération et de la récursion
• Invocation d’un sous programme
 Activités
 Pile d’exécution
• Processus engendré par un sous programme
▫ Récursion linéaire (factorielle)
 Processus récursif : fact()
 Trace d’exécution (décomposition et contraction)
 Processus itératif : fact_iter()
 Points de convergence et points de divergence
 Récursivité terminale vs récursivité non terminale
▫ Récursion en arbre binaire (suite de fibonacci)
 Trace d’exécution (décomposition et contraction)
• Analyse des algorithmes récursifs
▫ Factorielle
▫ Tour de Hanoi
3

LSI1

Intérêt de la récursion
• La notion de récursivité en informatique est très proche de la notion de
récurrence en mathématique
▫ De nombreuses définitions mathématiques sont récursives
• La récursion ou récursivité intervient fréquemment en programmation
 Un objet (pas au sens de la programmation objet) et dit récursif s’il fait appel à lui-
même (d’une façon directe ou indirecte) lors de sa définition ou composition

Il s’agit d’un moyen simple et élégant pour résoudre un problème


Lorsqu’elle est mal utilisée, cette subtilité informatique peut créer un code
totalement inefficace
4

LSI1

Exemples
5

LSI1

Intérêt de la récursion
Entiers naturels (IN) Factorielle (!) Structure de données
•0 est un entier naturel 0! =1 struct element{
•Tout entier naturel à un n>0 , n!=n*(n-1)! int cle;
successeur unique struct element * suivant;
•Tout entier naturel sauf 0 };
est le successeur d’un
unique entier

Récursivité liée aux données


LP non OO : est assurée grâce aux pointeurs
LP OO : est assurée grâce aux références
Récursivité liée aux traitements
LP non OO : est assurée grâce aux sous programmes
LP OO : est assurée grâce aux méthodes
6

LSI1

Intérêt de la récursion
• La récursivité directe correspond au fait qu’un programme fait
appel à lui-même
▫ Factorielle
• La récursivité indirecte correspond au cas où la suite d’appels
qui part de A peut passer par plusieurs autres sous programmes
avant d’arriver de nouveau à A
▫ La définition récursive des nombres pairs et impairs
 Un nombre positif n est pair si n-1 est impair et un nombre positif n est
impair si n-1 est pair
7

LSI1

Intérêt de la récursion
Récursion directe

void recursive(void)
{
<instructions de pré-traitement>
if (test de continuation)
recursive()
<instructions de post-traitement>
}

L’appel récursif est traité comme n’importe quel appel de fonction


8

LSI1

Intérêt de la récursion
void rec(void)
void rec(void)
1 { <inst pré>
if (cont) 2 { <inst pré> void rec(void)
rec() if (cont)
3 { <inst pré>
<inst post> 6 rec() if (cont)
} <inst post> 5 rec()
} <inst post> 4
}
appel récursif : branchement

retour récursif : instruction suivante de la fonction appelante


9

LSI1

Intérêt de la récursion
• La fonction rec() s'appelle elle même 3 fois
▫ chaque nouvel appel conserve l'adresse de retour
• Les instructions de pré-traitement sont exécutées avant les appels
récursifs
• Les instructions de post-traitement sont exécutées après les
appels récursifs
▫ dans l'ordre de dépilement, c'est à dire inverse
10

LSI1

Intérêt de la récursion
Récursion indirecte
Une fonction A appelle une fonction B
Une fonction B appelle la fonction A

void recursiveA(void) void recursiveB(void)


{ {
<instructions A1> <instructions B1>
if (test continuation B) if (test continuation A)
recursiveB(); recursiveA();
<instruction A2> <instruction B2>
}
}
11

LSI1

Propriétés des algorithmes récursifs


• Dans le processus d’analyse un problème peut être décomposé en
sous problèmes
▫ Chaque sous problème va être résolu avec un algorithme
 Sous programme qui peut être une fonction ou une procédure
• Un problème se prête à une analyse récursive s’il peut être
décomposé en sous problèmes
▫ De même nature mais de taille plus petite
▫ La solution du problème s’exprime par rapport à elle même

5!=5*4!
12

LSI1

Propriétés des algorithmes récursifs: démarche à


suivre
Paramétrer le problème
• Un ou plusieurs paramètres
• Un au moins des paramètres qui indique la taille du problème
• Dans le cas optimiste ce paramètre doit décroitre à chaque appel
Identifier les cas triviaux
• Qui ne nécessitent pas d’appels récursifs

Identifier les cas élaborés ou non triviaux


• Qui nécessitent des appels récursifs
• L'appel récursif d'un cas général doit toujours mener vers un des cas particuliers
13

LSI1

Exemples Vue fonctionnelle

Factorielle n fact fact(n) Fibonacci n fibo fibo(n)


Paramètre : n Paramètre : n
Cas triviaux : 0 et 1 Cas triviaux : 0 et 1
Cas élaborés : n>1 Cas élaborés : n>1
#include <stdio.h> #include <stdio.h>
unsigned fact (unsigned n) unsigned fibo (unsigned n)
{ {
if (n<=1) if (n==0)
return 1; /* cas triviaux */ return 0; /* cas trivial */
else /* cas général */ else if (n==1)
return n*fact(n-1); return 1; /* cas trivial */
} else /* cas général */
return fibo(n-1)+fibo(n-2);
}
void main() main()
{ {void
printf("6!=%u",fact(6)); printf("fibo(6)=%u",fibo(6));
} }
14

LSI1

Exemples
• Le problème a été réduit à lui-même mais pour un cas plus simple
• fact est une fonction récursive
▫ Chaque appel à fact est susceptible d’introduire un appel à elle-même
• fibo est une fonction récursive
▫ Chaque appel à fibo est susceptible d’introduire deux appels à elle
même
15

LSI1

Propriétés des algorithmes récursifs


• Tout programme peut être écrit à l’aide de boucles (par itération)
sans récursivité
• Inversement chaque type de boucle (while ou do… while) peut être
simulé par récursivité
▫ Il s’agit de deux manières différentes de programmer
▫ Utiliser l’une ou l’autre est une question de gout et de style de
programmation
 Chaque type peut s’avérer mieux adapté que l’autre à certaines classes de
problèmes
16

LSI1

Exemples
Sous programme non récursif Sous programme récursif
fact(0) = 1 , n = 0 ; fact(0) = 1, n = 0;
fact(n) = 1*2*3…*n, n > 0 ; fact(n) = fact(n-1)*n, n > 0;

#include <stdio.h> #include <stdio.h>


unsigned fact (unsigned n) unsigned fact (unsigned n)
{ {
unsigned res; if (n<=1)
unsigned i; return 1; /* cas triviaux */
res=1; else /* cas général */
for (i=2;i<=n;i++) return fact(n-1)*n;
res*=i;
}
return res;
void main()
}
void main() {
{ unsigned res;
unsigned res; res=fact(6);
res=fact(6); printf("6!=%u",res);
printf("6!=%u",res); }
}
17

LSI1

Propriétés des algorithmes récursifs


Utilisation de l’itération Utilisation de la récursivité
• Un avantage de l’itération est l’efficacité • Se met à un niveau d’abstraction supérieur
• Style de programmation impératif ou à l’itération
procédural • Style de programmation déclaratif
• Au lieu de préciser les actions à réaliser on
décrit ce qu’on veut obtenir

Le style déclaratif produit un code moins efficace à cause des appels


récursifs de fonctions (plus lent que l’incrémentation d’un compteur)
18

LSI1

Propriétés des algorithmes récursifs

Souvent il est aussi simple d’exprimer les deux types de


solution
Souvent la solution récursive est plus intuitive que celle
itérative et le programme à écrire est plus court, plus
lisible et plus concis cependant celui qui est habitué aux
boucles (style impératif) peut avoir des difficultés
Suivant le type de problème la solution s’exprime plus
naturellement par récursivité ou par itération
19

LSI1

Propriétés des algorithmes récursifs


• Schéma itératif
▫ Risque d’une boucle infinie
Risque d’un traitement non
• Schéma récursif convergeant ou infini
▫ Risque d’un traitement infini
Schéma itératif Schéma récursif
void main() unsigned fact (unsigned n)
{ {
while (1) ; /* corps du wihle : instruction /* absence de cas triviaux */
nulle */ return n*fact(n-1);
printf(" je suis la reine d’angleterre \n ",); }
}
20

LSI1

Propriétés des algorithmes récursifs


• Pour un traitement itératif
▫ Il faut s’assurer que le corps de la boucle agit sur la condition
• Dans un sous programme récursif
▫ Il faut s’assurer que la condition d’arrêt est atteinte après un nombre
fini d’appels
 La condition d’arrêt doit être choisie avec soin
 Elle correspond aux cas triviaux
 sinon certains cas ne seront pas couverts par le sous programme
21

LSI1

Invocation d’un sous programme


Invocation = appel = exécution = activation
Un sous programme peut être déclaré dans plusieurs endroits
PD d’un sous programme
En dehors des sous programmes
Un sous programme est défini une et une seule fois
Une entête (qui définit la nature, le nom et les paramètres formels)
Une partie déclarative PD
Une partie exécutive PE
Pas d’imbrication de sous programmes (on ne peut pas définir un sous
programme au sein d’un autre sous programme)
Un sous programme peut être appelé (activé) au tant de fois que l’application
l’exige
Au sein de la partie exécutive PE
22

LSI1

Invocation d’un sous programme : activités


Activités
Chaque appel de fonction implique une activation qui va être créée
1. Evaluation des paramètres effectifs

2. Création des paramètres formels et des variables locales au sein d’un


espace appelé pile d’exécution

3. Liaison entre les paramètres effectifs et les paramètres formels

4. Exécution de la partie exécutive (PE) du sous programme appelé

5. Faire disparaitre les variables locales et les paramètres formels


excepté les variables locales de classe d’allocation static (en langage C)
23

LSI1

Invocation d’un sous programme : pile d’exécution


• Prévoir le nombre d’appels d’une fonction récursive pouvant être
en cours simultanément est impossible
▫ La récursivité suppose donc une allocation dynamique de la mémoire
à l’exécution

Quand il n’ya pas de récursivité, on peut « réserver » à la


compilation les zones mémoires nécessaires à chaque appel de
fonction
24

LSI1

Invocation d’un sous programme : pile d’exécution


• La pile d’exécution d’un programme en cours d’exécution est un
emplacement mémoire destiné à mémoriser les paramètres formels
et les variables locales ainsi que l’adresse de retour
▫ Elle fonctionne selon le principe LIFO (Last In First Out)
▫ Quand la fonction se termine elle remet à jour les variables grâce aux
informations au sommet de la pile
 Le contrôle est ensuite transféré à la fonction appelante à l’adresse de retour
• La pile a une taille fixée
▫ Une mauvaise utilisation de la récursivité peut entraîner un débordement
de la pile (overheads)
▫ Les appels de fonctions sont assez couteux en temps et en espace
25

LSI1

Invocation d’un sous programme : pile d’exécution


• La création et la destruction de cet espace local a lieu à quatre
moments bien déterminés
1. L’activation de la fonction d’abord du point de vue de la fonction
appelante
2. Puis de celui de la fonction appelée
3. Ensuite le retour d’abord du point de vue de la fonction appelée
4. Puis de celui de la fonction appelante

La pile d’exécution est générée par l’exécutif du langage C


26

LSI1

Invocation d’un sous programme : pile d’exécution


1. La fonction appelante réserve un mot vide sur la pile où sera déposé
le résultat de la fonction appelée
▫ puis elle empile les valeurs des arguments effectifs
▫ ensuite elle exécute une instruction APPEL qui empile l’@ de retour
2. La fonction appelée alloue l’espace local de la fonction en cours
3. Terminaison du travail
▫ La fonction appelée effectue une instruction de retour et libère l’espace
occupé par les variables locales
4. Reprise du calcul interrompue par l’appel
▫ La fonction appelante libère l’espace occupé par les valeurs des
arguments effectifs
▫ Elle se retrouve avec une pile au sommet de laquelle il ya le résultat de la
fonction qui vient d’être appelée
27

LSI1

Invocation d’un sous programme : pile d’exécution


• Les arguments (les paramètres effectifs) sont installés dans la pile
par la fonction appelante
▫ Ce sont des valeurs qui vont être évaluées dans le contexte de la
fonction appelante
▫ Il est donc naturel de donner à cette dernière la responsabilité de les
enlever de la pile au retour de la fonction appelée
• Les variables locales sont allouées par la fonction appelée qui est
la seule à connaitre le nombre
▫ C’est donc cette fonction qui doit les enlever de la pile lorsqu’elle se
termine
28

LSI1

Processus engendré par un sous programme récursif


• D’une façon générale un sous programme est un procédé de calcul
 L’exécution d’un sous programme engendre un processus indiquant
(visualisant) les différentes étapes de calcul
unsigned fact(unsigned n) unsigned fact_iter(unsigned produit,unsigned
{ compteur,unsigned max_compteur)
unsigned res; {
if(n<=1) if (compteut>max_compteur)
res=1; return produit;
else else
res = fact(n-1) * n; return
return res; fact_iter(produit*compteur,compteur+1,max_compteur);
} }
unsigned fact_iterative(unsigned n)
{ return fact_iter(1,1,n);}
void main() void main()
{ {
printf(" 6!= " ,fact(6)); printf(" 6!= " ,fact_iterative(6));
} }
29
unsigned fact(unsigned 3)
{
unsigned res; LSI1
if (n <= 1)
res = 1;
else
res = fact(2) * 3;
return res;
}
30
unsigned fact(unsigned 3)
{
unsigned res; LSI1
if (n <= 1)
res = 1;
else
res = fact(2) * 3;
return res;
}

unsigned fact(unsigned 2)
{
unsigned res;
if (n<= 1)
res = 1;
else
res = fact(1) * 2;
return res;
}
31
unsigned fact(unsigned 3)
{
unsigned res; LSI1
if (n <= 1)
res = 1;
else
res = fact(2) * 3;
return res;
}

unsigned fact(unsigned 2)
{
unsigned res;
if (n <= 1)
res = 1;
else
res = fact(1) * 2;
return res;
}

unsigned fact(unsigned 1)
{
unsigned res;
Une fois fact(1) est atteint les appels if (n <= 1)
récursifs s’arrêtent et la récursion res = 1;
else
commence à retourner des résultats res = fact(n - 1) * n;
return res;
}
32
unsigned fact(unsigned 3)
{
unsigned res; LSI1
if (n <= 1)
res = 1;
else
res = fact(2) * 3;
return res;
}

unsigned fact(unsigned 2)
{
unsigned res;
if (n <= 1)
res = 1;
else
res = fact(1) * 2;
return res;
}

unsigned fact(unsigned 1)
{
unsigned res;
if (n <= 1)
res = 1;
else
res = fact(n - 1) * n;
return 1;
}
33
unsigned fact(unsigned 3)
{
unsigned res; LSI1
if (n <= 1)
res = 1;
else
res = fact(2) * 3;
return res;
}

unsigned fact(unsigned 2)
{
unsigned res;
if (n <= 1)
res = 1;
else
res = 1 * 2;
return res;
}

unsigned fact(unsigned 1)
{
unsigned res;
if (n <= 1)
res = 1;
else
res = fact(n - 1) * n;
return 1;
}
34
unsigned fact(unsigned 3)
{
unsigned res; LSI1
if (n <= 1)
res = 1;
else
res = fact(2) * 3;
return res;
}

unsigned fact(unsigned 2)
{
unsigned res;
if (n <= 1)
res = 1;
else
res = 1 * 2;
return 2;
}
35
unsigned fact(unsigned 3)
{
unsigned res; LSI1
if (n <= 1)
res = 1;
else
res = 2 * 3;
return res;
}

unsigned fact(unsigned 2)
{
unsigned res;
if (n <= 1)
res = 1;
else
res = 1 * 2;
return 2;
}
36
unsigned fact(unsigned 3)
{
unsigned res; LSI1
if (n <= 1)
res = 1;
else
res = 2 * 3;
return 6;
}
37

LSI1

Trace d’exécution (dèveloppement ou


decomposition)
fact(4)

fact(3) 4

fact(2) 3 Le processus engendré par un sous


programme récursif peut être linéaire
unsigned fact(unsigned n)
{
fact(1) 2 unsigned res;
if(n<=1) //cas trivial
res=1;
else //cas récursif (decomposition)
res = fact(n-1) * n; (composition)
return res;
}
38

LSI1

Trace d’exécution (contraction ou composition)

*
fact(3) 4
*
fact(2) 3

*
unsigned fact(unsigned n)
fact(1)->1 2 {
unsigned res;
if(n<=1) //cas trivial
res=1;
else //cas récursif (decomposition)
res = fact(n-1) * n; (composition)
return res;
}
39

LSI1

Trace d’exécution (contraction ou composition)

*
fact(3) 4
*
fact(2)->2 3

unsigned fact(unsigned n)
{
unsigned res;
if(n<=1) //cas trivial
res=1;
else //cas récursif (decomposition)
res = fact(n-1) * n; (composition)
return res;
}
40

LSI1

Trace d’exécution (contraction ou composition)

fact(4)
*
fact(3)->6 4

unsigned fact(unsigned n)
{
unsigned res;
if(n<=1) //cas trivial
res=1;
else //cas récursif (decomposition)
res = fact(n-1) * n; (composition)
return res;
}
41

LSI1

Trace d’exécution (contraction ou composition)

fact(4)->24

unsigned fact(unsigned n)
{
unsigned res;
if(n<=1) //cas trivial
res=1;
else //cas récursif (decomposition)
res = fact(n-1) * n; (composition)
return res;
}
42

LSI1

Pile d’exécution
dépilement
…..
fact(4)=24
empilement

…..
res = 1
n=1
3
res = fact(1)*2 ? fact(1)
res =1*2
n=2 n=2

…..
2
res = fact(2)*3 ? fact(2) res = fact(2)*3 ? res = 2*3

…..
n=3 n=3 n=3

1
res =fact(3)*4 ? fact(3) res =fact(3)*4 ? res =fact(3)*4 ? res =6*4
n=4 n=4 n=4 n=4
fact(4)
43

LSI1

Processus engendré par un sous programme récursif


• D’une façon générale un sous programme est un procédé de calcul
 L’exécution d’un sous programme engendre un processus indiquant
(visualisant) les différentes étapes de calcul
unsigned fact(unsigned n) unsigned fact_iter(unsigned produit,unsigned
{ compteur,unsigned max_compteur)
unsigned res; {
if(n<=1) if (compteut>max_compteur)
res=1; return produit;
else else
res = fact(n-1) * n; return
return res; fact_iter(produit*compteur,compteur+1,max_compteur);
} }
unsigned fact_iterative(unsigned n)
{ return fact_iter(1,1,n);}
void main() void main()
{ {
printf(" 6!= " ,fact(6)); printf(" 6!= " ,fact_iterative(6));
} }
44

LSI1

Processus engendré par fact() et fact_iter()


Visualisation du processus engendré par fact() Visualisation du processus engendré par fact_iter()

fact (6)=6*fact(5) fact (6)=fact_iter(1,1,6)


=6*(5*fact(4)) =fact_iter(1,2,6)
=6*(5*(4*fact(3))) =fact_iter(2,3,6)
= 6*(5*(4*(3*fact(2)))) =fact_iter(6,4,6)
= 6*(5*(4*(3*(2*fact(1))))) =fact_iter(24,5,6)
=6*(5*(4*(3*(2*1)))) =fact_iter(120,6,6)
=6*(5*(4*(3*2))) =fact_iter(720,7,6)
= 6*(5*(4*6))
= 6*(5*24)
=6*120
=720
unsigned fact_iter(unsigned produit,unsigned
45
compteur,unsigned max_compteur)
{
if (compteut>max_compteur) LSI1
return produit;
else
return
fact_iter(produit*compteur,compteur+1,max_compteur);
}

dépilement
…..
fact_iter(3)=6

return 6
empilement

…..
max_compteur = 3
compteur = 4
produit = 6 3
return ? fact_iter(6,4,3)
return 6
max_compteur = 3 max_compteur = 3
compteur = 3

…..
compteur = 3
produit = 2 produit = 2
2
return ?
max_compteur = 3 fact_iter(2,3,3) return ? return 6
max_compteur = 3 max_compteur = 3

…..
compteur = 2 compteur = 2 compteur = 2
produit = 1 produit = 1 produit = 1
return ?
1
return ? return ? return 6
max_compteur = 3 fact_iter(1,2,3)
max_compteur = 3 max_compteur = 3 max_compteur = 3
compteur = 1 compteur = 1 compteur = 1 compteur = 1
produit = 1 produit = 1 produit = 1 produit = 1
fact_iter(1,1,3) fact_iterative(3)
46

LSI1

Points de convergence
• Les deux fonctions fact et fact_iter
▫ Offrent le même service permettant de calculer n!
 Par rapport à la partie utilisation
▫ Produisent un nombre d’étapes de calcul proportionnel à n
T(n) = T(n-1) + C
T(n-1) = T(n-2) + C
T(n-2) = T(n-3) + C
……………………..
T(2) = T(1) + C
En additionnant membre à membre, on arrive à: T(n) = T(1) + C(n-1) = O(n)

▫ Engendrent les mêmes résultats intermédiaires


47

LSI1

Points de divergence
• Le processus engendré par fact est caractérisé par une activité de
développement suivie d’une activité de contraction
▫ Des opérations sont retardées
▫ L’activité de contraction effectue progressivement l’opération de
multiplication retardée
• Le processus engendré par fact_iter
▫ N’implique ni développement, ni contraction
48

LSI1

Points de divergence
• La reprise du processus de calcul engendré par fact_iter suite à un
arrêt (c-à-d à une étape donnée) est facile
• La reprise suite à un arrêt pour le sous programme fact est loin
d’être facile

Le processus engendré par fact est un processus


récursif linéaire
Le processus engendré par fact_iter est un processus
itératif linéaire
49

LSI1

Récursivité terminale vs récursivité non terminale


• Solution récursive qui engendre un processus récursif
▫ Récursivité non terminale (fact)
• Solution récursive qui engendre un processus itératif
▫ Récursivité terminale (fact_iter)

Solution itérative
Inspirée de celle basée sur la récursivité terminale
50

LSI1

Récursivité terminale
• Une fonction récursive terminale est en théorie plus efficace que
son équivalent non terminale
• En récursivité terminale les appels récursifs n’ont pas besoin
d’être empilés dans la pile d’exécution
▫ L’appel suivant remplace simplement l’appel précédent dans la pile
d’exécution
• Il est facile de transformer de façon simple une fonction récursive
terminale en une version itérative
▫ C’est la dérécusivation
51

LSI1

Récursivité terminale : dérécursivation


Version récursive terminale Version itérative correspondante
T recursive(P) T iteractive(P)
Debut Debut
I0 I0
si (C) alors tantque (non C) faire
I1 la dernière instruction de I2
cet algorithme est un appel
sinon récursif P<-f(P))
I2 I0
recursive(f(P)) fintantque
finsi I1
Fin Fin
T : type de retour
P : liste des paramètres et f(P) fonction de transformation des paramètres
C : condition d’arrêt
I0, I1, I2 : instructions
52

LSI1

Récursivité terminale : dérécursivation


Version récursive terminale Version itérative correspondante
unsigned fact_iter(unsigned produit,unsigned unsigned fact_iterative(unsigned max_compteur)
compteur,unsigned max_compteur) {
{ unsigned produit;
if (compteur>max_compteur) unsigned compteur;
return produit; produit = 1;
else compteur =1;
return while(compteur<=max_compteur)
fact_iter(produit*compteur,compte
ur+1,max_compteur); {
} produit*=compteur;
compteur++;
}
return produit;
}
53

LSI1

Récursivité non terminale


Version récursive non terminale
T recursive(P)
Debut
I0
si (C) alors
I1
La fonction itérative correspondante
sinon doit gérer la sauvegarde des contextes
I2 et donc elle est moins efficace qu’une
recursive(f(P)) fonction écrite directement en itératif
I3
finsi
Fin

T : type de retour
P : liste des paramètres et f(P) fonction de transformation des paramètres
C : condition d’arrêt
I0, I1, I2, I3 : instructions
54

LSI1

Suite de nombres de Fibonacci


• Possédant au départ un couple de lapins, le problème consiste à
trouver le nombre de lapins obtenus au bout de n mois, en
supposant que chaque couple de lapins engendre tous les mois un
nouveau couple à compter du second mois de son existence.
• Ce nombre est obtenu à l’aide de la formule récursive
55

LSI1

Suite des nombres de Fibonacci


• Rabbits give birth so often. If rabbits did not die, their population
would be quickly get out of hand
• Let us assume that
▫ Rabbits never die
▫ A rabbit reaches sexual maturity exactly two months after birth (at the
beginning of its third month of life)
▫ Rabbits are always born male-female pairs
▫ At the beginning of every month, each sexually mature male-female pair
gives birth to exactly one male-female pair

• Question
▫ Suppose we start with a single newborn male-female pair in the first month
▫ What will be the number rabbit pairs in month n?
56

LSI1

Suite des nombres de Fibonacci


Définition : Version itérative
Fibo(1) = Fibo(2) = 1,
Fibo(n) = Fibo(n-1) + Fibo(n-2) pour n > 2, unsigned fibo_iter(unsigned n)
{ unsigned u0,u1; /* deux pas en arrière */
unsigned u; /* terme courant */
unsigned i; /* rang du terme courant */
Version récursive
assert(n>1);
unsigned fibo(unsigned n) /* Initialisation */
{ u0=0; u1=1;
if (n == 0) for(i=2;i<=n;i++)
return 0; { u=u0+u1;
else if (n == 1) u0=u1;
return 1; u1=u;
else return fibo(n-1) + fibo(n-2); }
} return u;
}
57

LSI1

Trace d’exécution (decomposition)


Fibo(5)

fibo(4) fibo(3)

fibo(3) fibo(2) fibo(2) fibo(1)

fibo(2) fibo(1) fibo(1) fibo(0) fibo(1) fibo(0)

Le processus engendré par un sous programme


fibo(1) fibo(0) récursif peut être visualisé par un arbre et en
particulier un arbre binaire
58

LSI1

Trace d’exécution (composition)


fibo(4)
+

fibo(3) fibo(2)
+ +
fibo(2) fibo(1)->1 fibo(1)->1 fibo(0)->0
+

fibo(1)->1 fibo(0)->0
59

LSI1

Trace d’exécution (composition)


fibo(4)
+

fibo(3) fibo(2)
+ +

fibo(2)->1 fibo(1)->1 fibo(1)->1 fibo(0)->0


60

LSI1

Trace d’exécution (composition)


fibo(4)
+

fibo(3)->2 fibo(2)
+

fibo(1)->1 fibo(0)->0
61

LSI1

Trace d’exécution (composition)


fibo(4)
+

fibo(3)->2 fibo(2)->1
62

LSI1

Trace d’exécution (composition)


fibo(4)->3
63

LSI1

Suite des nombres de Fibonacci


• Le sous programme récursif fibo entraine des calculs redondants
▫ fibo(3) est calculé 2 fois
▫ fibo(2) est calculé 3 fois
▫ Ce qui agit négativement sur l’efficacité du sous programme
▫ Recours à la programmation dynamique
 Éviter les calculs redondants
64

LSI1

Suite des nombres de Fibonacci


#define n1 30
unsigned * T[n1] /* mémorisation des calculs
• La suite de Fibonacci est basée sur le déjà effectués */
unsigned fibo(unsigned n)
principe Diviser Pour Résoudre (DPR)
{ unsigned res;
• Pour éviter les calculs redondants et
if(T[n]) res= *T[n];
garder l’esprit de la fonction fibo
/* fibo(n) est déjà calculé : on récupère
▫ Réconcilier élégance et efficacité on juste le résultat correspondant */
propose la solution suivante else {
if (n==0) res=0;
else if (n==1) res=1;
void initialiser() else res=fibo(n-1)+fibo(n-2);
{ /* On stocke le résultat de fibo(n) */
unsigned i; T[n]=(unsigned *) malloc(sizeof(unsigned));
for(i=0; i<n; i++) T[i]= NULL; *T[n]=res; }
} return res;
}
65

LSI1

Analyse des algorithmes récursifs


Cette analyse revient à déterminer :
1. sa complexité temporelle
2. sa complexité spatiale: se résumant à la détermination de la
taille de la pile générée par cette récursivité

La réponse à ces deux questions passe


généralement par la résolution d’une équation de
récurrence
66

LSI1

Analyse de la fonction factorielle


• Pour déterminer la complexité
▫ déterminer combien de fois fact() fait appel à elle-même
▫ Une fois ce nombre connu, il est alors facile de déterminer sa complexité. En effet, dans le
corps de cette fonction il y a
unsigned fact(unsigned n)
{
if (n <=1 ) return 1;
return n * fact(n-1);
}
 Un test
 Un appel à elle même
 Une soustraction et une multiplication
 Une opération de sortie
 En tout, pour chaque exécution de cette fonction, il y a 5 opérations élémentaires qui sont
exécutées pour n >2
67

LSI1

Analyse de la fonction factorielle


• Soit T(n) la complexité de la fonction fact(n)
• T(n-1) va représenter la complexité de fact(n-1)
 T(n) et T(n-1) sont reliées par l’expression suivante
 T(n) = T(n-1) + 5; si n >2
 T(n) = ??
 Cette équation est connue sous le nom d’équation de récurrence

T(n) = T(n-1) + 5
T(n-1) = T(n-2) + 5
T(n-2) = T(n-3) + 5
……………………..
T(2) = T(1) + 5
En additionnant membre à membre on a T(n) = T(1) + 5(n-1) = O(n)
68

LSI1

Les Tours de Hanoi

• Problème: déplacer les n disques d’une tour à une


autre
• Contraintes
▫ Déplacer un disque à la fois
▫ Ne jamais placer un gros disque sur un autre plus petit
69

LSI1

Les Tours de Hanoi : origine


• Une légende raconte qu’un groupe de moines, gardiens des trois
tours, doivent déplacer 64 anneaux d’or d’une tour à une autre en
respectant la règle unique qui stipule qu’un anneau ne doit pas
reposer sur un anneau plus petit. Lorsque les moines auront
déplacé les 64 anneaux, le monde se détruira !!!!
70

LSI1

Les Tours de Hanoi


• Il s'agit d'écrire une fonction qui prend en arguments
▫ un nombre n de disques (étages)
▫ un piquet de départ A
▫ un piquet de destination C
▫ et un piquet transitoire B
• Affiche à l'écran les mouvements à effectuer pour faire passer les n
disques (étages) supérieurs de la tour (piquet) A vers la tour
(piquet) C en s'aidant de la tour (piquet) B
71

LSI1

Les Tours de Hanoi


• L'idée de l'algorithme est la suivante :
• Si n est égale à 0 rien à faire
Cas simple, condition d’arrêt de la récursion
• Si n n'est pas nul qui nécessite un appel récursif
• Déplacer récursivement n-1 disques du piquet A au piquet B en s'aidant du
piquet C
Problème moins complexe
• Déplacer le disque restant du piquet A au piquet C
Problème résolvable directement
• Enfin déplacer récursivement n-1 disques du piquet B au piquet C en s'aidant du
piquet A
Problème moins complexe
72

LSI1

Les Tours de Hanoi

#include <stdio.h> void main(void)


void Hanoi(unsigned n, int p, int q, int r) {
{ unsigned n;
if (n > 0) printf("Entrer le nombre de disques : ");
scanf("%d", &n);
{ Hanoi(n, 1, 2, 3);
Hanoi(n-1, p, r, q); }
printf("%d --> %d\n", p , r );
Hanoi(n-1, q, p, r);
}
}
73

LSI1

Les Tours de Hanoi


Déplacer 2 premier de A à B

Déplacer 2 de B à C
74

LSI1

Les Tours de Hanoi : exécution pour n =3

Hanoi (3,A,B,C)

Hanoi (2,A,C,B) Déplacer (A,C) Hanoi (2,B,A,C)

Déplacer (B,A)
Hanoi (1,A,B,C) Déplacer (A,B) Hanoi (1,C,A,B)
Déplacer (B,C)
Déplacer (A,C) Déplacer (C,B)
Déplacer (A,C)
75

LSI1

Les Tours de Hanoi : exécution pour n =3

A -> C
A -> B
C -> B
A -> C
B -> A
B -> C
A -> C
76

LSI1

Analyse de Hanoi
• Pour déterminer la complexité de cette fonction
▫ déterminer combien de fois elle fait appel à elle-même
▫ dans le corps de cette fonction, il y a:
 Un test
 Deux appels à elle même
 Deux soustractions
 Une opération de sortie

En tout, pour chaque exécution de cette fonction, il y a 6 opérations


élémentaires qui sont exécutées pour n > 0
77

LSI1

Analyse de Hanoi
• Soit t(n) la complexité de la fonction hanoi(n,i,j,k)
• La relation entre t(n) et t(n-1) est comme suit
t(n) = t(n-1)+ t(n-1) + 6, si n > 0
t(0) = 1 (un seul test)
t(n) = 2 t(n-1) + 6
2 t(n-1) = 2 (2t(n-2) + 6) =4 t(n-2) + 2.6
4t(n-2) = 8 t(n-3) + 4.6
...................................
...................................
2n-1 t(1) = 2nt(0) + 6.2n-1
En additionnant membre à membre, on obtient
t(n) = 2nt(0) +6(1+2+4+...... +2n-1)
= 2n + 6. 2n
= O(2n).
78

LSI1

En conclusion
• Vis-à-vis de l’utilisateur la même interface est proposée
▫ indépendamment de la nature de la solution (itérative ou récursive)
• Toute solution récursive peut être transformée en une solution
itérative
▫ le coût de transformation est un critère important à envisager

Il faut éviter d’utiliser la récursivité lorsqu’il ya


une solution itérative évidente
79

LSI1

En conclusion
• Occasionnellement, une solution récursive s'exécute de façon beaucoup plus
lente que son équivalent itératif
▫ les nombres de fibonacci
• Par contre, dans la majorité des cas, la solution récursive est légèrement plus
lente
• Dans la majorité des cas, la solution récursive est plus facile à comprendre et
à implanter correctement que la solution itérative correspondante
▫ ce qui est un atout
• Dans les langages (comme Java) qui offrent à a fois l’itération et la récursion on va
préférer la récursivité surtout dans les situations où la solution itérative
est difficile à obtenir
▫ Si les structures de données manipulées sont récursives (les arbres)
▫ Si le raisonnement lui-même est récursif
80

LSI1

Exemple
• Ecrire un sous-programme récursif qui calcule la somme des
éléments positifs d’un tableau
o Définir les paramètres du problème.
o Identifier les cas triviaux et les cas élaborés.
o Expliquer et illustrer par un exemple s’il s’agit d’une récursivité terminale ou
non terminale
o Ecrire un programme qui permet de tester le sous-programme proposé.
81

LSI1

Exemple 1 : Corrigé
Les paramètres du problème sont :
 Le tableau T
 Les indices de début (deb) et de fin (fin) de la partie du tableau
à traiter
Cas trivial :
 Deb>fin : somme = 0
Cas élaborés :
 Deb ≤ fin , ça nécessite un appel récursif.
82

LSI1

Exemple 1 : Corrigé (version 1)

#include <stdio.h>

int somme(int T[], int deb, int fin)


{
if (deb>fin) return 0;
else if (T[deb]>0) return T[deb]+ somme(T,deb+1,fin);
else return somme(T,deb+1,fin);
}

void main ()
{
int T[]={2,-5,3,8};
printf("%d\n", somme(T,0,3));
}
83

LSI1

Exemple 1 : Corrigé (version 2)


#include <stdio.h>

int somme(int T[], int deb, int fin, int som)


{
if (deb>fin) return som ;
else if (T[deb]>0)
return somme(T,deb+1,fin,som + T[deb]);
else return somme(T,deb+1,fin,som );
}
void main ()
{
int som;
int T[]={2,-5,-3,8};
som = somme (T,0,3,0);
printf("%d\n",som);
}

Vous aimerez peut-être aussi