Chapitre 1
Deux modèles calculatoires simples
1 / 64
Un peu d’histoire
• Il y a environ un siècle, certains mathématiciens cherchaient à
formaliser le concept de fonction pouvant être calculée
explicitement.
• Première tentative : les fonctions « primitives récursives »
• Définition originale un peu compliquée, on les définira avec un
petit langage de programmation appelé RÉPÉTER.
• On verra que cette notion est insuffisante pour caractériser tout
ce qui peut être calculé !
2 / 64
Les programmes RÉPÉTER
• Un nombre arbitrairement grand de registres est disponible :
𝑟0 , 𝑟1 … ;
• chaque registre contient un entier positif ou nul ;
• les registres sont implicitement initialisés à 0 avant utilisation ;
• Trois types d’instructions :
• 𝑟𝑖 ← 𝑟𝑗 : remplace le contenu du registre 𝑟𝑖 par celui de 𝑟𝑗 ;
• inc(𝑟𝑖 ) : incrémente de 1 le registre 𝑟𝑖 ;
• répéter 𝑟𝑖 fois [⟨Bloc⟩] : répète l’éxécution d’un bloc
d’instructions 𝑟𝑖 fois ;
3 / 64
• le nombre d’éxécution de ⟨Bloc⟩ est fixé une fois pour toutes
avant l’entrée dans la boucle, que 𝑟𝑖 y soit modifié ou non.
• Un programme RÉPÉTER implante une fonction
𝑓 ∶ ℕ×ℕ×⋯×ℕ
→
ℕ
(𝑟1 , 𝑟2 , … , 𝑟𝑘 )
↦
𝑟0 .
Au début de l’exécution, les registres 𝑟1 à 𝑟𝑘 contiennent les
arguments de 𝑓, et à la fin, 𝑟0 contient 𝑓(𝑟1 , … , 𝑟𝑘 ).
4 / 64
Addition
PLUS(𝑟1 , 𝑟2 ) = 𝑟1 + 𝑟2
𝑟0 ← 𝑟1
répéter 𝑟2 fois [
inc(𝑟0 )
]
5 / 64
Multiplication
MULT(𝑟1 , 𝑟2 ) = 𝑟1 ⋅ 𝑟2
répéter 𝑟1 fois [
répéter 𝑟2 fois [
inc(𝑟0 )
]
]
6 / 64
Exponentiation
𝑟
EXP(𝑟1 , 𝑟2 ) = 𝑟12
inc(𝑟0 )
répéter 𝑟2 fois [
𝑟3 ← 𝑟4
(𝑟3 ← 0)
répéter 𝑟0 fois [
(𝑟3 ← 𝑟0 ⋅ 𝑟1 )
répéter 𝑟1 fois [
inc(𝑟3 )
]
]
𝑟0 ← 𝑟3
]
7 / 64
Sucre syntaxique
• L’instruction
𝑟𝑖 ← PROC(𝑟𝑗1 , … , 𝑟𝑗𝑘 )
signifie que l’on doit substituer à cette ligne un bloc
d’instructions qui a pour effet de remplacer le contenu du
registre 𝑟𝑖 par la valeur calculée par PROC(𝑟𝑗1 , … , 𝑟𝑗𝑘 ), en
renommant au besoin les variables qui apparaissent dans le
code de la procédure PROC.
Les « appels » récursifs ne sont pas permis, car ceci reviendrait à
écrire un programme infiniment long.
8 / 64
• L’instruction
𝑟𝑖 ← 𝑘
signifie que l’on doit substituer à cette ligne 𝑘 incrémentations,
ce qui aura pour effet d’affecter la constante 𝑘 au registre 𝑟𝑖 .
Partout, on peut mettre une constante 𝑘 au lieu d’utiliser une
variable auxiliaire qu’on aurait incrémentée 𝑘 fois.
• On pourra utiliser des noms de variables (i.e. 𝑎, 𝑏, compteur) au
lieu de 𝑟1 , 𝑟2 , etc, sachant que chaque variable correspond à un
registre distinct.
9 / 64
EXP(𝑎, 𝑏) = 𝑎𝑏
𝑟0 ← 1
répéter 𝑏 fois [
𝑟0 ← MULT(𝑟0 , 𝑎)
]
10 / 64
Décrémentation
DEC(𝑥) = max(0, 𝑥 − 1)
répéter 𝑥 fois [
𝑟0 ← 𝑦
inc(𝑦)
]
11 / 64
Soustraction
MOINS(𝑥, 𝑦) = max(0, 𝑥 − 𝑦)
𝑟0 ← 𝑥
répéter 𝑦 fois [
𝑟0 ← DEC(𝑟0 )
]
12 / 64
Factorielle
FACT(𝑥) = 𝑥!
𝑟0 ← 1
répéter 𝑥 fois [
inc(𝑦)
𝑟0 ← MULT(𝑟0 , 𝑦)
]
13 / 64
Sucre syntaxique pour les variables booléennes
Nous adoptons les conventions syntaxiques suivantes :
• vrai pour la constante 1,
• faux pour la constante 0.
Pour évaluer ⟨Bloc⟩ conditionnellement à la valeur booléenne 𝑟𝑖 , on
répète ⟨Bloc⟩ 𝑟𝑖 fois.
L’instruction
sera mise pour
si 𝑟𝑖 alors [⟨Bloc⟩]
répéter 𝑟𝑖 fois [⟨Bloc⟩].
14 / 64
Et
ET(𝑥, 𝑦)
𝑟0 ← MULT(𝑥, 𝑦)
15 / 64
Négation
NEG(𝑥)
𝑟0 ← MOINS(1, 𝑥)
16 / 64
Ou
On utilise l’identité de De Morgan :
𝑥 ∨ 𝑦 = 𝑥̄ ∧ 𝑦 ̄
OU(𝑥, 𝑦)
xneg ← NEG(𝑥)
yneg ← NEG(𝑦)
𝑟0 ← ET(xneg, yneg)
𝑟0 ← NEG(𝑟0 )
17 / 64
Comparaisons
PG?(𝑥, 𝑦) = (𝑥 > 𝑦)
𝑧 ← MOINS(𝑥, 𝑦)
répéter 𝑧 fois [
𝑟0 ← vrai
]
PGE?(𝑥, 𝑦) = (𝑥 ≥ 𝑦)
𝑧 ← (𝑦 > 𝑥)
𝑟0 ← NEG(𝑧)
• On pourra aussi utiliser < et ≤.
18 / 64
Division
𝑎
DIV(𝑎, 𝑏) = ⌊ ⌋
𝑏
répéter 𝑎 fois [
acc ← PLUS(acc, 𝑏)
pasrendu ← (acc ≤ 𝑎)
si pasrendu alors [
inc(𝑟0 )
]
]
19 / 64
Modulo
𝑎 = 𝑥 mod y
𝑏 = 𝑥 div y
𝑐 = 𝑏𝑦
𝑥=𝑐+𝑎
MOD(𝑥, 𝑦) = 𝑥 mod 𝑦
𝑏 ← DIV(𝑥, 𝑦)
𝑐 ← MULT(𝑏, 𝑦)
𝑟0 ← MOINS(𝑥, 𝑐)
20 / 64
Test de primalité
PREMIER?(𝑥) = (𝑥 ∈ ℙ)
𝑟0 ← faux
si (𝑥 > 1) alors [
𝑟0 ← vrai
cand ← 1
répéter (𝑥 − 2) fois [
inc(cand)
mod ← MOD(𝑥, cand)
si (mod = 0); alors [ 𝑟0 ← faux ]
]
]
21 / 64
Prochain nombre premier
PREMIERSUIV(𝑥) = le plus petit nombre premier plus grand que 𝑥
maxboucle ← FACT(𝑥)
inc(maxboucle)
pasfini ← vrai
répéter maxboucle fois [
inc(𝑥)
prem ← PREMIER?(𝑥)
si ET(pasfini, prem) alors [
𝑟0 ← 𝑥
pasfini ← faux
]
]
22 / 64
𝑘-ième nombre premier
PREMIERK(𝑘) = le 𝑘-ième nombre premier
inc(𝑟0 )
répéter 𝑘 fois [
𝑟0 ← PREMIERSUIV(𝑟0 )
]
23 / 64
Structures de données : tableau
Nous allons implanter les tableaux d’entiers à l’aide du codage de
Gödel. Soit 𝑝𝑘 le 𝑘-ème nombre premier. Le tableau infini
(𝑎1 , 𝑎2 , … , 𝑎𝑛 , 0, 0, …), où 𝑎𝑘 ∈ ℕ,
est représentée sans ambiguïté par l’entier
𝑎
𝑎
𝑎
𝑝1 1 𝑝2 2 … 𝑝𝑛𝑛 .
24 / 64
Structures de données : codage de Gödel
La liste
(5, 2, 3, 17, 13, 0, 0, 0, …)
est représenté par l’entier
25 32 53 717 1113 = 289117305729902212346760865812000.
Une autre variable peut accessoirement être utilisée pour indiquer la
position du dernier élément.
25 / 64
Structures de données : codage de Gödel
Il est aussi possible d’interpréter un entier comme une liste de liste.
((1, 2, 3, 0, …), (4, 5, 0, …), (6, 7, 0, …), 0, …)
est représentée par l’entier
1
2
3
4
5
6
7
2(2 3 5 ) 3(2 3 ) 5(2 3 )
ayant 100366 décimales.
Les listes de ce type peuvent être utilisées pour encoder des
structures de données complexes comme des piles, files, arbres ou
graphes.
26 / 64
Extraction d’un élément d’un tableau
TABLVAL(𝑡, 𝑖) = 𝑖-ième élément du tableau 𝑡
𝑝 ← PREMIERK(𝑖)
testval ← 𝑝
répéter 𝑡 fois [
mod ← MOD(𝑡, testval)
si (mod = 0) alors [
inc(𝑟0 )
testval ← MULT(𝑝, testval)
]
]
27 / 64
Assignation d’un élément dans un tableau
TABLASS(𝑡, 𝑖, 𝑣) = tableau 𝑡 où le 𝑖-ième élément est remplacé par 𝑣
curval ← TABLVAL(𝑡, 𝑖)
𝑝 ← PREMIERK(𝑖)
curexp ← EXP(𝑝, curval)
𝑟0 ← DIV(𝑡, curexp)
nouvexp ← EXP(𝑝, 𝑣)
𝑟0 ← MULT(𝑟0 , nouvexp)
28 / 64
Puissance des programmes RÉPÉTER
Il semble que les programmes RÉPÉTER peuvent calculer des
fonctions complexes.
Peut-on calculer toutes les fonctions à valeurs entières avec un
programme RÉPÉTER ?
Remarque 1.1
Un programme RÉPÉTER ne peut pas entrer dans une boucle
infinie : son exécution se termine toujours.
Définition 1.2
Les fonctions calculables par un programme RÉPÉTER sont
appelées primitives récursives.
29 / 64
Pour une fonction 𝑓 ∶ ℕ → ℕ et un entier 𝑛, on note :
𝑓 ⟨0⟩ (𝑥)
=
𝑥
⟨1⟩
(𝑥)
=
𝑓(𝑥)
𝑓 ⟨2⟩ (𝑥)
=
𝑓(𝑓(𝑥))
𝑓
⋮
𝑓
⟨𝑛⟩
(𝑥)
= ⏟
𝑓(𝑓(⋯
𝑥⏟
⋯))
⏟⏟
⏟
𝑛 fois
répéter 𝑛 fois [
𝑟0 ← 𝑓(𝑟0 )
]
30 / 64
Boucles imbriquées
Comment trouver une fonction qui ne peut être calculé par un
programme RÉPÉTER donné ?
• Étant donné un programme quelconque, on peut regarder sa
profondeur de boucle 𝑖, et tenter de définir une fonction qui ne
peut pas être calculée par un programme avec cette profondeur
de boucle :
Définition 1.3
Pour tout 𝑖 ∈ ℕ, on définit 𝐵𝑖 ∶ ℕ → ℕ comme
𝐵𝑖 (𝑥) = {
si 𝑖 = 0
𝑥+1
𝐵𝑖−1
⟨𝑥+1⟩
(1)
si 𝑖 > 0
31 / 64
Lemme 1.4
Pour tout 𝑖 ≥ 0, 𝐵𝑖+1 est calculable par un programme RÉPÉTER
avec profondeur de boucle 𝑖.
Démonstration :
Procédons par récurrence sur 𝑖. Cas de base : 𝑖 = 0 :
B1 (𝑥) ∶
inc(𝑥)
inc(𝑥)
𝑟0 ← 𝑥
32 / 64
Pas de récurrence : supposons qu’il existe un programme répéter
B𝑖+1 de profondeur de boucle 𝑖 qui calcule 𝐵𝑖+1 . Alors, pour 𝑖 + 1,
considérons le programme suivant :
B𝑖+2 (𝑥) ∶
inc(𝑟0 )
inc(𝑥)
répéter 𝑥 fois [
𝑟0 ← B𝑖+1 (𝑟0 )
]
On remarque que le programme B𝑖+2 compte exactement 𝑖 + 1
boucles répéter et la profondeur d’imbrication est aussi 𝑖 + 1.
33 / 64
Que calcule 𝐵𝑖 ?
𝐵0 (𝑥) = 𝑥 + 1
𝐵1 (𝑥) = 𝐵0 (𝐵0 (⋯ 1 ⋯)) = 1 + (𝑥 + 1) = 𝑥 + 2
= 2 + (𝑥 + 3) − 3
𝐵2 (𝑥) = 𝐵1 (𝐵1 (⋯ 1 ⋯)) = 1 + (𝑥 + 1) × 2 = 2𝑥 + 3
= 2(𝑥 + 3) − 3
𝐵3 (𝑥) = 𝐵2 (𝐵2 (⋯ 1 ⋯))
= 2𝑥+3 − 3
⋮
Cette fonction croît à une vitesse hallucinante…
34 / 64
Hyperopérations : flèche de Knuth
Pour aller plus loin, il faut une notation permettant de généraliser
les opérations répétées :
Définition 1.5 (Flèche de Knuth)
Soient 𝑎, 𝑏 ∈ ℕ. On définit
𝑎 ↑0 𝑏 ∶= 𝑎𝑏
Pour 𝑛 > 0, on définit
𝑎 ↑𝑛 𝑏 ∶= 𝑎 ↑𝑛−1 𝑎 ↑𝑛−1 ⋯ ↑𝑛−1 𝑎,
où on a 𝑏 copies de 𝑎.
35 / 64
Pour revenir à 𝐵𝑖 :
𝐵0 (𝑥) = 𝑥 + 1
𝐵1 (𝑥) = 𝐵0 (𝐵0 (⋯ 1 ⋯)) = 1 + (𝑥 + 1) = 𝑥 + 2
= 2 + (𝑥 + 3) − 3
𝐵2 (𝑥) = 𝐵1 (𝐵1 (⋯ 1 ⋯)) = 1 + (𝑥 + 1) × 2 = 2𝑥 + 3
= 2 ↑0 (𝑥 + 3) − 3
𝐵3 (𝑥) = 𝐵2 (𝐵2 (⋯ 1 ⋯))
= 2 ↑1 (𝑥 + 3) − 3
⋮
𝐵𝑖 (𝑥) = 𝐵𝑖−1 (𝐵𝑖−1 (⋯ 1 ⋯))
= 2 ↑𝑖−2 (𝑥 + 3) − 3.
Conjecture tentante… encore faut-il la prouver !
36 / 64
Théorème 1.6
Pour tout 𝑖 ⩾ 2 et tout 𝑥 ∈ ℕ,
𝐵𝑖 (𝑥) = 2 ↑𝑖−2 (𝑥 + 3) − 3.
Démonstration :
Nous procéderons par récurrence.
Base de la récurrence. Il s’agit de 𝑖 = 2, on l’a déjà calculé
directement.
37 / 64
Pas de récurrence. Supposons que l’énoncé est vrai pour 𝑖 − 1 :
𝐵𝑖−1 (𝑥) = 2 ↑𝑖−3 (𝑥 + 3) − 3.
On a alors que :
𝐵𝑖 (𝑥) = 𝐵𝑖−1 ⟨𝑥+1⟩ (1)
= 2 ↑𝑖−3 (𝐵𝑖−1 ⟨𝑥⟩ (1) + 3) − 3
= 2 ↑𝑖−3 (2 ↑𝑖−3 (𝐵𝑖−1 ⟨𝑥−1⟩ (1) + 3) − 3 + 3) − 3
= 2 ↑𝑖−3 (2 ↑𝑖−3 (𝐵𝑖−1 ⟨𝑥−1⟩ (1) + 3)) − 3
⋮
= 2(↑𝑖−3 (2 ↑𝑖−3 (… (𝐵𝑖−1 (1) + 3) …)) − 3
= 2(↑𝑖−3 (2 ↑𝑖−3 (… (2 ↑𝑖−3 4) …)) − 3
= 2(↑𝑖−3 (2 ↑𝑖−3 (… (2 ↑𝑖−3 (2 ↑𝑖−3 2) …)) − 3
= 2 ↑𝑖−2 (𝑥 + 3) − 3.
38 / 64
Récurrence structurelle
On cherche à prouver un énoncé concernant un programme. Pour ce
faire, on aura besoin du concept de preuves par récurrence
structurelle.
Un programme RÉPÉTER peut prendre 4 formes différentes :
1. Un programme d’une ligne inc(𝑟𝑖 )
2. Un programme d’une ligne 𝑟𝑖 ← 𝑟𝑗
3. Une boucle répéter 𝑟𝑖 fois [𝑃 ], où 𝑃 est un autre programme
RÉPÉTER
4. Une concaténation de deux programme RÉPÉTER 𝑃 ; 𝑄.
Notons que les deux dernières règles sont récursives : on construit
des programmes en termes de programmes plus petits.
39 / 64
Récurrence structurelle
Pour prouver qu’un énoncé est vrai pour tout programme RÉPÉTER,
on peut donc prouver que l’énoncé est vrai pour les programmes
construits selon les 4 règles. Autrement dit, on doit montrer que :
• L’énoncé est vrai pour les programmes d’une seule ligne inc(𝑟𝑖 )
• L’énoncé est vrai pour les programmes d’une seule ligne 𝑟𝑖 ← 𝑟𝑗
• Si l’énoncé est vrai pour le programme 𝑃 , alors il est vrai pour
répéter 𝑟𝑖 fois [𝑃 ].
• Si l’énoncé est vrai pour 𝑃 et 𝑄, alors il est vrai pour 𝑃 ; 𝑄.
Ceci revient à faire une preuve par induction sur le nombre de fois
qu’on a dû appliquer les règles ci-haut pour construire notre
programme.
Vu que les deux premières règles ne sont pas récursives, ce sont les
cas de base de notre preuve.
40 / 64
Définition 1.7
Pour tout programme RÉPÉTER P, ℬ(P) est le nombre maximal
d’imbrications des boucles de P.
Définition 1.8
On dénote par evalP (𝑟0 , … , 𝑟𝑘 ) le contenu de l’ensemble des
registres après avoir exécuté le programme P sur les registres
initialisés avec les valeurs 𝑟0 , … , 𝑟𝑘 .
41 / 64
Propriétés de la famille des fonctions 𝐵𝑖
Remarque 1.9
Tous les énoncés suivants peuvent être facilement prouvés par
récurrence sur 𝑖, 𝑥 ou 𝑘.
⟨𝑘⟩
(a) 𝐵𝑖 (𝑥) est
• strictement croissante en 𝑥 pour tout 𝑖 et tout 𝑘,
• strictement croissante en 𝑖 pour tout 𝑥 et tout 𝑘 ≥ 1,
• strictement croissante en 𝑘 pour tout 𝑖 et tout 𝑥,
⟨𝑘⟩
(b) 2𝑘 𝑥 ≤ 𝐵𝑖 (𝑥) pour 𝑖 ≥ 2 et tout 𝑥, 𝑘.
42 / 64
Théorème 1.10
Pour tout programme RÉPÉTER P, si ℬ(P) = 𝑖, alors
∀𝑟0 , … , 𝑟𝑘 , max evalP (𝑟0 , … , 𝑟𝑘 ) ≤ 𝐵𝑖+1 ⟨ℓ(P)⟩ (max(𝑟0 , … , 𝑟𝑘 )),
où ℓ(P) est le nombre de lignes de code de P.
Démonstration :
On prouvera ce théorème par récurrence structurelle.
43 / 64
Base de la récurrence. On doit prouver l’énoncé pour tous les
programmes d’une ligne P, donc soit une assignation ou une
incrémentation de registre. On a donc que ℬ(P) = 0 et ℓ(P) = 1, et,
pour tout 𝑖 :
max evalP (𝑟0 , … , 𝑟𝑘 ) ≤ max(𝑟0 , … , 𝑟𝑘 ) + 1
⟨1⟩
≤ 𝐵1 (max(𝑟0 , … , 𝑟𝑘 )),
⟨1⟩
où on a utilisé le fait que 𝐵1 (𝑥) = 𝐵1 (𝑥) = 𝑥 + 2.
44 / 64
Règle 3 : Boucle répéter
Soit Q un programme répéter, et supposons que le théorème est vrai
pour celui-ci, et soit P le programme suivant :
répéter 𝑟𝛼 fois [Q]
Soit ℓ ≥ 1 le nombre de lignes de code de Q, (et donc P a ℓ + 1 ≥ 2
lignes), et supposons que Q a une profondeur de boucle 𝑖, ce qui
veut dire que celle de P est 𝑖 + 1.
45 / 64
Par l’hypothèse de récurrence, Q peut faire augmenter la valeur des
⟨ℓ⟩
registres par la fonction 𝐵𝑖+1 à chaque itération, et la boucle est
répétée au plus 𝑣 = max(𝑟0 , … , 𝑟𝑘 ) fois. On a donc que :
⟨ℓ⟩
max evalP (𝑟0 , … , 𝑟𝑘 ) ≤ (𝐵𝑖+1 )⟨𝑟𝛼⟩ (𝑣)
⟨ℓ⟩
≤ (𝐵𝑖+1 )⟨𝑣⟩ (𝑣)
⟨(ℓ)𝑣⟩
= 𝐵𝑖+1 (𝑣)
⟨(ℓ)𝑣⟩
⟨𝑣⟩
⟨(ℓ+1)𝑣⟩
(1)
≤ 𝐵𝑖+1 (𝐵𝑖+1 (1))
= 𝐵𝑖+1
⟨(ℓ+1)𝑣+1⟩
≤ 𝐵𝑖+1
(1)
= 𝐵𝑖+2 ((ℓ + 1)𝑣)
≤ 𝐵𝑖+2 (2ℓ 𝑣)
(b)
⟨ℓ⟩
≤ 𝐵𝑖+2 (𝐵𝑖+2 (𝑣))
⟨ℓ+1⟩
⟨ℓ(P)⟩
= 𝐵𝑖+2 (𝑣) = 𝐵ℬ(P)+1 (𝑣).
46 / 64
Règle 4 : Concaténation de deux programmes
On suppose que P est la concaténation de P1 et P2 avec ℓ1 ≥ 1 et
ℓ2 ≥ 1 lignes de code respectivement, et une profondeur de boucle
de 𝑖1 et 𝑖2 . Supposons que le théorème est vrai pour P1 et P2 , et soit
𝑖 = max{𝑖1 , 𝑖2 } la profondeur de boucle de P.
On a alors que :
⟨ℓ ⟩
⟨ℓ ⟩
2
1
max evalP (𝑟0 , … , 𝑟𝑘 ) ≤ 𝐵𝑖 2+1 (𝐵𝑖 1+1 (max(𝑟0 , … , 𝑟𝑘 )))
⟨ℓ2 ⟩
⟨ℓ1 ⟩
≤ 𝐵𝑖+1
(𝐵𝑖+1
(max(𝑟0 , … , 𝑟𝑘 )))
⟨ℓ +ℓ ⟩
1
2
= 𝐵𝑖+1
(max(𝑟0 , … , 𝑟𝑘 )).
Puisque P a ℓ1 + ℓ2 lignes de code, ceci conclut la preuve.
47 / 64
Corollaire 1.11
Pour tout 𝑖, la fonction 𝐵𝑖+2 n’est pas calculable par un programme
RÉPÉTER avec une profondeur de boucle 𝑖, mais est calculable par
un programme avec une profondeur de boucle 𝑖 + 1.
Démonstration :
On a déjà vu que 𝐵𝑖+2 est calculable par un programme avec
profondeur de boucle 𝑖 + 1. Or, supposons qu’un programme P avec
ℬ(P) = 𝑖 et ℓ(P) = ℓ peut calculer 𝐵𝑖+2 (𝑥) étant donné 𝑥 en entrée
sur 𝑟1 .
Le théorème précédent nous dit que
⟨ℓ⟩
evalP (0, 𝑥, 0 …)(0) ≤ 𝐵𝑖+1 (𝑥).
On doit maintenant montrer qu’il existe un 𝑥 tel que
⟨ℓ⟩
𝐵𝑖+1 (𝑥) < 𝐵𝑖+2 (𝑥) pour conclure.
48 / 64
Concentrons nous sur l’input 𝑥 = 2ℓ (de toute façon, il suffit qu’il
existe une seule valeur de 𝑥 pour laquelle P ne calcule pas la bonne
valeur) :
⟨ℓ⟩
(𝑎)
⟨ℓ⟩
⟨ℓ⟩
⟨ℓ⟩
⟨ℓ⟩
⟨2ℓ+1⟩
(1)
𝐵𝑖+1 (2ℓ) < 𝐵𝑖+1 (𝐵1 (1))
≤ 𝐵𝑖+1 (𝐵𝑖+1 (1))
≤ 𝐵𝑖+1
= 𝐵𝑖+2 (2ℓ),
⟨ℓ⟩
où (𝑎) utilise le fait que 𝐵1 (𝑥) = 𝑥 + 2, et donc 𝐵1 (1) = 2ℓ + 1.
49 / 64
La fonction d’Ackermann
Pour terminer, il nous reste à démontrer qu’il existe une fonction qui
n’est pas calculable par un programme RÉPÉTER, peu importe sa
profondeur de boucle.
Pour ce faire, on fait de la profondeur de boucle un input de la
fonction :
Définition 1.12 (Fonction d’Ackermann (1928))
La fonction d’Ackermann 𝐴 ∶ ℕ × ℕ → ℕ est définie comme
𝐴(𝑖, 𝑥) ∶= 𝐵𝑖 (𝑥).
50 / 64
Théorème 1.13
La fonction d’Ackermann 𝐴 n’est pas calculable par un programme
RÉPÉTER.
Démonstration :
Supposons que 𝐴(𝑖, 𝑥) soit calculable par un programme RÉPÉTER P
avec une profondeur de boucle 𝑖.
Donc, sur input ⟨𝑖 + 2, 𝑥⟩ sur les registres 𝑟1 , 𝑟2 , P devrait calculer
𝐵𝑖+2 (𝑥). Ce qui veut dire que le programme répéter suivant calcule la
fonction 𝐵𝑖+2 :
Algorithme 1 : Programme RÉPÉTER qui calcule 𝐵𝑖+2 .
𝑟2 ← 𝑟 1 ;
2 𝑟1 ← 𝑖 + 2 ;
3 P
1
Or, ce programme a une profondeur de boucle 𝑖 et son existence
contrevient donc au Corollaire 1.11, et donc P ne peut pas exister.
51 / 64
Remarquons que la fonction
𝐹 (𝑥) = 𝐴(𝑥, 𝑥)
croît plus rapidement que n’importe quelle des fonctions 𝐵𝑖 (𝑥) (car
𝐴(𝑥, 𝑥) > 𝐵𝑖 (𝑥) pour tout 𝑥 > 𝑖).
52 / 64
Les programmes TANTQUE
Les programmes TANTQUE sont semblables aux programmes
RÉPÉTER, mais les boucles sont différentes :
• Un nombre arbitrairement grand de registres est disponible :
𝑟0 , 𝑟1 … ;
• chaque registre contient un entier positif ou nul ;
• les registres sont implicitement initialisés à 0 avant utilisation ;
• Trois types d’instructions permises :
• 𝑟𝑖 ← 𝑟𝑗 : remplace le contenu du registre 𝑟𝑖 par celui de 𝑟𝑗 ;
• inc(𝑟𝑖 ) : incrémente de 1 le registre 𝑟𝑖 ;
• tant que 𝑟𝑖 ≠ 𝑟𝑗 faire [⟨Bloc⟩] répète l’éxécution d’un bloc
d’instructions tant que les valeurs des registres 𝑟𝑖 et 𝑟𝑗 diffèrent.
53 / 64
• dans une boucle, l’inégalité est réévaluée à chaque itération et
les valeurs de 𝑟𝑖 et 𝑟𝑗 peuvent changer.
• Un programme TANTQUE implante une fonction
𝑓 ∶ ℕ×ℕ×⋯×ℕ
→
ℕ ∪ {↑}
(𝑟1 , 𝑟2 , … , 𝑟𝑘 )
↦
𝑟
{ 0
↑
si le programme s’arrête,
si le programme boucle à l’infini.
Au début de l’exécution, les registres 𝑟1 à 𝑟𝑘 contiennent les
arguments de 𝑓, et à la fin, si le programme s’arrête, 𝑟0 contient
𝑓(𝑟1 , … , 𝑟𝑘 ).
54 / 64
Contrairement aux programmes RÉPÉTER, un programme TANTQUE
peut ne jamais s’arrêter, par exemple :
BOUCLE(𝑟1 ) = ↑
inc(𝑟1 )
tant que 𝑟1 ≠ 𝑟0 faire [ inc(𝑟2 ) ]
55 / 64
Remarque 1.14
Tout programme RÉPÉTER peut être simulé par un programme
TANTQUE.
Il suffit de remplacer les boucles de la forme
répéter 𝑟𝑖 fois [ … ]
par
𝑟𝑘 ← 𝑟𝑖
tant que 𝑟𝑗 ≠ 𝑟𝑘 faire [
…
inc(𝑟𝑗 )
]
où 𝑟𝑗 et 𝑟𝑘 sont des registres non utilisés.
56 / 64
Sucre syntaxique
À la lumière de la remarque 1.14, on se permettra d’utiliser les
instructions répéter dans les programmes TANTQUE.
On peut donc recycler en programmes TANTQUE tous les
programmes RÉPÉTER que nous avons vus.
57 / 64
Ackermann est calculable par un programme TANTQUE.
Nous allons exhiber un programme TANTQUE qui implante la
fonction d’Ackermann telle que présentée à la définition 1.12. Les
détails de la preuve qui montrent que ce programme implante
effectivement la fonction souhaitée seront omis.
Théorème 1.15
La fonction d’Ackermann 𝐴 est calculable par un programme
TANTQUE.
58 / 64
Aperçu de la démonstration :
La fonction d’Ackermann sera implantée à l’aide d’une pile.
Idée :
𝑥
𝑖
⋮
𝐴(𝑖, 𝑥)
⋮
59 / 64
Notons que 𝐴(𝑖, 𝑥) peut s’écrire sans les 𝐵𝑖 comme :
⎧𝑥 + 1
{
{
𝐴(𝑖, 𝑥) = ⎨𝐴(𝑖 − 1, 1)
{
{𝐴(𝑖 − 1, 𝐴(𝑖, 𝑥 − 1))
⎩
𝑖=0
𝑥
𝑖
⋮
𝑖 > 0 et 𝑥 = 0
sinon
si 𝑖 = 0
si 𝑖 > 0 et 𝑥 = 0
sinon.
𝑥+1
⋮
1
𝑖−1
⋮
𝑥−1
𝑖
𝑖−1
⋮
60 / 64
Écrivons ça d’abord en « pseudo-tantque » :
ACKERMANN(𝑖, 𝑥) = 𝐴(𝑖, 𝑥)
empile(𝑖)
empile(𝑥)
tant que la pile contient plus d’un élément faire [
𝑥′ ← dépile
𝑖′ ← dépile
si 𝑖′ = 0 alors [ empile(𝑥′ + 1) ]
si 𝑖′ > 0 et 𝑥′ = 0 alors [
empile(𝑖′ − 1) empile(1)
]
sinon [
empile(𝑖′ − 1) empile(𝑖′ ) empile(𝑥′ − 1)
]
]
𝑟0 ← dépile
61 / 64
En TANTQUE plus orthodoxe, avec 𝑃 et 𝑡 jouant le rôle de la pile et
de l’adresse du haut de la pile :
ACKERMANN(𝑖, 𝑥) = 𝐴(𝑖, 𝑥)
𝑃 ←1
inc(𝑡)
𝑃 ← TABLASS(𝑃 , 𝑡, 𝑖)
inc(𝑡)
𝑃 ← TABLASS(𝑃 , 𝑡, 𝑥)
tant que 𝑡 ≠ 1 faire [
𝑥′ ← TABLVAL(𝑃 , 𝑡)
𝑖′ ← TABLVAL(𝑃 , 𝑡 − 1)
si 𝑖′ = 0 alors [
𝑡 ← DEC(𝑡)
TABLASS(𝑃 , 𝑡, 𝑥′ + 1)
]
si ET(𝑖′ ≠ 0, 𝑥′ = 0) alors [
TABLASS(𝑃 , 𝑡 − 1, 𝑖′ − 1)
TABLASS(𝑃 , 𝑡, 1)
]
62 / 64
si ET(𝑖′ ≠ 0, 𝑥′ ≠ 0) alors [
inc(𝑡)
TABLASS(𝑃 , 𝑡 − 2, 𝑖′ − 1)
TABLASS(𝑃 , 𝑡 − 1, 𝑖′ )
TABLASS(𝑃 , 𝑡, 𝑥′ − 1)
]
]
𝑟0 ← TABLVAL(𝑃 , 1)
63 / 64
Programmes universels
• Un programme universel est un programme qui prend en entrée
le code d’un autre programme et l’exécute.
• Il existe un programme TANTQUE universel.
• Existe-t-il un programme RÉPÉTER universel ? Non :
• Supposons qu’il existe un programme RÉPÉTER universel
UNIV(𝑝, 𝑥), qui lit un programme 𝑝 et l’exécute sur 𝑥
• Considérons le programme
DIAG(𝑝) ∶
𝑟0 ← UNIV(𝑝, 𝑝)
inc(𝑟0 )
et soit 𝑝DIAG son code source sous forme interprétable par UNIV.
Donc UNIV(𝑝DIAG , 𝑥) calcule DIAG(𝑥).
• Quel est l’output de DIAG(𝑝DIAG ) ? Ça doit être à la fois
DIAG(𝑝DIAG ) et DIAG(𝑝DIAG ) + 1 ! ⇒ impossible
64 / 64
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )