Qu'est-ce que le temps pseudopolynomial? En quoi diffère-t-il du temps polynomial?

101

Qu'est-ce que le temps pseudopolynomial ? En quoi diffère-t-il du temps polynomial? Certains algorithmes qui fonctionnent en temps pseudopolynomial ont des temps d'exécution tels que O (nW) (pour le problème de sac à dos 0/1 ) ou O (√n) (pour la division d'essai ); pourquoi cela ne compte-t-il pas comme un temps polynomial?

templatetypedef
la source

Réponses:

254

Pour comprendre la différence entre le temps polynomial et le temps pseudopolynomial, nous devons commencer par formaliser ce que signifie «temps polynomial».

L'intuition commune pour le temps polynomial est «le temps O (n k ) pour un certain k». Par exemple, le tri par sélection s'exécute au temps O (n 2 ), qui est le temps polynomial, tandis que la résolution par force brute TSP prend le temps O (n · n!), Qui n'est pas un temps polynomial.

Ces runtimes font tous référence à une variable n qui suit la taille de l'entrée. Par exemple, dans le tri par sélection, n fait référence au nombre d'éléments dans le tableau, tandis que dans TSP n fait référence au nombre de nœuds dans le graphique. Afin de normaliser la définition de ce que signifie réellement «n» dans ce contexte, la définition formelle de la complexité temporelle définit la «taille» d'un problème comme suit:

La taille de l'entrée d'un problème est le nombre de bits requis pour écrire cette entrée.

Par exemple, si l'entrée d'un algorithme de tri est un tableau d'entiers 32 bits, alors la taille de l'entrée serait de 32n, où n est le nombre d'entrées dans le tableau. Dans un graphe avec n nœuds et m arêtes, l'entrée pourrait être spécifiée comme une liste de tous les nœuds suivie d'une liste de tous les arêtes, ce qui nécessiterait Ω (n + m) bits.

Compte tenu de cette définition, la définition formelle du temps polynomial est la suivante:

Un algorithme s'exécute en temps polynomial si son runtime est O (x k ) pour une constante k, où x désigne le nombre de bits d'entrée donnés à l'algorithme.

Lorsque vous travaillez avec des algorithmes qui traitent des graphiques, des listes, des arbres, etc., cette définition est plus ou moins en accord avec la définition conventionnelle. Par exemple, supposons que vous ayez un algorithme de tri qui trie des tableaux d'entiers 32 bits. Si vous utilisez quelque chose comme le tri par sélection pour ce faire, le runtime, en fonction du nombre d'éléments d'entrée dans le tableau, sera O (n 2 ). Mais comment n, le nombre d'éléments dans le tableau d'entrée, correspond-il au nombre de bits d'entrée? Comme mentionné précédemment, le nombre de bits d'entrée sera x = 32n. Par conséquent, si nous exprimons le temps d'exécution de l'algorithme en termes de x plutôt que n, nous obtenons que le temps d'exécution est O (x 2 ), et donc l'algorithme s'exécute en temps polynomial.

De même, supposons que vous effectuez une recherche en profondeur d'abord sur un graphe, ce qui prend le temps O (m + n), où m est le nombre d'arêtes dans le graphe et n est le nombre de nœuds. Comment cela se rapporte-t-il au nombre de bits d'entrée donné? Eh bien, si nous supposons que l'entrée est spécifiée comme une liste de contiguïté (une liste de tous les nœuds et arêtes), alors comme mentionné précédemment, le nombre de bits d'entrée sera x = Ω (m + n). Par conséquent, le runtime sera O (x), donc l'algorithme s'exécute en temps polynomial.

Les choses s'effondrent cependant lorsque nous commençons à parler d'algorithmes qui fonctionnent sur des nombres. Considérons le problème de tester si un nombre est premier ou non. Étant donné un nombre n, vous pouvez tester si n est premier en utilisant l'algorithme suivant:

function isPrime(n):
    for i from 2 to n - 1:
        if (n mod i) = 0, return false
    return true

Quelle est donc la complexité temporelle de ce code? Eh bien, cette boucle interne s'exécute O (n) fois et effectue à chaque fois un certain travail pour calculer n mod i (en tant que borne supérieure vraiment conservatrice, cela peut certainement être fait dans le temps O (n 3 )). Par conséquent, cet algorithme global s'exécute dans le temps O (n 4 ) et peut-être beaucoup plus rapidement.

En 2004, trois informaticiens ont publié un article intitulé PRIMES is in P donnant un algorithme de temps polynomial pour tester si un nombre est premier. Il a été considéré comme un résultat historique. Alors, quel est le problème? N'avons-nous pas déjà un algorithme de temps polynomial pour cela, à savoir celui ci-dessus?

Malheureusement, nous ne le faisons pas. Rappelez-vous, la définition formelle de la complexité temporelle parle de la complexité de l'algorithme en fonction du nombre de bits d'entrée. Notre algorithme s'exécute dans le temps O (n 4 ), mais qu'est-ce que c'est en fonction du nombre de bits d'entrée? Eh bien, l'écriture du nombre n prend O (log n) bits. Par conséquent, si nous laissons x le nombre de bits nécessaires pour écrire l'entrée n, le temps d'exécution de cet algorithme est en fait O (2 4x ), qui n'est pas un polynôme en x.

C'est le cœur de la distinction entre le temps polynomial et le temps pseudopolynomial. D'une part, notre algorithme est O (n 4 ), qui ressemble à un polynôme, mais d'autre part, sous la définition formelle du temps polynomial, ce n'est pas un temps polynomial.

Pour comprendre pourquoi l'algorithme n'est pas un algorithme en temps polynomial, pensez à ce qui suit. Supposons que je souhaite que l'algorithme fasse beaucoup de travail. Si j'écris une entrée comme celle-ci:

10001010101011

il faudra alors un certain temps, par exemple T, pour terminer. Si j'ajoute maintenant un seul bit à la fin du nombre, comme ceci:

100010101010111

Le runtime sera désormais (dans le pire des cas) 2T. Je peux doubler la quantité de travail de l'algorithme simplement en ajoutant un bit de plus!

Un algorithme s'exécute en temps pseudopolynomial si le temps d'exécution est un polynôme dans la valeur numérique de l'entrée , plutôt que dans le nombre de bits requis pour le représenter. Notre premier algorithme de test est un algorithme de temps pseudopolynomial, puisqu'il s'exécute dans le temps O (n 4 ), mais ce n'est pas un algorithme de temps polynomial car en fonction du nombre de bits x nécessaires pour écrire l'entrée, le runtime est O (2 4x ). La raison pour laquelle le papier "PRIMES is in P" était si significatif était que son temps d'exécution était (à peu près) O (log 12 n), qui en fonction du nombre de bits est O (x 12 ).

Alors pourquoi est-ce important? Eh bien, nous avons de nombreux algorithmes de temps pseudopolynomiaux pour factoriser les entiers. Cependant, ces algorithmes sont, techniquement, des algorithmes à temps exponentiel. Ceci est très utile pour la cryptographie: si vous souhaitez utiliser le cryptage RSA, vous devez pouvoir être sûr que nous ne pouvons pas factoriser facilement les nombres. En augmentant le nombre de bits dans les nombres à une valeur énorme (par exemple, 1024 bits), vous pouvez rendre le temps nécessaire à l'algorithme de factorisation en temps pseudopolynomial si grand qu'il serait complètement et totalement impossible de factoriser le Nombres. Si, par contre, nous pouvons trouver un algorithme de factorisation en temps polynomial , ce n'est pas forcément le cas. L'ajout de plus de bits peut faire augmenter considérablement le travail, mais la croissance ne sera qu'une croissance polynomiale, pas une croissance exponentielle.

Cela dit, dans de nombreux cas, les algorithmes de temps pseudopolynomiaux conviennent parfaitement car la taille des nombres ne sera pas trop grande. Par exemple, le tri de comptage a le temps d'exécution O (n + U), où U est le plus grand nombre du tableau. Il s'agit d'un temps pseudopolynomial (car la valeur numérique de U nécessite l'écriture de O (log U) bits, donc le temps d'exécution est exponentiel dans la taille d'entrée). Si nous contraignons artificiellement U afin que U ne soit pas trop grand (disons, si nous laissons U être 2), alors le temps d'exécution est O (n), qui est en fait le temps polynomial. Voici comment fonctionne le tri par base : en traitant les nombres un bit à la fois, le temps d'exécution de chaque tour est O (n), donc le temps d'exécution global est O (n log U). C'est en fait temps polynomial, car l'écriture de n nombres à trier utilise Ω (n) bits et la valeur de log U est directement proportionnelle au nombre de bits requis pour écrire la valeur maximale dans le tableau.

