J'apprends à propos des temps d'exécution Big O Notation et des temps amortis. Je comprends la notion de O (n) temps linéaire, ce qui signifie que la taille de l’entrée affecte la croissance de l’algorithme proportionnellement ... et il en va de même pour, par exemple, le temps quadratique Sur2) etc..even algorithmes, tels que les générateurs de permutation, avec O (n!) fois, qui croissent par facteurs.
Par exemple, la fonction suivante est O (n) car l’algorithme croît proportionnellement à son entrée n:
f(int n) {
int i;
for (i = 0; i < n; ++i)
printf("%d", i);
}
De même, s'il y avait une boucle imbriquée, le temps serait O (n2).
Mais qu'est-ce exactement que O (log n)? Par exemple, que signifie le fait de dire que la hauteur d'un arbre binaire complet est O (log n)?
Je sais (peut-être pas avec beaucoup de détails) ce qu'est le logarithme, en ce sens que:dix 100 = 2, mais je ne comprends pas comment identifier une fonction avec un temps logarithmique.
Je ne comprends pas comment identifier une fonction avec une heure de journalisation.
Les attributs les plus courants de la fonction d'exécution logarithmique sont les suivants:
ou
C'est pourquoi, par exemple, rechercher des personnes dans un annuaire téléphonique est O (log n). Vous n'avez pas besoin de vérifier chaque personne de l'annuaire pour trouver la bonne; au lieu de cela, vous pouvez simplement diviser pour mieux conquérir en recherchant par ordre alphabétique de leur nom. Dans chaque section, vous devez uniquement explorer un sous-ensemble de chaque section avant de trouver le numéro de téléphone de quelqu'un.
Bien sûr, un plus grand annuaire vous prendra encore plus de temps, mais il ne augmentera pas aussi rapidement que l'augmentation proportionnelle de la taille supplémentaire.
Nous pouvons développer l’exemple de l’annuaire téléphonique pour comparer d’autres types d’opérations et leur temps d’exécution . Nous supposerons que notre répertoire téléphonique contient des entreprises (les "Pages jaunes") qui ont des noms uniques et des personnes (les "Pages blanches" ") qui peuvent ne pas avoir de noms uniques. Un numéro de téléphone est attribué à au plus une personne ou entreprise. Nous supposerons également qu'il faut un temps constant pour passer à une page spécifique.
Voici les temps d'exécution de certaines opérations que nous pourrions effectuer dans l'annuaire téléphonique, du meilleur au pire:
O (1) (meilleur des cas): Étant donné la page sur laquelle se trouve le nom d'une entreprise et le nom de l'entreprise, recherchez le numéro de téléphone.
O (1) (cas moyen): Étant donné la page sur laquelle se trouve le nom d'une personne et son nom, recherchez le numéro de téléphone.
O (log n): Étant donné le nom d'une personne, recherchez le numéro de téléphone en sélectionnant un point aléatoire au milieu de la partie du livre que vous n'avez pas encore sélectionnée. cherché encore, puis vérifier pour voir si le nom de la personne est à ce point. Répétez ensuite le processus à peu près à mi-chemin de la partie du livre où se trouve le nom de la personne. (Ceci est une recherche binaire du nom d'une personne.)
O (n): Trouvez toutes les personnes dont le numéro de téléphone contient le chiffre "5".
O (n): À partir d'un numéro de téléphone, recherchez la personne ou l'entreprise qui possède ce numéro.
O (n log n): Il y a eu une confusion au bureau de l'imprimante et notre annuaire téléphonique a toutes ses pages insérées dans un ordre aléatoire. Corrigez l'ordre afin qu'il soit correct en regardant le prénom sur chaque page, puis en plaçant cette page à l'emplacement approprié dans un nouvel annuaire vide.
Pour les exemples ci-dessous, nous sommes maintenant au bureau de l’imprimeur. Les annuaires téléphoniques attendent d’être postés à chaque résident ou entreprise, et un autocollant est apposé sur chaque annuaire, indiquant l’endroit où ils doivent être envoyés. Chaque personne ou entreprise reçoit un annuaire téléphonique.
O (n log n): Nous souhaitons personnaliser l'annuaire téléphonique afin que nous puissions trouver le nom de chaque personne ou entreprise dans leur copie désignée, puis encerclez leur nom dans le livre et écrivez un court mot de remerciement pour leur parrainage.
O (n2): Une erreur est survenue au bureau et chaque entrée de chaque annuaire comporte un "0" supplémentaire à la fin du numéro de téléphone. Prenez du blanc et supprimez chaque zéro.
O (n · n!): Nous sommes prêts à charger les répertoires sur le quai d'expédition. Malheureusement, le robot qui était censé charger les livres est devenu féroce: il met les livres dans le camion dans un ordre aléatoire! Pire encore, il charge tous les livres dans le camion, puis vérifie s'ils sont dans le bon ordre. Sinon, il les décharge et recommence. (Ceci est le redouté sorte de bogo.)
O (nn): Vous réparez le robot pour qu'il se charge correctement. Le lendemain, l'un de vos collègues vous fait une blague et connecte le robot du quai de chargement aux systèmes d'impression automatisés. Chaque fois que le robot va charger un livre original, l’imprimante d’usine reproduit tous les annuaires! Heureusement, les systèmes de détection de bogues du robot sont suffisamment sophistiqués pour que le robot n'essaye pas d'imprimer encore plus de copies lorsqu'il doit charger un livre dupliqué, mais il doit tout de même charger tous les livres originaux et dupliqués imprimés.
Pour plus d'explications mathématiques, vous pouvez vérifier comment la complexité temporelle arrive à log n
ici. https://hackernoon.com/what-does-the- time-complexity-o-log-n-actually-mean-45f94bb5bfbf
De nombreuses bonnes réponses ont déjà été postées à cette question, mais je pense que nous en manquons une importante, à savoir la réponse illustrée.
Qu'est-ce que cela signifie de dire que la hauteur d'un arbre binaire complet est O (log n)?
Le dessin suivant représente un arbre binaire. Notez que chaque niveau contient le double du nombre de nœuds par rapport au niveau supérieur (donc binaire ):
La recherche binaire est un exemple complexe O(log n)
. Supposons que les nœuds situés au bas de l'arborescence de la figure 1 représentent des éléments d'une collection triée. La recherche binaire est un algorithme de division et de conquête, et le dessin montre comment nous aurons besoin (au plus) de 4 comparaisons pour trouver l'enregistrement que nous recherchons dans cet ensemble de 16 données.
Supposons que nous ayons plutôt un ensemble de données avec 32 éléments. Continuez le dessin ci-dessus pour constater que nous avons maintenant besoin de 5 comparaisons pour trouver ce que nous recherchons, car l'arbre n'a grandi que d'un niveau lorsque nous avons multiplié la quantité de données. En conséquence, la complexité de l'algorithme peut être décrite comme un ordre logarithmique.
Tracer log(n)
sur une feuille de papier vierge donnera un graphique où la montée de la courbe ralentira à mesure que n
augmentera:
O(log N)
signifie fondamentalement que le temps monte de façon linéaire tandis que n
monte de manière exponentielle. Donc, s'il faut 1
seconde pour calculer les éléments 10
, il faudra 2
secondes pour calculer les éléments 100
, 3
secondes pour calculer 1000
éléments, et ainsi de suite.
C'est O(log n)
lorsque nous divisons et conquérons le type d'algorithme, par exemple la recherche binaire. Un autre exemple est le tri rapide où chaque fois que nous divisons le tableau en deux parties et qu'il faut chaque fois O(N)
temps pour trouver un élément pivot. D'où il N O(log N)
L'explication ci-dessous utilise le cas d'un arbre binaire entièrement équilibré pour vous aider à comprendre comment nous obtenons la complexité logarithmique du temps.
L'arbre binaire est un cas où un problème de taille n est divisé en sous-problème de taille n/2 jusqu'à atteindre un problème de taille 1:
Et c’est comme ça que vous obtenez O (log n), c’est la quantité de travail à faire sur l’arbre ci-dessus pour trouver une solution.
Un algorithme commun de complexité temporelle O (log n) est Recherche binaire dont la relation récursive est T(n/2) + O(1) c'est-à-dire à chaque niveau ultérieur de l'arbre diviser le problème en deux et faire un travail constant supplémentaire.
Vue d'ensemble
D'autres ont donné de bons exemples de diagrammes, tels que les arborescences. Je n'ai vu aucun exemple de code simple. Donc, en plus de mon explication, je vais fournir des algorithmes avec des instructions d'impression simples pour illustrer la complexité des différentes catégories d'algorithmes.
Tout d'abord, vous voudrez avoir une idée générale du logarithme que vous pouvez obtenir de https://en.wikipedia.org/wiki/Logarithm . Utilisation des sciences naturelles e
et de la bûche naturelle. Les ingénieurs ingénieurs utiliseront log_10 (log base 10) et les informaticiens utiliseront beaucoup log_2 (log base 2), car les ordinateurs sont à base binaire. Parfois, vous voyez les abréviations de log naturel comme ln()
, les ingénieurs laissent normalement le _10 éteint et n'utilisent que log()
et log_2 est abrégé en lg()
. Tous les types de logarithmes se développent de la même manière, c'est pourquoi ils partagent la même catégorie de log(n)
.
Lorsque vous regardez les exemples de code ci-dessous, je vous recommande de regarder O (1), puis O (n), puis O (n ^ 2). Une fois que vous êtes bien avec ceux-ci, alors regardez les autres. J'ai inclus des exemples clairs ainsi que des variantes pour montrer comment des changements subtils peuvent toujours aboutir à la même catégorisation.
Vous pouvez penser à O (1), O (n), O (logn), etc. en tant que classes ou catégories de croissance. Certaines catégories prendront plus de temps que d’autres. Ces catégories nous permettent d’organiser les performances de l’algorithme. Certains ont grandi plus vite que l'entrée n grandit. Le tableau ci-dessous illustre ladite croissance en chiffres. Dans le tableau ci-dessous, pensez à log (n) en tant que plafond de log_2.
Exemples de code simples de différentes catégories de Big O:
O (1) - Exemples de temps constant:
L'algorithme 1 imprime bonjour une fois et ne dépend pas de n; il s'exécutera donc toujours dans le même temps. Il s'agit donc de O(1)
.
print "hello";
L'algorithme 2 imprime bonjour 3 fois, mais cela ne dépend pas de la taille de l'entrée. Même si n grandit, cet algorithme n’imprimera toujours bonjour que 3 fois. Cela étant dit 3, est une constante, donc cet algorithme est également O(1)
.
print "hello";
print "hello";
print "hello";
O (log (n)) - Exemples logarithmiques:
L'algorithme 3 illustre un algorithme qui s'exécute dans log_2 (n). Notez le post-opération de la boucle for multiple la valeur actuelle de i sur 2, donc i
va de 1 à 2 à 4 à 8 à 16 à 32 ...
for(int i = 1; i <= n; i = i * 2)
print "hello";
L'algorithme 4 illustre log_3. Remarquez que i
va de 1 à 3 à 9 à 27 ...
for(int i = 1; i <= n; i = i * 3)
print "hello";
L'algorithme 5 est important, car il permet de montrer que, tant que le nombre est supérieur à 1 et que le résultat est multiplié à maintes reprises contre lui-même, vous utilisez un algorithme logarithmique.
for(double i = 1; i < n; i = i * 1.02)
print "hello";
O (n) - Exemples de temps linéaire:
Cet algorithme est simple, qui affiche bonjour n fois.
for(int i = 0; i < n; i++)
print "hello";
Cet algorithme montre une variation, où il imprimera bonjour n/2 fois. n/2 = 1/2 * n. Nous ignorons la constante 1/2 et voyons que cet algorithme est O (n).
for(int i = 0; i < n; i = i + 2)
print "hello";
O (n * log (n)) - nlog (n) Exemples:
Pensez-y comme une combinaison de O(log(n))
et O(n)
. L'imbrication des boucles for nous aide à obtenir le O(n*log(n))
for(int i = 0; i < n; i++)
for(int j = 1; j < n; j = j * 2)
print "hello";
L'algorithme 9 est semblable à l'algorithme 8, mais chacune des boucles a permis des variations, ce qui a pour résultat que le résultat final est O(n*log(n))
.
for(int i = 0; i < n; i = i + 2)
for(int j = 1; j < n; j = j * 3)
print "hello";
O (n ^ 2) - n carré Exemples:
O(n^2)
s'obtient facilement par la norme d'imbrication pour les boucles.
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
print "hello";
Comme l’algorithme 10, mais avec quelques variantes.
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j = j + 2)
print "hello";
O (n ^ 3) - n cubed Exemples:
C'est comme l'algorithme 10, mais avec 3 boucles au lieu de 2.
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
for(int k = 0; k < n; k++)
print "hello";
Comme l’algorithme 12, mais avec quelques variantes qui donnent toujours O(n^3)
.
for(int i = 0; i < n; i++)
for(int j = 0; j < n + 5; j = j + 2)
for(int k = 0; k < n; k = k + 3)
print "hello";
Résumé
Les exemples ci-dessus donnent plusieurs exemples simples et des variantes permettant de montrer quels changements subtils peuvent être introduits et qui ne changent pas vraiment l'analyse. Espérons que cela vous donne suffisamment de perspicacité.
Si vous aviez une fonction qui prend:
1 millisecond to complete if you have 2 elements.
2 milliseconds to complete if you have 4 elements.
3 milliseconds to complete if you have 8 elements.
4 milliseconds to complete if you have 16 elements.
...
n milliseconds to complete if you have 2**n elements.
Ensuite, il faut se connecter2(n) heure. Le notation Big O , en gros, signifie que la relation ne doit être vraie que pour n grand et que des facteurs constants et des termes plus petits peuvent être ignorés.
Le temps d'exécution logarithmique (O(log n)
) signifie essentiellement que le temps d'exécution augmente proportionnellement au logarithme de la taille d'entrée - à titre d'exemple, si 10 éléments prennent au plus un certain temps x
, et 100 éléments prennent au plus, disons 2x
, et 10 000 éléments prennent au plus 4x
, alors cela ressemble à une complexité temporelle O(log n)
.
Le logarithme
Ok, essayons de bien comprendre ce qu'est un logarithme.
Imaginez que nous avons une corde et que nous l’avons attachée à un cheval. Si la corde est directement attachée au cheval, la force que le cheval aurait besoin de retirer (par exemple, d'un homme) est directement 1.
Maintenant, imaginez que la corde est enroulée autour d'un poteau. Le cheval qui s’échappera devra maintenant tirer plusieurs fois plus fort. Le nombre de fois dépendra de la rugosité de la corde et de la taille de la perche, mais supposons que cela multiplie la force par 10 (lorsque la corde fait un tour complet).
Maintenant, si la corde est bouclée une fois, le cheval devra tirer 10 fois plus fort. Si l'humain décide de rendre la tâche vraiment difficile pour le cheval, il peut à nouveau enrouler la corde autour d'un pôle, augmentant ainsi sa force de 10 fois. Une troisième boucle va encore augmenter la force de 10 fois.
Nous pouvons voir que pour chaque boucle, la valeur augmente de 10. Le nombre de tours requis pour obtenir un nombre est appelé le logarithme du nombre, c’est-à-dire que nous avons besoin de 3 postes pour multiplier votre force par 1000, 6 postes pour multiplier votre force par 1 000 000.
3 est le logarithme de 1 000 et 6 est le logarithme de 1 000 000 (base 10).
Alors que signifie réellement O (log n)?
Dans notre exemple ci-dessus, notre "taux de croissance" est O (log n) . Pour chaque boucle supplémentaire, la force que notre corde peut supporter est 10 fois plus:
Turns | Max Force
0 | 1
1 | 10
2 | 100
3 | 1000
4 | 10000
n | 10^n
Maintenant, l'exemple ci-dessus utilisait la base 10, mais heureusement la base du journal est insignifiante lorsque nous parlons de la grande notation o.
Maintenant, imaginons que vous essayez de deviner un nombre compris entre 1 et 100.
Your Friend: Guess my number between 1-100!
Your Guess: 50
Your Friend: Lower!
Your Guess: 25
Your Friend: Lower!
Your Guess: 13
Your Friend: Higher!
Your Guess: 19
Your Friend: Higher!
Your Friend: 22
Your Guess: Lower!
Your Guess: 20
Your Friend: Higher!
Your Guess: 21
Your Friend: YOU GOT IT!
Maintenant, il vous a fallu 7 suppositions pour bien comprendre. Mais quelle est la relation ici? Quelle est la plus grande quantité d’éléments que vous pouvez deviner de chaque conjecture supplémentaire?
Guesses | Items
1 | 2
2 | 4
3 | 8
4 | 16
5 | 32
6 | 64
7 | 128
10 | 1024
En utilisant le graphique, nous pouvons voir que si nous utilisons une recherche binaire pour deviner un nombre compris entre 1 et 100, il nous faudra au plus 7 tentatives. Si nous avions 128 nombres, nous pourrions aussi deviner le nombre en 7 tentatives mais 129 nombres nous prend au plus 8 tentatives (en relation aux logarithmes, ici nous aurions besoin de 7 hypothèses pour une plage de 128 valeurs, de 10 hypothèses pour une plage de valeurs de 1024. 7 correspond au logarithme de 128, 10 correspond au logarithme de 1024 (base 2)).
Notez que j'ai audacié "au plus". La notation Big o fait toujours référence au pire des cas. Si vous êtes chanceux, vous pouvez deviner le nombre en une tentative et le meilleur des cas est donc O (1), mais c'est une autre histoire.
Nous pouvons constater que chaque fois que nous supposons, notre ensemble de données diminue. Une bonne règle empirique pour identifier si un algorithme a un temps logarithmique est de voir si le jeu de données est réduit d'un certain ordre après chaque itération.
Qu'en est-il de O (n log n)?
Vous rencontrerez éventuellement un algorithme de temps linéarithmique O (n log (n) . La règle empirique ci-dessus s'applique à nouveau, mais cette fois la fonction logarithmique a exécuter n fois, par exemple en réduisant la taille d’une liste n fois , ce qui se produit dans des algorithmes tels que le mergesort.
Vous pouvez facilement identifier si le temps algorithmique est n log n. Recherchez une boucle externe qui parcourt une liste (O (n)). Ensuite, regardez s'il y a une boucle interne. Si la boucle interne est coupant/réduisant le jeu de données à chaque itération, cette boucle est (O (log n)), de sorte que l'algorithme général est = O (n log n) .
Avertissement: L'exemple de la corde-logarithme a été tiré de l'excellent livre du mathématicien Delight de W.Sawyer .
Vous pouvez penser intuitivement à O (log N) en disant que l'heure est proportionnelle au nombre de chiffres dans N.
Si une opération effectue un travail à temps constant sur chaque chiffre ou bit d'une entrée, la totalité de l'opération prendra du temps proportionnel au nombre de chiffres ou de bits de l'entrée, pas à la magnitude de l'entrée; ainsi, O (log N) plutôt que O (N).
Si une opération prend une série de décisions en temps constant dont chacune réduit de moitié (réduit d'un facteur 3, 4, 5 ..) la taille de l'entrée à prendre en compte, le tout prendra un temps proportionnel à la base logarithmique 2 (base 3). , base 4, base 5 ...) de la taille N de l’entrée, plutôt que d’être O (N).
Etc.
La meilleure façon pour moi de toujours visualiser mentalement un algorithme qui s'exécute en O (log n) est la suivante:
Si vous augmentez la taille du problème d’un nombre multiplicatif (c’est-à-dire que vous multipliez sa taille par 10), le travail n’est augmenté que d’un montant supplémentaire.
Si vous appliquez cela à votre question sur l’arbre binaire afin d’obtenir une bonne application: si vous doublez le nombre de nœuds dans un arbre binaire, la hauteur n’augmente que de 1 (un montant additif). Si vous doublez à nouveau, il n'a encore augmenté que de 1 (évidemment, je suppose qu'il reste équilibré, etc.). Ainsi, au lieu de doubler votre travail lorsque la taille du problème est multipliée, vous ne faites que très légèrement plus de travail. C'est pourquoi les algorithmes O (log n) sont géniaux.
Tout d'abord, je vous recommande de lire le livre suivant;
Voici quelques fonctions et leurs complexités attendues. Les nombres indiquent les fréquences d'exécution des instructions .
En suivant le diagramme de complexité Big-O également tiré de bigocheatsheet
Enfin, la présentation très simple montre comment le calcul est effectué;
Anatomie des fréquences d’exécution des instructions d’un programme.
Analyse du temps d'exécution d'un programme (exemple).
Quel est le journalb(n)?
C'est le nombre de fois que vous pouvez couper un journal de longueur n à plusieurs reprises en b parties égales avant d'atteindre une section de taille 1.
Les algorithmes de division et de conquête ont généralement une composante logn
au temps d'exécution. Cela vient de la réduction répétée de moitié de l'entrée.
Dans le cas d'une recherche binaire, à chaque itération, vous perdez la moitié de l'entrée. Il convient de noter que dans la notation Big-O, log est la base de journal 2.
Edit: Comme indiqué précédemment, la base de journal importe peu, mais lors de la déduction de la performance Big-O d’un algorithme, le facteur de journalisation provient de la réduction de moitié, raison pour laquelle j’envisage la base 2.
Mais en quoi consiste exactement O (log n)? Par exemple, qu'est-ce que cela signifie de dire que la hauteur d'un arbre binaire> complet est O (log n)?
Je reformulerais ceci en tant que 'hauteur d'un arbre binaire complet est log n'. Déterminer la hauteur d'un arbre binaire complet serait O (log n), si vous avançiez pas à pas.
Je ne comprends pas comment identifier une fonction avec un temps logarithmique.
Le logarithme est essentiellement l'inverse de l'exponentiation. Ainsi, si chaque "étape" de votre fonction élimine un facteur d'éléments de l'ensemble d'éléments d'origine, il s'agit d'un algorithme logarithmique temporel.
Pour l'exemple de l'arborescence, vous pouvez facilement voir que descendre un niveau de nœuds réduit un nombre exponentiel d'éléments au fur et à mesure que vous continuez à parcourir. L'exemple populaire de recherche dans un annuaire téléphonique trié par nom est essentiellement équivalent à parcourir un arbre de recherche binaire (la page du milieu est l'élément racine et vous pouvez en déduire à chaque étape si vous voulez aller à gauche ou à droite).
Ces 2 cas prendront O (log n) temps
case 1: f(int n) {
int i;
for (i = 1; i < n; i=i*2)
printf("%d", i);
}
case 2 : f(int n) {
int i;
for (i = n; i>=1 ; i=i/2)
printf("%d", i);
}
O(log n)
fait référence à une fonction (ou à un algorithme ou à une étape dans un algorithme) travaillant dans un laps de temps proportionnel au logarithme (généralement base 2 dans la plupart des cas, mais pas toujours, et dans tous les cas insignifiant -O notation *) de la taille de l'entrée.
La fonction logarithmique est l'inverse de la fonction exponentielle. En d'autres termes, si votre entrée croît de manière exponentielle (plutôt que linéaire, comme vous le considéreriez normalement), votre fonction croît de manière linéaire.
O(log n)
les temps d'exécution sont très courants dans toutes les applications de type diviser pour régner, car vous réduisez (idéalement) le travail à chaque fois. Si, dans chacune des étapes de division ou de conquête, vous effectuez un travail à temps constant (ou un travail qui n'est pas à temps constant mais dont la durée croît plus lentement que O(log n)
), votre fonction entière est alors O(log n)
. Il est assez courant que chaque étape nécessite à la place une durée linéaire. cela équivaut à une complexité temporelle totale de O(n log n)
.
La complexité en temps d'exécution de la recherche binaire est un exemple de O(log n)
. En effet, dans la recherche binaire, vous ignorez toujours la moitié de vos entrées à chaque étape ultérieure en divisant le tableau en deux et en vous concentrant sur une moitié à chaque étape. Chaque étape est à temps constant, car dans la recherche binaire, il vous suffit de comparer un élément avec votre clé pour déterminer la prochaine action à prendre, quelle que soit la taille du tableau que vous envisagez. Donc, vous faites environ log (n)/log (2) étapes.
La complexité en temps d'exécution du tri par fusion est un exemple de O(n log n)
. En effet, vous divisez le tableau en deux à chaque étape, ce qui donne un total d'approximativement log (n)/log (2). Cependant, à chaque étape, vous devez effectuer des opérations de fusion sur tous les éléments (qu’il s’agisse d’une opération de fusion sur deux sous-listes de n/2 éléments ou de deux opérations de fusion sur quatre sous-listes de n/4 éléments, n’est pas pertinente car elle faites ceci pour n éléments dans chaque étape). Ainsi, la complexité totale est O(n log n)
.
* N'oubliez pas que la notation big-O, par définition , les constantes importent peu. De plus, par le règle de changement de base pour les logarithmes, la seule différence entre les logarithmes de bases différentes est un facteur constant.
O (log n) est un peu trompeur, plus précisément c’est O (log2 n), c'est-à-dire (logarithme avec base 2).
La hauteur d’un arbre binaire équilibré est O (log2 n), puisque chaque noeud en a deux (notez les "deux" comme dans log2 n) nœuds enfants. Donc, un arbre avec n noeuds a une hauteur de log2 n.
Un autre exemple est la recherche binaire, dont le temps d’exécution est O (log2 n) car à chaque étape, vous divisez l’espace de recherche par 2.
Cela signifie simplement que le temps nécessaire à cette tâche augmente avec log (n) (exemple: 2s pour n = 10, 4s pour n = 100, ...). Lisez les articles de Wikipedia sur Algorithme de recherche binaire et Notation O pour plus de précisions.
En termes simples: à chaque étape de votre algorithme, vous pouvez diviser le travail en deux. (Asymptotiquement équivalent aux troisième, quatrième, ...)
Si vous tracez une fonction logarithmique sur une calculatrice graphique ou quelque chose de similaire, vous verrez qu'elle augmente très lentement, encore plus lentement qu'une fonction linéaire.
C'est pourquoi les algorithmes à complexité temporelle logarithmique sont très recherchés: même pour de très grands n (disons n = 10 ^ 8, par exemple), ils fonctionnent plus qu'acceptablement.
Mais quel est exactement O (log n)
Cela signifie précisément que "lorsque n
tend vers infinity
, le time
tend vers a*log(n)
où a
est un facteur d'échelle constant".
Ou en fait, cela ne signifie pas tout à fait cela; plus vraisemblablement, cela signifie quelque chose comme "time
divisée par a*log(n)
tend vers 1
".
"Tendance vers" a le sens mathématique habituel tiré de "analyse": par exemple, que "si vous choisissez n'importe lequel une constante non nulle arbitrairement petite k
, alors je peux trouver une valeur correspondante X
tel que ((time/(a*log(n))) - 1)
soit inférieur à k
pour toutes les valeurs de n
supérieur à X
. "
En termes simples, cela signifie que l'équation du temps peut avoir d'autres composantes: par ex. il peut avoir un temps de démarrage constant; mais ces autres composants sont presque insignifiantes pour les grandes valeurs de n, et a * log (n) est le terme dominant pour les grands n.
Notez que si l'équation était, par exemple ...
temps (n) = a + b log (n) + c n + d n n
... alors ce serait O (n carré) car, quelles que soient les valeurs des constantes a, b, c et non-nul d, le terme d*n*n
dominerait toujours les autres suffisamment longtemps grande valeur de n.
C’est ce que signifie la notation bit O: il signifie "quel est l’ordre des termes dominants pour tout n suffisamment grand".
Je peux ajouter quelque chose d'intéressant, que j'ai lu dans un livre de Kormen, etc., il y a longtemps. Maintenant, imaginons un problème dans lequel nous devons trouver une solution dans un espace de problèmes. Cet espace de problème devrait être fini.
Maintenant, si vous pouvez prouver que, à chaque itération de votre algorithme, vous coupez une fraction de cet espace, ce n’est pas moins qu’une limite, cela signifie que votre algorithme s’exécute dans O(logN) time .
Je devrais souligner que nous parlons ici d'une limite de fraction relative, pas de la limite absolue. La recherche binaire est un exemple classique. À chaque étape, nous jetons la moitié de l’espace problématique. Mais la recherche binaire n'est pas le seul exemple. Supposez, vous avez prouvé que, à chaque étape, vous jetez au moins 1/128 de l’espace posant problème. Cela signifie que votre programme est toujours en cours d'exécution à O(logN) heure, bien que beaucoup plus lent que la recherche binaire. C'est un très bon indice pour l'analyse d'algorithmes récursifs. On peut souvent prouver qu'à chaque étape, la récursivité n'utilisera pas plusieurs variantes, ce qui conduit à la coupure d'une fraction dans l'espace de problème.
Je peux donner un exemple pour une boucle perdue et peut-être qu'une fois saisi le concept, il sera peut-être plus simple à comprendre dans différents contextes.
Cela signifie que dans la boucle, le pas croît de manière exponentielle. Par exemple.
for (i=1; i<=n; i=i*2) {;}
La complexité en notation O de ce programme est O (log (n)). Essayons de le parcourir à la main (n étant compris entre 512 et 1023 (sauf 1024):
step: 1 2 3 4 5 6 7 8 9 10
i: 1 2 4 8 16 32 64 128 256 512
Bien que n se situe entre 512 et 1023, seulement 10 itérations ont lieu. En effet, le pas dans la boucle croît de manière exponentielle et ne nécessite donc que 10 itérations pour atteindre la fin.
Le logarithme de x (à la base de a) est la fonction inverse de a ^ x.
C'est comme dire que le logarithme est l'inverse de l'exponentielle.
Maintenant, essayez de le voir de cette façon, si exponentielle grandit très vite, le logarithme croît (inversement) très lentement.
La différence entre O(n) et O(log(n)) est énorme, semblable à la différence entre O(n) et O (a ^ n) (a étant une constante).
Chaque fois que nous écrivons un algorithme ou un code, nous essayons d'analyser sa complexité asymptotique. Il est différent de son complexité temporelle.
La complexité asymptotique est le comportement du temps d'exécution d'un algorithme, tandis que la complexité temporelle est le temps d'exécution réel. Mais certaines personnes utilisent ces termes de manière interchangeable.
Parce que la complexité du temps dépend de divers paramètres, à savoir.
1. Système physique
2. Langage de programmation
3. Style de codage
4. Et beaucoup plus ......
Le temps d'exécution réel n'est pas une bonne mesure pour l'analyse.
Au lieu de cela, nous prenons la taille d'entrée comme paramètre car, quel que soit le code, l'entrée est la même. Le temps d'exécution est donc fonction de la taille de l'entrée.
Voici un exemple d’algorithme de temps linéaire
Recherche linéaire
Etant donné n éléments d’entrée, pour rechercher un élément dans le tableau dont vous avez besoin au plus 'n' comparaisons. En d’autres termes, quel que soit le langage de programmation que vous utilisez, le style de codage que vous préférez, sur quel système vous l’exécutez. Dans le pire des cas, il ne nécessite que n comparaisons. Le temps d'exécution est linéairement proportionnel à la taille de l'entrée.
Et ce n’est pas simplement une recherche, quel que soit le travail (incrémentation, comparaison ou toute opération), c’est une fonction de la taille de l’entrée.
Ainsi, lorsque vous dites que l’un des algorithmes est O (log n), cela signifie que le temps d’exécution est la valeur du journal fois la taille d’entrée n.
Au fur et à mesure que la taille de l'entrée augmente, le travail effectué (ici le temps d'exécution) augmente (d'où la proportionnalité).
n Work
2 1 units of work
4 2 units of work
8 3 units of work
Voir que la taille d'entrée a augmenté, le travail effectué est augmenté et il est indépendant de toute machine. Et si vous essayez de connaître la valeur des unités de travail, cela dépend en fait des paramètres spécifiés ci-dessus. Cela changera en fonction des systèmes et de tous.
En informatique, cela signifie que:
f(n)=O(g(n)) If there is suitable constant C and N0 independent on N,
such that
for all N>N0 "C*g(n) > f(n) > 0" is true.
Il semble que cette notation ait été principalement empruntée aux mathématiques.
Dans cet article, il y a une citation: D.E. Knuth, "BIG OMICRON ET BIG OMEGA ET BIG THETA", 1976 :
Sur la base des questions abordées ici, je propose que les membres du SIGACT et les rédacteurs en chef de revues d'informatique et de mathématiques adoptent les notations définies ci-dessus, à moins que une meilleure solution ne soit trouvée dans un délai raisonnable .
Nous sommes en 2016, mais nous l'utilisons encore aujourd'hui.
En analyse mathématique, cela signifie que:
lim (f(n)/g(n))=Constant; where n goes to +infinity
Mais même en analyse mathématique, ce symbole était parfois utilisé pour signifier "C * g (n)> f(n)> 0".
Comme je le sais de l'université, le symbole a été introduit par le mathématicien allemand Landau (1877-1938)
log x to base b = y
est l'inverse de b^y = x
Si vous avez un arbre à mailles de profondeur d et de taille n, alors:
traverser l'arbre entier ~ O (M ^ d) = O (n)
Marcher un seul chemin dans l’arbre ~ O(d) = O (log n à la base M)
En fait, si vous avez une liste de n éléments et créez un arbre binaire à partir de cette liste (comme dans l'algorithme Divide and Conquer), vous continuerez à diviser par 2 jusqu'à atteindre des listes de taille 1 (les feuilles).
A la première étape, vous divisez par 2. Vous avez ensuite 2 listes (2 ^ 1), vous divisez chacune par 2, vous avez donc 4 listes (2 ^ 2), vous divisez à nouveau, vous avez 8 listes (2 ^ 3 ) et ainsi de suite jusqu'à ce que la taille de votre liste soit 1
Cela vous donne l'équation:
n/(2^steps)=1 <=> n=2^steps <=> lg(n)=steps
(vous prenez la lg de chaque côté, lg étant la base de bûche 2)
Si vous cherchez une réponse basée sur l’intuition, je voudrais vous donner deux interprétations.
Imaginez une très haute colline avec une base très large également. Pour atteindre le sommet de la colline, il y a deux manières: l'une est un sentier dédié qui tourne en spirale autour de la colline et atteint le sommet, l'autre: une petite terrasse en forme de sculpture découpée pour servir d'escalier. Maintenant, si la première manière atteint le temps linéaire O (n), la seconde est O (log n).
Imaginez un algorithme, qui accepte un entier, n
comme entrée et se termine dans le temps proportionnellement à n
, alors il s’agit de O(n) ou de theta (n), mais si proportion temporelle par rapport au number of digits or the number of bits in the binary representation on number
puis l'algorithme s'exécute en temps O (log n) ou thêta (log n).
Les algorithmes du paradigme Divide and Conquer sont de complexité O (logn). Un exemple ici, calcule ta propre fonction de puissance,
int power(int x, unsigned int y)
{
int temp;
if( y == 0)
return 1;
temp = power(x, y/2);
if (y%2 == 0)
return temp*temp;
else
return x*temp*temp;
}
de http://www.geeksforgeeks.org/write-a-c-program-to-calculate-powxn/
L'exemple binaire complet est O (ln n) car la recherche ressemble à ceci:
1 2 3 4 5 6 7 8 9 10 11 12
La recherche de 4 donne 3 résultats: 6, 3, puis 4. Et log2 12 = 3, ce qui est une bonne approximation du nombre de résultats, le cas échéant.
J'aimerais ajouter que la hauteur de l'arbre est la longueur du plus long chemin allant de la racine à une feuille et que la hauteur d'un nœud est la longueur du plus long chemin de ce nœud à une feuille. Le chemin désigne le nombre de nœuds rencontrés lors de la traversée de l'arbre entre deux nœuds. Pour atteindre la complexité temporelle O (log n), l’arbre doit être équilibré, ce qui signifie que la différence de hauteur entre les enfants de tout nœud doit être inférieure ou égale à 1. Par conséquent, les arbres ne garantissent pas toujours une complexité temporelle. O (log n), sauf s'ils sont équilibrés. En fait, dans certains cas, la complexité temporelle de la recherche dans une arborescence peut être O(n) dans le pire des cas.
Vous pouvez consulter les arbres d'équilibre tels que AVL tree
. Celui-ci travaille sur l'équilibrage de l'arborescence lors de l'insertion de données afin de conserver une complexité temporelle de (log n) lors de la recherche dans l'arborescence.
O (logn) est l’une des complexités temporelles polynomiales permettant de mesurer les performances d’exécution de tout code.
J'espère que vous avez déjà entendu parler de l'algorithme de recherche binaire.
Supposons que vous deviez trouver un élément dans le tableau de taille N.
En gros, l’exécution du code est comme N N/2 N/4 N/8 .... etc
Si vous additionnez tout le travail effectué à chaque niveau, vous vous retrouverez avec n (1 + 1/2 + 1/4 ....), ce qui est égal à O (logn)