Qu'est-ce qui causerait la complexité O(log n) d'un algorithme?
ma connaissance du big-O est limitée, et quand les Termes logarithmes apparaissent dans l'équation, cela me perturbe encore plus.
quelqu'un peut-il m'expliquer en termes simples ce qu'est un algorithme O(log n)
? D'où vient le logarithme venir?
c'est précisément ce qui est venu quand j'ai essayé de résoudre cette question de pratique à mi-parcours:
Laissez-X, Paragraphe 1..n) et Y(1..n) contiennent deux listes d'entiers, chaque trié nondecreasing commande. Donnez un algorithme o (log n)-time pour trouver la médiane (ou le nième plus petit entier) de tous les 2N éléments combinés. Pour ex, X = (4, 5, 7, 8, 9) et Y = (3, 5, 8, 9, 10), puis 7 est la médiane de la liste (3, 4, 5, 5, 7, 8, 8, 9, 9, 10). [Conseil: utiliser les concepts de recherche binaire]
6 réponses
je dois admettre que c'est assez bizarre la première fois que vous voyez un algorithme O(log n)... où sur la terre, n'est que le logarithme venir? Cependant, il s'avère qu'il y a plusieurs façons différentes d'obtenir un terme logarithmique pour apparaître dans la notation big-O. Voici quelques-uns:
divisant à plusieurs reprises par une constante
prenez n'importe quel numéro n; dites, 16. Combien de fois pouvez-vous diviser n par deux avant d'obtenir un nombre inférieur ou égal à un? Pour 16, nous avons que
16 / 2 = 8
8 / 2 = 4
4 / 2 = 2
2 / 2 = 1
Avis que cela prend quatre étapes. Il est intéressant de noter que nous avons aussi ce journal 2 16 = 4. Hmmm... et la 128?
128 / 2 = 64
64 / 2 = 32
32 / 2 = 16
16 / 2 = 8
8 / 2 = 4
4 / 2 = 2
2 / 2 = 1
cela a pris sept étapes, et le log 2 128 = 7. Est-ce une coïncidence? Nope! Il y a une bonne raison à cela. Supposons que nous divisions un nombre n par deux fois et demie. Puis nous obtenons le numéro n / 2 i . Si nous voulons résoudre pour la valeur de i, si cette valeur est à 1, nous obtenons
n / 2 je ≤ 1
n ≤ 2 je
log 2 n ≤ i
en d'autres termes, si nous choisissons un entier i tel que je ≥ log 2 n, puis après la division de n en demi-i fois nous allons ont une valeur qui est au plus 1. Le plus petit i pour lequel cela est garanti est à peu près log 2 n, Donc si nous avons un algorithme qui divise par 2 jusqu'à ce que le nombre devient suffisamment petit, alors nous pouvons dire qu'il se termine en o(log n) étapes.
un détail important est qu'il n'a pas d'importance de quelle constante vous divisez n Par (tant qu'il est plus grand qu'un); si vous divisez par la constante k, il prendra log k n étapes pour atteindre 1. Ainsi, tout algorithme qui divise à plusieurs reprises la taille d'entrée par une fraction quelconque aura besoin D'itérations O(log n) pour se terminer. Ces itérations peuvent prendre beaucoup de temps et donc le temps d'exécution net n'a pas besoin d'être O(log n), Mais le nombre d'étapes sera logarithmique.
alors où est-ce que ça se présente? Un exemple classique est recherche binaire , un algorithme rapide pour la recherche d'un tableau trié pour une valeur. L'algorithme fonctionne comme ceci:
- si le tableau est vide, retournez que l'élément n'est pas présent dans le tableau.
- sinon:
- regardez l'élément central du tableau.
- si elle est égale à l'élément que nous recherchons, retour succès.
- si c'est plus grand que l'élément que nous cherchons:
- jetez la seconde moitié du tableau.
- Répéter
- si elle est inférieure à l'élément que nous recherchons:
- jetez la première moitié du tableau.
- Répéter
Par exemple, pour rechercher 5 dans le tableau
1 3 5 7 9 11 13
regardons d'abord l'élément central:
1 3 5 7 9 11 13
^
depuis 7 > 5, et puisque le tableau est trié, nous savons pour un fait que le nombre 5 ne peut pas être dans la moitié arrière du tableau, donc nous pouvons juste le jeter. Cela laisse
1 3 5
donc maintenant nous regardons l'élément central ici:
1 3 5
^
depuis 3 < 5, nous savons que 5 ne peut pas apparaître dans la première moitié du tableau, de sorte que nous pouvons jeter la première moitié du tableau de quitter
5
encore une fois, nous regardons au milieu de ce tableau:
5
^
Car c'est exactement le nombre que nous recherchons, nous pouvons signaler que 5 est en effet dans le tableau.
est-ce efficace? À chaque itération, on jette au moins la moitié des éléments restants. L'algorithme s'arrête dès que le tableau est vide ou nous trouvons la valeur que nous voulons. Dans le pire des cas, l'élément n'est pas là, donc nous continuons à réduire de moitié la taille du tableau jusqu'à ce que nous soyons à court d'éléments. Combien de temps cela prend-il? Eh bien, puisque nous continuons à couper le tableau en deux encore et encore, nous allons le faire à la plupart des itérations O(log n), puisque nous ne pouvons pas couper le tableau en deux fois plus que O(log n) Avant de nous lancer à court d'éléments du tableau.
algorithmes suivant la technique générale de diviser pour mieux régner (couper le problème en morceaux, résoudre ces morceaux, puis remettre le problème ensemble) ont tendance à ayez des termes logarithmiques en eux pour cette même raison - vous ne pouvez pas continuer à couper un objet en deux fois plus de o(log n). Vous voudrez peut-être chercher à fusion tri comme un bon exemple de cela.
valeurs de traitement un chiffre à la fois
combien de chiffres y a-t-il dans la base-10 numéro n? S'il y a k chiffres dans le numéro, alors le plus gros est un multiple de 10. k . Le plus grand nombre de chiffres en k est le 999...9, k fois, et ceci est égal à 10 k + 1 - 1. Par conséquent, si nous savons que n a k chiffres, alors nous savons que la valeur de n est d'au plus 10 k + 1 - 1. Si nous voulons résoudre pour k en termes de n, nous obtenons
n ≤ 10 k+1 - 1
n + 1 ≤ 10 k+1
log 10 (n + 1) ≤ k + 1
(log 10 (n + 1)) - 1 ≤ k
D'où nous obtenons que k est approximativement la base-10 logarithme de N. En d'autres termes, le nombre de chiffres dans n est o(log n).
par exemple, pensons à la complexité de l'ajout de deux grands nombres qui sont trop grands pour rentrer dans un mot machine. Supposer que nous avons ces nombres représentés dans la base 10, et nous appellerons les nombres m et N. Une façon de les ajouter est d'utiliser la méthode de l'école primaire: écrire les chiffres un chiffre à la fois, puis travailler de droite à gauche. Par exemple, pour ajouter 1337 et 2065, nous commencerions par écrire les nombres comme
1 3 3 7
+ 2 0 6 5
==============
nous ajoutons le dernier chiffre et portons le 1:
1
1 3 3 7
+ 2 0 6 5
==============
2
ensuite nous ajoutons l'avant-dernier ("avant-dernier") chiffre et portons le 1:
1 1
1 3 3 7
+ 2 0 6 5
==============
0 2
ensuite, nous ajoutons l'avant-dernier chiffre ("antepenultimate"):
1 1
1 3 3 7
+ 2 0 6 5
==============
4 0 2
enfin, nous ajoutons l'avant-dernier ("preantepenultimate")... I love English) chiffre:
1 1
1 3 3 7
+ 2 0 6 5
==============
3 4 0 2
maintenant, combien de travail avons-nous fait? Nous faisons un total de O(1) travail par chiffre (c'est-à-dire une quantité constante de travail), et il y a O(Max{log n, log m}) Nombre total de chiffres qui doivent être traités. Cela donne un total de O(max{log n, log m}) complexité, parce que nous avons besoin de visiter chaque chiffre dans les deux numéros.
de nombreux algorithmes reçoivent un terme O(log n) en travaillant un chiffre à la fois dans une base. Un exemple classique est Radix sort , qui trie des entiers un chiffre à la fois. Il ya beaucoup de saveurs de tri radix, mais ils fonctionnent généralement dans le temps O(N log U), où U est le plus grand nombre entier possible qui est en cours de tri. La raison pour cela est chaque passage du tri prend du temps O(n), et il y a un total D'itérations O(log U) nécessaires pour traiter chacun des chiffres O (log U) du plus grand nombre trié. De nombreux algorithmes avancés , tels que l'algorithme de Gabow Shortt-paths ou la version de mise à l'échelle de Ford-Fulkerson algorithme max-flow , ont un terme logarithmique dans leur complexité parce qu'ils travaillent un chiffre à la fois.
Quant à votre deuxième question sur la façon dont vous résoudre ce problème, vous pouvez vouloir regarder cette question liée qui explore une application plus avancée. Étant donné la structure générale des problèmes qui sont décrits ici, vous pouvez maintenant avoir une meilleure idée de la façon de penser aux problèmes quand vous savez qu'il ya un terme log dans le résultat, donc je conseillerais de ne pas regarder la réponse jusqu'à ce que vous avez donné une certaine réflexion.
Espérons que cette aide!
Lorsque nous parlons grand-Oh descriptions, nous parlons habituellement le temps qu'il faut pour résoudre les problèmes d'un taille . Et habituellement, pour les problèmes simples, cette taille est juste caractérisée par le nombre d'éléments d'entrée, et c'est généralement appelé n, ou N. (évidemment ce n'est pas toujours vrai-- les problèmes avec les graphiques sont souvent caractérisés par le nombre de Sommets, V, et le nombre de bords, E; mais pour l'instant, nous allons parler de listes de objets, avec N objets dans les listes.)
Nous disons qu'un problème de "big-Oh (fonction de N)" si et seulement si :
Pour tout N > arbitraire N_0, il existe une constante c, tels que le moteur d'exécution de l'algorithme est moins de que la constante c temps (une fonction de N.)
en d'autres termes, ne pensez pas à de petits problèmes où le "overhead constant" de la mise en place de la les problèmes comptent, pensez aux gros problèmes. Et quand on pense à de gros problèmes, big-Oh of (une fonction de N) signifie que le temps d'exécution est toujours toujours moins que certains temps constants que la fonction. Toujours.
En bref, cette fonction est une limite supérieure, jusqu'à un facteur constant.
ainsi, " big-Oh of log(n)" signifie la même chose que j'ai dit plus haut, sauf que "une certaine fonction de N" est remplacée par "log (n)."
donc, votre problème vous dit de penser à la recherche binaire, alors réfléchissons-y. Supposons que vous avez, disons, une liste de N éléments sont triés dans l'ordre croissant. Vous voulez savoir si un nombre donnī existe dans cette liste. Une façon de faire ce qui est pas une recherche binaire est de simplement scanner chaque élément de la liste et voir si c'est votre nombre cible. Vous pourriez avoir de la chance et le trouver au premier essai. Mais dans le pire des cas, vous cochez N différent temps. Ce n'est pas une recherche binaire, et ce n'est pas un gros-Oh de log(N) parce qu'il n'y a aucun moyen de le forcer dans les critères que nous avons esquissés ci-dessus.
vous pouvez choisir cette constante arbitraire pour être c=10, et si votre liste A N=32 éléments, vous êtes bien: 10*log(32) = 50, ce qui est plus grand que la durée d'exécution de 32. Mais si N=64, 10*log(64) = 60, ce qui est inférieur à la durée d'exécution de 64. Vous pouvez choisir c = 100, ou 1000, ou un gazillion, et vous pourrez toujours trouver quelque chose qui viole cette exigence. En d'autres termes, il n'y a pas de N_0.
si nous faisons une recherche binaire, cependant, nous choisissons l'élément du milieu, et faire une comparaison. Puis nous jeter la moitié des numéros, et le faire encore, et encore, et ainsi de suite. Si votre N=32, vous ne pouvez le faire qu'environ 5 fois, ce qui est log(32). Si votre N=64, Vous ne pouvez le faire qu'environ 6 fois, etc. Maintenant, vous peut choisir cette constante arbitraire c, de sorte que l'exigence est toujours respectée pour les grandes valeurs de N.
avec tout ce fond, ce que o(log(N)) signifie habituellement est que vous avez un moyen de faire une chose simple, qui Coupe votre taille de problème en deux. Tout comme la recherche binaire fait ci-dessus. Une fois que vous coupez le problème à moitié, vous pouvez le couper en deux, encore et encore, et encore. Mais, de façon critique, ce que vous ne pouvez pas faire est une étape de prétraitement qui prendrait plus de temps que ce temps O(log(N)). Ainsi, par exemple, vous ne pouvez pas mélanger vos deux liste en une seule grande liste, à moins que vous ne trouviez un moyen de le faire dans le temps O(log(N)), aussi.
(NOTE: presque toujours, Log (N) signifie log-base-deux, ce qui est ce que je suppose ci-dessus.)
Dans la solution suivante, toutes les lignes avec un appel récursif sont fait sur la moitié des tailles données des sous-tableaux de X et Y. Les autres lignes sont faites en un temps constant. La fonction récursive est T(2n)=T(2n/2)+c=T(n)+C=O(lg(2n))=O (lgn).
vous commencez avec la médiane(X, 1, n, Y, 1, n).
MEDIAN(X, p, r, Y, i, k)
if X[r]<Y[i]
return X[r]
if Y[k]<X[p]
return Y[k]
q=floor((p+r)/2)
j=floor((i+k)/2)
if r-p+1 is even
if X[q+1]>Y[j] and Y[j+1]>X[q]
if X[q]>Y[j]
return X[q]
else
return Y[j]
if X[q+1]<Y[j-1]
return MEDIAN(X, q+1, r, Y, i, j)
else
return MEDIAN(X, p, q, Y, j+1, k)
else
if X[q]>Y[j] and Y[j+1]>X[q-1]
return Y[j]
if Y[j]>X[q] and X[q+1]>Y[j-1]
return X[q]
if X[q+1]<Y[j-1]
return MEDIAN(X, q, r, Y, i, j)
else
return MEDIAN(X, p, q, Y, j, k)
nous appelons la complexité temporelle O(log n), lorsque la solution est basée sur des itérations sur n, où le travail effectué dans chaque itération est une fraction de l'itération précédente, que l'algorithme travaille vers la solution.
ne peut pas encore commenter... necro il est! La réponse d'Avi Cohen est incorrecte, essayez:
X = 1 3 4 5 8
Y = 2 5 6 7 9
aucune des conditions ne sont vraies, donc la médiane(X, p, q, Y, j, k) coupera les deux fives. Ce sont des séquences non décroissantes, toutes les valeurs ne sont pas distinctes.
essayez aussi cet exemple de longueur uniforme avec des valeurs distinctes:
X = 1 3 4 7
Y = 2 5 6 8
maintenant médian (X, p, q, Y, j+1, k) coupera les quatre.
à la place j'offre cet algorithme, l'appeler avec la médiane(1,n,1,n):
MEDIAN(startx, endx, starty, endy){
if (startx == endx)
return min(X[startx], y[starty])
odd = (startx + endx) % 2 //0 if even, 1 if odd
m = (startx+endx - odd)/2
n = (starty+endy - odd)/2
x = X[m]
y = Y[n]
if x == y
//then there are n-2{+1} total elements smaller than or equal to both x and y
//so this value is the nth smallest
//we have found the median.
return x
if (x < y)
//if we remove some numbers smaller then the median,
//and remove the same amount of numbers bigger than the median,
//the median will not change
//we know the elements before x are smaller than the median,
//and the elements after y are bigger than the median,
//so we discard these and continue the search:
return MEDIAN(m, endx, starty, n + 1 - odd)
else (x > y)
return MEDIAN(startx, m + 1 - odd, n, endy)
}
le terme logarithmique apparaît très souvent dans l'analyse de la complexité de l'algorithme. Voici quelques explications:
1. Comment représenter un nombre?
permet de prendre le nombre X = 245436. Cette notation de "245436" contient des informations implicites. Expliciter cette information:
X = 2 * 10 ^ 5 + 4 * 10 ^ 4 + 5 * 10 ^ 3 + 4 * 10 ^ 2 + 3 * 10 ^ 1 + 6 * 10 ^ 0
, Qui est l'expansion décimale du nombre. Donc, le minimum d'information nous devons représenter ce numéro est 6 chiffres. Ce n'est pas une coïncidence, car tout nombre inférieur à 10^d peut être représenté par les chiffres d .
donc combien de chiffres sont nécessaires pour représenter X? Thats égal au plus grand exposant de 10 dans X plus 1.
= = > 10 ^ d > X
= = > log (10 ^ d) > log (X)
= = > d * log(10) > log (X)
= = > d > log(X) // et log apparaît de nouveau...
= = > d = plancher (log(x)) + 1
notez aussi que c'est le moyen le plus concis pour indiquer le nombre dans cette gamme. Toute réduction entraînera une perte d'information, car un chiffre manquant peut être mappé à 10 autres numéros. Par exemple: 12* peut être mappé à 120, 121, 122, ..., 129.
2. Comment rechercher un nombre dans (0, N-1)?
en prenant N = 10^D, nous utilisons notre observation la plus importante:
la quantité minimale d'information permettant d'identifier de façon unique une valeur comprise entre 0 et N - 1 = log(N) chiffres.
cela implique que, lorsqu'il est demandé de rechercher pour un nombre sur la ligne entière, allant de 0 à N - 1, nous avons besoin au moins log(N) essaie de le trouver. Pourquoi? Tout algorithme de recherche devra choisir un chiffre après l'autre dans sa recherche du numéro.
le nombre minimum de chiffres qu'il doit choisir est log(N). D'où le nombre minimum d'opérations prises pour chercher un numéro dans un espace de taille N est log(N).
Pouvez-vous deviner l'ordre complexité de la recherche binaire, de la recherche ternaire ou de la recherche déca?
Son O(log(N))!
3. Comment triez-vous un ensemble de chiffres?
lorsqu'on demande de trier un ensemble de nombres A dans un tableau B, voici à quoi il ressemble - >
chaque élément du tableau original doit être associé à l'index correspondant dans le tableau trié. Ainsi, pour le premier élément, nous avons n positions. Pour trouver correctement l'index correspondant dans cette plage de 0 à n-1, nous avons besoin...opérations log(n).
l'élément suivant nécessite des opérations log(n-1), le log suivant(n-2) et ainsi de suite. Le total se chiffre à être:
= = > log (n) + log (n - 1) + log (n - 2) + ... + log(1)
l'utilisation de log(a) + log (b) = log(a * b),
==> log(n!)
cela peut être approximé à nlog (n) - N.
qui est O (N*log (n))!
nous concluons donc qu'il ne peut y avoir d'algorithme de tri qui puisse faire mieux que O(n*log(n)). Et certains algorithmes ayant cette complexité sont la fusion populaire tri et Tri tas!
ce sont quelques-unes des raisons pour lesquelles nous voyons log(n) apparaître si souvent dans l'analyse de la complexité des algorithmes. La même chose peut être étendue aux nombres binaires. J'ai fait une vidéo à ce sujet ici.
pourquoi le log(n) apparaît-il si souvent lors de l'analyse de la complexité de l'algorithme?
santé!