Recurs I Vite
Recurs I Vite
Recurs I Vite
La récursivité
Éléments de cours, 98 exercices
Introduction et prérequis . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
La récursivité, c’est juste ça ? . . . . . . . . . . . . . . . . . . . . . . . . . 4
Une fonction récursive typique . . . . . . . . . . . . . . . . . . . . . . . 6
Pile des appels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
La somme des n premiers entiers . . . . . . . . . . . . . . . . . . . . . . 9
Limitation du nombre d’appels récursifs . . . . . . . . . . . . . . . . . . 10
Ecrire un algorithme récursif . . . . . . . . . . . . . . . . . . . . . . . . . 13
Résolution de problèmes par des algorithmes récursifs . . . . . . . . . . 15
GirafariG . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Récursivité illustrée par la conversion en base b . . . . . . . . . . . . . . 16
Récursivité illustrée par la recherche dichotomique . . . . . . . . . . . . 18
Récursivité inefficace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Qu’est-ce qu’on mange ? . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Arbre de Pythagore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Récursivité illustrée par le tri rapide . . . . . . . . . . . . . . . . . . . . . 35
Récursif ou itératif ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
TABLE DES MATIÈRES 2
Fonction ensureRange Concaténer des entiers consécutifs Recherche dichotomique sans slices
Somme des chiffres d’un entier Algorithme de remplissage Produit des chiffres d’un entier
Plus petit diviseur, factorisation Nombre d’occurrences dans une liste Suite de Prouhet en récursif
Anagrammes
Paniers de fruits
Cloner un dossier
TABLE DES MATIÈRES 4
Introduction et prérequis
Une fonction f est dite « récursive » si la fonction f, lors de son exécution, fait un appel à ... elle-
même. Oui, c’est un peu curieux, comme si les pompiers appelaient les pompiers ! Ce genre de
situation se rencontre parfois assez naturellement.
Ainsi, imaginons une fonction trier(a, b, c) qui renvoie la liste des trois entiers a, b et c
triés dans l’ordre croissant. Par exemple,
3 # cas où a <= b
4 if a <= b <= c:
5 return [a,b,c]
6 if a <= c <= b:
7 return [a,c,b]
8 if c <= a <= b:
9 return [c,a,b]
10
11 # L’autre cas
12 if b <= a <= c:
13 return [b,a,c]
14 if b <= c <= a:
15 return [b,c,a]
16 if c <= b <= a:
17 return [c,b,a]
18
L’exemple précédent n’est pas typique d’une fonction récursive car lorsque la fonction s’exécute,
il y a tout au plus un appel récursif. Voici un exemple plus représentatif.
On va écrire une fonction récursive afficherAnnees(debut, fin) qui affiche, une par une,
toutes les années depuis l’année debut jusqu’à l’année fin. Par exemple, l’appel afficherAnnees(2020, 202
affichera :
2020
2021
2022
2023
2024
Si debut ≤ fin, l’appel afficherAnnees(debut, fin) est équivalent aux deux actions sui-
vantes
— afficher l’année debut
— afficher toutes les années de l’année debut + 1 à l’année fin.
Or, la 2e action, par définition de la fonction afficherAnnees, peut être accomplie par un appel
afficherAnnees(debut + 1, fin). On a donc le code pour une fonction récursive :
6 afficherAnnees(2020, 2024)
La fonction afficherAnnees s’appelle elle-même ligne 4. La fonction affiche :
2020
2021
2022
2023
2024
Noter que lorsque la condition debut ≤ fin devient fausse, la fonction n’affiche rien et l’appel
est terminé.
7 afficherAnnees(2020,2022)
TABLE DES MATIÈRES 7
8 print("FIN")
J’ai rajouté un affichage après l’affichage (ligne 5) pour dire "Bye Bye" à l’année qu’on vient de
quitter. Il est intéressant d’observer l’affichage produit :
1 2020
2 2021
3 2022
4 Bye bye 2022
5 Bye bye 2021
6 Bye bye 2020
7 FIN
Python tutor
Pour bien comprendre le flux d’exécution de ce code, vous pouvez vous rendre sur le site Py-
thontutor. Il propose un outil en ligne permettant de visualiser l’exécution de votre code et l’état
de la mémoire pendant l’exécution, en particulier de conteneurs (listes, chaînes, etc). Cet outil
est en particulier très pratique pour pouvoir observer la pile des appels d’une fonction récursive.
Pour utiliser l’outil :
— se rendre sur cette page
— coller votre code dans la zone de texte
— cliquer sur le bouton Visualize Execution.
Apparaît alors une interface :
TABLE DES MATIÈRES 8
qui permet de progresser ligne à ligne (cf. la flèche rouge) dans l’exécution du code en appuyant
sur le bouton Forward. Au fur et à mesure que la récursion se déroule, la pile des appels aug-
mente ou diminue. On peut accéder au code que j’utilise ci-dessus dans Python tutor en cliquant
ICI.
Faisons une description du code ci-dessus mais le mieux est d’utiliser l’outil en ligne (cf. copie
d’écran ci-après). Pour une meilleure lisibilité, je reproduis le code :
7 afficherAnnees(2020,2022)
8 print("FIN")
9 2020
10 2021
11 2022
12 Bye bye 2022
13 Bye bye 2021
14 Bye bye 20
— Le premier appel afficherAnnees(2020, 2022) à la ligne 7 va provoquer l’affichage de
2020 (lignes 3 et 9 ci-dessus) et provoquer l’appel récursif afficherAnnees(2021, 2022)
— Les deux appels suivants afficherAnnees(2021 2022) et afficherAnnees(2022, 2022)
provoquent l’affichage de 2021 et 2022.
— Noter que le code à la ligne 5 n’a toujours pas été exécuté.
— Après l’appel afficherAnnees(2022, 2022) l’appel afficherAnnees(2023, 2022) est
lancé ; la pile des appels contients alors 4 appels qui s’empilent cf. image ci-dessous :
TABLE DES MATIÈRES 9
L’exemple précédent est typique de la récursivité mais l’exemple qui suit va bien mettre en
évidence ce qu’on appelle le cas de base et l’exemple nous montrera une difficulté que peut
poser la récursivité.
Soit la fonction f telle que, pour tout entier n ≥ 1 on ait f(n) = 1 + 2 + · · · + n, somme des
entiers entre 1 et n inclus. Par exemple,
— f(3) = 1 + 2 + 3 = 6,
— f(4) = 1 + 2 + 3 + 4 = 10,
— f(10) = 1 + 2 + 3 + · · · + 9 + 10 = 55.
On va implémenter la fonction f dans une version récursive. La construction algorithmique de
f est basée sur l’observation suivante : pour connaître, par exemple, la somme S des entiers de
1 à 4, il suffit de connaître la somme T des entiers de 1 à 3 et dans ce cas S = T + 4. Autrement
dit
TABLE DES MATIÈRES 10
f(4) = f(3) + 4
ce qui n’est qu’un cas particulier de la relation suivante, valable pour n ≥ 2 :
f(n) = f(n − 1) + n
Cette relation est appelée parfois relation de récurrence. Il est essentiel de noter que la relation
précédente ne s’applique pas si n = 1 puisque f(0) n’a pas été définie. Toutefois, f(1) existe et
vaut 1.
Ci-dessous, voici un code qui implémente la fonction f en Python en utilisant la relation ci-
dessus :
1 def f(n):
2 if n == 1:
3 return 1
4 else:
5 return f(n-1)+n
6
7 print(f(3))
8 print(f(4))
9 print(f(10))
10 6
11 10
12 55
— ligne 5 : la fonction f s’appelle elle-même.
— lignes 2-3 : c’est ce qu’on appelle le « cas de base » dans une récursion.
On notera que l’implémentation de f suit exactement la relation de récurrence ci-dessus (cf.
ligne 5 du code), y compris pour son cas d’exclusion (n = 1, cf. lignes 2-3).
Enfin, il aurait été possible de définir la somme pour le cas de base n = 0 en lui donnant 0 pour
valeur (c’est la valeur conventionnelle attribuée à une somme vide) et en définissant la relation
de récurrence f(n) = f(n − 1) + n pour n ≥ 1.
Il existe de nombreuses situations algorithmiques où il est efficace d’utiliser une fonction récur-
sive. Toutefois, les appels sont effectués sur une zone de mémoire plutôt limitée (dite stack alias
la pile) et donc la pile d’appels ne doit pas dépasser une certaine limite. Par défaut, elle est de
1000 dans l’implémentation courante de Python. Sinon, on obtient une erreur :
1 def f(n):
2 # Calcule 1+2+...+n
3 if n==0:
4 return 0
5 else:
6 return n+f(n-1)
7
TABLE DES MATIÈRES 11
8 print(f(1200))
qui affiche
1 RuntimeError: maximum recursion depth exceeded in comparison
Remède
Le plafond de 1000 peut varier selon les réglages de Python. Par exemple, avec la version Jupyter-
Notebook d’Anaconda, le plafond semble plutôt autour de 2000 appels récursifs.
En Python, il est possible de modifier le plafond du nombre d’appels :
1 import sys
2 sys.setrecursionlimit(1500)
3
4 def f(n):
5 # Calcule 1+2+...+n
6 if n==0:
7 return 0
8 else:
9 return n+f(n-1)
10
11 print(f(1200))
12 720600
— Lignes 1-2 : la taille de la pile est augmentée à 1500 appels.
Depuis la version 3.5 de Python, une exception de type RecursionError est déclenchée en cas
de dépassement de la limite.
Précaution
Toutefois, comme indiqué dans la documentation, pour des raisons de portabilité, on modifiera
avec prudence le plafond des appels. En outre, même si on le lève, un crash peut se produire :
import sys
N=20000
sys.setrecursionlimit(N)
def f(n):
# Calcule 1+2+...+n
if n==0:
return 0
else:
return n+f(n-1)
print(f(N))
Enfin, même en levant de façon raisonnable le plafond de la pile, le programme peut être plus
lent que son équivalent itératif :
import sys
N = 20000
sys.setrecursionlimit(N)
def it(n):
s = 0
for k in range(1, n + 1):
s += k
return s
def rec(n):
return n + rec(n - 1) if n else 0
test(N / 2, rec)
test(N / 2, it)
rec : 3.89s
it : 0.38s
Ici, le code récursif est 10 fois plus lent.
Autre langages
Le problème rencontré est dû à un débordement de la pile des appels (un stack overflow en
anglais) et se retrouve, à des degrés divers, dans de nombreux langages de programmation non
fonctionnels (C/C++, Java, Javascript, etc).
Par exemple, en Java, le code suivant
5 System.out.println(somme(12000));
6 }
7
12 }
13 }
fait un stack overflow alors qu’il y a moins de 12000 appels (comme en Python, on pourrait
augmenter la taille de la pile et améliorer le plafond).
En C++, le code
1 #include<iostream>
2 using namespace std;
3
11 int main()
12 {
13 cout<<somme(1000000000)<<endl;
14
15 return 0;
16 }
semble supporter un milliard d’appels (compilé avec -O2).
Pour un langage comme OCaml où la programmation récursive est courante, le stack overflow
se produit, sur mon système, autour de 263000 appels si on compile en bytecode et autour de
530000 si on compile en code natif :
1 let rec somme n =
2 if (n=1) then 1
3 else n + somme (n-1) ;;
4
7 $ ocaml s.ml
8 Stack overflow during evaluation (looping recursion?).
L’algorithme précédent montre bien les deux étapes d’un algorithme récursif. Pour écrire un
algorithme récursif résolvant un problème X appliqué à un objet N, on dégage les deux éléments
suivants :
— le sous-problème : on identifie le même problème que le problème X mais appliqué à un objet
M de « taille » inférieure et dont la résolution permet de résoudre le problème X appliqué à
l’objet N
— le cas de base : on isole le cas du problème X appliqué à un objet de taille telle que le problème
pour X ne peut être ramené à un sous problème ; il s’agit en quelque sorte d’un cas irréductible.
TABLE DES MATIÈRES 14
Par exemple, dans le problème de la somme précédente, le problème X est de calculer la somme S
des n premiers entiers et l’objet N est l’entier strictement positif n ; on se rend compte que pour
connaître S, il suffit de savoir résoudre le même problème mais pour un objet plus petit, à savoir
n − 1 puisque si l’on connaît la somme T des n − 1 premiers entiers alors S = T + n. Cependant,
la règle précédente ne s’applique que si on peut effectivement décomposer le problème ce qui
suppose que n vaut au moins 1 et ce qui fournit le cas de base.
Le problème de la terminaison de la récursion doit être examiné, même s’il est parfois délicat de
la prouver, elle nécessite souvent de raisonner par récurrence voire par induction.
Placement du cas de base
1 def f(n):
2 if n == 1:
3 return 1
4 else:
5 return f(n-1)+n
Chaque appel commence par le test de la condition de terminaison (ligne 2). Si n = 100, le test
va être négatif dans 99 cas et positif dans le dernier cas. Autant faire en sorte que le test soit
positif le plus souvent possible. D’où le code suvant :
def f(n):
if n != 1:
return f(n-1)+n
else:
return 1
voire les code plus simples suivants :
def f(n):
if n != 1:
return f(n-1)+n
return 1
ou encore
def f(n):
return f(n-1)+n if n != 1 else 1
En pratique, cela ne change pas les temps d’exécution mais c’est plus logique [la remarque sur
le placement du cas de base est suggérée ici].
Le saviez-vous ?
— En programmation CUDA, les fonctions qui s’exécutent sur le GPU (les kernels) ne peuvent
être récursives, comme indiqué dans le guide 2020 de programmation CUDA C++.
— Dans des langages compilés comme C/C++ ou Java, le compilateur peut optimiser certains
appels de fonctions en procédant à l’inlining du code de la fonction. Une fonction récursive
aura un inlining qui sera limité.
Certains problèmes sont définis de manière éminemment récursive ; voici quelques exemples :
— le tri rapide (quicksort)
— le tri fusion (mergesort)
— la recherche dichotomique dans une liste ordonnée
— l’évaluation des expressions algébriques
— tracé de certains fractals (courbes de Von Koch, éponge de Sierpinsky, etc)
— le parcours en profondeur dans un graphe
— l’algorithme de Karatsuba de multiplication de deux grands entiers
— les problèmes de convergence de suite du type xn+1 = f (xn ) comme la méthode de Newton.
Pour certains problèmes, qu’ils soient définis de manière récursive ou pas, l’algorithme de réso-
lution peut être implémenté en version récursive ou en version itérative. C’est par exemple le
cas :
— d’une recherche dichotomique,
— d’un parcours en profondeur,
— d’une conversion d’un entier en base 2,
— du calcul du pgcd par l’algorithme d’Euclide.
— de l’algorithme de transformée de Fourier rapide et discrète (en traitement du signal)
Le choix d’une version de l’algorithme plutôt que l’autre sera dicté par les facteurs suivants :
— la facilité de codage,
— les performances.
Dans certains cas, un algorithme récursif sera beaucoup plus concis que son équivalent itératif.
Enfin, itération et récursion ne sont pas antinomiques. Par exemple, de nombreux problèmes de
combinatoire et de dénombrement utilisent des appels récursifs dans des structures itératives,
cf. exercices.
GirafariG
Un palindrome est une chaîne de caractères qui est identique lue de gauche à droite ou de droite
à gauche. Par exemple, la chaîne GIRAFARIG est un palindrome : si on inverse le mot, il reste
identique.
Pour coder récursivement un test de palindrome (cf. dessin ci-dessous), il suffit de vérifier que
— les lettres aux extrémités sont les mêmes (les lettres en bleu sur la figure) ;
TABLE DES MATIÈRES 16
— le mot privé de ses deux extrémités est encore un palindrome (en orange sur le dessin), d’où
appel récursif.
Précaution : quand on vérifie que le mot privé de ses deux extrémités est encore un palindrome,
il faut faire attention à ce que le retrait des extrémités soit possible. Ce problème ne se pose que
si le mot a une lettre ou n’a aucune lettre. Dans ces cas, le mot est un palindrome, ce qui donne
le cas de base de la récursivité. Remarquons que si le mot a deux lettres, ce n’est pas un cas de
base car quand on lui retire ses extrémités, la chaîne devient vide et on tombe sur un cas de base.
On en déduit le code suivant :
1 def estPalindrome(mot):
2 if len(mot)<=1:
3 return True
4 return mot[0]==mot[-1] and estPalindrome(mot[1:-1])
5
6 print(estPalindrome("girafarig"))
7 print(estPalindrome("girafariga"))
— Ligne 4 : mot[1:-1] est un slice : c’est la chaîne mot amputée de son premier caractère (elle
commence à l’indice 1 et se termine juste avant l’indice -1, ce dernier indice référençant le
dernier caractère de la chaîne).
C’est anecdotique mais on peut même simplifier le code :
def estPalindrome(mot):
return not mot or mot[0]==mot[-1] and estPalindrome(mot[1:-1])
Soit à déterminer la liste des chiffres de la représentation en base b d’un entier n. Par exemple, si
n = 13 et b = 2 alors les chiffres de la représentation de n en base b sont les éléments de la liste
[1, 1, 0, 1] puisque 13 = 8 + 4 + 0 + 1. Autre exemple et qui permet de mieux comprendre : la
liste des chiffres en base 10 de l’entier 2038 est [2, 0, 3, 8].
Je précise qu’ici les chiffres sont vus comme entiers entre 0 et b − 1 et non comme des caractères.
On cherche donc à écrire une fonction récursive chiffres(n, b) qui renvoie la liste des chiffres
de n en base b. Pour cela essayons de voir comment on peut ramener la conversion de n en base
b à la conversion d’un autre nombre. Reprenons l’exemple n = 2038 et b = 10 et effectuons la
division entière de n par b, comme illustrée sur la figure :
TABLE DES MATIÈRES 17
On observe alors que le reste r n’est autre que le dernier chiffre de n et que le quotient q n’est
autre que l’entier n privé de son dernier chiffre. On peut donc reconstituer la liste des chiffres
de n à partir de celle de q et de celle de r. Dans notre exemple, et avec la syntaxe Python des
listes, on a [2, 0, 3, 8] = [2, 0, 3] + [8].
On en déduit le code (encore incomplet) suivant :
Etant donné une liste L d’objets triés par ordre croissant et un objet x, l’algorithme de re-
cherche dichotomique (binary search en anglais) est un algorithme efficace de recherche de la
présence de x dans la liste L.
Etude d’un exemple
En comparant avec 46 (dernier terme de la première liste), on voit que x est forcément dans la
première liste, disons M = [12, 31, 46].
On découpe à nouveau cette liste en les deux listes suivantes qui sont moitié moins grandes que
la liste M :
En comparant avec 12 (dernier terme de la première liste), on voit que x est forcément dans la
deuxième liste [31, 46].
On découpe à nouveau cette liste en deux :
[31] et [46]
En comparant avec 31 (dernier terme de la première liste), on voit que x est forcément dans la
deuxième liste [46]. Comme cette liste est de taille 1, il suffit de regarder si l’élément x = 42
est cet élément : ce n’est pas le cas, donc la liste initiale ne contient pas l’élément 42.
Principe de la recherche dichotomique
Le principe est le suivant : on découpe la liste en deux sous-listes de taille « environ » la moitié
de la liste initiale (le « environ » est précisé ci-dessous) et on identifie la sous-liste M susceptible
de contenir x et on recommence la même recherche dans cette nouvelle sous-liste.
Pour préciser le « environ » ci-dessus, si n est la taille de L le découpage par moitiés se fera
suivant les longueurs définies par les décompositions ci-dessous :
— si n = 2k est pair alors on décomposera en n = k + k
— si n = 2k + 1 est pair alors on décomposera en n = k + (k + 1)
et en particulier, on décide, conventionnellement, que la liste de gauche n’est jamais plus longue
que celle de droite.
Noter que si les indices de L commencent à 0 alors, avec les notations n = 2k ou n = 2k + 1
ci-dessus, la longueur de la première sous-liste M du découpage est toujours k et donc l’indice
i du dernier élément de M est k-1. Or, que n soit pair ou pas, k est le quotient de la division
entière de n par 2 et donc, i = n//2 - 1 où // est la division entière.
TABLE DES MATIÈRES 19
De l’étude ci-dessus de la recherche dichotomique d’un élément x dans une liste croissante L,
on peut déduire le code ci-dessous :
1 def dicho(L,x):
2 n=len(L)
3 if n>1:
4 p=n//2-1
5 if x <= L[p]:
6 return dicho(L[:p+1], x)
7 else:
8 return dicho(L[p+1:], x)
9
14 print(dicho(L, 42))
15 print(dicho(L, 80))
— Ligne 10 : cas où la liste L contient un seul élément.
— Lignes 3-8 : cas où la liste L contient plus d’un élément.
— Lignes 6 et 8 : on partage la liste en deux sous-listes, le première étant de longueur n//2.
— Ligne 4 : l’indice p est le dernier indice de la première moitié.
— Lignes 5-6 : si x <= L[p] alors x ne peut être que dans la première sous-liste, c’est-à-dire
L[:p+1] qui est la liste de tous les éléments de L d’indice 0 ≤ j < p + 1 autrement dit
0 ≤ j ≤ p. D’où un appel récursif pour rechercher x dans cette sous-liste.
— Lignes 7-8 : dans l’autre cas, on recherche récursivement x dans la 2e sous-liste.
Compléments
Il faudrait faire davantage de tests, en testant tous les entiers entre par exemple 0 et 100 :
1 def dicho(L,x):
2 n=len(L)
3 if n>1:
4 p=n//2-1
5 if x <= L[p]:
6 return dicho(L[:p+1], x)
7 else:
8 return dicho(L[p+1:], x)
9
14
15 for x in range(100):
TABLE DES MATIÈRES 20
16 wrong = dicho(L, x) != (x in L)
17 if wrong:
18 break
19 if not wrong:
20 print("TEST OK")
21 else:
22 print("TEST KO")
L’implémentation de dicho a juste pour objectif d’illustrer la récursivité appliquée à la dichoto-
mie. En pratique, ce n’est pas ainsi (en utilisant des slices) qu’une dichotomie est implémentée.
En outre, quand on fait une dichotomie, on souhaite pouvoir encadrer l’élément à chercher avec
les éléments de la liste.
En réalité, une recherche dichotomique est un algorithme tellement basique qu’il est implé-
menté dans la majorité des bibliothèques standard des langages. Pour Python, le module stan-
dard bisect implémente (en langage C) la recherche dichotomique dont on peut lire un code
source Python au lien suivant : bisect.py.
Récursivité inefficace
Chaque coefficient, dit coefficient binomial, s’obtient en faisant la somme des deux coefficients
qui sont au-dessus et à gauche, par exemple (ligne numérotée 7 dans la figure) : 35 = 20 + 15.
Chaque coefficient est indexé par son numéro de ligne et son numéro de colonne. L’usage est de
faire commencer les indices à 0. Par exemple, le coefficient 35 est à l’indice de ligne 7 et l’indice
de colonne 4.
On cherche à écrire une fonction pascal(n,p) qui renvoie le coefficient situé à la ligne d’in-
dice n et à la colonne d’indice p, par exemple n (dernière ligne), pascal(10, 6) = 210. Donc,
d’après la propriété du tableau de Pascal, on a la relation
pascal(n, p) = pascal(n − 1, p) + pascal(n − 1, p − 1)
TABLE DES MATIÈRES 21
Précision
Toutefois cette extension ne sera pas implémentée dans les codes ci-dessous.
L’algorithme itératif
1 def ligne_suivante(L):
2 LL=[]
3 LL.append(1)
4 n=len(L)
5 for k in range(1, n):
6 LL.append(L[k-1]+L[k])
7 LL.append(1)
8 return LL
9
10 def pascal_it(n,p):
11 L=[1, 1]
12 for i in range(0, n-1):
13 LL=ligne_suivante(L)
14 L=LL
15 return L[p]
16
17
18
19 pascal(1000,200)
— Lignes 1-8 : à partir d’une ligne du tableau de Pascal, on construit la suivante en appliquant
la propriété du tableau de Pascal (une ligne du tableau est une liste).
— Lignes 10-15 : on génère toutes les lignes du tableau de Pascal jusqu’à la ligne d’indice n et
on lit l’élément d’indice p de la liste.
— Ligne 17 : on obtient le résultat (une nombre de 216 chiffres) presque instantanément.
L’algorithme récursif
Revenons à la formule :
Elle donne aussi le schéma d’une fonction récursive pascal(n, p) pour calculer chaque coef-
ficient du tableau de Pascal :
1 def pascal(n, p):
2 # ! Code encore incomplet !
3 return pascal(n-1, p) + pascal (n-1, p-1)
Il faut simplement faire attention aux cas de base. La relation ci-dessus est vraie si le coeffi-
cient n’est pas à l’extrémité d’une ligne d’une tableau, ie si 0 < p < n. Dans ces autres cas,
pascal(n, p) vaut 1 [je rappelle que le cas p > n n’est pas implémenté].
TABLE DES MATIÈRES 22
6 print(pascal(10,6))
7 210
Simple ! n’est-ce pas ?
Pourtant, l’exécution va se montrer très lente. Le calcul de pascal(30,14) pourra mettre jus-
qu’à une minute :
1 from time import time
2
8 debut = time()
9 print(pascal(30,14))
10 duree=time()-debut
11 print(int(duree), "secondes")
12 145422675
13 61 secondes
ce qui est énorme pour un résultat aussi simple.
Recomptages multiples
Comment expliquer ce phénomène ? Il n’est pas dû à débordement de la pile d’appel car pascal(30,14)
engendre une pile d’au plus 30 appels, on est loin de la limite.
Pour comprendre, regardons comment s’effectue le calcul de, par exemple, pascal(10,6) comme
le montre le dessin ci-dessous :
TABLE DES MATIÈRES 23
8 cpt=0
9 print(pascal(30,14))
10 print(cpt, "appels")
11 145422675
12 290845349 appels
On voit qu’il y a eu presque 300 millions d’appels alors que pascal(30, 14) ne nécessite, en
théorie, que la connaissance de quelques dizaines de valeurs du tableau de Pascal !
En définitive, on se rend compte que notre algorithme a un problème de mémoire : il passe son
temps à calculer des valeurs qu’il a déjà calculées mais qu’il n’a pas « notées ». L’algorithme
itératif n’a pas ce problème, il retient chaque ligne du tableau de Pascal.
Complément : du poisson dans notre algorithme
11 memoire={}
12 print(pascal(30,14))
13 145422675
— Ligne 11 : on définit un dictionnaire memoire initialement vide.
— Ligne 8 : chaque fois que la fonction pascal(n, p) calcule le coefficient, le résultat est mé-
morisé dans le dictionnaire.
— Lignes 2-3 : chaque fois que la fonction pascal(n, p) est appelée, pour éviter un recalcul,
on commence par si le tuple (n, p) est dans le dictionnaire, et s’il y est, on récupère sans
recalcul la valeur pascal(n, p).
L’exécution de pascal(30,14) est alors instantanée et aussi rapide qu’avec la méthode itérative.
Le cas de la suite de Fibonacci
Pour illustrer l’inefficacité de certaines récursions, il est courant d’évoquer la suite de Fibonacci.
Il s’agit d’une suite d’entiers telle que les deux premiers termes sont 1 et encore 1 et, chaque
terme de la suite à partir du troisième s’obtient en faisant la somme des deux précédents.
Voici les 12 premiers termes de la suite de Fibonacci :
6 165580141
Toutefois, le calcul est très long, par exemple le calcul de f(41) peut nécessiter jusqu’à une
minute alors qu’une version itérative fournit instantanément le résultat. Le problème est exac-
tement le même que pour le triangle de Pascal : une croissance exponentielle du nombre d’appels
récursifs par défaut de mémorisation.
La différence avec le triangle de Pascal est qu’il est assez simple de modifier fibo pour garder
une fonction récursive mais qui soit efficace. Il suffit de donner un peu de mémoire à la fonction
en plaçant dans ses arguments deux termes consécutifs de la suite de Fibonacci. D’où le code :
TABLE DES MATIÈRES 25
6 print(fibo(41,0, 1))
7 165580141
qui calcule le n-ème terme de la suite et qui, cette fois, s’exécute instantanément.
Pour comprendre comment fonctionne ce code, il suffit d’observer l’évolution des arguments
lors des appels successifs :
7 fibo(12,0, 1)
0 1
1 1
1 2
2 3
3 5
5 8
8 13
13 21
21 34
34 55
55 89
89 144
144 233
On voit qu’il s’agit, à chaque appel, de deux termes consécutifs de la suite, si bien qu’on comprend
qu’au bout de n étapes, l’un d’entre eux soit le terme recherché.
Quelle est la liste de tous les repas possibles ? Pour cela, il suffit de choisir une entrée parmi 2,
puis pour chaque entrée, un plat principal parmi 3 (ce qui fait 6 choix possibles au total) et pour
chacun de ces choix, un des 3 desserts possibles ce qui fait au total 6 × 3 = 18 menus possibles.
Il s’agit mathématiquement de construire le produit cartésien E × P × D des trois ensembles
Bien sûr, il est possible d’imbriquer des boucles for pour répondre à la question initiale :
5 for e in entrees:
6 for p in plats:
7 for d in desserts:
8 print(e, p, d)
qui affiche
1 guacamole poulet mousse
2 guacamole poulet tarte
3 guacamole poulet glace
4 guacamole saumon mousse
5 guacamole saumon tarte
6 guacamole saumon glace
7 guacamole omelette mousse
8 guacamole omelette tarte
9 guacamole omelette glace
10 quiche poulet mousse
11 quiche poulet tarte
12 quiche poulet glace
13 quiche saumon mousse
14 quiche saumon tarte
15 quiche saumon glace
16 quiche omelette mousse
17 quiche omelette tarte
18 quiche omelette glace
Mais le code ne fonctionne que pour 3 étapes dans le repas et pas pour un nombre d’étapes
quelconque (s’il y a 10 étapes il faut récrire le code et 10 boucles imbriquées ...).
Fonction récursive
On va coder une fonction récursive menu(etapes) où etapes est une liste des p étapes d’un
repas, chaque étape étant elle-même une liste, par exemple
TABLE DES MATIÈRES 27
Il existe un algorithme récursif répondant à la question. Voyons sur un exemple comment se fait
l’appel récursif. Supposons que l’on dispose sous forme d’une liste M de tous les menus possibles
à partir des étapes entrées, plats et desserts et que l’on veuille compléter le menu avec le
choix d’un élément dans une liste de fruits. Pour composer la liste R de tous les repas possibles,
il faut et il suffit de considérer tous les menus m dans M et compléter la liste m par n’importe quel
fruit possible.
Pour le cas de base qui correspond à p = 1 alors la liste cherchée n’est autre que [L[0]] où L[0]
est l’unique liste.
Concernant le code, il est important de comprendre que la fonction menus(etapes) renvoie
une liste de tous les repas possibles et qu’un repas est lui-même une liste constituée d’un item
de chaque étape du repas. D’où le code suivant :
1 def menus(etapes):
2 n=len(etapes)
3 if n==1:
4 return [[choix] for choix in etapes[0]]
5
6 M=menus(etapes[:n-1])
7 E=etapes[-1]
8 R=[]
9 for m in M:
10 for choix in E:
11 R.append(m+[choix])
12 return R
13
14
15
23 for r in repas:
24 print(*r)
Rien à voir avec la récursivité, mais le module standard itertools permet de générer automa-
tiquement les menus possibles :
from itertools import product
Arbre de Pythagore
Un arbre (disons T ) de hauteur n > 0 sera construit à l’aide d’une fonction récursive arbre(A,B,C,D,n)
où ABCD représente le tronc de T (A en haut à gauche, B en haut à droite) et où n désigne la hau-
teur de T .
Pour simplifier le codage, on fournit le code suivant :
11 def droite(A,B):
12 a, b=A
13 c,d=B
14 M=(1/2*a + 1/2*b + 1/2*c - 1/2*d,
15 -1/2*a + 1/2*b + 1/2*c + 1/2*d)
16 S=(b + c - d, -a + c + d)
17 T=(-1/2*a + 1/2*b + 3/2*c - 1/2*d,
18 -1/2*a - 1/2*b + 1/2*c + 3/2*d)
19
20 return M, S, T
21
22
25 up()
26
27 O=(0,0)
28 A=(200,0)
29 B=(100,100)
30
31 begin_fill()
32 goto(O)
33 goto(A)
34 goto(B)
35 goto(O)
36 end_fill()
37
38 exitonclick()
On y lit deux fonctions gauche(A, B) et droite(A,B) qui à partir d’un tronc dont la base su-
périeure est le segment AB (A à gauche et B à droite) renvoie les coordonnées des trois nouveaux
points M, U et V de la branche gauche et de même pour la branche droite (M, S et T), cf. le dessin
ci-dessous :
Le code ci-dessus explique aussi la syntaxe Turtle pour remplir de noir un polygone (avec les
commandes begin_fill() et end_fill()).
Fonction remplir
5 def remplir(x,y,z,t):
6 begin_fill()
7 goto(x)
8 goto(y)
9 goto(z)
10 goto(t)
11 goto(x)
12 end_fill()
13
14 u=100
15 O=(0,0)
16 A=(u,0)
17 B=(u,-u)
18 C=(0,-u)
19
20 remplir(O,A,B,C)
21
22 exitonclick()
— Lignes 7-11 : on dessine le contour du carré
— Lignes 6 et 12 : on encadre le contour de deux instructions de remplissage.
La fonction récursive
Soit à dessiner un arbre (disons T ) de hauteur n ≥ 1, de tronc ABCD où A et B sont les points
de la partie supérieure du tronc, là où vont pousser les branches. Cet arbre va être dessiné par
une fonction récursive arbre(A, B, C, D, n).
Supposons n ≥ 2. La fonction doit
— dessiner le tronc ABCD
— faire pousseur deux arbres sur ce tronc, l’un à gauche (disons T1 ), l’autre à droite (disons T2 ).
TABLE DES MATIÈRES 32
Pour faire pousser T1 , la fonction arbre fera un appel récursif. De même pour T2 . Il reste à
déterminer les arguments des appels récursifs.
Pour construire T1 , il faut connaître son tronc AMUV. On connaît un sommet du tronc (le sommet
A qui est un paramètre). Les autres sommets seront obtenus avec une fonction gauche(A, B).
De même, le tronc BMST de T2 sera déterminé par une fonction droite(A, B).
Finalement, une fois les sommets des troncs trouvés, il faut :
— appeler arbre(V, U, n-1) pour dessiner les branches au-dessus du tronc AMUV
— appeler arbre(S, T, n-1) pour dessiner les branches au-dessus du tronc BMST.
On a écrit n-1 car, le tronc de T étant dessiné (hauteur 1), il reste des arbres T1 et T2 de longueur
n-1 à faire pousser.
Lorsque n = 1, arbre(A, B, C, D, n) dessinera juste le tronc ABCD.
D’où le code suivant :
1 def gauche(A, B):
2 a,b=A
3 c,d=B
4 M=(1/2*a + 1/2*b + 1/2*c - 1/2*d, -1/2*a + 1/2*b + 1/2*c + 1/2*d)
5 U=(a + b - d, -a + b + c)
6 V=(3/2*a + 1/2*b - 1/2*c - 1/2*d, -1/2*a + 3/2*b + 1/2*c - 1/2*d)
7 return M, U, V
8
9 def droite(A,B):
10 a,b=A
11 c,d=B
12 M=(1/2*a + 1/2*b + 1/2*c - 1/2*d, -1/2*a + 1/2*b + 1/2*c + 1/2*d)
13 S=(b + c - d, -a + c + d)
14 T=(-1/2*a + 1/2*b + 3/2*c - 1/2*d, -1/2*a - 1/2*b + 1/2*c + 3/2*d)
15 return M, S, T
16
17
22 def remplir(x,y,z,t):
23 begin_fill()
24 goto(x)
25 goto(y)
26 goto(z)
27 goto(t)
28 goto(x)
29 end_fill()
30
35 M,S,T=droite(A,B)
36 arbre(V, U, M, A, n-1)
37 arbre(S, T, B, M, n-1)
38
39 speed(0)
40 d=100
41 A=(0,0)
42 B=(d,0)
43 C=(d,-d)
44 D=(0,-d)
45 n=5
46
47 arbre(A, B, C, D, 0)
48
49 mainloop()
Compléments : version en couleur
On va rajouter de la couleur à l’arbre. Le tronc initial sera dessiné couleur chocolat et les feuilles
finales seront couleur verte. Les couleurs intermédiaires proviendront d’un gradient.
Le gradient est déterminé, composante RGB par composante RGB, de manière proportionnelle
entre la composante de la couleur initiale et la couleur finale.
1 from turtle import *
2
8 return [a]
9 h=(b-a)/(n-1)
10 r=[]
11 for k in range(n):
12 v=hex(round(a+k*h))[2:]
13 if len(v)==1:
14 v="0"+v
15 r.append(v)
16 return r
17
34 def droite(A,B):
35 a,b=A
36 c,d=B
37 M=(1/2*a + 1/2*b + 1/2*c - 1/2*d, -1/2*a + 1/2*b + 1/2*c + 1/2*d)
38 S=(b + c - d, -a + c + d)
39 T=(-1/2*a + 1/2*b + 3/2*c - 1/2*d, -1/2*a - 1/2*b + 1/2*c + 3/2*d)
40 return M, S, T
41
56 M,S,T=droite(A,B)
57 arbre(V, U, M, A, n-1, colors)
58 arbre(S, T, B, M, n-1, colors)
59
60 def go(n):
61 colors=gradient(CHOCO, GREEN, n)
62
63 hideturtle()
64 up()
65
66 speed(0)
67 d=100
68 A=(0,0)
69 B=(d,0)
70 C=(d,-d)
71 D=(0,-d)
72
73 arbre(A, B, C, D, 8, colors)
74
75 mainloop()
76
77 go(n=9)
Les algorithmes du type « diviser pour régner », comme la recherche dichotomique, utilisent
souvent, de par leur nature, des fonctions récursives. C’est le cas de l’algorithme du quicksort
(tri rapide en français).
Voici son principe de fonctionnement. On se donne une liste L, par exemple d’entiers, que l’on
veut trier dans l’ordre croissant, par exemple la liste suivante :
L = [21, 14, 19, 18, 36, 35, 21, 15, 16, 42, 13, 33, 12]
L = [14, 19, 18, 21, 15, 16, 13, 12, 21, 33, 42, 35, 36].
On constate que les éléments à gauche du pivot étant plus petits que le pivot, ils resteront du
côté gauche une fois la liste L triée. De même, pour les éléments à droite du pivot. Donc,
— le pivot est à sa place définitive
— si on trie le sous-tableau Lg à gauche du pivot et si on trie le sous-tableau Ld des éléments à
droite du pivot, alors la liste L sera complètement triée.
Toute la subtilité du quicksort réside dans le fait que Lg et Ld vont être triés par la méthode
du quicksort et donc avec deux appels récursifs (qui représentent les deux dernières étapes).
Restent à traiter les cas de base : ils apparaissent lorsque le partitionnement n’a pas de sens,
c’est-à-dire lorsque la liste L est vide.
Pour présenter l’algorithme et à des fins pédagogiques, le partitionnement se fera dans un ta-
bleau auxiliaire. En pratique, les implémentations n’utilisent jamais de tableaux auxiliaires car
cela nuirait gravement aux performances du tri, qui comme son nom l’indique, doit rester rapide.
D’où le code suivant et dont le seul but est d’illustrer la récursivité et pas d’implémenter de
manière efficace un quicksort :
1 def quicksort(L):
2 n=len(L)
3 if n==0:
4 return []
5 pivot=L[0]
6 gauche=[x for x in L[1:] if x <= pivot]
7 droite=[x for x in L[1:] if pivot < x]
8 tri_gauche = quicksort(gauche)
9 tri_droite = quicksort(droite)
10 return tri_gauche + [pivot] + tri_droite
11
12 L=[21, 14, 19, 18, 36, 35, 21, 15, 16, 42, 13, 33, 12]
13 print(quicksort(L))
14 print(L)
15 [12, 13, 14, 15, 16, 18, 19, 21, 21, 33, 35, 36, 42]
16 [21, 14, 19, 18, 36, 35, 21, 15, 16, 42, 13, 33, 12]
— Lignes 3-4 : le cas de base : la liste est vide. Si la liste contient un seul élément, elle est
partitionnée en deux listes entourant le pivot, ce qui ramène au cas de base.
TABLE DES MATIÈRES 37
Il est même possible d’écrire le corps de la fonction quicksort sur une seule ligne (logique) :
1 def quicksort(L):
2 return (quicksort([z for z in L if z<L[0]]) +
3 [z for z in L if z==L[0]] +
4 quicksort([z for z in L if z>L[0]])
5 if L else [])
6
7 L=[21, 14, 19, 18, 36, 35, 21, 15, 16, 42, 13, 33, 12]
8 print(quicksort(L))
9 [12, 13, 14, 15, 16, 18, 19, 21, 21, 33, 35, 36, 42]
Récursif ou itératif ?
Comment choisir entre une implémentation récursive et une implémentation itérative d’un
même algorithme ? La récursivité s’exprime souvent très simplement : un algorithme ayant une
définition récursive s’implémentera naturellement de façon récursive. C’est le cas par exemple
pour le tri rapide ou le tri fusion qui s’implémentent la plupart du temps récursivement.
Toutefois, dans des langages tels que C, C++, Python, Rust où un appel de fonction peut avoir un
coût non négligeable, la récursivité peut engendrer une pénalisation. Par ailleurs, la récursivité
peut entraîner une saturation de la pile. Donc, sauf contexte particulier, d’apprentissage par
exemple, on évitera d’utiliser un code récursif engendrant un nombre d’appels en O(n) où n est
la valeur traitée et on pourra envisager de l’utiliser si le nombre d’appels est un O(log n) ou si
on est certain de ne traiter que de petites instances ou si on dispose pas d’alternative itérative.
Typiquement, si on est soucieux de performance, on évitera de calculer un maximum ou une
somme récursivement.
De même, en théorie des graphes, on évitera de lancer récursivement un parcours en profondeur
(DFS) ; d’ailleurs, les DFS des bibliothèques comme
— NetworkX,
— Networkit,
— Scipy,
— Sagemath,
TABLE DES MATIÈRES 38
EXERCICES
6. Tours de Hanoï
On dispose de 3 tiges verticales A, B et C et de n ≥ 0 disques percés en leur centre, de diamètres
strictement croissants, empilés sur la tige A et disposés en sorte que chaque disque repose sur
un disque de diamètre strictement supérieur :
TABLE DES MATIÈRES 40
Le problème des tours de Hanoï est de translater cette pile sur la tige C
par une succession de déplacements de disques d’une tige à une autre (on a besoin de la tige B),
un seul disque à la fois, mais avec la contrainte qu’au cours des déplacements de disques, on ne
pose jamais un disque sur un disque de diamètre strictement inférieur.
Ecrire une fonction récursive hanoi(n, source, but, aux) qui prend en entrée
— le nombre n de disques à déplacer
— le nom de la tige où se trouvent les disques à déplacer (source)
— le nom de la tige de destination finale des n disques (but)
— le nom de la troisième tige et qui va servir de tige auxiliaire ou temporaire pour réaliser les
déplacements (aux).
TABLE DES MATIÈRES 41
La fonction hanoi ne renvoie rien mais affiche chaque déplacement de disque à effectuer (le nom
de la tige de départ et de la tige d’arrivée) afin de mouvoir tous les disques depuis leur position
initiale (source) à leur position finale (but).
Par exemple, si l’appel est hanoi(3, "A", "C", "B") alors le programme pourra afficher :
A C
A B
C B
A C
B A
B C
A C
ce qui signifie que
— le disque au sommet de la tige A est déplacé sur la tige C
— le disque au sommet de la tige A est déplacé sur la tige B
— le disque au sommet de la tige C est déplacé sur la tige B
— etc.
Le schéma ci-dessous aide à comprendre comment la fonction récursive doit être écrite :
Quelques explications au schéma : pour déplacer les 9 disques de la tige A vers la tige C, il y a
trois étapes :
— on déplace (par appel récursif) les 8 disques supérieurs (un disque de moins que ce qu’il y a
au départ) vers la tige provisoire B,
— on déplace le disque restant de la tige A vers sa position finale, la tige C,
TABLE DES MATIÈRES 42
Étant donné un entier naturel n ≥ 0 et un nombre complexe z écrit sous forme algébrique
z = x + iy où x, y ∈ R, on demande de calculer les parties réelle et imaginaire du nombre com-
plexe zn . On écrira une fonction récursive cpow(x, y, n) qui renverra les parties réelle et
imaginaire de (x + iy)n . Python permet de calculer avec de véritables nombres complexes mais
on utlisera pas cette possibilités, on travaillera uniquement avec des nombres réels.
Par exemple, l’appel cpow(-2, 1, 5) doit renvoyer le couple (38, 41) ce qui traduit que
(−2 + i)5 = 38 + 41i ce que l’on peut vérifier avec Python par le calcul suivant :
z = -2 + 1j
print(z**5)
(38+41j)
Pour construire la fonction récursive, on écrira tout simplement que zn−1 = zn−1 × z et le membre
de droite sera calculé en faisant le produit de deux nombres complexes.
9. Le premier chiffre
L’entier 2030 commence par 2, l’entier 1999 commence par 1, l’entier 42 commence par 4.
Ecrire une fonction récursive premier_chiffre(n) qui reçoit un entier n ≥ 0 et qui renvoie le
nombre valant le premier chiffre de l’écriture de n en base 10.
On appliquera la technique d’extraction employée dans le cours pour convertir un entier en base
b.
10. Somme des chiffres d’un entier
Ecrire une fonction récursive somme_chiffres(n) qui renvoie la somme des chiffres en base
10 de l’entier n ≥ 0.
Exemple de comportements :
0 → 0
1 → 1
8 → 8
42 → 6
2024 → 8
100000000000000000000 → 1
On décomposera n entre son chiffre des unités (n%10) et ses autres chiffres, obtenus par n//10,
cf. la technique employée dans le cours pour convertir un entier en base b.
TABLE DES MATIÈRES 43
1 1 1 1
Hn = + + + ···
1 2 3 n
Par exemple, le 3e terme de la série harmonique est H3 = 1 + 1/2 + 1/3 = 11/6. On remar-
quera que chaque terme de la suite s’obtient aisément à l’aide du précédent. Ecrire une fonction
récursive harm(n) qui renvoie la liste des n premiers termes de la série harmonique.
Par exemple harm(3)=[1, 3/2, 11/6].
Pour manipuler des fractions en Python, on utilisera le module fractions :
>>> from fractions import Fraction
>>> q = Fraction(11, 6)
>>> print(q)
11/6
>>>
Voici un exemple d’éxécution du code :
from fractions import Fraction
def harm(n):
# votre code ICI
H10 = harm(10)
print(*map(str, H10), sep=’, ’)
et qui affiche :
a 1 a−1 a−1
= + +
b b b + 1 b(b + 1)
On demande d’écrire une fonction récursive dev_egypt(a, b) qui renvoie une liste des déno-
minateurs des fractions du développement égyptien que fournit l’algorithme ci-dessus.
Par exemple, dev_egypt(3, 7) renvoie [7, 8, 9, 72, 56, 57, 3192] et on peut vérifier
que
3 1 1 1 1 1 1 1
= + + + + + +
7 7 8 9 72 56 57 3192
29
Cet exercice provient de HackerRank : Recursive Digit Sum.
La somme est également répertoriée sur le site EOIS sous la dénomination de Digital root.
L = [0, 5, 8, 6, 4, 7, 2]
M = [0, 5, 8, 9, 4, 7, 2, 4]
P = [0, 5, 8]
----------------
L = [0, 5, 8, 6, 4, 7, 2]
M = [0, 5, 8, 6, 4, 7, 2, 4]
P = [0, 5, 8, 6, 4, 7, 2]
----------------
L = [0, 5, 8, 6, 4, 7, 2]
M = [1, 5, 8, 6, 4, 7, 2, 4]
P = []
TABLE DES MATIÈRES 45
----------------
[1, 4, 2, 8, 5, 7, 1, 4, 2, 8, 5, 7, 1, 4, 2, 8, 5, 7, 1, 4]
Pour obtenir les décimales, on appliquera la méthode vue en classe de 6e, voir par exemple
Pratique avec DÉCIMALES pour vous rafraîchir la mémoire.
En déduire une fonction div_dec(a, b, n) où a, b, n > 0 sont des entiers qui renvoie la liste
L des chiffres du quotient de a par b avec n décimales. Par exemple, si a = 22 et b = 7 et n = 20
on obtiendra que L vaut :
[3, 1, 4, 2, 8, 5, 7, 1, 4, 2, 8, 5, 7, 1, 4, 2, 8, 5, 7, 1, 4]
Application
def list2digits(L):
return str(L[0]) + ’,’ + ’’.join(map(str,L[1:]))
L=[3, 1, 4, 2, 8, 5, 7, 1, 4, 2, 8, 5, 7, 1, 4, 2, 8, 5, 7, 1, 4]
print( list2digits(L))
3,14285714285714285714
1 Implémenter l’addition par une suite d’incrémentations (d’une unité). Ainsi calculer 5 + 3
s’obtient en incrémentant d’une unité la somme 5 + 2.
2 Implémenter la multiplication par une suite d’additions. Ainsi calculer 10 × 6 s’obtient en
additionnant 6 au produit 9 × 6.
3 Implémenter la puissance par une suite de multiplications. Ainsi, calculer 37 s’obtient en
multipliant 36 par 3.
1 Ecrire une fonction récursive power(a, n) qui renvoie an en appliquant la méthode square
and multiply qui consiste à ne faire que des successions d’élévation au carré ou de multi-
plication. Cette méthode consiste à remarquer que a2k = (ak )2 et a2k+1 = a(ak )2 .
Par exemple, pour calculer x = 1036 , on calculera y = 1018 puis y2 ; et pour calculer y = 1018 ,
on calculera z = 109 puis y = z2 ; pour calculer z = 109 , on remarquera que 9 = 2 × 4 + 1
et donc on calculera u = 104 et on obtiendra z = 10 × u2 ; et ainsi de suite.
2 Modifier le code de la fonction pour qu’on puisse connaître le nombre de produits effectués.
Combien faut-il de produits pour calculer 102048 ?
N=100_000_000
def jouer():
secret=randrange(N+1)
print(secret)
def disMoi(choix):
if secret < choix:
return -1
if secret > choix:
return 1
return 0
return disMoi
disMoi = jouer()
TABLE DES MATIÈRES 48
print(deviner(0, N))
Exemple de partie :
22595266
22595266
Cet exercice est directement inspiré du problème Leetcode : Guess Number Higher or Lower.
21. Etre une puissance de 2
Les puissances de 2 sont les entiers de la forme 2k où k ≥ 0 est un entier ; les premières puis-
sances de 2 sont :
1 2 4 8 16 32 64 128 256
Construire une fonction récursive estPuissance2(n) qui renvoie True si n (entier strictement
positif) est une puissance de 2 et False sinon. Par exemple estPuissance2(2048) vaut True et
estPuissance2(2024) vaut False
Indications pour construire la fonction récursive estPuissance2(n) :
— si n est impair et différent de 1 alors la fonction renvoie False
— si n est pair raisonner à l’aide de l’entier m tel que n = 2m.
1982816527257334209808869577157761718909124662429638965435953370389693781
1 Tout entier n > 1 admet un plus petit diviseur autre que 1 que j’appelerai « le plus petit
diviseur » de n. Par exemple,
— le plus petit diviseur de 42 est 2 ;
— le plus petit diviseur de 43 est 43 ;
— le plus petit diviseur de 49 est 7 ;
— le plus petit diviseur de 1000019 est 47.
Ecrire une fonction récursive plus_petit_diviseur(n, d) qui renvoie le plus petit divi-
seur de n et qui soit au moins égal à d. Par exemple, plus_petit_diviseur(45, 4) vaut
5.
Cette fonction résout la question du plus petit diviseur de n puisque celui-ci n’est autre que
plus_petit_diviseur(n, 2).
Dans un premier temps, écrire une fonction qui soit capable de traiter tous les entiers n
jusqu’à 1000. L’idée est qu’on teste tous les d possibles à partir de 2 et jusqu’à obtention.
Dans un 2e temps, pour pouvoir passer la barre des 1000 appels récursifs et pouvoir traiter
des entiers de l’ordre de 1 million, on utilisera les résultats suivants :
√
— le seul diviseur de n qui soit strictement supérieur à n est n lui même ;
— si le nombre n est impair, son plus petit diviseur est parmi 3, 5, 7, 9, 11, etc (cela va de
deux en deux à partir de 3).
Déterminer le plus petit diviseur de 2053351.
2 En déduire une fonction récursive factoriser(n) qui renvoie la liste des facteurs premiers
de n ≥ 2. Par exemple, factoriser(269500) est la liste [2, 2, 5, 5, 5, 7, 7, 11].
Factoriser
n=718512839393861635200000000000.
Pour résoudre facilement cette question, il importe de remarquer que le plus petit diviseur
de n est un facteur premier de n.
25. Répunits
Un entier positif u est dit un répunit si son écriture en base 10 n’est formée que de chiffres valant
1. Par exemple, cent onze est un répunit mais 42 n’est pas un répunit. Le nom répunit provient
de la contraction des mots anglais REPeat et UNIT.
1 Ecrire une fonction récursive est_repunit(u) qui teste si un entier positif u est un ré-
punit. Par exemple, est_repunit(0) et est_repunit(42) doivent renvoyer False et
est_repunit(111) doit renvoyer True
2 Ecrire une fonction repunit(n) qui renvoie le répunit ayant n chiffres. Par exemple, repunit(3)
doit renvoyer l’entier 111.
TABLE DES MATIÈRES 50
n=2020
it1=log2(n)
print(f"{it1:.2f}")
it2=log2(it1)
print(f"{it2:.2f}")
it3=log2(it2)
print(f"{it3:.2f}")
it4=log2(it3)
print(f"{it4:.2f}")
# FIN car résultat <= 1
10.98
3.46
1.79
0.84
Ecrire une implémentation récursive de la fonction logstar. Calculer logstar(n) pour n = 265537 .
1 Ecrire une fonction récursive nc(n) qui renvoie le nombre de chiffres (décimaux) de l’entier
n. Par exemple, nc(2020) vaut 4.
2 Si on écrit côte-à-côte tous les entiers entre 1 et n = 42, on obtient le très grand entier N
suivant :
123456789101112131415161718192021222324252627282930313233343536373839404142
On peut vérifier que N est un entier composé de p = 75 chiffres. Ce qui peut se calculer de
la manière suivante :
— pour les entiers entre 1 et 9 : 9 chiffres
— pour les entiers entre 10 et 42 : (42 − 10 + 1) × 2 = 66 chiffres
d’où le total de 9 + 66 = 75 chiffres.
Plus généralement, étant donné un entier n ≥ 1, on place côte-à-côte tous les entiers entre 1
et n ce qui construit un très grand entier N. Ecrire une fonction récursive concat_chiffres(n)
qui renvoie le nombre p de chiffres de N. Par exemple, concat_chiffres(2030) vaudra
TABLE DES MATIÈRES 51
7013. La fonction concat_chiffres(n) doit pouvoir s’exécuter pour des valeurs de n très
grandes ayant des dizaines de chiffres.
L’algorithme récursif pourra être basé sur le calcul utilisé dans l’exemple pour déterminer
concat_chiffres(42). On pourra écrire une fonction récursive auxiliaire f(k) qui calcule
le nombre de chiffres quand on concatène tous les entiers
— ayant exactement k chiffres (le nombre de chiffres du concaténé s’obtient facilement
puisque tous les entiers ont même nombre de chiffres)
— ayant strictement moins de k chiffres (le nombre de chiffres du concaténé s’obtient par
appell récursif)
Par exemple, on trouvera que f(1) = 9 ou f(2) = 189.
Cette famille de nombres est enregistrée dans la base de suites d’entiers OEIS.
La suite des longueurs est répertoriée sur le site de suites OEIS mais définie autrement.
Par exemple, le couple (2, 6) en colonne d’indice 2 et ligne d’indice 6 est numéroté 42. Au cas
où le schéma ci-dessus ne se suffirait pas à lui-même, la numéroration commence en (0, 0) et
s’effectue suivant des diagonales orientées sud-est vers nord-ouest. Arrivée sur la colonne la
plus à gauche, la numérotation se poursuit sur la diagonale montante suivante en commençant
par la ligne inférieure de la grille.
1 Ecrire une fonction récursive nro2point(nro) qui partant d’un numéro entier nro ≥ 0
renvoie les coordonnées du points numéroté par nro. Par exemple, nro2point(42) est le
couple (2,6).
2 Ecrire une fonction récursive point2nro(point) qui partant d’un point point = (x, y) ∈ N × N
renvoie le numéro de point. Par exemple, point2nro((2,6)) vaut 42.
Par exemple, le couple (6, 2) en colonne d’indice 6 et ligne d’indice 2 est numéroté 42. Lorqu’une
diagonale montante de la numérotation parvient à un point M de la colonne la plus à gauche,
elle se poursuit avec le point N de la grille qui est immédiatement au-dessus de M et continue
sur la diagonale descendante contenant N . Arrivée en un point P de la ligne inférieure de la
grille, la numérotation se poursuit avec le point Q immédiatement à droite de P et se poursuit
sur la diagonale montante contenant Q, et ainsi de suite.
Pour la suite, il pourra être utile de trouver un critère de distinction des diagonales montantes
par rapport aux diagonales descendantes.
1 Ecrire une fonction récursive nro2point(nro) qui partant d’un numéro entier nro ≥ 0
renvoie les coordonnées du points numéroté par nro. Par exemple, nro2point(42) est le
couple (6,2).
2 Ecrire une fonction récursive point2nro(point) qui partant d’un point point = (x, y) ∈ N × N
renvoie le numéro de point. Par exemple, point2nro((6,2)) vaut 42.
Chaque ligne et chaque colonne est indexée par un indice de range(n). L’algorithme de re-
cherche est le suivant : on suppose correctement placées k dames dans les k premières colonnes.
On cherche alors à placer une dame de plus dans la colonne suivante (d’indice k donc), en pro-
gressant suivant les indices croissants de lignes. Si k vaut n alors c’est qu’une position est trou-
vée.
On écrira une fonction récursive dames(cols, n) où cols est une liste des indices de lignes
où on a déjà placé sans collision des dames et n est la taille de l’échiquier.
Plus précisément, soit un appel dames(cols, n) où cols est une liste de k entiers entre 0 et n-1
représentant des indices de lignes de l’échiquier. Alors, cet appel affichera toutes les solutions au
problème telles que pour chaque indice de colonne j < k, la dame de cette colonne soit placée
à la ligne d’indice cols[j]. Par exemple, l’appel dames([3, 0, 4, 7], 8) devra afficher :
[3, 0, 4, 7, 1, 6, 2, 5]
[3, 0, 4, 7, 5, 2, 6, 1]
le premier tableau représentant la solution montrée en début d’énoncé.
La recherche sera lancée par un appel dames([], n) et affichera chacune des solutions (ou les
comptera).
On écrira une fonction de détection de collision. Plus précisément, on se donne deux colonnes
col1 et col2 telle que col1 < col2. On suppose que
— col1 contient exactement une dame à la ligne d’indice d1
— col2 contient exactement une dame à la ligne d’indice d2
Un appel collision(col1, d1, col2, d2) renvoie True si les deux dames sont en prises et
False sinon.
On écrira aussi une fonction is_valid(sol, i) qui, étant donnée
— une liste sol de k indices de lignes où sont placées des dames
— une dame placée dans la colonne d’indice k et à la ligne i
renvoie True si la dame en colonne d’indice k n’est pas en prise et False sinon.
TABLE DES MATIÈRES 55
Ci-dessous, le cavalier commencera le parcours depuis la case dans le coin en haut à gauche. On
numérotera les cases par un couple (i, j) indiquant la ligne et la colonne, i et j variant dans
range(8).
Ecrire un fonction récursive cavalier(P, L) où P est le plateau marqué par les cases qui ont
été occupées par le cavalier courant et L la liste courante des positions successives du chemin que
le cavalier a emprunté avant d’arriver au dernier élément de la liste L. Le plateau est représenté
par une liste de 8 listes, au départ remplies de huits zéros, sauf en position (0, 0) où la valeur
sera 1. Quand le cavalier occupera une case, elle sera marquée 1 dans le plateau P. La fonction
sera lancée par cavalier(P, L) où L = [(0,0)] et P initialisé comme indiqué ci-dessus.
L’algorithme consistera à
— considérer la dernière position (i, j) occupée par le cavalier, à savoir la dernière valeur de
L,
— examiner les cases à portée du cavalier depuis la case (i, j),
— sélectionner les cases non déjà occupées (on les connaît grâce au plateau P)
— relancer le parcours depuis chacune de ces cases avec un appel récursif (sans oublier de mettre
à jour P et L avant l’appel).
— replacer, après les appels récursifs, le plateau P et la liste L dans leur état antérieur (c’est le
backtrack).
Par commodité, on pourra écrire une fonction voisin(i, j) qui renvoie la liste des cases à
portée du cavalier placé en position (i, j).
On surveillera la longueur de la liste L et, une fois une solution trouvée, on interrompra la fonc-
tion cavalier en levant une exception du type StopIteration ou en appelant exit.sys(0)
car un simple return ne suffirait pas. La recherche pourra durer plusieurs dizaines de secondes.
On pourra afficher le parcours effectué, par exemple :
TABLE DES MATIÈRES 56
1 60 39 34 31 18 9 64
38 35 32 61 10 63 30 17
59 2 37 40 33 28 19 8
36 49 42 27 62 11 16 29
43 58 3 50 41 24 7 20
48 51 46 55 26 21 12 15
57 44 53 4 23 14 25 6
52 47 56 45 54 5 22 13
L=[
[’0’, ’0’, ’0’, ’1’, ’0’, ’0’, ’0’],
[’0’, ’0’, ’0’, ’1’, ’0’, ’0’, ’0’],
[’0’, ’0’, ’1’, ’0’, ’0’, ’0’, ’0’],
[’1’, ’1’, ’0’, ’0’, ’0’, ’1’, ’1’],
[’0’, ’0’, ’0’, ’0’, ’1’, ’0’, ’0’],
[’0’, ’0’, ’0’, ’1’, ’0’, ’0’, ’0’],
[’0’, ’0’, ’0’, ’1’, ’0’, ’0’, ’0’]]
et affiché de manière plus lisible comme ci-dessous :
0 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 1 0 0 0 0
1 1 0 0 0 1 1
0 0 0 0 1 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 0
où, dans la liste, le blanc est codé par le caractère ’0’ et le noir par le caractère ’1’.
On choisit un pixel du tableau L et on demande de colorer avec une 3e couleur toute la zone
connexe constituée du pixel choisi et des pixels voisins et ayant la même couleur que le pixel
choisi. La couleur de remplissage sera représentée par le caractère "X" et sera placé dans la liste
L. Le pixel sera choisi par ses indices de ligne et colonne (commençant à 0).
Avec le tableau de pixels de l’exemple ci-dessus, et le pixel en position (3, 3) la liste deviendra :
L=[
[’0’, ’0’, ’0’, ’1’, ’X’, ’X’, ’X’],
[’0’, ’0’, ’0’, ’1’, ’X’, ’X’, ’X’],
[’0’, ’0’, ’1’, ’X’, ’X’, ’X’, ’X’],
[’1’, ’1’, ’X’, ’X’, ’X’, ’1’, ’1’],
[’X’, ’X’, ’X’, ’X’, ’1’, ’0’, ’0’],
[’X’, ’X’, ’X’, ’1’, ’0’, ’0’, ’0’],
[’X’, ’X’, ’X’, ’1’, ’0’, ’0’, ’0’]]
ou de manière plus lisible :
TABLE DES MATIÈRES 57
0 0 0 1 X X X
0 0 0 1 X X X
0 0 1 X X X X
1 1 X X X 1 1
X X X X 1 0 0
X X X 1 0 0 0
X X X 1 0 0 0
ou de façon animée :
On écrira une fonction récursive remplir(L, pos, coul) où pos est la position courante d’es-
sai d’écriture et coul la couleur qu’il faut remplacer.
Pour plus d’information, on pourra consulter l’article Flood fill.
36. Suite de Syracuse en récursif
Soit la fonction f définie pour n > 0 entier par :
(
n/2 si n est pair
f(n) =
3n + 1 sinon
par exemple f(13) = 40 ou f(10) = 5.
La conjecture de Syracuse affirme que si on itère f depuis n’importe quel entier n > 0 alors,
on tombera forcément sur 1 ( comme c’est le cas dans l’exemple ci-dessus).
Écrire une fonction récursive saut(x) qui renvoie le nombre d’itérations pour arriver à 1
quand la suite commence avec x (par exemple, si x = 13 alors il y a donc 9 itérations pour
arriver à 1).
Tester et vérifier en écrivant une version non récursive de la fonction saut.
Écrire un algorithme récursif qui calcule le n-eme terme de la suite de Fibonacci. Calculer Fn
pour n valant 10 millions (compter de l’ordre de plusieurs dizaines de secondes, ça dépend de
la puissance du processeur) et vérifier que les 10 premiers chiffres sont 1129834378 (envoyer le
nombre dans un fichier pour pouvoir lire).
est un calculinefficace
d’un coefficient binomial puisque le calcul d’un simple coefficient bino-
30
mial tel que peut nécessiter plusieurs minutes.
15
A partir de la formule
n n n−1
= ,
p p p−1
n
écrire une fonction binomial(n, p) qui calcule le coefficient binomial et vérifier que le
p
calcul de binomial(30, 15) est alors instantané.
40. Coefficients binomiaux via le tableau de Pascal
On a vu dans le cours que la fonction récursive « évidente » basée sur la formule
TABLE DES MATIÈRES 59
n n−1 n−1
= +
p p−1 p−1
ne permet pas, telle quelle, un calcul efficace d’un coefficient binomial. Toutefois, il est facile
d’émuler récursivement la génération ligne par ligne du tableau de Pascal et obtenir ainsi un
calcul efficace de chaque coefficient binomial.
Il est inutile de créer la totalité du tableau 2D, une ligne « extensible » suffit. En effet, si on
dispose d’une liste comme L=[8, 4, 7, 2, 3] on peut construire la liste LL commençant par
1 suivi des sommes de termes voisins, dans notre cas LL=[1, 12, 11, 9, 5] et ce procédé
permet de générer une ligne du tableau de Pascal connaissant la précédente.
1 Ecrire une fonction (non récursive) next_line(L) qui partant d’une liste d’entiers, construit
la liste LL commençant par 1 et dont les termes suivants sont les sommes des paires de
termes successifs de L, comme dans l’exemple ci-dessus.
2 En déduire une fonction récursive nth_line(n) qui renvoie la ligne d’indice n du tableau
de Pascal
3 En déduire une fonction non récursive pascal(n, p) retournant le coefficient binomial
aux indices n et p.
Bien sûr, cette fonction pascal serait améliorable : ainsi, pascal(2000, 3) calcule la to-
talité de la ligne d’indice 1999 alors que seulement les 4 premiers termes sont nécessaires.
L = [42, 81, 31, 81, 12, 99, 81], k = 4 --> [31, 81]
L = [42, 81, 31, 81, 12, 99, 81], k = 2 --> [42, 81]
L = [42], k = 1 --> [42, 42]
L = [42, 42, 42, 42], k = 3 --> [42, 42]
Explication du premier exemple. Comme k = 4, on recherche le plus petit et le plus grand élément
des 4 premiers éléments de la liste L autrement dit, le plus petit et le plus grand élément de la
liste [42, 81, 31, 81]. Le plus petit élément est bien 31 et le plus grand est bien 81 d’où la
réponse [31, 81].
43. Somme récursive
Ecrire une fonction récursive somme(L, p) qui calcule la somme des p premiers éléments d’une
liste L d’entiers. Utiliser cette fonction pour calculer la somme des éléments d’une liste.
TABLE DES MATIÈRES 60
Écrire une fonction récursive f qui prend en paramètre une liste L d’entiers non nuls et un indice
i et qui renvoie True si les éléments de L à partir de l’indice i se suivent en alternant de signe
(et False sinon). Par exemple, f([−5, 2, −4, 7], 1) de même que f([3, −5, 2, −4, 7], 1) ainsi que
f([−2], 0) vaudront True tandis que f([−5, 2, 4, −7], 1) ou encore f([5, −3, −1], 0) vaudront
False. Pour tester si une liste complète L est en alternance de parité, on lancera f(L, 0).
46. Liste en alternance de zéro et un
Écrire une fonction récursive alterne(L) qui teste si une liste L est constituée de 0 et de 1 en
alternance.
Exemples de comportement :
[5, 5, 4, 11, 9, 1, 5]
On appelle inversion de L tout couple (a, b) de valeurs dans L telles que :
— a apparaisse à gauche de b
— a > b.
Par exemple, (4, 1) est un inversion de la liste précédente.
Ecrire une fonction récursive nb_inversions(L) qui renvoie le nombre d’inversions de la liste
L. Dans le cas de l’exemple ci-dessus, comme les inversions de L sont :
5 4
5 1
5 4
5 1
4 1
11 9
11 1
TABLE DES MATIÈRES 61
11 5
9 1
9 5
nb_inversions(L) doit renvoyer 10.
48. Eléments distincts d’une liste
On donne une liste L d’entiers et on veut écrire une fonction récursive distincts qui renvoie
la liste M formée des éléments distincts de la liste L. Exemples :
Étant donné deux listes L, M, l’une ou l’autre vide ou alors formées d’entiers, on peut comparer
lexicographiquement L et M : c’est comme l’ordre dans un dictionnaire, les lettres étant rem-
placées par les entiers de chaque liste, voir les exemples ci-dessous. En Python, la comparaison
L <= M utilise justement par défaut l’ordre lexicographique.
Ecrire une fonction récursive lexico(L, M) qui renvoie True si L <= M au sens lexicogra-
phique et False sinon. Evidemment, il ne faut pas utiliser d’opérateur de comparaison, tel que
<=, entre listes.
Les exemple ci-dessous permettent de bien comprendre comment fonctionne l’ordre lexicogra-
phique :
L = [1, 2, 1, 2]
M = [2, 2, 2]
True
---------------
L = [3, 2, 1]
M = [1, 1]
False
---------------
TABLE DES MATIÈRES 62
L = []
M = [1, 3, 3]
True
---------------
L = [1, 3, 2]
M = []
False
---------------
L = [1, 3, 1, 2]
M = [1, 3]
False
---------------
L = [3]
M = [3, 1, 3]
True
---------------
L = [1]
M = [2]
True
[[33, 17], [], [56, [15, 33, [21, 42]], 14], [22, 81]]
-> [33, 17, 56, 15, 33, 21, 42, 14, 22, 81]
42 = 3 x (5 + 3 x 3)
Ecrire une fonction récursive formule(n) qui renvoie une formule comme ci-dessus s’évaluant
à n et None si ce n’est pas possible. On essaiera d’éviter les parenthèses et les 1 inutiles.
En particulier, on pourra obtenir les formules suivantes :
2022 = 3 x (5 + 3 x (5 + 5 + 3 x (5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 + 5 +
2023 = 5 + 5 + 3 x (5 + 3 x (3 x (5 + 3 x (5 + 3 x (5 + 1)))))
2024 = 5 + 3 x (5 + 5 + 3 x (5 + 3 x (3 x (3 x (5 + 3)))))
2025 = impossible
2026 = 5 + 5 + 3 x (3 x (5 + 3 x (5 + 5 + 3 x (5 + 5 + 5 + 5 + 1))))
On utilisera la concaténation de chaînes :
a= "Py"
b= "Thon"
print(a + b)
qui affiche
PyThon
Cette question est proposée dans la 3e édition de Eloquent Javascript.
L=[-98, 35, 12, 7, 42, -2, -99, -87, -10, 42, -44]
Ecrire une fonction récursive qui renvoie une sous-liste de la liste formée d’éléments dont la
somme est nulle et qui renvoie None si une telle sous-liste n’existe pas. Dans le cas de la liste
TABLE DES MATIÈRES 64
0 8 4 12 2 10 6 14 1 9 5 13 3 11 7 15
loto(interdits, n)
et elle renverra un tirage de n numéros entre 1 et 49 et tels qu’aucun numéro ne soit dans la liste
des interdits. Le tirage du loto sera lancé par loto([], 6).
57. Chiffres parmi 1 ou 8
Ecrire une fonction récursive chiffres81(n) qui renvoie la liste de tous les entiers positifs
ayant n chiffres et dont tous les chiffres sont parmi 1 ou 8. Par exemple, pour n=3, la fonction
renvoie la liste suivante :
[111, 811, 181, 881, 118, 818, 188, 888]
On pourra utiliser que tout nombre à n chiffres s’écrit sous la forme n = 10d + u où d est un
nombre à n − 1 chiffres et u est le chiffre des unités ; par exemple, 818 = 81 × 10 + 8.
58. Liste de zéros ou uns
TABLE DES MATIÈRES 65
Ecrire une fonction récursive in01(L) qui teste si la liste L est constituée d’éléments parmi 0
ou 1. Exemples de comportements :
100 = 102 = 62 + 82 = 12 + 32 + 42 + 52 + 72 .
1 Soit la fonction merge(L, R) qui partant de deux listes triées renvoie une nouvelle liste
qui est la liste triée de la réunion des deux listes.
a Ecrire une version récursive de merge, sans aucune boucle.
b Ecrire une version itérative de merge.
2 a En déduire une fonction mergesort(T) qui implémente le tri fusion de la liste T.
b Avec la version récursive de merge, tester sur des listes aléatoires d’un peu moins de
1000 éléments.
TABLE DES MATIÈRES 67
c Avec la version itérative de merge, tester sur des listes aléatoires de plusieurs millions
d’éléments. Comparer avec la fonction sorted (s’attendre à un temps de 10 à 20 fois
plus rapide de sorted par par rapport à mergesort).
Ecrire une fonction récursive carre(cote) qui génère sous Turtle la figure ci-dessus. Cette
fonction se contentera de couper en deux le carré puis de couper une moitié en deux avant de
se rappeler elle-même.
Carrés emboîtés
Plus précisément, on demande d’écrire une fonction carres(x, y, cote, delta, n) qui affiche n
carrés emboîtés, le premier côté valant cote, la position initiale du coin supérieur droit étant au
point de coordonnées depart = (x, y) et la distance séparant les côtés de deux carrés voisins
étant de delta.
Le dessin ci-dessus a été réalisé sous Matplotlib pour les données suivantes
n=20
x=y=0
cote=100
delta=2
Le motif est une succession de carrés concentriques formés de disques de couleur alternative-
ment orange et bleue. Le carré extérieur est de côté n et formé de disques bleus (sur le dessin,
n = 12).
On écrira une fonction récursive carres(pos, n, r, color) où pos désigne la position du
coin en haut à gauche, n le côté du carré, r le rayon de chaque disque et color la couleur du
bord extérieur.
67. Triangles emboîtés
Construire un emboîtements de triangles comme dans la figure ci-dessous.
TABLE DES MATIÈRES 70
Le principe est simple : étant donné un triangle ABC, on construit un autre triangle U V W tel
que U soit sur le segment AB et tel que AU = AB/10 et de même pour les autres côtés :
puis pour n = 2 :
TABLE DES MATIÈRES 72
On demande d’écrire une fonction récursive diable(A, B, n) qui dessine l’escalier du diable
d’extrémités A et B et ayant n marches.
On utilisera la fonction auxiliaire plateau(A,B) qui renvoie la liste des points C et D.
a=(0,0)
b=(1,0)
c=(0.5, 3**.5/2)
T=[a, b, c]
plt.axis(’equal’)
plt.axis(’off’)
plt.show()
Ce triangle est un triangle de Sierpinki de profondeur 0.
On pourra utiliser la fonction suivante qui calcule les coordonnées du milieu d’un segment :
La génération initiale est constitué d’un seul cercle. On écrira une fonction récursive, n’utilisant
aucune boucle for et qui ne renvoie rien. On essayera de faire en sorte que chaque cercle soit
dessiné une fois et une seule.
72. Courbe de Koch
On se propose de dessiner la courbe K suivante :
TABLE DES MATIÈRES 78
On dispose des extrémités A et B d’un segment. On trace une ligne avec le procédé suivant :
— on coupe le segment AB aux tiers, on obtient les points C et D,
— on élève au milieu de CD le point M tel que le triangle CDM soit un triangle équilatéral direct,
— on trace la ligne ACMDB.
TABLE DES MATIÈRES 79
Ecrire une fonction récursive koch(A, B, n) qui partant d’un segment d’extrémités A et B
(connus par leurs coordonnées sous forme de listes de deux entiers) dessine la ligne brisée ci-
dessus après la n-ème itération. On utilisera la fonction equi(A,B) donnée dans le code equi.py
et qui renvoie la liste des trois sommets C, M et D (dans cet ordre, par leurs coordonnées) du
triangle équilatéral.
On remarquera que la courbe à l’étape n > 0 s’obtient en juxtaposant convenablement 4 exem-
plaires de la courbe de l’étape n − 1. Si n = 0, la fonction tracera juste le segment entre A et
B.
Ci-dessous, la courbe obtenue pour n = 2 puis n = 3 :
TABLE DES MATIÈRES 80
def equi(A,B):
a,b=A
c,d=B
C=(2/3*a + 1/3*c, 2/3*b + 1/3*d)
M=(1/6*sqrt(3)*b - 1/6*sqrt(3)*d + 1/2*a + 1/2*c,
-1/6*sqrt(3)*a + 1/6*sqrt(3)*c + 1/2*b + 1/2*d)
D=(1/3*a + 2/3*c, 1/3*b + 2/3*d)
return C,M,D
Plus précisément 1 , pour tout carré ABCD et pour chaque entier n ≥ 1, la courbe de Hilbert Hn
relative au carré ABCD est une ligne polygonale définie récursivement de la manière suivante :
en haut à gauche. Il pourra être utile que le retour de hilbert(a, b, c , d, n) soit la liste
[u, v] des deux extrémités de la ligne polygonale.
Il pourra être utile d’utiliser la fonction milieu suivante qui renvoie la liste des coordonnées du
milieu du segment d’extrémités a et b :
N=600
L=[randrange(N) for _ in range(N)]
tri(L, 0)
Vous observerez que le code est relativement lent (en fait, de complexité exponentielle). Essayez
d’améliorer la fonction ci-dessus. En effet, lorsqu’elle procède au 2e tri, la liste, à partir de son
deuxième élément, va être re-triée inutilement. Pour parer à cela, changez légèrement la signa-
ture de la fonction en
TABLE DES MATIÈRES 83
L = [81, 12]
M = [31]
S = L + M
print(S)
implémentée comme une liste d’éléments de A. Une permutation p étant donnée, raisonner en
fonction de l’élément en dernière position dans p.
On pourra utiliser que si on dispose de deux listes L et M alors L + M est une nouvelle liste formée
des éléments de L suivis des éléments de M.
78. Générer toutes les parties ayant un nombre d’éléments donné
Générer toutes les parties à p éléments d’un ensemble A donné de n éléments. Pour cela, on
implémentera l’algorithme récursif naïf suivant : on se donne x dans A et on observe qu’une
partie B de A ayant p élément est obtenue :
— soit à partir de x et d’une partie à p − 1 élements ne contenant pas x
— soit d’une partie à p élements ne contenant pas x.
Quelle est l’énorme limitation de cette méthode ?
Ecrire une fonction récursive groupes(n, k) qui renvoie la liste des regroupements. Les indi-
vidus seront numérotés de 0 à n − 1, et les groupes seront numérotés de 0 à k − 1. Un regrou-
pement sera vu comme une liste L de n entiers dans range(k), l’individu référencé par l’indice
i appartenant au groupe L[i].
On pourra raisonner comme suit. Un groupe s’obtient
— soit en sélectionnant un groupe de n − 1 individus répartis suivant k groupes, le dernier
individu étant placé dans un des k groupes ;
— soit en sélectionnant un groupe de n − 1 individus répartis suivant k − 1 groupes, le dernier
individu étant placé dans le groupe manquant.
Mathématiquement parlant, il s’agit de trouver toutes les surjectionsd’un
ensemble
à n éléments
n n
sur un ensemble à k éléments. Le nombre de telles surjections est k! où est un nombre
k k
de Stirling de seconde espèce.
Si Pierre est le fils de Paul, et si Paul est le frère de Jacqueline, qui est Pierre pour Jacqueline ?
et on souhaite effectuer des substitutions de prénoms, pour que la nouvelle phrase soit
Si Paul est le fils de Tom, et si Tom est le frère de Mathilde, qui est Paul pour Mathilde ?
— Jacqueline → Mathilde
Ecrire une fonction récursive subs(text, names, alt) qui effectue dans text la substitu-
tion des noms de names par les noms de alt. On pourra procéder comme suit, en utilisant les
méthodes de chaînes split et join :
— on découpe le texte (avec split) par le premier prénom,
— récursivement, on substitue dans les différents morceaux les autres prénoms,
— on recolle avec le prénom alternatif au premier en utilisant join.
Cet exercice trouve son origine dans une discussion sur le forum Python d’OpenClassrooms
83. Anagrammes
Ecrire une fonction récursive anagrammes(mot) qui génère tous les anagrammes d’un mot
donné. On pourra raisonner de la manière suivante :
— considérer la première lettre possible de l’anagramme (c’est une parmi l’ensemble des lettres
deux à deux distinctes du mot), disons init ;
— la suite de l’anagramme est formée d’un anagramme bâti sur le mot ayant les mêmes lettres
que le mot initial sauf que la lettre init est privé d’une occurrence.
On utilisera des ensembles et des slices.
84. Obtenir une somme à partir de termes donnés
Cet exercice est inspiré du problème Composition de crêpes donné en demi-finale de Prologin
2018.
On donne une liste L d’entiers strictement positifs et une valeur entière s > 0 et on demande
d’écrire une fonction récursive estSomme(L, s) qui renvoie True si on peut obtenir s comme
somme d’éléments de L et placés à des indices distincts.
Par exemple, si L=[8, 2, 1, 3] et
— si s=7 alors estSomme(L, s) renvoie False
— si s=11 alors estSomme(L, s) renvoie True.
Le code devrait pouvoir traiter en quelques secondes au plus une liste L aléatoire ayant jusqu’à
25 entiers.
85. Gain maximal aux dames
On donne le damier 10 x 10 d’une partie de dames en cours de déroulement :
TABLE DES MATIÈRES 87
Dames
Les pions noirs sont représentés par une croix (x) et les pions blancs par un cercle (o). Le damier
est donné sous forme de chaîne de caractères, par exemple la chaîne triple :
damier = """\
.x........
..o.....o.
.x.....x..
..o.o.o...
...x......
..o.o..x..
.o...o....
..o.o.o...
...o......
....x....."""
C’est aux noirs de jouer.
1 On demande de calculer le nombre maximal de pions blancs qu’un pion noir peut « manger »
(le pion est un pion simple, pas une « dame »). Pour cela on écrira une fonction récursive
manger(lig, col, damier) qui renverra le nombre maximal en question. Par exemple,
dans la partie ci-dessus, ce nombre est 6. L’idée est simple :
— on se donne un pion noir
— on regarde les sauts qu’il peut faire
— on lance récursivement la fonction pour chacun des sauts
— on prend le maximum des valeurs retournée
— on fait attention à remettre le plateau en état quand on termine la fonction récursive
— on refait la même pourchaque pion noir.
2 Modifier la fonction précédente pour qu’elle donne une succession maximale de prises. Par
exemple, dans l’exemple ci-dessus, et si on numérote à partir de 0 le coin en haut à gauche,
et qu’une position est donnée par un couple (ligne, colonne), un chemin qui donne 6
prises est :
TABLE DES MATIÈRES 88
print("----------")
L.extend(M)
print(L)
et qui affiche
[5, 0, 0]
[4, 1, 0]
[3, 2, 0]
[2, 3, 0]
[1, 4, 0]
[0, 5, 0]
[4, 0, 1]
[3, 1, 1]
[2, 2, 1]
[1, 3, 1]
[0, 4, 1]
[3, 0, 2]
TABLE DES MATIÈRES 91
[2, 1, 2]
[1, 2, 2]
[0, 3, 2]
[2, 0, 3]
[1, 1, 3]
[0, 2, 3]
[1, 0, 4]
[0, 1, 4]
[0, 0, 5]
1 Ecrire une fonction récursive regler(m) qui renvoie une manière de régler (n2, n5, n9)
le montant m avec un minimum de pièces ou qui renvoie None si le montant ne peut être
réglé. On raisonnera suivant qu’on utilise soit une pièce de 2U, soit une pièce de 5U, soit
une pièce de 9U. Vérifier que regler(62)=(1, 3, 5).
2 Associer à la fonction regler(m) un procédé de mémoïzation comme vu dans le codage
du tableau de Pascal pour traiter des valeurs bien plus grandes (de l’ordre de 1000). Vérifier
que regler(993)=(1, 2, 109).
[0, 0, 0, 0, 0, 0, 0]
On veut déterminer le nombre de façons différentes de ranger 3 boules dans les cases en sorte que
deux boules ne soient jamais dans des cases côte-à-côte ; un tel rangement sera dit confortable.
Une boule étant représentée par un 1, le rangement
[0, 1, 0, 1, 0, 1, 0]
ou encore le rangement
[1, 0, 0, 0, 1, 0, 1]
[1, 0, 0, 1, 1, 0, 0]
TABLE DES MATIÈRES 92
ne l’est pas car la 4e et la 5e cases sont des cases voisines et occupées par des boules.
Plus généralement, on cherche à déterminer le nombre de rangements confortables de k boules
dans une succession de n cases vides, autrement dit, les rangements où deux boules ne sont
jamais placées dans des cases immédiatement voisines.
Résoudre ce problème en définissant une fonction récursive f(n, k) qui renvoie le nombre de
rangements confortables. On raisonnera en considérant deux cas :
— ou bien la dernière case est vide et il suffit de placer les k boules dans les n − 1 premiers
emplacements
— ou bien la dernière case est occupée par une boule et alors, il suffit de placer les k − 1 autres
boules dans les n − 2 premiers emplacements.
Vous penserez à traiter au début du code de votre fonction récursive tous les cas d’exclusion du
raisonnement ci-dessus.
Votre code doit pouvoir calculer en quelques secondes f(n, k) pour un entier n ≤ 35. Voici
quelques exemples d’appels de la fonction f :
— f(4, 3) vaut 0 (il n’y a aucun rangement valide possible) ;
— f(2, 0) vaut 1 (seul rangement confortable : [[0, 0]])
— f(2, 1) vaut 2, les rangements confortables sont [[1, 0], [0, 1]]
— f(7, 3) vaut 10, ci-dessous tous les rangements confortables possibles :
[1, 0, 1, 0, 1, 0, 0] [1, 0, 1, 0, 0, 1, 0]
[1, 0, 0, 1, 0, 1, 0] [0, 1, 0, 1, 0, 1, 0]
[1, 0, 1, 0, 0, 0, 1] [1, 0, 0, 1, 0, 0, 1]
[0, 1, 0, 1, 0, 0, 1] [1, 0, 0, 0, 1, 0, 1]
[0, 1, 0, 0, 1, 0, 1] [0, 0, 1, 0, 1, 0, 1]
Modifier la fonction récursive de comptage pour qu’elle renvoie la liste de tous les rangements
confortables.
91. Équipe minimale ayant tous les talents
On dispose d’un ensemble E de personnes 1, 2, . . . , n. On considère par ailleurs un ensemble A de
p aptitudes, disons A1 , A2 , . . . , Ap . Chaque personne de E possède un certain nombre d’aptitudes
parmi les aptitudes de l’ensemble A. La totalité des aptitudes est couverte par l’ensemble des
personnes dans E. Ecrire une fonction (récursive) trouver_equipe qui détermine une équipe
d’effectif minimal formée d’individus de E réunissant toutes les aptitudes de la liste A.
Par exemple, voici pour n = 9 et p = 10, une répartition d’aptitudes notées, pour alléger, A, B, . . . , I, J :
1 ACIJ
2 EFHJ
3 BF
4 C
5 AEG
6 C
7 BCDFJ
8 ABE
9 B
Alors, une équipe d’effectif minimal ayant toutes les aptitudes contient au moins 4 personnes,
par exemple :
TABLE DES MATIÈRES 93
1 ACIJ
2 EFHJ
5 AEG
7 BCDFJ
Ce problème est celui de la couverture minimale par des ensembles : Wikipedia.
Une fois la fonction écrite, on utilisera la fonction pour déterminer la présence d’un entier pair
dans une liste d’entiers positifs et la valeur de celui présent au plus petit indice si un tel entier
existe dans la liste.
95. Pas d’impair (version récursive)
Soit une liste L formée d’entiers et i ≥ 0 un indice valide de cette liste. Écrire, une fonction
récursive queDesPairs(L, i) et renvoyant True si la liste L ne contient que des entiers pairs
entre les indices 0 et i (indices inclus) et False sinon
Par exemple, si L = [82, 32, 48, 81, 42] alors queDesPairs(L, 2) vaut True tandis que
queDesPairs(L, 4) vaut False à cause de L[3] = 81 qui est impair.
Étant donné deux chaînes de caractères s et t, supposées de même longueur, on appelle distance
de Hamming entre s et t, le nombre de positions où les chaînes ont des caractères différents.
Par exemple, si s et t sont les chaînes
s = "pointes"
t = "voisins"
alors, la distance de Hamming entre ces deux chaînes est 4.
Ecrire une fonction récursive hamming(s, t, k) qui renvoie la distance de Hamming entre les
deux chaînes de caractères formée des k premiers caractères de s et des k premiers caractères
de t où k est un entier positif ou nul, et s et t deux chaînes de même longueur. Par exemple, si
s et t sont les chaînes ci-dessus, alors hamming(s, t, 4) = 2 et hamming(s, t, 6) = 4
Pour finir, écrire un appel qui calcule la distance de Hamming entre deux chaînes de même
longueur.
path="toto.txt"
# teste si un item est un fichier ou pas
# Adresse relative à l’appel
print(os.path.isfile(path))
# Crée un répertoire
my_folder="./mes_fichiers/tres_important"
os.mkdir(my_folder)
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: