web-dev-qa-db-fra.com

Pourquoi la recherche dans un arbre de recherche binaire est O (log (n))?

Je peux voir comment, lorsque nous recherchons une valeur dans un BST nous laissons la moitié de l'arbre à chaque fois que nous comparons un nœud avec la valeur que nous recherchons.

Cependant, je ne vois pas pourquoi la complexité temporelle est O(log(n)). Donc, ma question est:

Si nous avons un arbre de N éléments, pourquoi la complexité temporelle de rechercher l'arbre et de vérifier si une valeur particulière existe est O (log (n)), comment l'obtenir?

21
Hommer Smith

Votre question semble avoir une bonne réponse ici mais pour résumer par rapport à votre question spécifique, il serait peut-être préférable d'y penser à l'envers; "qu'arrive-t-il au temps de la solution BST lorsque le nombre de nœuds augmente"?

Essentiellement, dans un BST chaque fois que vous doublez le nombre de nœuds, vous n'augmentez que le nombre d'étapes à résoudre d'une unité. Pour étendre cela, quatre fois les nœuds donnent deux étapes supplémentaires. Huit fois, les nœuds donnent trois étapes supplémentaires. Seize fois, les nœuds donnent quatre étapes supplémentaires. Etc.

Le journal de base 2 du premier nombre de ces paires est le deuxième nombre de ces paires. C'est le journal de base 2 car il s'agit d'une recherche binaire (vous divisez par deux l'espace du problème à chaque étape).

32
Mike Higginbottom

Pour moi, la façon la plus simple était de regarder un graphique de log2 (n), où n est le nombre de nœuds dans l'arbre binaire. En tant que tableau, cela ressemble à ceci:

          log2(n) = d

          log2(1) = 0
          log2(2) = 1 
          log2(4) = 2
          log2(8) = 3
          log2(16)= 4 
          log2(32)= 5
          log2(64)= 6             

puis je dessine un petit arbre binaire, celui-ci passe de la profondeur d = 0 à d = 3:

            d=0          O
                        / \
            d=1        R   B
                      /\   /\
            d=2      R  B R  B
                    /\ /\ /\ /\
            d=3    R B RB RB R B

Donc, comme le nombre de nœuds, n, dans l'arbre effectivement double (par exemple, n augmente de 8 en passant de 7 à 15 (ce qui est presque un doublant) lorsque la profondeur d passe de d = 2 à d = 3, augmentant de 1.) Ainsi, la quantité supplémentaire de traitement requise (ou le temps requis) augmente de seulement 1 calcul supplémentaire (ou itération) , car la quantité de traitement est liée à d.

Nous pouvons voir que nous descendons seulement 1 niveau supplémentaire de profondeur d, de d = 2 à d = 3, pour trouver le nœud que nous voulons parmi tous les nœuds n, après avoir doublé le nombre de nœuds. C'est vrai parce que nous avons maintenant cherché dans tout l'arbre, eh bien, la moitié de celui-ci que nous devions rechercher pour trouver le nœud que nous voulions.

Nous pouvons l'écrire sous la forme d = log2(n), où d nous indique la quantité de calcul (combien d'itérations) nous devons faire (en moyenne) pour atteindre n'importe quel nœud de l'arbre, lorsqu'il y a n nœuds dans l'arbre.

7
Will

Cela peut être démontré mathématiquement très facilement.

Avant de présenter cela, permettez-moi de clarifier quelque chose. La complexité de la recherche ou de la recherche dans un arbre de recherche binaire équilibré est O (log (n)). Pour un arbre de recherche binaire en général, c'est O (n). Je vais montrer les deux ci-dessous.

Dans un arbre de recherche binaire équilibré, dans le pire des cas, la valeur que je recherche est dans la feuille de l'arbre. Je vais essentiellement traverser de la racine à la feuille, en ne regardant chaque couche de l'arbre qu'une seule fois, en raison de la structure ordonnée des BST. Par conséquent, le nombre de recherches que je dois faire est le nombre de couches de l'arbre. Par conséquent, le problème se résume à trouver une expression de forme fermée pour le nombre de couches d'un arbre à n nœuds.

C'est là que nous ferons une simple induction. Un arbre avec seulement 1 couche a seulement 1 nœud. Un arbre de 2 couches a 1 + 2 nœuds. 3 couches 1 + 2 + 4 nœuds, etc. Le motif est clair: un arbre avec k couches a exactement

n = 2 ^ 0 + 2 ^ 1 + ... + 2 ^ {k-1}

nœuds. Il s'agit d'une série géométrique, ce qui implique

n = 2 ^ k-1,

de manière équivalente:

k = log (n + 1)

Nous savons que big-oh s'intéresse aux grandes valeurs de n, donc les constantes ne sont pas pertinentes. D'où la complexité O(log(n)).

Je vais donner un autre moyen, beaucoup plus court, pour montrer le même résultat. Puisque, tout en recherchant une valeur, nous divisons constamment l'arbre en deux moitiés, et nous devons le faire k fois, où k est le nombre de couches, ce qui suit est vrai:

(n + 1)/2 ^ k = 1,

ce qui implique exactement le même résultat. Vous devez vous convaincre d'où vient ce +1 dans n + 1, mais c'est correct même si vous n'y prêtez pas attention, car nous parlons de grandes valeurs de n.

Voyons maintenant l'arborescence de recherche binaire générale. Dans le pire des cas, il est parfaitement déséquilibré, ce qui signifie que tous ses nœuds n'ont qu'un seul enfant (et il devient une liste chaînée) Voir par ex. https://www.cs.auckland.ac.nz/~jmor159/PLDS210/niemann/s_fig33.gif

Dans ce cas, pour trouver la valeur dans la feuille, j'ai besoin d'itérer sur tous les nœuds, d'où O (n).

Une dernière remarque est que ces complexités sont valables non seulement pour les opérations de recherche, mais aussi d'insertion et de suppression.

(Je modifierai mes équations avec un style mathématique Latex plus beau lorsque j'atteindrai 10 points de répétition. SO ne me le permettra pas pour le moment.)

4
FatihAkici

Chaque fois que vous voyez un runtime qui a un facteur O (log n), il y a de très bonnes chances que vous regardiez quelque chose de la forme "continuez à diviser la taille d'un objet par une constante." Donc, probablement la meilleure façon de réfléchir à cette question est - comme vous effectuez des recherches dans un arbre de recherche binaire, qu'est-ce qui se coupe exactement par un facteur constant, et quelle est exactement cette constante?

Pour commencer, imaginons que vous avez un arbre binaire parfaitement équilibré, quelque chose qui ressemble à ceci:

                     *
              /             \
             *               *
          /     \         /     \
         *       *       *       *
        / \     / \     / \     / \
       *   *   *   *   *   *   *   *

À chaque étape de la recherche, vous examinez le nœud actuel. Si c'est celui que vous recherchez, tant mieux! Vous avez totalement terminé. D'un autre côté, si ce n'est pas le cas, vous descendez soit dans le sous-arbre gauche ou le sous-arbre droit, puis répétez ce processus.

Si vous entrez dans l'un des deux sous-arbres, vous dites essentiellement "Je ne me soucie pas du tout de ce qu'il y a dans cet autre sous-arbre". Vous jetez tous les nœuds dedans. Et combien de nœuds y a-t-il? Eh bien, avec une inspection visuelle rapide - idéalement suivie d'un bon calcul - vous verrez que vous jetez environ la moitié des nœuds dans l'arbre.

Cela signifie qu'à chaque étape d'une recherche, vous (1) trouvez le nœud que vous recherchez ou (2) jetez la moitié des nœuds dans l'arborescence. Étant donné que vous effectuez une quantité constante de travail à chaque étape, vous examinez le comportement caractéristique du comportement O (log n) - le travail diminue d'un facteur constant à chaque étape, et il ne peut donc le faire que de manière logarithmique. fois.

Maintenant, bien sûr, tous les arbres ne ressemblent pas à ceci. Les arbres AVL ont la propriété amusante que chaque fois que vous descendez dans un sous-arbre, vous jetez environ une fraction du nombre d'or du nombre total de nœuds. Cela garantit donc que vous ne pouvez effectuer que logarithmiquement de nombreuses étapes avant de manquer de nœuds - d'où la hauteur O (log n). Dans un arbre rouge/noir, chaque étape jette (environ) un quart du nombre total de nœuds, et puisque vous rétrécissez d'un facteur constant, vous obtenez à nouveau le temps de recherche O (log n) que vous souhaitez. L'arbre bouc émissaire très amusant a un paramètre réglable qui est utilisé pour déterminer son équilibre, mais encore une fois, vous pouvez montrer que chaque étape que vous faites jette un facteur constant basé sur ce paramètre réglable, donnant des recherches O (log n).

Cependant, cette analyse se décompose pour les arbres déséquilibrés. Si vous avez un arbre purement dégénéré - celui où chaque nœud a exactement un enfant - alors chaque étape dans l'arbre que vous prenez ne jette qu'un seul nœud, pas une fraction constante. Cela signifie que le temps de recherche atteint O(n) dans le pire des cas, car le nombre de fois que vous pouvez soustraire une constante de n est O (n).

1
templatetypedef

Si nous avons un arbre de N éléments, pourquoi la complexité temporelle de rechercher l'arbre et de vérifier si une valeur particulière existe est O (log (n)), comment l'obtenir?

Ce n'est pas vrai. Par défaut, une recherche dans une arborescence de recherche binaire n'est pas O(log(n)), où n est un certain nombre de nœuds. Dans le pire des cas, il peut devenir O(n). Par exemple, si nous insérons des valeurs de la séquence suivante n, n - 1, ..., 1 (Dans le même ordre), alors l'arbre sera représenté comme ci-dessous:

                  n
                 /
              n - 1
               /
            n - 2
             /
           ...
           1

La recherche d'un nœud avec la valeur 1 A une complexité temporelle de O(n).

Pour rendre une recherche plus efficace, l'arborescence doit être équilibrée afin que sa hauteur maximale soit proportionnelle à log(n). Dans ce cas, la complexité temporelle de la recherche est O(log(n)) car la recherche d'une feuille est limitée par des opérations log(n).

Mais encore une fois, tous les arbre de recherche binaire n'est pas arbre de recherche binaire équilibré. Vous devez l'équilibrer pour garantir la complexité temporelle de O(log(n)).

0
Anatolii