Exercices : calculabilité et décidabilité

Exercice 0. QCM d’activation

Pour chaque question, une seule réponse est correcte.

Question 1. Le problème de l’arrêt consiste à déterminer :

  • A) si un programme s’exécute en temps polynomial
  • B) si un programme finira par s’arrêter pour une entrée donnée
  • C) combien de temps un programme met à s’exécuter
  • D) si un programme contient des erreurs de syntaxe
Correction

Réponse B. Le problème de l’arrêt demande : « étant donné un programme P et une entrée E, est-ce que P finit par s’arrêter quand on l’exécute sur E ? ». Alan Turing a démontré en 1936 qu’il est impossible d’écrire un programme qui résout ce problème dans tous les cas.

  • A est faux : déterminer un temps d’exécution polynomial relève de la complexité, pas de la terminaison ;
  • C est faux : la complexité temporelle est un autre problème ;
  • D est faux : les erreurs de syntaxe sont détectables par un compilateur ou un interpréteur.

Question 2. Un problème est dit « décidable » si :

  • A) il admet au moins une solution
  • B) il existe un algorithme qui répond toujours correctement par oui ou non en temps fini
  • C) il peut être résolu uniquement par un humain
  • D) il ne peut être résolu que par force brute
Correction

Réponse B. Un problème de décision est décidable s’il existe un algorithme (une machine de Turing) qui, pour toute instance du problème, s’arrête en temps fini et donne la bonne réponse (oui ou non).

  • A est faux : admettre une solution ne signifie pas qu’on peut la trouver algorithmiquement ;
  • C est faux : la décidabilité concerne les algorithmes, pas les humains ;
  • D est faux : la méthode de résolution n’est pas contrainte à la force brute.

Question 3. Le problème de l’arrêt est :

  • A) décidable et de complexité polynomiale
  • B) décidable mais de complexité exponentielle
  • C) indécidable
  • D) décidable uniquement pour les programmes récursifs
Correction

Réponse C. Turing a prouvé par un raisonnement par l’absurde qu’aucun algorithme ne peut résoudre le problème de l’arrêt dans le cas général. C’est l’exemple fondateur de problème indécidable.

  • A et B sont faux : le problème n’est pas décidable du tout ;
  • D est faux : le problème est indécidable quel que soit le type de programme.

Question 4. Un problème de la classe NP est un problème :

  • A) impossible à résoudre
  • B) dont on peut vérifier une solution en temps polynomial
  • C) qui ne peut être résolu qu’en temps exponentiel
  • D) qui n’a pas de solution
Correction

Réponse B. Un problème est dans NP (nondeterministic polynomial) si, étant donnée une solution candidate, on peut vérifier en temps polynomial qu’elle est correcte. Cela ne signifie pas que trouver la solution est nécessairement long.

  • A est faux : les problèmes NP sont résolubles (en temps éventuellement exponentiel) ;
  • C est faux : certains problèmes NP sont aussi dans P (résolubles en temps polynomial) ;
  • D est faux : les problèmes NP ont des solutions.

Question 5. La question ouverte « P = NP ? » demande si :

  • A) tous les problèmes informatiques sont résolubles
  • B) tout problème dont on peut vérifier la solution rapidement peut aussi être résolu rapidement
  • C) les machines de Turing sont plus puissantes que les ordinateurs actuels
  • D) le problème de l’arrêt peut être résolu
Correction

Réponse B. La question P = NP demande si tout problème dont la solution est vérifiable en temps polynomial (classe NP) est aussi résoluble en temps polynomial (classe P). C’est l’un des plus grands problèmes ouverts en informatique et en mathématiques.

  • A est faux : certains problèmes sont indécidables, indépendamment de P vs NP ;
  • C est faux : les machines de Turing sont un modèle théorique équivalent en puissance aux ordinateurs ;
  • D est faux : le problème de l’arrêt est indécidable, ce qui est un résultat établi.

