INTRODUCTION |
ILe nombre de problèmes qu’il est possible de résoudre par ordinateur est de plus en plus important. Dans ce chapitre, nous serons cependant confrontés à des problèmes qui se laissent formuler très aisément mais qui ne pourront sans doute jamais être résolus de manière algorithmique. Il y a de fortes chances pour que cela ne change pas malgré la croissance rapide de la puissance de traitement des ordinateurs et toutes les recherches scientifiques qui se consacrent à ces problèmes.
|
PROBLÈMES INSOLUBLES |
Il reste encore aujourd’hui de nombreux problèmes non résolus alors même qu’ils sont très faciles à énoncer et qu’ils présentent un intérêt conséquent en pratique. L’un de ces problèmes, nommé le problème de la somme des sous-ensembles, peut être formulé de la manière suivante [plus...Le problème de la somme des sous-ensembles est un cas particulier du problème du sac à dos]:
from gamegrid import * import itertools coins = ["one", "two", "five", "ten", "twenty", "fifty"] def value(coin): if coin == "one": return 1 if coin == "two": return 2 if coin == "five": return 5 if coin == "ten": return 10 if coin == "twenty": return 20 if coin == "fifty": return 50 return 0 def getSum(moneybag): count = 0 for coin in moneybag: count += value(coin) return count def showMoneybag(moneybag, y): x = 0 for coin in moneybag: loc = Location(x, y) removeActor(getOneActorAt(loc)) coinActor = Actor("sprites/" + coin + "cent.png") addActor(coinActor, loc) x += 1 addActor(TextActor(str(getSum(moneybag))), Location(x, y)) makeGameGrid(8, 20, 40, False) setBgColor(Color.white) show() n = 6 k = 1 while k <= n: combinations = list(itertools.combinations(coins, k)) setTitle("(n, k) = (" + str(n) + ", " + str(k) + ") nb = " + str(len(combinations))) y = 0 for moneybag in combinations: showMoneybag(moneybag, y) y += 1 getKeyCodeWait() removeAllActors() k += 1
|
MEMENTO |
La fonction combinations() du module itertools permet d’obtenir facilement toutes les combinaisons de k éléments que l’on peut fabriquer à partir des éléments d’une liste de longueur n. Il est cependant nécessaire de convertir la valeur de retour en une liste pour en extraire une à une chacune des combinaisons sous forme de tuple. Les combinaisons ainsi obtenues sont ordonnées selon un ordre naturel semblable à celui que l’on obtiendrait si l’on avait fait le travail à la main. On peut calculer exactement le nombre de combinaisons de longueur k issues d’un ensemble de n éléments grâce au fameux coefficient binomial : où n! est la factorielle de n, à savoir le produit de tous les nombres de 1 à n. Pour n=6, on pourrait avoir 6, 15, 20, 15, 6, 1 et, de ce fait, un total de 63 combinaisons |
from gamegrid import * import itertools coins = ["one", "one", "one", "two", "five", "five", "ten", "ten", "ten", "ten", "twenty", "twenty", "fifty", "fifty", "fifty"] def value(coin): if coin == "one": return 1 if coin == "two": return 2 if coin == "five": return 5 if coin == "ten": return 10 if coin == "twenty": return 20 if coin == "fifty": return 50 return 0 def getSum(moneybag): count = 0 for coin in moneybag: count += value(coin) return count def showMoneybag(moneybag, y): x = 0 for coin in moneybag: loc = Location(x, y) removeActor(getOneActorAt(loc)) coinActor = Actor("sprites/" + coin + "cent.png") addActor(coinActor, loc) x += 1 addActor(TextActor(str(getSum(moneybag))), Location(x, y)) makeGameGrid(15, 20, 40, False) setBgColor(Color.white) show() target = 100 k = 1 result = [] count = 0 while k <= len(coins): combinations = tuple(itertools.combinations(coins, k)) nb = len(combinations) for moneybag in combinations: count += 1 count = getSum(moneybag) if count == target: if not moneybag in result: result.append(moneybag) k += 1 y = 0 for moneybag in result: showMoneybag(moneybag, y) y += 1 setTitle("Step: " + str(count) + ". number of solutions for the count " + str(target) + ": " + str(len(result)))
|
MEMENTO |
Pour un nombre restreint de 15 pièces de monnaie, une méthode par énumération nécessite déjà la bagatelle de 32'767 étapes pour résoudre le problème de la somme des sous-ensembles. On peut être tout fou d’être en mesure de développer un programme qui s’acquittera de cette tâche très rapidement mais on déchantera rapidement lorsque l’on sera confronté à un nombre légèrement supérieur de pièces de monnaies, par exemple 50 ou 100. Si l’on compte le nombre de pas nécessaires pour un porte-monnaie comptant n pièces et que l’on affiche ce résultat dans un graphique lorsque n augmente, on constate qu’il y a une véritable explosion combinatoire pour n=20 qui dépasse tout ce qui est imaginable avec les ordinateurs actuels. plus... Le livre Sieben Wundern der Informatik de Hromkovic présente d’autres problèmes qui poussentles ordinateurs dans leur ultime retranchement"]. |
from gpanel import * from math import factorial z = 100 def nbCombi(n, k): return factorial(n) / factorial(k) / factorial(n - k) makeGPanel(-5, 55, -1e5, 1.1e6) drawGrid(0, 50, 0, 1e6, "gray") setColor("black") lineWidth(2) for n in range(2, z + 1): count = 0 for k in range(1, n): count += nbCombi(n, k) print "n =", n, ", nb =", count if n == 2: move(n, count) else: draw(n, count) print "Runtime with 10^9 operations per second:", count / 3.142e16, "years" print "or:", int(count / 2e20), "times the age of the universe" |
MEMENTO |
Si l’on utilise la méthode de l’énumération, le problème de la somme des sous-ensembles est déjà insoluble pour un nombre relativement faible d’éléments, alors même que l’algorithme de résolution est connu. Il reste encore à savoir s’il n’existerait pas des algorithmes qualitativement très supérieurs dont la complexité temporelle serait une puissance de n (complexité polynomiale) comme le sont les algorithmes de tri vus dans le chapitre précédent. Malheureusement, personne n’a jusqu’à présent trouvé un tel algorithme pour le problème de la somme des sous-ensembles et on part en général du principe qu’il n’y en pas. Par contre, il n’existe pas non plus de preuve qu’un tel algorithme n’existe pas. On sait du moins de l’informatique théorique qu’il existe de nombreux problèmes de la même classe de difficulté et que si l’on trouve une méthode efficace de résolution pour l’un de ces problèmes, alors tous les problèmes de cette difficulté sont d’emblée résolubles à partir de cette méthode [plus... On dit de cette classe de problèmes qu’il sont NP-complets]. |
PROBLÈMES INDÉCIDABLES |
Les limites de l’esprit humain et de la technologie informatique se révèlent également dans un contexte différent de celui de la théorie de la complexité. Le mathématicien et théoricien des nombres Lothar Collatz s’est penché sur certaines suites de nombres et a formulé en 1939 la question suivante: Prenons une suite de nombres dont le terme initial est un nombre naturel quelconque dont les termes consécutifs sont construits à partir des règles de récurrence suivantes :
Question : Cette suite converge-t-elle toujours vers 1 quel que soit le terme initial n? Collatz ainsi que de nombreux autres théoriciens des nombres et chercheurs en informatique ont tenté de répondre à cette question puisque même les plus puissants ordinateurs de la planète obtiennent sans arrêt des suites qui atteignent le nombre 1 (les suites ne convergent pas car elles répètent de manière infinie la séquence 4, 2, 1). Il apparaît donc vraisemblable que le théorème suivant soit vérifié: Pour tout terme initial n, la suite 3n+1 atteint le nombre 1 en un nombre fini d’étapes. On peut faire soi-même l’expérience et parcourir la suite (3n+1) à l’aide d’un programme informatique pour un nombre initial n quelconque. from gpanel import * def collatz(n): while n != 1: if n % 2 == 0: n = n // 2 else: n = 3 * n + 1 print n, print "Result 1" while True: n = inputInt("Enter a start number:") collatz(n)
|
MEMENTO |
En Python, il est même possible de calculer les termes de la suite 3n+1 pour un terme initial très grand. Selon le théorème précédent, la suite en question va toujours finir, après un nombre suffisamment grand mais fini d’itérations, par tomber sur le nombre 1. Évidemment, ceci ne constitue aucunement une preuve de la question posée par Collatz. |
from gpanel import * def collatz(n): nb = 0 while n != 1: nb += 1 if n % 2 == 0: n = n // 2 else: n = 3 * n + 1 return nb z = 10000 # max n yval = [0] * (z + 1) for n in range(1, z + 1): yval[n] = collatz(n) ymax = (max(yval) // 100 + 1) * 100 makeGPanel(-0.1 * z, 1.1 * z, -0.1 * ymax, 1.1 * ymax) title("Collatz Assumption") drawGrid(0, z, 0, ymax, "gray") for x in range(1, z + 1): move(x, yval[x]) fillCircle(z / 200)
|
MEMENTO |
L’hypothèse de Collatz est un problème vraiment très difficile. En supposant que l’hypothèse soit vraie, il n’est pas possible de la prouver en effectuant un très grand nombre de tests par ordinateurs pour des nombres n toujours plus grands. Il est même possible que l’hypothèse soit vraie mais qu’il ne soit pas possible de prouver sa véracité. En 1931, le mathématicien Kurt Gödel a montré avec son théorème d’incomplétude qu’il peut exister des affirmations qui sont vraies à l’intérieur d’une théorie mais dont la véracité ne peut pas être prouvée. Le problème de Collatz peut également être formulé comme un problème de décision: Un algorithme qui calcule les termes de la suite 3n+1 et qui s’arrête à 1 s’arrête-t-il vraiment pour tous les termes initiaux possibles? On peut essayer de résoudre cette question par ordinateur. Malheureusement, cette tentative est probablement complètement vaine elle aussi car le grand mathématicien Alan Turing a déjà prouvé avec son problème de l’arrêt (Halting Problem en anglais) qu’il n’existera jamais un algorithme général permettant de décider si un programme va s’arrêter quelles que soient les données qu’on lui fournit en entrée. Il se peut donc que l’hypothèse de Collatz soit correcte mais qu’elle constitue un problème indécidable. |