Vous êtes sur la page 1sur 13

Algorithmique et programmation 3

STPI - 2A
2020-2021

Moncef Hidane, Pascal Berthomé, Hugo Raguet

Séance 5: Récursivité

1 / 26

Récursivité
• Dans la résolution d’un problème, il est courant d’avoir à résoudre des
instances plus petites du même problème. Exemple mathématique :
calcul du déterminant d’une matrice.
• C’est ce principe qui est appelé récursivité.
• Stratégie qui permet de simplifier l’écriture, les preuves de correction
et le calcul de complexité des algorithmes.
• Utilisation des preuves par récurrence !
• Les premiers langages de programmation qui ont autorisé l’emploi de
la récursivité sont LISP et Algol 60.
• Tous les langages de programmation (ou presque) proposent une mise
en œuvre de la récursivité
• Les algorithmes récursifs sont souvent opposés aux algorithmes dits
itératifs.

2 / 26
Première approche incomplète
Définition de la fonction factorielle
• n! = n ∗ (n − 1)!

factorielle naïve ... un peu trop !


Fonction naive(In Variable n : Entier) : Entier
Retourne n * naive(n-1)
FinFonction
Calculons 3!
naive(3) :
retourner 3*naive(2)
naive(2) : retourner 2*naive(1)
naive(1) : retourner 2*naive(0)
naive(0) : retourner
2*naive(-1)
naive(-1) : En
route
3 / 26
vers
−∞

Condition d’arrêt
Condition d’arrêt
• Puisqu’une fonction récursive s’appelle elle-même, il est impératif de
prévoir le cas où on ne doit pas continuer le calcul.
• Cette condition doit être testée en premier, avant de lancer l’appel
récursif.

Définition correcte de la fonction factorielle pour n ≥ 0



1 si n = 0
n! =
n ∗ (n − 1)! sinon

factorielle
Fonction factorielle(In Variable n : Entier) : Entier
Si n=0 Alors
Retourne 1
Sinon
Retourne n * factorielle(n-1)
FinFonction 4 / 26
Calcul de factorielle(3)

5 / 26

Mode de fonctionnement d’un appel de fonction

Pile d’appel
• Lors d’un appel de fonction, il faut se placer dans un contexte
spécifique qui isole les variables locales et les calculs locaux
• A la sortie de cet appel, il faut se souvenir d’où venait l’appel
• Une fonction f peut en appeler une fonction g qui peut appeler une
fonction h. À chaque fois, il faut se rappeler où on va et d’où l’on
vient !
• Utilisation d’une structure de données de type pile

6 / 26
Exemple simplifié

Fonction f() : Vide


Variables x,y : Entiers
x ← 7
y ← g (x)
FinFonction

Fonction g(In Variable n : Entier) : Entier


Variable m : Entier
m ← n + 1
Retourne m
FinFonction

État de la pile à chaque instant de l’exécution de f

7 / 26

Pile d’exécution et récursivité

• Les appels récursifs de fonctions sont basés sur le même principe. Les
zones dans la piles identifient clairement les différents appels.
• Prévoir à l’avance la profondeur d’appels récursifs d’une fonction est
souvent complexe, voire impossible. La pile permet d’avoir une
allocation dynamique.
• Les langages de programmation acceptent pour la plupart les
fonctions récursives, mais ce n’est pas le cas de certains langages
anciens (COBOL, FORTRAN 77, BASIC, . . .).
• En programmation fonctionnelle (LISP, CAML, . . .) ou en
programmation logique (PROLOG), les programmes sont
majoritairement récursifs.

8 / 26
Limite de la pile d’exécution
Trop d’appels récursif ?
• Débordement possible de la pile
• Problème pratique d’allocation d’espace mémoire
• Pas un souci en algorithmique
• Pas un souci avec les machines actuelles
#include <stdio.h>
Appel recursif 0
Appel recursif 1
// Fonction trop recursive
Appel recursif 2
void recurs(int n){
Appel recursif 3
printf("Appel recursif %i\n",n
Appel recursif 4
);
Appel recursif 5
recurs(n+1);
...
}
Appel recursif 261848
Appel recursif 261849
// Utilisation minimale
Appel recursif 261850
int main(){
Appel recursif 261851
recurs(0);
Appel recursif 261852
return 0;
Erreur de segmentation (core dumped)
}
9 / 26

Récursif versus itératif


• Tout algorithme récursif possède une version itérative
factorielle Itérative
Fonction factorielleBis(In Variable n : Entier) : Entier
Variable res, i : Entier
res ← n
i ← n
Tant Que i>0
i ← i -1
res ← res * i
Retourne res
FinFonction
• De manière générale, on simule la pile d’appel (système) par une pile
gérée par le programmeur.
• L’exécution de la version récursive est généralement un (petit) peu
moins rapide que la version itérative, même si le nombre d’instructions
est le même.
• La compréhension d’un algorithme récursif est (souvent) plus facile
qu’un algorithme itératif.
10 / 26
Attention malgré tout !
• Un algorithme récursif peut engendrer un surcoût par rapport une
version itérative efficace.
Suite de Fibonacci