Exercice 1 – Programme comme donnée

  1. Expliquer en quelques phrases pourquoi un programme peut être considéré comme « une donnée comme une autre ».
  2. Donner un exemple concret où un programme prend un autre programme en paramètre.
  3. Qu’est-ce qu’un quine ? Exécuter dans un interpréteur Python la ligne suivante et observer le résultat :
    a='a=%r;print(a%%a)';print(a%a)
    
Correction
  1. Un programme est stocké sous forme de texte (son code-source), qui est une suite de caractères. Cette suite de caractères peut être lue, transmise ou analysée par un autre programme, exactement comme n’importe quelle autre donnée. Par exemple, quand on exécute python3 test.py, le programme python3 reçoit en paramètre le fichier test.py qui contient le code-source d’un autre programme.

  2. L’interpréteur Python est un programme qui prend en paramètre le code-source d’un programme Python. De même, un compilateur C prend en paramètre un fichier .c et produit un exécutable. Autre exemple : un débogueur est un programme qui analyse l’exécution d’un autre programme.

  3. Un quine est un programme qui affiche son propre code-source sans lire aucun fichier. La ligne a='a=%r;print(a%%a)';print(a%a) affiche exactement a='a=%r;print(a%%a)';print(a%a), c’est-à-dire elle-même.

Exercice 2 – Le programme countdown

Considérons le programme :

def countdown(n):
    while n != 0:
        print(n)
        n = n - 1
    print("fini")
  1. Que fait countdown(5) ? S’arrête-t-il ?
  2. Que fait countdown(-3) ? Justifier.
  3. Que fait countdown(2.5) ? Justifier.
  4. Modifier le programme pour qu’il s’arrête dans tous les cas (entier, flottant, négatif).
Correction
  1. countdown(5) affiche 5, 4, 3, 2, 1, puis « fini ». Il s’arrête car n atteint 0 après cinq itérations.

  2. countdown(-3) ne s’arrête jamais (boucle infinie). La condition n != 0 est toujours vraie car n vaut successivement −3, −4, −5, … et ne sera jamais égal à 0.

  3. countdown(2.5) ne s’arrête jamais (boucle infinie). Les valeurs successives de n sont 2.5, 1.5, 0.5, −0.5, −1.5, … La valeur 0 n’est jamais atteinte.

  4. Version corrigée :

    def countdown(n):
        while n > 0:         # remplacer != par >
            print(int(n))
            n = n - 1
        print("fini")
    

    Avec n > 0, la boucle s’arrête dès que n devient négatif ou nul, quel que soit le type de n.

Exercice 3 – La preuve par l’absurde du problème de l’arrêt

On suppose qu’il existe une fonction halt(prog, entree) qui renvoie True si le programme prog s’arrête quand on lui donne entree, et False sinon. On définit alors :

def sym(prog):
    if halt(prog, prog):
        while True:
            pass
    else:
        return 0
  1. Que fait sym(sym) si halt(sym, sym) renvoie True ?
  2. Que fait sym(sym) si halt(sym, sym) renvoie False ?
  3. En déduire la contradiction et conclure.
Correction
  1. Si halt(sym, sym) renvoie True, cela signifie que sym(sym) devrait s’arrêter. Or, dans ce cas, sym entre dans la boucle infinie while True et ne s’arrête jamais. Contradiction.

  2. Si halt(sym, sym) renvoie False, cela signifie que sym(sym) ne devrait pas s’arrêter. Or, dans ce cas, sym exécute return 0 et s’arrête. Contradiction.

  3. Dans les deux cas, la prédiction de halt est fausse. Donc la fonction halt ne peut pas exister : le problème de l’arrêt est indécidable.

La preuve procède par l’absurde : on suppose que halt existe, on construit sym qui contredit systématiquement la prédiction, et on arrive à une contradiction dans tous les cas.

Exercice 4 – Décidable ou indécidable ?

