Y a-t-il des cas où vous préféreriez un algorithme de complexité temporelle big-O supérieur à l'algorithme inférieur?

242

Y a-t-il des cas où vous préféreriez la O(log n)complexité O(1)temporelle à la complexité temporelle? Ou O(n)pour O(log n)?

Avez-vous des exemples?

V.Leymarie
la source
67
Je préférerais un O(log n)algorithme à un O(1)algorithme si je comprends bien le premier, mais pas le second ...
Codor
14
Il y a des tonnes de structures de données impraticables avec des opérations O (1) de l'informatique théorique. Un exemple serait select () sur les vecteurs de bits, qui peuvent être pris en charge dans un espace supplémentaire o (n) et O (1) par opération, en utilisant 5 couches d'indirection. La recherche binaire simple combinée à O (1) rank () s'avère plus rapide dans la pratique selon l'auteur de la Succinct Data Structure Library
Niklas B.
17
Une complexité asymptotique plus faible ne garantit pas des temps d'exécution plus rapides. Multiplier la matrice de recherche pour un exemple concret.
Connor Clark
54
Aussi ... tout algorithme peut être converti en O (1), étant donné une recherche de table suffisamment grande;)
Connor Clark
19
@Hoten - Cela suppose que la recherche de table est O (1), ce qui n'est pas du tout donné pour la taille des tables dont vous parlez! :)
Jander

Réponses:

267

Il peut y avoir de nombreuses raisons de préférer un algorithme avec une complexité de temps O plus élevée à celui inférieur:

  • la plupart du temps, une complexité plus faible du big-O est plus difficile à atteindre et nécessite une implémentation qualifiée, beaucoup de connaissances et beaucoup de tests.
  • big-O cache les détails d'une constante : l'algorithme qui fonctionne 10^5est meilleur du point de vue big-O que 1/10^5 * log(n)( O(1)vs O(log(n)), mais pour la plupart raisonnable, nle premier fonctionnera mieux. Par exemple, la meilleure complexité pour la multiplication matricielle est, O(n^2.373)mais la constante est si élevée qu'aucune bibliothèque informatique (à ma connaissance) ne l'utilise.
  • big-O est logique lorsque vous calculez sur quelque chose de grand. Si vous devez trier un tableau de trois nombres, peu importe que vous utilisiez O(n*log(n))ou un O(n^2)algorithme.
  • Parfois, l'avantage de la complexité temporelle en minuscules peut être vraiment négligeable. Par exemple, il y a un arbre de tango de structure de données qui donne une O(log log N)complexité temporelle pour trouver un élément, mais il y a aussi un arbre binaire qui le trouve dans O(log n). Même pour un grand nombre de n = 10^20différences, la différence est négligeable.
  • la complexité du temps n'est pas tout. Imaginez un algorithme qui s'exécute O(n^2)et nécessite de la O(n^2)mémoire. Cela peut être préférable dans le O(n^3)temps et dans l' O(1)espace lorsque le n n'est pas vraiment grand. Le problème est que vous pouvez attendre longtemps, mais vous doutez fortement que vous puissiez trouver une RAM suffisamment grande pour l'utiliser avec votre algorithme
  • la parallélisation est une bonne fonctionnalité dans notre monde distribué. Il existe des algorithmes qui sont facilement parallélisables, et certains ne se parallélisent pas du tout. Parfois, il est logique d'exécuter un algorithme sur 1000 machines de base avec une complexité plus élevée que d'utiliser une machine avec une complexité légèrement meilleure.
  • dans certains endroits (sécurité), une complexité peut être une exigence. Personne ne veut avoir un algorithme de hachage qui peut hacher incroyablement rapide (car alors d'autres personnes peuvent vous brutaliser beaucoup plus rapidement)
  • bien que cela ne soit pas lié au changement de complexité, mais certaines des fonctions de sécurité doivent être écrites de manière à empêcher une attaque temporelle . Ils restent pour la plupart dans la même classe de complexité, mais sont modifiés de manière à ce qu'il soit toujours pire de faire quelque chose. Un exemple est de comparer que les chaînes sont égales. Dans la plupart des applications, il est logique de rompre rapidement si les premiers octets sont différents, mais en termes de sécurité, vous attendez toujours la fin pour annoncer la mauvaise nouvelle.
  • quelqu'un a breveté l'algorithme de moindre complexité et il est plus économique pour une entreprise d'utiliser une complexité plus élevée que de payer de l'argent.
  • certains algorithmes s'adaptent bien à des situations particulières. Le tri par insertion, par exemple, a une complexité temporelle moyenne de O(n^2), pire que le tri rapide ou le fusionnement, mais en tant qu'algorithme en ligne, il peut trier efficacement une liste de valeurs à mesure qu'elles sont reçues (en tant qu'entrée utilisateur) où la plupart des autres algorithmes ne peuvent fonctionner efficacement sur une liste complète de valeurs.
Salvador Dali
la source
6
De plus, j'ai vu à quelques reprises que les gens se concentraient sur le big-O de leur algorithme central, mais ignoraient les coûts de configuration. Construire une table de hachage, par exemple, peut être plus coûteux que de parcourir un tableau de façon linéaire si vous n'avez pas besoin de le faire encore et encore. En fait, en raison de la façon dont les processeurs modernes sont construits, même quelque chose comme la recherche binaire peut être aussi rapide sur les tableaux triés qu'une recherche linéaire - le profilage est une nécessité.
Luaan
@Luaan "En fait, en raison de la façon dont les processeurs modernes sont construits, même quelque chose comme la recherche binaire peut être aussi rapide sur les tableaux triés qu'une recherche linéaire - le profilage est une nécessité." Intéressant! Pouvez-vous expliquer comment la recherche binaire et la recherche linéaire peuvent prendre le même temps sur un processeur moderne?
DJG
3
@Luaan - Peu importe, j'ai trouvé ceci: schani.wordpress.com/2010/04/30/linear-vs-binary-search
DJG
2
@DenisdeBernardy: Non, en fait ce n'est pas le cas. Ils pourraient être des algorithmes en P. Et même si ceux-ci n'étaient pas, selon des définitions raisonnables de ce que signifie paralléliser, cela n'impliquerait pas non plus P! = NP. Rappelez-vous également que la recherche dans l'espace des exécutions possibles d'une machine de turing non déterministe est assez parallélisable.
einpoklum
228

Il y a toujours la constante cachée, qui peut être inférieure sur l' algorithme O (log n ). Il peut donc fonctionner plus rapidement dans la pratique pour les données réelles.

Il y a aussi des problèmes d'espace (par exemple courir sur un grille-pain).

Il y a aussi le souci du temps du développeur - O (log n ) peut être 1000 × plus facile à implémenter et à vérifier.

Alistra
la source
Bien merci. Je pensais qu'il pourrait également être utile d'envisager un algorithme O (logn) pour assurer la stabilité du programme (par exemple dans les arbres binaires auto-équilibrés)
V.Leymarie
16
Un exemple auquel je peux penser: pour un petit tableau trié, il serait plus facile et plus compact pour le programmeur d'implémenter une fonction de recherche binaire que d'écrire une implémentation de carte de hachage complète et de l'utiliser à la place.
Colonel trente-deux
5
Un exemple de complexité: trouver la médiane d'une liste non triée est facile à faire dans O (n * log n) mais difficile à faire dans O (n).
Paul Draper
1
-1, ne mettez pas de bûches dans votre grille-pain ... Blague à part, c'est parfait. lg nest tellement, donc, si proche de kgrande nque la plupart des opérations ne remarqueraient jamais la différence.
corsiKa
3
Il y a aussi le fait que les complexités algorithmiques que la plupart des gens connaissent ne prennent pas en compte les effets de cache. Rechercher quelque chose dans un arbre binaire est O (log2 (n)) selon la plupart des gens, mais en réalité c'est bien pire parce que les arbres binaires ont une mauvaise localité.
Doval
57

Je suis surpris que personne n'ait encore mentionné d'applications liées à la mémoire.

Il peut y avoir un algorithme qui a moins d'opérations en virgule flottante soit en raison de sa complexité (ie O (1) < O (log n )) soit parce que la constante devant la complexité est plus petite (ie 2 n 2 <6 n 2 ) . Quoi qu'il en soit, vous pouvez toujours préférer l'algorithme avec plus de FLOP si l'algorithme FLOP inférieur est plus lié à la mémoire.

Ce que je veux dire par «lié à la mémoire», c'est que vous accédez souvent à des données qui sont constamment hors du cache. Afin de récupérer ces données, vous devez extraire la mémoire de votre espace mémoire réel dans votre cache avant de pouvoir effectuer votre opération dessus. Cette étape de récupération est souvent assez lente - beaucoup plus lente que votre opération elle-même.

Par conséquent, si votre algorithme nécessite plus d'opérations (pourtant ces opérations sont effectuées sur des données qui sont déjà dans le cache [et donc pas de récupération requise]), il surclassera toujours votre algorithme avec moins d'opérations (qui doivent être effectuées sur out-of -cache data [et nécessite donc une extraction]) en termes de temps réel du mur.

NoseKnowsAll
la source
1
Alistra a abordé cette question indirectement en parlant de "problèmes d'espace"
Zach Saucier
2
Une énorme quantité de cache ne multiplie que l'exécution finale par une valeur constante (qui n'est pas supérieure à 8 pour un processeur 4 cœurs à 3,2 GHz avec 1,6 GHz de RAM, généralement elle est beaucoup plus faible), elle est donc considérée comme une constante fixe dans le grand -O notation. Ainsi, la seule chose que le cache manque est de déplacer le seuil de n où cette solution O (n) commence à être plus lente que la solution O (1).
Marian Spanik
1
@MarianSpanik Vous avez bien sûr raison. Mais cette question a demandé une situation où nous préférerions O(logn)plus O(1). Vous pourriez très facilement imaginer une situation où, pour tout votre possible n, l'application moins liée à la mémoire s'exécuterait dans un temps de mur plus rapide, même à une complexité plus élevée.
NoseKnowsTous
@MarianSpanik n'est-il pas un cache manquer jusqu'à 300 cycles d'horloge? D'où vient le 8?
J'espère que ce sera
43

Dans les contextes où la sécurité des données est une préoccupation, un algorithme plus complexe peut être préférable à un algorithme moins complexe si l'algorithme plus complexe a une meilleure résistance aux attaques de synchronisation .

Kevin K
la source
6
Alors que ce que vous avez dit est vrai, dans ce cas, un algorithme s'exécutant en O (1) est par définition invulnérable aux attaques temporelles.
Justin Lessard
17
@JustinLessard: Être O (1) signifie qu'il existe une certaine taille d'entrée après laquelle l'exécution de l'algorithme est limitée par une constante. Ce qui se passe en dessous de ce seuil est inconnu. En outre, le seuil peut même ne pas être atteint pour une utilisation réelle de l'algorithme. L'algorithme peut être linéaire et ainsi divulguer des informations sur la longueur de l'entrée, par exemple.
Jörg W Mittag
12
Le runtime peut également varier de différentes manières, tout en étant limité. Si le temps d'exécution est proportionnel à (n mod 5) + 1, il l'est toujours O(1), mais révèle des informations sur n. Un algorithme plus complexe avec un temps d'exécution plus fluide peut donc être préférable, même s'il peut être asymptotiquement (et peut-être même en pratique) plus lent.
Christian Semrau
C'est essentiellement pourquoi bcrypt est considéré comme bon; cela ralentit les choses
David dit Réintégrer Monica
@DavidGrinberg C'est la raison pour laquelle bcrypt est utilisé et répond à la question. Mais cela n'a rien à voir avec cette réponse, qui parle de synchronisation des attaques.
Christian Semrau
37

Alistra l'a cloué mais n'a fourni aucun exemple, donc je vais le faire.

Vous avez une liste de 10 000 codes UPC pour ce que votre magasin vend. CUP à 10 chiffres, entier pour le prix (prix en centimes) et 30 caractères de description pour le reçu.

Approche O (log N): Vous avez une liste triée. 44 octets si ASCII, 84 si Unicode. Alternativement, traitez l'UPC comme un int64 et vous obtenez 42 et 72 octets. 10000 enregistrements - dans le cas le plus élevé, vous regardez un peu moins d'un mégaoctet de stockage.

Approche O (1): Ne stockez pas l'UPC, au lieu de cela, vous l'utilisez comme entrée dans le tableau. Dans le cas le plus bas, vous regardez près d'un tiers d'un téraoctet de stockage.