1 si n = 0 ou n = 1
f (n) =
f (n − 1) + f (n − 2) sinon

Fibonacci récursif
Fonction Fibo(In Variable n : Entier) : Entier
Si i<2 Alors
Retourne 1
Sinon
Retourne Fibo(n-1) + Fibo(n-2)
FinFonction
Exercices
1. Exécuter Fibo(4)
2. Evaluer la complexité
11 / 26

Version itérative plus efficace !


Fibonacci Itérative
Fonction FiboIt(In Variable n : Entier) : Entier
Variable res, resMoinsUn,resMoinsDeux,i : Entier
res ← 1
resMoinsUn ← 1
resMoinsDeux ← 1
Pour i allant de 2 à n par pas de 1
res ← resMoinsUn + resMoinsDeux
resMoinsDeux ← resMoinsUn // On décale
resMoinsUn ← res
Retourne res
FinFonction

Exercices
1. Exécuter Fibo(4)
2. Evaluer la complexité

12 / 26
Un tri récursif
Tri fusion
• Idée principale :
1. Diviser le tableau en deux parties égales
2. Trier chaque partie
3. Fusionner les deux parties

Algorithme
Fonction TriFusion(In-Out Variable tab : tableau d’entiers,
In Variable i,j : Entier) : Vide
Variable mil : Entier
Si i=j Alors
Retourne // Une case est triée!
Sinon

mil ← (i+j) div 2

TriFusion(tab, i, mil)
TriFusion(tab, mil+1, j)

Fusion(tab, i, mil, j)
FinFonction 13 / 26

Étape 3
Fonction Fusion(In-Out Variable tab : tableau d’entiers,
In Variable i,j,k : Entier) : Vide
// On suppose que 1 ≤ i ≤ j ≤ k ≤ taille(tab), que tab[i..j] et tab[(j+1)..k] sont
triés
Variable i0, j0, l0 : Entiers
Variable tab2 : tableau d’Entiers
alloue(tab2, k-i+1) // Pour avoir le bon nombre d’éléments

i0 ← i, j0 ← j+1, l0 ← 1

Tant Que i0 ≤ j et j0 ≤ k
Si tab[i0]<tab[j0] Alors
tab2[l0] ← tab[i0], i0 ← i0 +1
Sinon
tab2[l0] ← tab[j0], j0 ← j0 +1
l0 ← l0 +1
Tant Que i0 ≤ j
tab2[l0] ← tab[i0], i0 ← i0 +1, l0 ← l0 +1
Tant Que j0 ≤ k
tab2[l0] ← tab[j0], j0 ← j0 +1, l0 ← l0 +1
Pour l0 allant de 1 à k-i+1 par pas de 1
tab[i-1+l0] ← tab2[l0] 14 / 26
// à la fin, tab[i..k] est trié
FinFonction
Etude de la complexité du tri fusion
Fusion des deux listes triées
• Principe de la fermeture éclair
• Pour la création du tableau tab2, à chaque étape du Tant-que, on
ajoute un élément
• Exactement (k − i + 1) recopies
• La recopie de tab2 dans tab prend le même temps.
• Au total O(k − i) étapes, soit la longueur du tableau fusionné.

Tri Fusion

O(1) si n = 1
Fusion(n) =
2 * Fusion(n/2)+ O(n) sinon

Fusion(n) = O(n lg n)
15 / 26

Récursif = traduction directe des formules de récurrence


Fonction pgcd(a,b) : Entier
PGCD version 1 Si a=b Alors
pgcd(a,b)
 = Retourne a
 a si a = b Si a>b Alors
pgcd(a-b, b) si a > b Retourne pgcd(a-b, b)
Retourne pgcd(a, b-a)
pgcd(a, b-a) sinon

FinFonction

Fonction pgcd2(a,b) : Entier


PGCD version 2
Si a=b Alors
pgcd(a,b)
 = Retourne a
a si a = b Retourne pgcd2(b, a mod b)
pgcd(b, a mod b) sinon FinFonction

Correction
La correction de l’algorithme est directe si la formule est juste
mathématiquement !
16 / 26
Terminaison des algorithmes récursifs
Potentiel
• Partie complexe dans le cas général
• Il faut montrer qu’il existe une fonction positive entière, dépendant
des paramètres qui décroit à chaque appel : un potentiel !
factorielle : On prend comme fonction potentiel n. Cette fonction
est toujours positive ou nulle, entière, l’appel suivant est
n − 1 donc strictement décroissant
pgcd : a + b

Conjecture de Syracuse
• Suite de Syracuse pour un entier N > 0 : U0 = N et

Un /2 si Un est pair
Un+1 =
3Un + 1 sinon

• Programme associé facile à écrire en itératif et en récursif.


17 / 26
• Il n’existe pas (actuellement) de preuve que cette suite passe par 1
pour tout entier N.

Récursivité croisée
• Cas de deux fonctions s’interdéfinissant !

Fonction estPair(In Variable n : Entier) : Booléen