Pour chacun des problèmes suivants, indiquer s’il est décidable ou indécidable. Justifier brièvement.

  1. Déterminer si un nombre entier donné est premier.
  2. Déterminer si un programme Python donné affiche "bonjour" au moins une fois.
  3. Déterminer si une liste triée contient un élément donné.
  4. Déterminer si deux programmes Python calculent toujours la même chose pour les mêmes entrées.
  5. Déterminer si un entier donné est pair.
  6. Déterminer si un programme Python donné contient une boucle while.
Correction
  1. Décidable. Il existe des algorithmes de test de primalité qui terminent toujours (divisions successives, Miller-Rabin, AKS).

  2. Indécidable. C’est une question sémantique (sur le comportement) d’un programme arbitraire. Par le théorème de Rice, toute question sémantique non triviale sur un programme est indécidable.

  3. Décidable. La recherche dichotomique termine toujours et donne une réponse en \(\mathcal{O}(\log n)\).

  4. Indécidable. C’est le problème de l’équivalence de programmes, auquel se réduit le problème de l’arrêt (théorème de Rice).

  5. Décidable. Il suffit de tester si n % 2 == 0. L’algorithme termine toujours.

  6. Décidable. C’est une question syntaxique (sur la forme du code, pas sur son comportement). Il suffit de chercher le mot-clé while dans le code-source. Le théorème de Rice ne s’applique qu’aux questions sémantiques.

Exercice 5 – P, NP, NP-complet

Classer les problèmes suivants dans la catégorie qui convient (P, NP, NP-complet). Justifier.

  1. Trier une liste de \(n\) nombres.
  2. Déterminer si un graphe admet un cycle hamiltonien (passant par chaque sommet exactement une fois).
  3. Rechercher un élément dans une liste triée.
  4. Le problème du sac à dos (version décision).
  5. Multiplier deux matrices \(n \times n\).
Correction
  1. P. Le tri fusion résout ce problème en \(\mathcal{O}(n \log n)\), qui est polynomial.

  2. NP-complet. Aucun algorithme polynomial n’est connu. En revanche, si on propose un cycle, on peut vérifier en temps polynomial qu’il est hamiltonien (il suffit de vérifier que chaque sommet apparaît exactement une fois et que les arêtes existent).

  3. P. La recherche dichotomique résout ce problème en \(\mathcal{O}(\log n)\).

  4. NP-complet. La version décision du sac à dos (« peut-on atteindre exactement la valeur \(V\) sans dépasser la capacité ? ») est NP-complète. Vérifier qu’un ensemble d’objets satisfait les contraintes est polynomial, mais trouver cet ensemble ne l’est pas (en l’état des connaissances).

  5. P. La multiplication de matrices se fait en \(\mathcal{O}(n^3)\) (algorithme classique) ou mieux avec l’algorithme de Strassen en \(\mathcal{O}(n^{2.81})\). Ces complexités sont polynomiales.

Exercice 6 – Vrai ou faux

Pour chaque affirmation, indiquer si elle est vraie ou fausse et justifier.

  1. « Le problème de l’arrêt montre que certains programmes ne s’arrêtent jamais. »
  2. « Si un problème est dans NP, alors il est difficile à résoudre. »
  3. « Tous les problèmes de P sont aussi dans NP. »
  4. « NP signifie “non polynomial”. »
  5. « Si on prouvait que P = NP, la cryptographie RSA serait menacée. »
  6. « Le théorème de Rice dit que toute question sur un programme est indécidable. »
Correction
  1. Faux. Le problème de l’arrêt ne dit pas que certains programmes ne s’arrêtent pas (cela, on le sait déjà avec les boucles infinies). Il dit qu’aucun algorithme ne peut prédire de manière générale si un programme donné va s’arrêter ou non.

  2. Faux. Tout problème de P est aussi dans NP, et les problèmes de P ne sont pas considérés comme difficiles. NP contient des problèmes faciles (P) et des problèmes potentiellement difficiles (NP-complets).

  3. Vrai. Si un problème peut être résolu en temps polynomial, alors sa solution peut aussi être vérifiée en temps polynomial. Donc P ⊂ NP.

  4. Faux. NP signifie « Non-déterministe Polynomial », c’est-à-dire résolu en temps polynomial par une machine de Turing non déterministe.

  5. Vrai. La sécurité du RSA repose sur le fait que la factorisation de grands nombres est considérée comme difficile (pas dans P). Si P = NP, la factorisation aurait un algorithme polynomial, ce qui rendrait le RSA vulnérable.

  6. Faux. Le théorème de Rice concerne les questions sémantiques (sur le comportement) et non triviales. Les questions syntaxiques (sur la forme du code) restent décidables. Par exemple, « ce programme contient-il une boucle for ? » est décidable.

Exercice 7 – Réduction au problème de l’arrêt

On veut montrer que le problème suivant est indécidable : « étant donné un programme prog, est-ce que prog(0) renvoie la valeur 42 ? »

  1. Supposons qu’il existe un programme test42(prog) qui renvoie True si prog(0) renvoie 42, et False sinon. Construire, à partir de test42, un programme capable de résoudre le problème de l’arrêt.
  2. En déduire que le problème « prog(0) renvoie-t-il 42 ? » est indécidable.

Indication : pour un programme p et une entrée x donnés, construire un nouveau programme q tel que q(0) renvoie 42 si et seulement si p(x) s’arrête.

Correction
  1. Soit p un programme et x une entrée. On construit le programme q suivant :

    def q(n):
        p(x)       # on exécute p(x) ; si p(x) ne s'arrête pas, q non plus
        return 42   # atteint seulement si p(x) s'arrête
    

    Si p(x) s’arrête, alors q(0) exécute p(x) puis renvoie 42. Donc test42(q) renvoie True.

    Si p(x) ne s’arrête pas, alors q(0) boucle indéfiniment et ne renvoie jamais 42. Donc test42(q) renvoie False.

    On a donc : test42(q) renvoie True si et seulement si p(x) s’arrête. Autrement dit, test42 permet de résoudre le problème de l’arrêt.

  2. Or le problème de l’arrêt est indécidable. Si test42 existait, on pourrait résoudre le problème de l’arrêt, ce qui est impossible. Donc test42 ne peut pas exister : le problème « prog(0) renvoie-t-il 42 ? » est indécidable.

C’est un exemple de réduction : on a montré que résoudre le nouveau problème permettrait de résoudre un problème déjà connu comme indécidable.

Exercice 8 – Vérification vs résolution (classe NP)

Le problème du sudoku 9×9 est NP-complet.

  1. Expliquer ce que signifie « la vérification d’une solution est polynomiale » pour le sudoku. Quelle est la complexité de cette vérification ?
  2. Pourquoi ne suffit-il pas de « vérifier toutes les grilles possibles » pour résoudre un sudoku ? Estimer le nombre de grilles à tester.
  3. Le problème du tri d’une liste est dans P. Est-il aussi dans NP ? Justifier.
Correction
  1. Vérifier une grille de sudoku remplie consiste à contrôler que chaque ligne, chaque colonne et chaque bloc 3×3 contient les chiffres de 1 à 9 sans répétition. Il y a 27 groupes de 9 cases à vérifier, soit un nombre fixe de vérifications : la complexité est \(\mathcal{O}(1)\) pour une grille 9×9 (ou \(\mathcal{O}(n^2)\) pour une grille \(n \times n\), ce qui reste polynomial).

  2. Essayer toutes les grilles possibles reviendrait à tester jusqu’à \(9^{81} \approx 2 \times 10^{77}\) configurations (en réalité moins avec les contraintes, mais c’est un nombre astronomique). C’est une approche exponentielle, inenvisageable en pratique.

  3. Oui, le tri est aussi dans NP. Puisque P ⊂ NP, tout problème résolu en temps polynomial est aussi vérifiable en temps polynomial. Concrètement, vérifier qu’une liste est triée se fait en \(\mathcal{O}(n)\) (parcourir la liste et vérifier que chaque élément est inférieur ou égal au suivant).