Chapitre 1 Techniques de programmation
3
ièmepartie : Récursivité
2
1.5 La récursivité
l
Il existe 2 types de solutions aux problèmes
–
Itératif
–
Récursif
l
La récursivité est une technique de traitement d’une tâche T par traitement d’une autre tâche T’
–
Semblable à l’approche descendante.
–
La différence: dans la récursivité la tâche T’ est de même nature que la tâche T
l
Les solutions récursives, lorsqu’elles sont bien structurées et modulaires, sont des moyens puissants et uniques pour résoudre les problèmes.
l
Point crucial: T’ ≡ T, mais T’ < T
l
Est-ce qu’une solution récursive est meilleur qu’une solution itérative?
NON, ce n’est pas toujours vrai!
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
3
1.5 La récursivité
Exemple:
Considérer le problème de recherche d’un mot dans un dictionnaire. Supposer qu’on voulait chercher le mot
“imagination”. Imaginer qu’on commence à regarder du début du dictionnaire et voir tous les mots dans l’ordre jusqu’à trouver le mot “ imagination ”.
⇒ ceci est précisément: La recherche séquentielle
Une solution rapide: (Binary search) (Recherche dichotomique ou binaire - qui est similaire à votre utilisation du dictionnaire)
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
4
1.5 La récursivité
l Même si vous pouvez écrire une solution itérative au problème, considérer la recherche dichotomique récursive du dictionnaire :
Search(dictionnaire, mot)
ifdictionnaire est réduit à une page thenbalayer la page pour mot else
ouvrir le dictionnaire à un point près du milieu, déterminer quelle partie du dictionnaire contient mot
ifmot est dans la première partie du dictionnaire thenSearch(première partie du dictionnaire, mot);
else
Search(seconde partie du dictionnaire, mot);
l La stratégie de recherche réduit le problème de recherche du mot dans le dictionnaire au problème de recherche du mot dans la moitié du dictionnaire.
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
5
quand il ne reste qu’une seule page. Le problème est suffisamment petit pour balayer cette page pour un mot. Ce cas spécial est appelé cas limite.
Recherche dans la première moitié du dictionnaire
Recherche dans la deuxième moitié du dictionnaire Recherche dans le dictionnaire
Le mot se trouve dans la première moitié du dictionnaire
Le mot se trouve dans la deuxième moitié du dictionnaire
6
1.5 La récursivité 1.5.1 Concepts clés
l Quatre questions sont à poser pour construire une solution récursive:
–
Comment peut-on définir un problème en terme de sous- problème de même nature?
–
Comment faire chaque appel récursif pour diminuer la taille du problème?
–
Quel instant du problème peut servir à un cas limite?
–
Comme la taille du problème diminue, peut-on atteindre le
cas limite?
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
7
1.5.2 Une fonction récursive
l Une définition itérative de la factorielle:
Fact(n) = n * (n - 1) * (n - 2) * ... * 1, pour n > 0 Fact(0) = 1.
Fact(n) est indéfini lorsque n < 0.
Ex : Si n = 4 → Fact(n) = 4*3*2*1 = 24
l Une définition récursive de la factorielle:
La relation de récurrence: Fact(n) = n * Fact(n - 1)
( )
>
−
×
= =
0 n si )
1 (
limite) (cas 0 n si 1
n Fact n n
Fact
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
8
1.5 La récursivité
1.5.2 Une fonction récursive
l Exemple
Calcul de Fact(4): 4 > 0 Fact(4) = 4 * Fact(3) Fact(3) = 3 * Fact(2) Fact(2) = 2 * Fact(1)
Fact(1) = 1 * Fact(0)⇒atteint le cas limite Fact(0) = 1, arrêt des appels récursifs
⇓
puisque Fact(0) = 1, alors Fact(1) = 1 * 1 = 1 puisque Fact(l) = 1, alors Fact(2) = 2 * 1 = 2 puisque Fact(2) = 2, alors Fact(3) = 3 * 2 = 6 puisque Fact(3) = 6, alors Fact(4) = 4 * 6 = 24
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
9
l Écriture du programme C qui implante cette définition
int Fact(int n)
/* calcule la factorielle d’un entier non négatif n.
précondition: n doit être >= 0
postcondition: retourne la factorielle de n, n est inchangé. */
{
int res;
if (n < 0)
erreur ("fact(n) / n < 0 ??");
if (n == 0) res = 1;
else
res = n * Fact(n - 1);
return(res);
} /* Fin de Fact */
Exemple d’appel de la fonction
printf (" Factorielle de %d = %d ", 3, Fact(3));
Méthode des boîtes
10
1.5 La récursivité
1.5.3 La méthode des boîtes
l
La méthode “ boîte ” est une manière systématique de tracer les actions d’une fonction ou procédure récursive. Cette méthode permet de comprendre le principe de la récursivité et sert à la conception et au déverminage. Pour ce faire, on suit les étapes suivantes:
1.
Étiqueter chaque appel récursif dans le corps du sous-programme récursif.
ex: dans le corps de Fact(n) on note: res = n * Fact(n - 1);
⇓ A
2.
Dans chaque boîte, mentionner l’environnement local du sous- programme associé;
3.
Dessiner une flèche étiquetée et qui initie un nouvel appel récursif
4.
Au retour, suivez les flèches inversées et remplacez les valeur dans les étiquettes mentionnées sur les flèches.
Méthode des boîtes
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
11
1.5.3 La méthode des boîtes
l Remarques
–
int Fact(n) permet de déboguer la fonction si n < 0 (car, sinon un appel récursif infini).
–
une solution itérative est plus directe.
int Produit = n;
for (i = n - 1; i > 1; i--) Produit *= i;
Méthode des boîtes
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
12
1.5 La récursivité
1.5.4 Une fonction récursive
l L’inverse d’une chaîne de caractères:
Affichez une chaîne de caractères dans l’ordre inverse.
Exemple : "Cat" donne « taC"
l Cas limite : Imprimer l’inverse de la chaîne vide.
l Problème : Comment peut-on inverser une chaîne
de n caractères, si on peut inverser une chaîne de
(n-1) caractères?
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
13
l
Supposons que la fonction reçoit deux paramètres: un tableau s contenant la chaîne à inverser et size spécifiant la longueur de la chaîne s
l
1
ièresolution
void Writebackward(char s[], int size) {
/* imprime une chaîne inversée
précondition: la chaîne est s[0.. size-1] où size >= 0;
postcondition: imprime la chaîne inversée, et ne change aucun paramètre
*/
if (size > 0) {
/* imprimer le dernier caractère */
printf("%c", s[size - 1]);
/* imprimer le reste de la chaîne inversée * Writebackward(s, size - 1) ... /* A */
}
/* size = 0, cas limite, on ne fait rien */
}/* Fin de Writebackward */
Méthode des boîtes
14
1.5 La récursivité
1.5.4 Une fonction récursive
l 2 ième solution
Enlever le 1
ercaractère!
Writebackward1(s)
{
si la chaîne est vide
alors rien --- ceci est le cas limite sinon
{
Writebackward1(s moins son premier caractère) print le 1er caractère de s
}
} /* Fin de Writebackward1 */
Méthode des boîtes
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
15
1.5.4 Une fonction récursive
l Amélioration de la lisibilité de la 1 ière solution
Writebackward(s) {
printf("inscrire writebackward avec la chaîne:, S);
if chaîne est nulle
rien --- ceci est une cas dégénéré else
{
printf("imprimer le dernier caractère de la chaîne: ",s);
Writebackward(s moins son dernier caractère);
... /* A */
}
printf("quitter writebackward avec la chaîne: ", s);
} /* Fin de Writebackward */
Méthode des boîtes
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
16
1.5 La récursivité
1.5.4 Une fonction récursive
l Amélioration de la lisibilité de la 2 ième solution
Writebackward1(s) {
printf("inscrire writebackward avec la chaîne ", s);
if chaîne nulle
rien --- ceci est un cas dégénéré else
{
Writebackward1(s moins son premier caractère);
--- /* A */
printf("imprimer le premier caractère de la chaîne: ", s);
print le premier caractère de s;
}
printf("quitter writebackward avec la chaîne: ", s);
} /* Fin de Writebackward1*/
Méthode des boîtes
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
17
l Exemple d’utilisation de messages d’écriture pour déboguer les sous-programmes récursifs
Voici un exemple indiquant où on doit placer les messages d’écriture dans les sous-programmes récursifs durant le débogage.
Void R(...)
{ /* ceci est le point A */
printf("appel procédure R du point A, ", ...);
R(...);
...
/* ceci est le point B */
printf("appel procédure R du point B, ", ...);
R(...);
...} /* Fin R */
En plus de ces messages d’écriture dans les appels récursifs, les messages d’écriture doivent afficher les valeurs de tous les paramètres et variables locales.
18
1.5 La récursivité
1.5.5 Une fonction récursive
l Élever un entier à une puissance entière:
On veut calculer x
navec x entier et n ≥ 0 l Une forme itérative:
int Pow1(int x, int n) {
/* fonction puissance --- solution itérative précondition: x est un entier, n ≥ 0 entier;
postcondition: retourne x élevé à la puissance n. */
int temp = 1, i;
for (i = 1, i <= n; i++) temp *= x;
return(temp);
} /* Fin de Pow1 */
Méthode des boîtes
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
19
1.5.5 Une fonction récursive
• Une solution récursive (1 ière version)
xn= x * xn-1, calcul de xnrécursivement::
où x0= 1
xn= x * xn-1, n > 0
étant donné n > 0 ⇒ le cas limite est toujours atteint.
int Pow2(int x, int n)
{ /* fonction de puissance --- version récursive précondition: x entier, n entier non négatif;
postcondition: retourne x élevé à la puissance n */
int res;
if (n == 0) res = 1;
else
res = x * Pow2(x, n - 1);
return(res);
} /* Fin de Pow2 */
Méthode des boîtes
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
20
1.5 La récursivité
1.5.5 Une fonction récursive
• Une solution récursive (2 ième version)
Pour calculer xn: (n + 1)appels de Pow2 Le nombre peut être réduit. En effet,
xn = (nn/m)men particulier m = 2 xn = (xn/2)2
⇒ le cas limite est atteint après peu d’appels récursifs.
int Pow3(int x, int n) {
int res;
if (n == 0) res = 1 else
if (odd(n))
res =
x
* sqr(Pow3(x, n / 2));else
res = sqr(Pow3(x, n / 2));
return(res);
}/* Fin de Pow3 */
Méthode des boîtes
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
21
2(x,10) Pow x*
= 2(x,11) Pow
2(x,11) Pow
)) 2 qr(Pow3(x, x*
= ) 5 Pow3(x,
) 5 Pow3(x, Pow2(x,9) s
* x
= Pow2(x,10)
2(x,10) Pow
) 2 , ( 2
) 9 , ( 2 x Pow
x Pow
Pow2(x,0)
* x
= Pow2(x,1)
2(x,1) Pow
1
= Pow2(x,0)
2(x,0) Pow
3(x,5)) qr(Pow x*
= 3(x,11) Pow
3(x,11) Pow
s
,1)) sqr(Pow3(x x
= Pow3(x,2) *
3(x,2) Pow
x,0)) sqr(Pow3(
= Pow3(x,1)
Pow3(x,1)
1
= Pow3(x,0)
3(x,0) Pow
…
22
1.5 La récursivité
1.5.6 Une fonction récursive
• La suite de Fibonacci
Les lapins se reproduisent très vite. Si les lapins ne meurent pas, leur population deviendra plus vite incontrôlable. Si on suppose les faits suivants:
- Les lapins ne meurent jamais.
- Un lapin atteint la maturité sexuel 2 mois après sa naissance, c.-à-d. au début du 3emois de vie.
- Les lapins naissent toujours en couple mâle-femelle. Au début de chaque mois, chaque paire mâle-femelle, sexuellement mûre, donne naissance exactement à une paire mâle-femelle
-
Hypothèse: Supposer qu’on commence avec une paire mâle- femelle nouvellement née.
-
Problème: Combien de paires on aura après six mois, en
comptant les naissances qui vont y être au début du 6e mois?
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
23
1.5.6 Une fonction récursive
l
Solution :
–
Mois 1 : 1 paire, l’originale.
–
Mois 2 : 1 paire, car non mûre encore.
–
Mois 3 : 2 paires; l’originale est mûre + la paire nouvellement née.
–
Mois 4 : 3 paires; l’originale a encore donné une naissance, mais la paire nouvellement née au début du 3e mois n’est pas mûre sexuellement.
–
Mois 5 : 5 paires; tous les lapins en vie au 3e mois (2 paires) sont mûrs sexuellement maintenant. Ajouter leur progéniture à ceux en vie au 4e mois (3).
–
Mois 6 : 8 paires; 3 sexuellement mûres + 5 en vie au mois précédent.
–
Par récurrence, vous pouvez observer que:
–
Rabbit(n) = Rabbit(n - 1) + Rabbit(n - 2)
–
donne le nombre de paires au mois n.
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
24
1.5 La récursivité
1.5.6 Une fonction récursive
l Sélection de cas limites:
–
2 cas limites:
Rabbit(1) = 1 Rabbit(2) = 1
l La définition récursive devient alors:
( )
>
− +
−
= ≤
2 n si ) 2 ( ) 1 (
2 n si 1
n
Fib
n
n Fib
Fib
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
25
l Un pseudo-code pour calculer Fib(n):
int Rabbit(int n)
/* retourne un entier positif */
{ /* hypothèse: n est un entier positif */
int res;
if (n ≤2) res = 1;
else
res = Rabbit(n - 1) + Rabbit(n - 2) return(res);
} /* Fin de Rabbit */
l Exercices:
– Implanter cette définition et calculer le nombre d’appels en variant n.
- Quelle remarque pouvez-vous en tirer?
- Donner le schéma de Fib(7)
- Donner le nombre d’appels engendré par Fib(10)
Remarque: La fonction Fib(n) est inefficace; pour ngrand est non utilisable.
Nous allons discuter à la fin de cette section une solution alternative.
26
1.5 La récursivité
1.5.7 Une fonction récursive
l Choisir k objets parmi n (la combinatoire)
int C(n, k)
/* retourne un entier positif */
{ /* hypothèse: n et k sont des entiers non négatifs */
int res;
if (n < 0 || k < 0) return -1;
if (k > n) res = 0;
else
if (k == 0) res = 1;
else
if (k == n) res = 1;
else
res = C(n - 1, k - 1) + C(n - 1, k);
return(res);
} /* Fin de C */
( )
>
<
<
− +
−
−
=
=
=
n k si 0
n k 0 si ) , 1 ( ) 1 , 1 (
n k si 1
0 k si 1
k
n
C
k
n
n C
C
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
27
1.5.8 Une fonction récursive
l Le plus grand élément d’un tableau
ifA possède un seul élément MxArray(A) est l’élément dans A else
ifA possède plus d’un 1 élément
MaxArray(A) = MaxArray(partie gauche de A, partie droite de A);
C’est une solution qui découle de l’approche diviser pour conquérir
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
28
1.5 La récursivité
1.5.8 Une fonction récursive
l Le plus grand élément d’un tableau
l Écrire le programme
MaxArray<1, 6, 8, 3>
MaxArray<1, 6> MaxArray<8, 3>
MaxArray<6> MaxArray<8>
MaxArray<1> MaxArray<3>
MaxArray<1, 6> = Max(1, 6) = 6
MaxArray<8, 3> = Max(8, 3) = 8
MaxArray<1, 6, 8, 3> = Max(6, 8) = 8
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
29
l Recherche dichotomique
Le tableau A doit être trié sinon la recherche dichotomique n’est pas applicable.
C’est-à-dire A[1]
≤A[2]
≤A[3] ...
≤A[n] où n est la taille du tableau
BinSearch(int A[], v) {
if Aest de taille 1
/* cas limite (insuffisant pour l'implantation) */
déterminer si cet élément égal à v else
{
trouver le milieu de A
déterminer quelle partie de A contient v if v est dans la première partie de A
BinSearch(première partie de A, v);
else
BinSearch(deuxième partie de A, v);
}
} /* Fin de BinSearch */
30
1.5 La récursivité
1.5.9 Une fonction récursive
l Recherche dichotomique
Nous avons deux cas limites
- first > last
⇒v
∉A
- A[mid] = v ⇒ v ∈ A
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
31
1.5.9 Une fonction récursive
l Recherche dichotomique
int A [n];
int BinSearch(int A[], int v, int first, int last){
int res, mid;
/* recherche l'élément vdans A[first....last]
précondition: 1 < first, last < n, and A [first] ≤A [first + 1] ≤A... ≤A [last].
postcondition: Si vest dans A, retourne res > 0 tel que A[res] = v;
sinon retourne res = -1 */
if (first > last)
res = -1; /* v ∉A */
else { /* invariant: si v ∈A: A [first] ≤v ≤A [last] */
mid = (first + last) / 2;
if (v == A[mid])
res = mid; /* v est trouvé dans A [mid] */
else
if (v < A[mid])
res = BinSearch(A, v, first, mid - 1); /* Partie inférieure */
else
res = BinSearch(A, v, mid + 1, last); /* Partie supérieure */
} return(res);
} /* Fin de BinSearch */
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
32
1.5 La récursivité
1.5.9 Une fonction récursive
l Recherche dichoto- mique
On cherche l’élément 6 dans A
1 5 9 12 15 21 29 31
0 1 2 3 4 5 6 7 first = 0
last = 7 mid = (7+0)/2 = 3
v < A[3]
1 5 9 12 15 21 29 31
0 1 2 3 4 5 6 7 first = 0
last = 2 mid = (2+0)/2 = 1
v > A[1]
1 5 9 12 15 21 29 31
0 1 2 3 4 5 6 7 first = 2
last = 2 mid = (2+2)/2 = 2
v < A[2]
1 5 9 12 15 21 29 31
0 1 2 3 4 5 6 7 first = 2
last = 1 first > last
v n'est pas dans A
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
33
l
La récursivité est un outil puissant pour la résolution de problèmes et même de problèmes complexes.
l
La récursivité peut clarifier des solutions complexes.
l
Les facteurs contribuant à l’inefficacité de la récursivité:
– le grand nombre d’appels de sous-programmes – l’inefficacité inhérente à certains algorithmes récursifs.
l
La récursivité n’est pas toujours appropriée, particulièrement quand une solution itérative claire et efficace existe.
Exemple: La version récursive de Fib(n) est
inefficace. Voir le diagramme deFib(7), Fib(10) ou Fib(100)?
34
1.5 La récursivité
1.5.10 Récursivité et efficacité
l Pour solutionner la suite de Fibonacci, on peut utiliser la relation de récurrence pour construire une solution itérative efficace.
int ItRabbit(int n) {
/* Version itérative au problème de lapins */
/* Initialisation des cas limites: R = R1 = Rabbit(2), R2 = Rabbit (1) */
R1 = 1; R2 = 1; R = 1;
for (i = 3; i <= n; i ++) {
R = R1 + R2; /* calcule la valeur de Rabbit(i)*/
R2 = R1; R1 = R; /*préparer les valeurs pour la prochaine itération*/
} return(R);
} /* Fin de ItRabbit */
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
35
1.5 La récursivité
1.5.10 Récursivité et efficacité
l
Une solution itérative s’avère plus efficace qu’une solution récursive.
l
La conversion de la forme récursive à une forme itérative est plus simple quand l’appel récursif est terminal.
l
Une observation sur la version itérative est que celle-ci calcule un nombre d’additions nettement inférieur à celui calculé par la version récursive.
– la version itérative de Fib(N) ⇒ N - 2 additions (N ≥ 2)
– la version récursive de Fib(N) ⇒ Fib(N) – 1 additions (N ≥ 2) Version itérative Version récursive Fib(30) = 832040 28 additions 832039 additions
0.1 seconde* 166 secondes*
* avec un IBM-XT l
On remarque que la solution itérative est nettement supérieure à celle de la
solution récursive. Mais, celle-ci reste élégante et parfois pas aussi inefficace que dans l’exemple de la suite de Fibonacci
(voir l’exemple sur les tours de Hanoï).GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
36
1.5 La récursivité
1.5.10 Récursivité et efficacité
l
On peut améliorer la solution récursive tel que le nombre d’additions se réduit à N-2 au lieu de Fib(N) – 1.
Voici une version optimisée récursive:
int Fib(int n) {
int res;
res = F (n, 1, 1) return(res);
}/* Fin de Fib */
int F(int n, int precedent, int courant) {
int res;
if (n ≤2) res = courant;
else
res = F(n - 1, courant, courant + precedent);
return(res);
} /* Fin de F */
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
37
l Conversion décimale à binaire
Une des tâches de l’ordinateur est la conversion de la représentation décimale (base 10) dans la représentation binaire (base 2).
Problème: Soit Nun entier non négatif, écrire son équivalent en binaire.
Ex: si Nest 25, la sortie est 11001
⇒ Une des approches existantes:
Ayant N = (N/2)* 2 + N % 2,le bit le plus à droite possède la valeur N%2 et les autres bits sont la représentation binaire de N/2
38
1.5 La récursivité
1.5.10 Récursivité et efficacité
l Conversion décimale à binaire
Par exemple: si N est 25, le bit le plus à droite est 25 % 2 = 1;
les bits restants représentent la valeur binaire de 25/2 = 12. On peut obtenir les bits comme suit :
l
On écrit ces bits du bas vers le haut, tel que le bit le plus à droite soit écrit le dernier: 11001.
25 % 2 = 1 25 / 2 = 12
12 % 2 = 0 12 / 2 = 6
6 % 2 = 0 6 / 2 = 3
3 % 2 = 1 3 / 2 = 1
1 % 2 = 1 1 / 2 = 0
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
39
1.5 La récursivité
1.5.10 Récursivité et efficacité
l
Cet exemple permet de conclure que tous les calculs doivent être faits avant d'imprimer le résultat.
WriteBinary(int n)
{
/* imprime la représentation binaire de n */
if (n
≤1)
printf("%d: ", n);
else {
WriteBinary(n / 2);
printf("%d", n % 2);
}
} /* Fin de WriteBinary */
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
40
1.5 La récursivité
1.5.10 Récursivité et efficacité
l
Trace pour n = 20
n n / 2 n % 2
20 10 0
10 5 0
5 2 1
2 1 0
1
1 0 1 0 0
GPA665 - Hiver 2002 © Mohamed Cheriet, ing., Ph. D.
41
l Graphique
Exercice Écrire le programme sous forme récursive qui réalise ce graphique O
n arrête de tracer lorsque r > 10.Dans votre librairie graphique vous avez la fonction DrawCircle(x, y, r)