Exemples de fonctions récursives

I/ Exemples historiques

1) Ackermann

En 1927, Gabriel Sudan a inventé une fonction récursive à deux variables entières, pour répondre à une question mathématique de David Hilbert.

L'année suivante, Wilhelm Ackermann a publié une fonction similaire mais avec trois variables. Ackermann semble avoir créé cette fonction en 1926.

Par la suite, la « fonction d'Ackermann » a été simplifiée en une fonction à deux variables, que voici :

def A(m,n):
	assert m==int(m) and n==int(n) and m>=0 and n>=0
	if m==0:
		return n+1
	elif n==0:
		return A(m-1,1)
	else:
		return A(m-1,A(m,n-1))

Pour des petites valeurs de m et n, le nombre d'appels récursifs de A(m,n) est énorme et le calcul de A(m,n) peut prendre un temps rédhibitoire. Par exemple en calculant A(4,3) on a typiquement

RuntimeError: maximum recursion depth exceeded while calling a Python object

2) McCarthy

C'est en 1970 que John McCarthy a publié sa fonction 91, dont voici la version Python :

def M91(n):
    assert type(n)==int and n>=0
    if n>100:
        return n-10
    else:
        return M91(M91(n+11))

La représentation graphique de cette suite laisse conjecturer que pour n ≤ 100, M91(n)==91 (d'où le nom de la fonction) :

McCarthy a inventé cette fonction pour lancer un défi : prouver que la fonction est constante pour n ≤ 101. La suite de McCarthy est A103847.

II/ Douglas Hofstadter

Douglas Hofstadter, dans son livre, a étudié plusieurs fonctions récursives nommées G, H, F et M, et finalement Q.

1) La fonction G

def G(n):
	assert n==int(n) and n>=0
	if n==0:
		return 0
	else:
		return n-G(G(n-1))

Hofstadter trouve un moyen de dessiner la fonction G sous la forme d'un arbre : pour toute valeur de n, on relie les nœuds n et G(n) par une arête (on dit que n est le parent de G(n)) :

%3 1 1 2 2 2--1 3 3 3--2 4 4 4--3 5 5 5--3 6 6 6--4 7 7 7--4 8 8 8--5 9 9 9--6 10 10 10--6 11 11 11--7 12 12 12--8 13 13 13--8 14 14 14--9 15 15 15--9 16 16 16--10 17 17 17--11 18 18 18--11 19 19 19--12 20 20 20--12 21 21 21--13

La fonction G est A005206. Le quotient de deux termes successifs de la suite tend vers la solution positive de l'équation x²+x-1=0 (environ 0,618).

2) La fonction H

def H(n):
	assert n==int(n) and n>=0
	if n==0:
		return 0
	else:
		return n-H(H(H(n-1)))

L'arbre de H a aussi une allure fractale :

%3 1 1 2 2 2--1 3 3 3--2 4 4 4--3 5 5 5--4 6 6 6--4 7 7 7--5 8 8 8--5 9 9 9--6 10 10 10--7 11 11 11--7 12 12 12--8 13 13 13--9 14 14 14--10 15 15 15--10 16 16 16--11 17 17 17--12 18 18 18--13 19 19 19--13 20 20 20--14 21 21 21--14 22 22 22--15 23 23 23--16 24 24 24--17 25 25 25--17 26 26 26--18 27 27 27--18 28 28 28--19

La fonction H est A005374. Le quotient de deux termes successifs de la suite tend vers la solution de l'équation x³+x-1=0 (environ 0,682).

3) Les fonctions F et M

F comme femelle et M comme mâle. Ces deux fonctions font l'objet d'une récurrence croisée.

a) La fonction F

def F(n):
	assert n==int(n) and n>=0
	if n==0:
		return 1
	else:
		return n-M(F(n-1))

La fonction F est A005378. Pour construire son arbre, on a besoin de la fonction M ci-dessous :

%3 2 2 3 3 3--2 4 4 4--3 5 5 5--3 6 6 6--4 7 7 7--5 8 8 8--5 9 9 9--6 10 10 10--6 11 11 11--7 12 12 12--8 13 13 13--8 14 14 14--9 15 15 15--9 16 16 16--10 17 17 17--11 18 18 18--11 19 19 19--12 20 20 20--13 21 21 21--13

b) La fonction M

def M(n):
	assert n==int(n) and n>=0
	if n==0:
		return 0
	else:
		return n-F(M(n-1))

La fonction M est A005379. Pour construire son arbre, on a besoin de la fonction F ci-dessus :

%3 1 1 2 2 2--1 3 3 3--2 4 4 4--3 5 5 5--3 6 6 6--4 7 7 7--5 8 8 8--5 9 9 9--6 10 10 10--6 11 11 11--6 12 12 12--8 13 13 13--8 14 14 14--8 15 15 15--10 16 16 16--9 17 17 17--10 18 18 18--11 19 19 19--11 20 20 20--12 21 21 21--12 22 22 22--12 23 23 23--12

4) La fonction Q

def Q(n):
	assert n==int(n) and n>=1
	if n<=2:
		return 1
	else:
		return Q(n-Q(n-1))+Q(n-Q(n-2))

La fonction Q (qui n'est pas définie en 0) est A005185. On ne sait pas à l'heure actuelle si Q est définie pour tous les entiers à partir de 1. Hofstadter la décrit comme chaotique. Voici son arbre :

%3 1 1 2 2 2--1 3 3 3--2 4 4 4--3 5 5 5--3 6 6 6--4 7 7 7--5 8 8 8--5 9 9 9--6 10 10 10--6 11 11 11--6 12 12 12--8 13 13 13--8 14 14 14--8 15 15 15--10 16 16 16--9 17 17 17--10 18 18 18--11 19 19 19--11 20 20 20--12 21 21 21--12 22 22 22--12 23 23 23--12 24 24 24--16 25 25 25--14 26 26 26--14

III/ John Conway

La fonction notée a s'appelle fonction à 10 000 $ de Conway.

def a(n):
	assert n==int(n) and n>=1
	if n<=2:
		return 1
	else:
		return a(a(n-1))+a(n-a(n-1))

Il s'agit de la suite A004001 sur l'OEIS (encyclopédie de Conway et Sloane). Conway a présenté cette fonction le 15 juillet 1988 et a promis 10 000 $ au premier qui trouverait une valeur de a(n) qui soit proche de n/2. Douglas Hofstadter dit avoir inventé cette fonction des années auparavant. Voici son arbre :

%3 1 1 2 2 2--1 3 3 3--2 4 4 4--2 5 5 5--3 6 6 6--4 7 7 7--4 8 8 8--4 9 9 9--5 10 10 10--6 11 11 11--7 12 12 12--7 13 13 13--8 14 14 14--8 15 15 15--8 16 16 16--8 17 17 17--9 18 18 18--10 19 19 19--11

Pour étudier la fonction de Conway-Hofstadter, il est nécessaire de dérécursifier la fonction, qui, sous sa forme récursive, est trop longue à calculer. Par exemple avec ce script :

N = 65
X = list(range(N))
Y = [0,1,1]
for n in range(3,N):
    Y.append(Y[Y[n-1]]+Y[n-Y[n-1]])
plot(X,Y,'b-o')

on obtient ce graphique :

Conway a prouvé que a(n)≥n/2, d'où l'idée de représenter la différence :

Z = [Y[k]-X[k]/2 for k in range(len(X))]
plot(X,Z,'b-o')

ce qui donne

et on peut dézoomer :