Si n<0 Alors
Retourne estPair(-n)
Si n=0 Alors
Retourne VRAI
Retourne estImpair(n-1)
FinFonction

Fonction estImpair(In Variable n : Entier) : Booléen


Si n<0 Alors
Retourne estImpair(-n)
Si n=0 Alors
Retourne FAUX
Retourne estPair(n-1)
FinFonction
18 / 26
Récursivité imbriquée

• Consiste à faire un appel récursif dans un autre appel récursif


• Exemple emblématique : la fonction d’Ackermann :

 n+1 si m = 0
A(m, n) = A(m − 1, 1) si n = 0 et m > 0
A(m − 1, A(m, n − 1)) sinon

Fonction Ackermann(In Variable m, n : Entier) : Entier


Si m=0 Alors
Retourne n+1
Si n=0 Alors
Retourne Ackermann(m-1, 1)
Retourne Ackermann(m-1, Ackermann(m,n-1))
FinFonction

19 / 26

Ackermann, une fonction indomptable

Quelques éléments
• A(O, n) = n + 1,A(1, n) = n + 2, A(2, n) = 2n + 3
• A(3, n) = 2n+3 − 3
• A(4, 0) = 13, A(4, 1) = 65533,
65536
A(4, 2) = 265536 − 3,A(4, 3) = 22 − 3 ...

Ackermann(3, 0) = 5
Ackermann(3, 1) = 13
Ackermann(0, 0) = 1 Ackermann(3, 2) = 29
Ackermann(1, 0) = 2
Ackermann(1, 1) = 3 ...
Ackermann(2, 1) = 5
Ackermann(2, 2) = 7 Ackermann(3, 12) = 32765
Ackermann(3, 2) = 29
Ackermann(3, 3) = 61 Ackermann(3, 13) = 65533
Erreur de segmentation (
Erreur de segmentation ( Ackermann(3, 14) =
core dumped)
core dumped) 131069
Erreur de segmentation (
core dumped)

20 / 26
Structures de données et récursivité

Tableaux
• Séparation d’un tableau en sous-tableaux
• Utilisation des indices pour faire la récursivité
• Exemple du tri fusion

Listes
• Vision récursive d’une liste :
• soit vide (∅)
• soit une cellule suivie d’une liste
• Structure le traitement naturellement de manière récursive
• On peut assimiler alors l.tête et l
• l.suivant est de type liste.

21 / 26

Exercice avec un tableau


Exercice
Écrire une fonction récursive qui détermine si une valeur est présente dans
un tableau. La fonction renverra l’indice du tableau où se trouve l’élément,
-1 si l’élément n’est pas présent.

Fonction RechRec(In Variable tab : tableau de X,


In Variable ind : Entier,
In Variable val : X) : Entier
// Cas où l’élément n’est pas dans le tableau
Si ind = taille(tab) + 1 Alors
Retourne -1
// Cas où l’élément est trouvé
Si tab[ind] = val Alors
Retourne ind
// Récursion
Retourne RechRec(tab, ind+1, val)
FinFonction

22 / 26
Exercice avec une liste
Exercice
Écrire une fonction récursive qui détermine si une valeur est présente dans
une liste. La fonction renverra une référence sur la cellule où se trouve
l’élément, ∅ si l’élément n’est pas présent.

Fonction RechListe(In Variable l : liste de X, In Variable val :


X) : référence sur Cellule
// Cas où l’élément n’est pas dans la liste
Si est-vide(l) Alors
Retourne ∅
// Cas où l’élément est trouvé
Si val-tête(l) = val Alors
Retourne l
// Récursion
Retourne RechListe(l.suivant, val)
FinFonction

23 / 26

Tapis de Sierpiński
• Ensemble fractal
• Défini récursivement à l’infini

24 / 26
Se prendre les pieds dans le tapis
Exercice
Sachant que le côté du carré initial est 1 et qu’on le divise en 9 parties
identiques, écrire une fonction qui prend en entrée un point du carré (x, y )
et indique s’il est noir.

Fonction NoirSierpinski1D(In Variable x : réel) : Booléen


Variable x’ : réel
Assert 0 ≤ x ≤ 1
Si 1/3 ≤ x et x ≤ 2/3 Alors
Retourne Vrai
x’ ← 3 ∗ x − b3 ∗ xc
Retourne NoirSierpinski1D(x’)
FinFonction

• Quelle supposition doit-on faire sur les réels ?


• Quelle est la propriété de la fonction récursive obtenue ?
• Comment palier ce problème ?
25 / 26

Et se relever

Fonction NoirBlancSierpinski1D(In Variable x : réel,


In Variable prof : Entier) : Booléen
Variable x’ : réel
Assert 0 ≤ x ≤ 1

Si 1/3 ≤ x et x ≤ 2/3 Alors


Retourne Vrai
Si prof = 0 Alors
Retourne Faux
x’ ← 3 ∗ x − b3 ∗ xc
Retourne NoirBlancSierpinski1D(x’, prof -1)
FinFonction

26 / 26

Vous aimerez peut-être aussi