Récursivité

Cette page contient un ensemble de fonctions qu’il est possible de programmer de manière fonctionnelle pure, en utilisant des algorithmes récursifs. A chaque fois, on insiste sur la description formelle de la fonction à représenter, on discute les problèmes de terminaison, et de complexité.

1ère partie : Calcul de puissance

Les deux définitions suivantes permettent de calculer la fonction $\mathrm{pow}(a,n) = a^n$  :

\[\begin{cases} \mathrm{pow}_1(a, 0) & = 1 \\ \mathrm{pow}_1(a, n) & = a * \mathrm{pow}_1(a, n-1) \quad \textrm{si}~ n \geq 1 \end{cases}\] \[\begin{cases} \mathrm{pow}_2(a, 0) & = 1 \\ \mathrm{pow}_2(a, 1) & = a \\ \mathrm{pow}_2(a, 2*n) & = \mathrm{pow}_2(a, n)^2 \quad \textrm{si}~ n \geq 1 \\ \mathrm{pow}_2(a, 2*n+1) & = a * \mathrm{pow}_2(a, n)^2 \quad \textrm{si}~ n \geq 1 \end{cases}\]

Écrire chacun de ces algorithmes en Python. Évaluer pour chacun leur complexité. Expliquer pourquoi le second algorithme nécessite plus de cas pour sa définition que le premier.

2ème partie : PGCD

Une façon de calculer le plus grand commun diviseur entre deux nombres consiste à implémenter l’algorithme d’Euclide, dont on fournit une définition ici :

\[\begin{cases} \mathrm{pgcd}(0, b) & = b \\ \mathrm{pgcd}(b, b) & = b \\ \mathrm{pgcd}(a, b) & = \mathrm{pgcd}(b, a) \quad \textrm{si}~ a > b \\ \mathrm{pgcd}(a, b) & = \mathrm{pgcd}(b \mod a, a) \quad \textrm{sinon} \end{cases}\]

Dessiner sur un quart de plan le chemin réalisé par le calcul de $\mathrm{pgcd}(7,5)$. Donner une borne supérieure de la complexité du calcul de $\mathrm{pgcd}(a,b)$. Écrire cet algorithme en Python, d’abord de manière récursive, et ensuite avec une boucle while.

Note : la complexité de cet algorithme est bien étudiée, mais ne correspond pas à une réponse simple. Cf. par exemple ce lien pour simplement se rendre compte de la difficulté de la question.

3ème partie : suite de Syracuse

La suite de Syracuse est une suite d’entiers naturels paramétrée par une valeur de départ donnée, qui peut être construite à l’aide de la fonction suivante :

\[\begin{cases} \mathrm{syra}(1) & = 1 \\ \mathrm{syra}(n) & = \mathrm{syra}(n/2) \quad \textrm{si}~ n ~\textrm{est pair et} \geq 1\\ \mathrm{syra}(n) & = \mathrm{syra}(3*n+1) \quad \textrm{sinon} \end{cases}\]

Cette suite est connue pour la fameuse conjecture de Syracuse qui lui est associée. En 2017, il a été vérifié que pour tout entier $n \leq 87×2^{60}$, $\mathrm{syra}(n) = 1$.

Écrire le code en Python calculant la fonction précédente. Écrire le code calculant la liste des valeurs atteintes lors du calcul de $\mathrm{syra}(n)$, aussi appelé $\mathrm{vol}(n)$. Par exemple, pour $n=12$ :

\[\mathrm{vol}(12) = [12, 6, 3, 10, 5, 16, 8, 4, 2, 1]\]

Plus difficileCalculer l’entier ayant le vol le plus long compris entre $1$ et un entier $n$ donné.

4ème partie : récursivité et listes

Les listes sont des types de données que l’on peut appeler inductifs, signifiant ainsi qu’ils peuvent être pensés d’une manière récursive. En effet, une liste l est :

  • soit la liste vide ([]),

  • soit composée d’un premier élément (la tête, l[0]), et d’une sous-liste de taille plus petite (la queue, l[1:]).

Écrire un code en Python recherchant de manière récursive si un élément appartient à une liste Python.

\[\begin{cases} \mathrm{search}([], x) & = False \\ \mathrm{search}(l, x) & = True \quad \textrm{si}~ l ~\textrm{est non vide et}~ x = l[0] \\ \mathrm{search}(l, x) & = \mathrm{search}(l[1:], x) \quad \textrm{sinon} \end{cases}\]

Comparer au code réalisant la même chose avec une boucle for.

Adapter ce code pour compter le nombre d’occurrences d’un élément apparaissant dans une liste, cela d’abord de manière récursive, puis avec une boucle for. Déterminer parmi les fonctions que vous venez d’écrire celles qui sont pures et celles qui font des effets de bord.