L'approche que vous utilisez dépend de votre matériel. Sur la plupart des configurations modernes raisonnables, vous allez utiliser l'approche log N. Je peux imaginer que la deuxième approche est la bonne réponse si, pour une raison quelconque, vous exécutez dans un environnement où la RAM est extrêmement courte, mais vous avez beaucoup de stockage de masse. Un tiers de téraoctet sur un disque n'est pas un gros problème, obtenir vos données dans une seule sonde du disque vaut quelque chose. L'approche binaire simple prend 13 en moyenne. (Notez, cependant, qu'en regroupant vos clés, vous pouvez obtenir cela à 3 lectures garanties et en pratique, vous mettriez en cache la première.)

Loren Pechtel
la source
2
Je suis un peu confus ici. Parlez-vous de créer un tableau de 10 milliards d'entrées (dont la plupart ne seront pas définies) et de traiter l'UPC comme un index dans ce tableau?
David Z
7
@DavidZ Oui. Si vous utilisez un tableau fragmenté, vous n'obtiendrez peut-être pas O (1), mais il n'utilisera que 1 Mo de mémoire. Si vous utilisez un tableau réel, vous êtes assuré d'avoir un accès O (1) mais il utilisera 1/3 To de mémoire.
Navin
Sur un système moderne, il utilisera 1/3 To d'espace d'adressage, mais cela ne signifie pas qu'il se rapprochera de la mémoire de sauvegarde allouée. La plupart des systèmes d'exploitation modernes n'engagent pas de stockage pour les allocations jusqu'à ce qu'ils en aient besoin. En faisant cela, vous cachez essentiellement une structure de recherche associative pour vos données à l'intérieur du système de mémoire virtuelle du système d'exploitation / matériel.
Phil Miller,
@Novelocrat True, mais si vous le faites à des vitesses RAM, le temps de recherche n'aura pas d'importance, aucune raison d'utiliser 40 Mo au lieu de 1 Mo. La version de la baie n'a de sens que lorsque l'accès au stockage est coûteux - vous allez sur le disque.
Loren Pechtel
1
Ou lorsque ce n'est pas une opération critique pour les performances, et que le temps du développeur est coûteux - malloc(search_space_size)il est aussi facile de dire et d'indiquer ce qu'il retourne.
Phil Miller,
36

Prenons un arbre rouge-noir. Il a accès, recherche, insertion et suppression de O(log n). Comparez avec un tableau qui a accès à O(1)et le reste des opérations le sont O(n).

Donc, étant donné une application où nous insérons, supprimons ou recherchons plus souvent que nous n'y accédons et un choix entre seulement ces deux structures, nous préférerions l'arbre rouge-noir. Dans ce cas, vous pourriez dire que nous préférons le O(log n)temps d'accès plus lourd de l'arbre rouge-noir .

Pourquoi? Parce que l'accès n'est pas notre préoccupation principale. Nous faisons un compromis: les performances de notre application sont plus fortement influencées par des facteurs autres que celui-ci. Nous permettons à cet algorithme particulier de souffrir de performances car nous réalisons des gains importants en optimisant d'autres algorithmes.

La réponse à votre question est donc simplement la suivante: lorsque le taux de croissance de l'algorithme n'est pas ce que nous voulons optimiser , quand nous voulons optimiser autre chose. Toutes les autres réponses en sont des cas particuliers. Parfois, nous optimisons le temps d'exécution d'autres opérations. Parfois, nous optimisons pour la mémoire. Parfois, nous optimisons pour la sécurité. Parfois, nous optimisons la maintenabilité. Parfois, nous optimisons le temps de développement. Même la constante dominante étant suffisamment faible pour avoir de l'importance optimise le temps d'exécution lorsque vous savez que le taux de croissance de l'algorithme n'est pas le plus grand impact sur le temps d'exécution. (Si votre ensemble de données était en dehors de cette plage, vous optimiseriez le taux de croissance de l'algorithme car il finirait par dominer la constante.) Tout a un coût, et dans de nombreux cas, nous échangeons le coût d'un taux de croissance plus élevé pour le algorithme pour optimiser autre chose.

