Existe-t-il des algorithmes O(1/n)?
Ou quelque chose d'autre qui est inférieur à O (1)?
Cette question n'est pas aussi stupide que cela puisse paraître. Du moins théoriquement, quelque chose comme O (1/n) est tout à fait logique lorsque nous prenons la définition mathématique de la notation Big O :
Maintenant, vous pouvez facilement remplacer g (x) par 1/x… Il est évident que la définition ci-dessus est toujours valable pour certains f.
Pour estimer la croissance asymptotique au moment de l'exécution, cette méthode est moins viable… un algorithme significatif ne peut pas être plus rapide à mesure que le nombre d'entrées augmente. Bien sûr, vous pouvez construire un algorithme arbitraire pour y parvenir, par exemple. le suivant:
def get_faster(list):
how_long = (1 / len(list)) * 100000
sleep(how_long)
Clairement, cette fonction passe moins de temps à mesure que la taille d’entrée augmente… au moins jusqu’à une limite, imposée par le matériel (précision des nombres, minimum de temps que sleep
peut attendre, délai de traitement des arguments, etc.): une borne inférieure constante donc, en fait, la fonction ci-dessus toujours a le temps d'exécution O (1).
Mais il y a en fait des algorithmes du monde réel où l'exécution peut diminuer (au moins partiellement) lorsque la taille d'entrée augmente. Notez que ces algorithmes pas présentent un comportement d’exécution inférieur à O (1). Pourtant, ils sont intéressants. Par exemple, prenons le très simple algorithme de recherche de texte de Horspool . Ici, le temps d'exécution attendu diminuera à mesure que la longueur du motif de recherche augmentera (mais la longueur de la meule de foin augmentera de nouveau).
Oui.
Il existe précisément un algorithme avec le temps d'exécution O (1/n), l'algorithme "vide".
Pour qu'un algorithme soit O(1/n), cela signifie qu'il s'exécute de manière asymptotique en moins d'étapes que l'algorithme consistant en une seule instruction. Si elle s'exécute en moins d'une étape pour tout n> n0, elle ne doit contenir aucune instruction pour ceux-ci. Etant donné que cocher 'si n> n0' coûte au moins une instruction, il ne doit contenir aucune instruction pour tous les n.
Résumer: Le seul algorithme qui est O(1/n) est l'algorithme vide, composé de no instruction.
Ce n'est pas possible. La définition de Big-O est la pas plus grande que l'inégalité:
A(n) = O(B(n))
<=>
exists constants C and n0, C > 0, n0 > 0 such that
for all n > n0, A(n) <= C * B(n)
Donc, B(n) est en fait la valeur maximale. Par conséquent, si elle diminue à mesure que n augmente, l'estimation ne changera pas.
sharptooth est correct, O(1) est la meilleure performance possible. Cependant, cela n'implique pas une solution rapide, mais simplement une solution temporelle.
Une variante intéressante, et peut-être ce qui est réellement suggéré, est de savoir quels problèmes deviennent plus faciles avec l’augmentation de la population. Je peux penser à une réponse, même si elle est artificielle et ironique:
Est-ce que deux personnes dans un set ont le même anniversaire? Si n dépasse 365, retourne vrai. Bien que pour moins de 365, c'est O (n ln n). Ce n’est peut-être pas une bonne réponse car le problème ne devient pas lentement plus facile mais devient simplement O(1) pour n> 365.
De mon apprentissage précédent de la grande notation O, même si vous avez besoin d’une étape (comme vérifier une variable, faire une affectation), c’est O (1).
Notez que O(1) est identique à O (6), car la "constante" n'a pas d'importance. C'est pourquoi nous disons que O(n) est identique à O (3n).
Donc, si vous avez besoin d’une étape, c’est O (1) ... et puisque votre programme a besoin d’au moins une étape, le minimum qu’un algorithme puisse utiliser est O (1). À moins que nous ne le fassions pas, alors c'est O (0), je pense? Si nous faisons quoi que ce soit, alors c'est O (1), et c'est le minimum que cela puisse faire.
(Si nous choisissons de ne pas le faire, alors cela peut devenir une question zen ou tao ... dans le domaine de la programmation, O(1) est toujours le minimum).
Ou que diriez-vous de ceci:
programmeur : patron, j'ai trouvé un moyen de le faire à l’époque O(1)!
patron : inutile de le faire, nous sommes en faillite ce matin.
programmeur : oh alors, il devient O (0).
Non, ce n'est pas possible:
Comme n tend vers l'infini dans 1/n, nous obtenons finalement 1/(inf), qui est effectivement 0.
Ainsi, la grande classe du problème serait O(0) avec un n massif, mais plus proche du temps constant avec un n bas. Ce n'est pas raisonnable, car la seule chose qui peut être faite plus rapidement que le temps constant est:
void nothing() {};
Et même cela est discutable!
Dès que vous exécutez une commande, vous êtes au moins dans O (1), alors non, nous ne pouvons pas avoir une grosse classe de O (1/n)!
J'utilise souvent O(1/n) pour décrire les probabilités de plus en plus petites au fur et à mesure que les entrées grossissent - par exemple, la probabilité qu'une bonne pièce de monnaie se termine en queue de log2 (n) est O (1/n ).
Qu'en est-il de ne pas exécuter la fonction du tout (NOOP)? ou en utilisant une valeur fixe. Cela compte-t-il?
O (1) signifie simplement "temps constant".
Lorsque vous ajoutez une sortie précoce à une boucle [1], vous êtes (en notation Big-O) en train de transformer un algorithme O(1) en O (n), mais en l'accélérant.
L'astuce est en général l'algorithme à temps constant est le meilleur, et linéaire est meilleur qu'exponentiel, mais pour de petites quantités de n, l'algorithme exponentiel pourrait en réalité être plus rapide.
1: en supposant une longueur de liste statique pour cet exemple
Je crois que les algorithmes quantiques peuvent effectuer plusieurs calculs "à la fois" via une superposition ...
Je doute que ce soit une réponse utile.
Pour ceux qui liront cette question et voudront comprendre le sujet de la conversation, ceci pourrait aider:
| |constant |logarithmic |linear| N-log-N |quadratic| cubic | exponential |
| n | O(1) | O(log n) | O(n) |O(n log n)| O(n^2) | O(n^3) | O(2^n) |
| 1 | 1 | 1 | 1| 1| 1| 1 | 2 |
| 2 | 1 | 1 | 2| 2| 4| 8 | 4 |
| 4 | 1 | 2 | 4| 8| 16| 64 | 16 |
| 8 | 1 | 3 | 8| 24| 64| 512 | 256 |
| 16 | 1 | 4 | 16| 64| 256| 4,096 | 65536 |
| 32 | 1 | 5 | 32| 160| 1,024| 32,768 | 4,294,967,296 |
| 64 | 1 | 6 | 64| 384| 4,069| 262,144 | 1.8 x 10^19 |
beaucoup de gens ont eu la bonne réponse (Non) Voici un autre moyen de le prouver: Pour avoir une fonction, vous devez l'appeler et vous devez lui renvoyer une réponse. Cela prend un certain temps constant. MÊME SI le reste du traitement a pris moins de temps pour les entrées plus volumineuses, l’impression de la réponse (qui peut être supposée être un bit) prend au moins un temps constant.
Si la solution existe, elle peut être préparée et consultée en temps constant = immédiatement. Par exemple, utilisez une structure de données LIFO si vous savez que la requête de tri est par ordre inverse. Ensuite, les données sont déjà triées, étant donné que le modèle approprié (LIFO) a été choisi.
Vous ne pouvez pas descendre en dessous de O (1), cependant O(k) où k est inférieur à N est possible. Nous les avons appelés algorithmes de temps sous-linéaires . Dans certains problèmes, l'algorithme temporel sublinaire ne peut que donner des solutions approximatives à un problème particulier. Cependant, parfois, une solution approximative est très bien, probablement parce que le jeu de données est trop volumineux, ou qu'il est trop coûteux en calcul de tout calculer.
Quels problèmes deviennent plus faciles avec l'augmentation de la population? Une réponse est un peu comme bittorrent où la vitesse de téléchargement est une fonction inverse du nombre de nœuds. Contrairement à une voiture, qui ralentit le chargement, un réseau de partage de fichiers comme bittorrent accélère le nombre de nœuds connectés.
Comme il a été souligné, en dehors de la possible exception de la fonction null, il ne peut y avoir aucune fonction O(1/n)
, car le temps nécessaire devra s'approcher de 0.
Bien sûr, il existe certains algorithmes, comme celui défini par Konrad, qui semblent devoir être inférieurs à O(1)
dans au moins un sens.
def get_faster(list):
how_long = 1/len(list)
sleep(how_long)
Si vous souhaitez étudier ces algorithmes, vous devez définir votre propre mesure asymptotique ou votre propre notion de temps. Par exemple, dans l’algorithme ci-dessus, je pourrais autoriser l’utilisation d’un certain nombre d’opérations "libres" plusieurs fois. Dans l'algorithme ci-dessus, si je définis t 'en excluant le temps pour tout sauf le sommeil, alors t' = 1/n, qui est O (1/n). Il existe probablement de meilleurs exemples, car le comportement asymptotique est trivial. En fait, je suis sûr que quelqu'un peut émettre des sens qui donnent des résultats non négligeables.
O (1/n) n'est pas inférieur à O (1), cela signifie essentiellement que plus vous avez de données, plus l'algorithme est rapide. Disons que vous obtenez un tableau et remplissez-le toujours jusqu'à 10100 éléments si elle a moins que cela et ne fait rien s'il y a plus. Celui-ci n'est bien sûr pas O(1/n) mais quelque chose comme O(-n) :) Dommage que la notation O-big ne permet pas les valeurs négatives.
La plupart des autres réponses interprètent big-O comme se limitant au temps d'exécution d'un algorithme. Mais comme la question ne l’a pas mentionné, j’ai pensé qu’il valait la peine de mentionner l’autre application du big-O dans l’analyse numérique, qui concerne l’erreur.
De nombreux algorithmes peuvent être O (h ^ p) ou O (n ^ {- p}) selon que vous parlez de step-size (h) ou du nombre de divisions (n). Par exemple, dans la méthode d'Euler , vous recherchez une estimation de y(h) étant donné que vous connaissez y(0) et dy/dx (la dérivée de y). Votre estimation de y(h) est d'autant plus précise que h est proche de 0. Donc, pour trouver y(x) pour un x arbitraire, on prend l'intervalle 0 à x, on le scinde jusqu’à n morceaux et exécute la méthode d’Euler à chaque point, pour aller de y(0) à y(x/n) à y (2x/n), et ainsi de suite.
La méthode d'Euler est donc un algorithme O(h) ou O(1/n), où h est généralement interprété comme une taille de pas et n est interprété comme le nombre de fois que vous divisez un intervalle.
Vous pouvez également avoir O(1/h) dans les applications d'analyse numérique réelles, en raison de erreurs d'arrondi en virgule flottante . Plus vous réduisez votre intervalle, plus la mise en œuvre de certains algorithmes est annulée, plus les chiffres significatifs perdus, et donc plus le nombre d'erreurs propagées par l'algorithme étant important.
Pour la méthode d'Euler, si vous utilisez des points flottants, utilisez une étape et une annulation suffisamment petites et vous ajoutez un petit nombre à un grand nombre, en laissant le grand nombre inchangé. Pour les algorithmes qui calculent la dérivée en soustrayant l'un de l'autre deux nombres d'une fonction évaluée à deux positions très proches, approximant y '(x) avec (y (x + h) - y(x)/h), dans les fonctions lissées, y (x + h) se rapproche de y(x), ce qui entraîne une annulation importante et une estimation de la dérivée avec moins de chiffres significatifs. Cela se répercutera alors sur l'algorithme pour lequel vous avez besoin de la dérivée (par exemple, un problème de valeur limite).
En analyse numérique, les algorithmes d'approximation devraient avoir une complexité asymptotique sous-constante dans la tolérance d'approximation.
class Function
{
public double[] ApproximateSolution(double tolerance)
{
// if this isn't sub-constant on the parameter, it's rather useless
}
}
OK, j’y ai un peu réfléchi et il existe peut-être un algorithme qui pourrait suivre cette forme générale:
Vous devez calculer le problème de voyageur de commerce pour un graphique à 1000 nœuds. Toutefois, une liste de nœuds que vous ne pouvez pas consulter vous est également fournie. À mesure que la liste des nœuds non consultables s'allonge, le problème devient plus facile à résoudre.
Je vois un algorithme qui est O(1/n) certes à une borne supérieure:
Vous avez une grande série d'entrées qui changent en raison d'un élément externe à la routine (elles reflètent peut-être du matériel ou il peut même s'agir d'un autre cœur du processeur qui le fait.) Et vous devez en sélectionner un aléatoire mais valide.
Maintenant, si cela ne changeait pas, vous feriez simplement une liste d’articles, choisissez-en un au hasard et obtenez le temps O(1). Cependant, la nature dynamique des données empêche de dresser une liste, il vous suffit d'analyser de manière aléatoire et de tester la validité de l'analyse. (Et notez que par nature, il n’ya aucune garantie que la réponse soit toujours valide quand elle sera renvoyée. Cela pourrait toujours avoir des utilisations - disons, l’IA pour une unité dans un jeu. Elle pourrait tirer sur une cible qui a disparu de la tirant la gâchette.)
Cela a des performances dans le cas le plus défavorable de l'infini, mais une performance dans le cas moyen qui diminue à mesure que l'espace de données se remplit.
Et ça:
void FindRandomInList(list l)
{
while(1)
{
int Rand = Random.next();
if (l.contains(Rand))
return;
}
}
au fur et à mesure que la taille de la liste augmente, la durée d'exécution prévue du programme diminue.
Je suppose que moins de O(1) n'est pas possible. Tout le temps pris par algo est appelé O (1). Mais pour O(1/n), qu'en est-il de la fonction ci-dessous. (Je sais que de nombreuses variantes ont déjà été présentées dans cette solution, mais je suppose qu’elles présentent toutes des failles (non majeures, elles expliquent bien le concept). Voici donc une, juste pour les besoins de l’argumentation:
def 1_by_n(n, C = 10): #n could be float. C could be any positive number
if n <= 0.0: #If input is actually 0, infinite loop.
while True:
sleep(1) #or pass
return #This line is not needed and is unreachable
delta = 0.0001
itr = delta
while delta < C/n:
itr += delta
Ainsi, à mesure que n augmente, la fonction prendra de moins en moins de temps. De plus, il est garanti que si l'entrée est réellement 0, alors la fonction prendra une éternité pour revenir.
On pourrait faire valoir qu'il sera limité par la précision de la machine. donc sinc eit a une borne supérieure c’est O (1). Mais nous pouvons également éviter cela en prenant les entrées de n et C dans string. Et l'addition et la comparaison sont effectuées sur une chaîne. L'idée est que, avec cela, nous pouvons réduire n arbitrairement petit. Ainsi, la limite supérieure de la fonction n'est pas limitée, même lorsque nous ignorons n = 0.
Je crois aussi que nous ne pouvons pas simplement dire que le temps d'exécution est O (1/n). Mais nous devrions dire quelque chose comme O (1 + 1/n)