J'espère que cela t'aides!

templatetypedef
la source
27
Cela devrait être l'explication de Wikipedia ...
Sandro Meier
4
Pourquoi isPrimela complexité de s est-elle estimée comme O (n ^ 4) et pas simplement O (n)? Je ne comprends pas. À moins que la complexité de n mod isoit O (n ^ 3) .... ce qui ne l'est sûrement pas.
fons le
4
@Nobody Normalement, nous considérons le coût de la modification de deux nombres comme O (1), mais lorsque vous avez affaire à des nombres arbitrairement grands, le coût de la multiplication augmente en fonction de la taille des nombres eux-mêmes. Pour être extrêmement prudent, j'ai prétendu que vous pouvez calculer le modding par un nombre inférieur à n comme O (n ^ 3), ce qui est un surdénombrement grossier mais pas trop mal.
templatetypedef
1
@AndrewFlemming Cela dépend de la façon dont le nombre est représenté en mémoire. Je supposais que nous utilisions une représentation binaire standard, où nous aurions besoin de log_2 n bits pour représenter le nombre. Vous avez raison de dire que changer la représentation sous-jacente modifiera le temps d'exécution en fonction de la taille de l'entrée, cependant.
templatetypedef
1
Choisir O (n ^ 3) pour n mod iest trop prudent. La synchronisation de modest une fonction du nombre de bits dans n, pas le nlui - même, donc il devrait être O ((log n) ^ 3).
dasblinkenlight
2

La complexité temporelle pseudo-polynomiale signifie polynôme dans la valeur / amplitude de l'entrée mais exponentielle dans la taille de l'entrée.

Par taille, nous entendons le nombre de bits requis pour écrire l'entrée.

À partir du pseudo-code de sac à dos, nous pouvons trouver la complexité temporelle O (nW).

// Input:
// Values (stored in array v) 
// Weights (stored in array w)
// Number of distinct items (n) //
Knapsack capacity (W) 
for w from 0 to W 
    do   m[0, w] := 0 
end for  
for i from 1 to n do  
        for j from 0 to W do
               if j >= w[i] then 
                      m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i]) 
              else 
                      m[i, j] := m[i-1, j]
              end if
       end for 
end for

Ici, W n'est pas polynomial dans la longueur de l'entrée, ce qui le rend pseudo-polynomial.

Soit s le nombre de bits requis pour représenter W

i.e. size of input= s =log(W) (log= log base 2)
-> 2^(s)=2^(log(W))
-> 2^(s)=W  (because  2^(log(x)) = x)

Maintenant, running time of knapsack= O (nW) = O (n * 2 ^ s) qui n'est pas polynomial.

Adi agarwal
la source