jpmc26
la source
Je ne sais pas comment les opérations qui vous permettent d'utiliser le tableau avec la recherche O (1) et les mises à jour O (n) correspondent à l'arbre rouge-noir, les gens y pensaient (au moins moi). La plupart du temps, je pensais d'abord à la recherche par clé pour l'arbre rouge-noir. Mais pour correspondre au tableau, la structure devrait être légèrement différente et conserver la quantité de sous-nœuds dans les nœuds supérieurs pour fournir une recherche basée sur un index et réindexer lors de l'insertion. Bien que je convienne que le rouge-noir peut être utilisé pour maintenir l'équilibre, vous pouvez utiliser un arbre équilibré si vous voulez être vague sur les détails des opérations correspondantes.
ony
@ony Un arbre rouge-noir peut être utilisé pour définir une structure de type carte / dictionnaire, mais ce n'est pas nécessaire. Les nœuds peuvent simplement être des éléments, implémentant essentiellement une liste triée.
jpmc26
la liste triée et le tableau qui définit l'ordre des éléments ont une quantité différente d'informations. L'un est basé sur l'ordre entre les éléments et l'ensemble et l'autre définit une séquence arbitraire qui ne définit pas nécessairement l'ordre entre les éléments. Autre chose, qu'est-ce que "accès" et "recherche" que vous déclarez être O(log n)"d'arbre rouge-noir"? L'insertion de la 5position 2 du tableau [1, 2, 1, 4]entraînera [1, 2, 5, 1 4](l'élément 4obtiendra l'index mis à jour de 3 à 4). Comment allez-vous obtenir ce comportement dans O(log n)"l'arbre rouge-noir" que vous référencez comme "liste triée"?
ony
@ony "la liste et le tableau triés qui définissent l'ordre des éléments ont une quantité différente d'informations." Oui, et c'est en partie pourquoi ils ont des caractéristiques de performances différentes. Vous manquez le point. L'un n'est pas une baisse de remplacement de l'autre dans toutes les situations. Ils optimisent différentes choses et font différents compromis , et le fait est que les développeurs prennent constamment des décisions sur ces compromis.
jpmc26
@ony L'accès, la recherche, l'insertion et la suppression ont des significations spécifiques dans le contexte des performances de l'algorithme. Access récupère un élément par position. La recherche consiste à localiser un élément par valeur (qui n'a qu'une application pratique comme contrôle de confinement pour une structure non cartographique). L'insertion et la suppression doivent cependant être simples. Un exemple d'utilisation peut être vu ici .
jpmc26
23

Oui.

Dans un cas réel, nous avons effectué des tests pour effectuer des recherches de table avec des clés de chaîne courtes et longues.

Nous avons utilisé un std::map, un std::unordered_mapavec un hachage qui échantillonne au plus 10 fois sur la longueur de la chaîne (nos clés ont tendance à être guidées, donc c'est décent), et un hachage qui échantillonne chaque caractère (en théorie, réduit les collisions), un vecteur non trié où nous faisons une ==comparaison, et (si je me souviens bien) un vecteur non trié où nous stockons également un hachage, comparons d'abord le hachage, puis comparez les caractères.

Ces algorithmes vont de O(1)(unordered_map) à O(n)(recherche linéaire).

Pour un N de taille modeste, assez souvent l'O (n) bat l'O (1). Nous pensons que cela est dû au fait que les conteneurs basés sur les nœuds ont exigé que notre ordinateur se déplace davantage dans la mémoire, contrairement aux conteneurs basés sur les linéaires.

O(lg n)existe entre les deux. Je ne me souviens pas comment ça s'est passé.

La différence de performances n'était pas si grande, et sur des ensembles de données plus volumineux, celui basé sur le hachage fonctionnait beaucoup mieux. Nous sommes donc restés avec la carte non ordonnée basée sur le hachage.

En pratique, pour n de taille raisonnable, O(lg n)c'est O(1). Si votre ordinateur ne dispose que de 4 milliards d'entrées dans votre tableau, il O(lg n)est délimité ci-dessus par 32. (lg (2 ^ 32) = 32) (en informatique, lg est l'abréviation de log based 2).

En pratique, les algorithmes lg (n) sont plus lents que les algorithmes O (1) non pas à cause du facteur de croissance logarithmique, mais parce que la partie lg (n) signifie généralement qu'il y a un certain niveau de complexité à l'algorithme, et que la complexité ajoute un facteur constant plus grand que n'importe quelle "croissance" du terme lg (n).

Cependant, les algorithmes O (1) complexes (comme le mappage de hachage) peuvent facilement avoir un facteur constant similaire ou supérieur.

Yakk - Adam Nevraumont
la source
21

La possibilité d'exécuter un algorithme en parallèle.

Je ne sais pas s'il existe un exemple pour les classes O(log n)et O(1), mais pour certains problèmes, vous choisissez un algorithme avec une classe de complexité plus élevée lorsque l'algorithme est plus facile à exécuter en parallèle.

Certains algorithmes ne peuvent pas être parallélisés mais ont une classe de complexité si faible. Considérez un autre algorithme qui atteint le même résultat et peut être parallélisé facilement, mais a une classe de complexité plus élevée. Lorsqu'il est exécuté sur une machine, le deuxième algorithme est plus lent, mais lorsqu'il est exécuté sur plusieurs machines, le temps d'exécution réel diminue de plus en plus tandis que le premier algorithme ne peut pas accélérer.

Simulant
la source
Mais tout ce que la parallélisation fait est de réduire le facteur constant dont d'autres ont parlé, non?
gengkev
1
Oui, mais un algorithme parallèle peut diviser le facteur constant par 2 chaque fois que vous doublez le nombre de machines en cours d'exécution. Un autre algorithme à thread unique peut réduire le facteur constant une seule fois de manière constante. Ainsi, avec un algorithme parallèle, vous pouvez réagir dynamiquement à la taille de n et être plus rapide en temps d'exécution d'horloge murale.
Simulant
15

Imaginons que vous implémentiez une liste noire sur un système embarqué, où des nombres entre 0 et 1 000 000 peuvent être sur liste noire. Cela vous laisse deux options possibles:

  1. Utilisez un jeu de bits de 1 000 000 bits
  2. Utilisez un tableau trié des entiers sur liste noire et utilisez une recherche binaire pour y accéder

L'accès au jeu de bits aura un accès constant garanti. En termes de complexité temporelle, elle est optimale. D'un point de vue théorique et pratique (c'est O (1) avec un surcoût constant extrêmement faible).

Pourtant, vous voudrez peut-être préférer la deuxième solution. Surtout si vous vous attendez à ce que le nombre d'entiers sur liste noire soit très petit, car il sera plus efficace en mémoire.

Et même si vous ne développez pas pour un système embarqué où la mémoire est rare, je peux simplement augmenter la limite arbitraire de 1 000 000 à 1 000 000 000 000 et faire le même argument. Ensuite, le jeu de bits nécessiterait environ 125 Go de mémoire. Avoir une complexité garantie dans le pire des cas de O (1) pourrait ne pas convaincre votre patron de vous fournir un serveur aussi puissant.

Ici, je préférerais fortement une recherche binaire (O (log n)) ou un arbre binaire (O (log n)) au jeu de bits O (1). Et probablement, une table de hachage avec sa pire complexité d'O (n) les battra tous dans la pratique.

Philipp Claßen
la source
12

Les gens ont déjà répondu à votre question exacte, je vais donc aborder une question légèrement différente à laquelle les gens peuvent penser en venant ici.

De nombreux algorithmes et structures de données «O (1) time» ne prennent en fait que le temps O (1) attendu , ce qui signifie que leur moyenne temps de fonctionnement est O (1), éventuellement uniquement sous certaines hypothèses.

Exemples courants: tables de hachage, extension de "listes de tableaux" (alias tableaux / vecteurs de taille dynamique).

Dans de tels scénarios, vous préférerez peut-être utiliser des structures de données ou des algorithmes dont le temps est garanti d'être absolument borné logarithmiquement, même si leur performance peut être pire en moyenne.
Un exemple pourrait donc être un arbre de recherche binaire équilibré, dont le temps d'exécution est pire en moyenne mais meilleur dans le pire des cas.

user541686
la source
11

Une question plus générale est de savoir s'il y a des situations où l' on aimerait un O(f(n))algorithme à un O(g(n))algorithme , même si g(n) << f(n)comme ntend vers l' infini. Comme d'autres l'ont déjà mentionné, la réponse est clairement «oui» dans le cas où f(n) = log(n)et g(n) = 1. C'est parfois oui même dans le cas qui f(n)est polynomial mais g(n)exponentiel. Un exemple célèbre et important est celui de l' algorithme Simplex pour résoudre des problèmes de programmation linéaire. Dans les années 1970, cela a été démontré O(2^n). Ainsi, son comportement dans le pire des cas est irréalisable. Mais - son comportement de cas moyen est extrêmement bon, même pour des problèmes pratiques avec des dizaines de milliers de variables et de contraintes. Dans les années 80, les algorithmes de temps polynomiaux (telsL'algorithme du point intérieur de Karmarkar) pour la programmation linéaire ont été découverts, mais 30 ans plus tard, l'algorithme simplex semble toujours être l'algorithme de choix (à l'exception de certains très gros problèmes). C'est pour la raison évidente que le comportement de cas moyen est souvent plus important que le comportement de cas pire, mais aussi pour une raison plus subtile que l'algorithme simplex est en quelque sorte plus informatif (par exemple, les informations de sensibilité sont plus faciles à extraire).

John Coleman
la source
10

Pour mettre mes 2 cents:

Parfois, un algorithme de complexité pire est sélectionné à la place d'un meilleur, lorsque l'algorithme s'exécute sur un certain environnement matériel. Supposons que notre algorithme O (1) accède de manière non séquentielle à chaque élément d'un très grand tableau de taille fixe pour résoudre notre problème. Ensuite, placez cette matrice sur un disque dur mécanique ou une bande magnétique.

Dans ce cas, l'algorithme O (logn) (supposons qu'il accède au disque séquentiellement), devient plus favorable.

uylmz
la source
Je pourrais ajouter ici que sur le lecteur ou la bande à accès séquentiel, l'algorithme O (1) devient plutôt O (n), c'est pourquoi la solution séquentielle devient plus favorable. De nombreuses opérations O (1) dépendent de l'ajout et de la recherche indexée comme étant un algorithme à temps constant, ce qui n'est pas dans un espace à accès séquentiel.
TheHansinator
9

Il existe un bon cas d'utilisation pour utiliser un algorithme O (log (n)) au lieu d'un algorithme O (1) que les nombreuses autres réponses ont ignoré: l'immuabilité. Les cartes de hachage ont O (1) met et obtient, en supposant une bonne distribution des valeurs de hachage, mais elles nécessitent un état mutable. Les cartes d'arbres immuables ont O (log (n)) met et obtient, ce qui est asymptotiquement plus lent. Cependant, l'immuabilité peut être suffisamment précieuse pour compenser de mauvaises performances et dans le cas où plusieurs versions de la carte doivent être conservées, l'immuabilité vous permet d'éviter d'avoir à copier la carte, qui est O (n), et peut donc s'améliorer performance.

Réintégrer Monica
la source
9

Tout simplement: parce que le coefficient - les coûts associés à la configuration, au stockage et au temps d'exécution de cette étape - peut être beaucoup, beaucoup plus grand avec un problème big-O plus petit qu'avec un problème plus important. Big-O n'est qu'une mesure de l' évolutivité des algorithmes .

Prenons l'exemple suivant du Hacker's Dictionary, proposant un algorithme de tri basé sur l' interprétation des mondes multiples de la mécanique quantique :

  1. Permutez le tableau au hasard en utilisant un processus quantique,
  2. Si le tableau n'est pas trié, détruisez l'univers.
  3. Tous les univers restants sont désormais triés [y compris celui dans lequel vous vous trouvez].

(Source: http://catb.org/~esr/jargon/html/B/bogo-sort.html )

Notez que le big-O de cet algorithme est O(n), ce qui bat tout algorithme de tri connu à ce jour sur les éléments génériques. Le coefficient du pas linéaire est également très faible (car ce n'est qu'une comparaison, pas un swap, qui se fait linéairement). Un algorithme similaire pourrait, en fait, être utilisé pour résoudre tout problème à la fois NP et co-NP en temps polynomial, puisque chaque solution possible (ou preuve possible qu'il n'y a pas de solution) peut être générée en utilisant le processus quantique, puis vérifiée dans Temps polynomial.

Cependant, dans la plupart des cas, nous ne voulons probablement pas prendre le risque que plusieurs mondes ne soient pas corrects, sans mentionner que l'acte de mise en œuvre de l'étape 2 est toujours "laissé comme un exercice pour le lecteur".

TheHansinator
la source
7

À tout moment lorsque n est borné et que le multiplicateur constant de l'algorithme O (1) est supérieur à la borne sur log (n). Par exemple, le stockage de valeurs dans un hachage est O (1), mais peut nécessiter un calcul coûteux d'une fonction de hachage. Si les éléments de données peuvent être comparés de manière triviale (par rapport à un certain ordre) et que la limite sur n est telle que log n est nettement inférieur au calcul de hachage sur un élément quelconque, le stockage dans un arbre binaire équilibré peut être plus rapide que le stockage dans un hachage.

Dmitry Rubanovich
la source
6

Dans une situation en temps réel où vous avez besoin d'une borne supérieure ferme, vous devez par exemple sélectionner un heapsort par opposition à un Quicksort, car le comportement moyen de heapsort est également son comportement le plus défavorable.

Marquis de Lorne
la source
6

Ajout aux réponses déjà bonnes.Un exemple pratique serait les index Hash vs les index B-tree dans la base de données postgres.

Les index de hachage forment un index de table de hachage pour accéder aux données sur le disque tandis que btree comme son nom l'indique utilise une structure de données Btree.

En temps Big-O, ce sont O (1) vs O (logN).

Les index de hachage sont actuellement découragés dans les postgres car dans une situation réelle, en particulier dans les systèmes de bases de données, le hachage sans collision est très difficile (peut conduire à une complexité O (N) dans le pire des cas) et de ce fait, il est encore plus difficile à faire les écraser en toute sécurité (appelé écriture en avance de journalisation - WAL en postgres).

Ce compromis est fait dans cette situation car O (logN) est assez bon pour les index et l'implémentation de O (1) est assez difficile et la différence de temps n'aurait pas vraiment d'importance.

Madusudanan
la source
4

Quand nest petit et O(1)constamment lent.

HoboBen
la source
3
  1. Lorsque l'unité de travail "1" dans O (1) est très élevée par rapport à l'unité de travail dans O (log n) et que la taille de jeu attendue est petite. Par exemple, il est probablement plus lent de calculer des codes de hachage Dictionary que d'itérer un tableau s'il n'y a que deux ou trois éléments.

ou

  1. Lorsque la mémoire ou d'autres ressources non temporelles requises dans l'algorithme O (1) sont exceptionnellement grandes par rapport à l'algorithme O (log n).
Joel Coehoorn
la source
3
  1. lors de la refonte d'un programme, une procédure s'avère optimisée avec O (1) au lieu de O (lgN), mais si ce n'est pas le goulot d'étranglement de ce programme, et il est difficile de comprendre l'alg O (1). Ensuite, vous n'auriez pas à utiliser l'algorithme O (1)
  2. quand O (1) a besoin de beaucoup de mémoire que vous ne pouvez pas fournir, alors que le temps de O (lgN) peut être accepté.
yanghaogn
la source
1

C'est souvent le cas pour les applications de sécurité que l'on souhaite concevoir des problèmes dont les algorithmes sont lents à dessein afin d'empêcher quelqu'un d'obtenir une réponse à un problème trop rapidement.

Voici quelques exemples du haut de ma tête.

  • Le hachage de mot de passe est parfois rendu arbitrairement lent afin de rendre plus difficile de deviner les mots de passe par force brute. Ce message sur la sécurité de l'information a une puce (et bien plus).
  • Bit Coin utilise un problème lent et contrôlable pour un réseau d'ordinateurs à résoudre afin de "miner" les pièces. Cela permet à la monnaie d'être extraite à un taux contrôlé par le système collectif.
  • Les chiffrements asymétriques (comme RSA ) sont conçus pour rendre le déchiffrement sans les clés intentionnellement lent afin d'empêcher quelqu'un d'autre sans la clé privée de casser le chiffrement. Les algorithmes sont conçus pour être piratés dans le O(2^n)temps, espérons -le, où se ntrouve la longueur en bits de la clé (c'est la force brute).

Ailleurs dans CS, le tri rapide est O(n^2)dans le pire des cas, mais dans le cas général l'est O(n*log(n)). Pour cette raison, l'analyse «Big O» n'est parfois pas la seule chose qui vous intéresse lors de l'analyse de l'efficacité d'un algorithme.

Frank Bryce
la source