826 votes

Construire la complexité des tas

Quelqu'un peut-il expliquer comment construire un segment de mémoire possible complexité d’o (n) ? Insertion d’un élément dans un tas de O(logN), et l’insert est répété n/2 fois (le reste est des feuilles et ne peut pas violer la propriété de tas. Donc cela signifie que la complexité doit être O (nLog n) je pense.

Autrement dit, pour chaque élément, nous « heapify », il a le potentiel d’avoir à filtrer une fois pour chaque niveau pour le tas jusqu'à présent (qui est Journal n niveaux).

Ce qui me manque ?

901voto

Jeremy West Points 877

Je réalise que c'est une vieille question, et peut-être cela devrait être une réponse à un commentaire de la accepté de répondre, mais hélas, je n'ai pas la condition de la réputation (sur ce site) pour poster un commentaire... :). Aussi, je peux fournir plus de détails ici.

La accepté de répondre donne la bonne analyse pour montrer que buildHeap s'exécute en O(n) fois. Mais la question a été posée:

"C'est une bonne explication...mais pourquoi est-il alors que le tas de tri s'exécute en O(n log n). Pourquoi ne pas le même raisonnement s'applique à des tas de tri?"

Une réponse a été donnée dans les commentaires qui suggère que la différence réside dans siftDown vs siftUp que je pense un peu à côté du point (ou est tout simplement trop brève pour qu'il soit clair). En effet, vous n'avez pas besoin d'utiliser siftUp lors de la mise en œuvre d'un tas binaire ou d'un segment de tri.

Rappelez-vous que le segment de la propriété spécifie que chaque nœud doit être plus petite que ses deux enfants. Le tamisage vers le bas et le tamisage sont essentiellement la même opération dans des directions opposées: déplacement d'un nœud fautif jusqu'à ce qu'il satisfait la propriété tas. Le tamisage se déplace vers le bas d'un nœud vers le bas jusqu'à ce qu'il est plus petit que ses enfants. Le tamisage se déplace d'un nœud jusqu'à ce qu'elle est supérieure à celle de son parent. Le nombre d'opérations nécessaires pour chaque opération est proportionnelle à la distance du nœud peut avoir à se déplacer. Pour siftDown, c'est la distance entre le bas de l'arbre, de sorte siftDown est cher pour les nœuds en haut de l'arbre. Avec siftUp, le travail est proportionnel à la distance à partir du haut de l'arbre, de sorte siftUp est cher pour les noeuds sur le bas de l'arbre.

Une façon de construire un segment à partir d'un tableau d'éléments non triés est de commencer au sommet de la pile et de l'appel siftUp sur chaque élément. À chaque étape, préalablement tamisée éléments de forme valide d'un tas, et le tamisage de l'élément suivant jusqu'le place dans une position valide dans le tas. Ainsi, après le tamisage de chaque nœud, tous les éléments de satisfaire la propriété tas. Cependant, la grande majorité des nœuds sont dans le bas de l'arbre (près de la moitié dans le fond de la couche seul) et en appelant siftUp est cher pour ces nœuds.

Une approche plus efficace est celui qui est indiqué par le a accepté réponse: commencez par le bas et appel siftDown. Maintenant, les nœuds dans la couche de fond ne bouge pas du tout (ils sont déjà plus petits que leurs inexistante enfants). Les nœuds dans l'avant-dernière couche déplacer sur une distance maximale de un. Le compromis est que les nœuds en haut peut-être déplacer beaucoup, mais il y a beaucoup moins de nœuds près du haut de l'arbre que le fond. Le travail effectué peut être délimitée comme suit: la moitié des nœuds sont dans la couche de fond et nécessitent 0 travail. Un quart sont dans la deuxième couche et 1 travail de chacun. Un huitième besoin de 2 travaux, etc. Si h = log n est la hauteur, la somme est

0* n/2 + 1 * n/4 + 2 * n/8 + ... + h * 1.

Bien que ce n'est pas une série (c'est juste une somme finie), vous pouvez utiliser la série de Taylor pour montrer qu'elle est bornée par une constante multiple de n. Si l'intérêt est là, je peux modifier ma réponse à inclure les détails.

Alors, pourquoi ne tas de tri nécessite O(n log n) le temps? Eh bien, segment tri se fait en deux étapes: buildHeap, ce qui nécessite O(n) fois si en œuvre de façon optimale, suivi en supprimant les éléments dans l'ordre:

while (!empty)
    deleteMin

Clairement, la boucle s'exécute n fois (une fois pour chaque élément). La quantité de travail nécessaire à chaque itération? deleteMin travaille en remplaçant le plus petit élément (l'élément à la racine de l'arbre) avec le dernier élément dans l'arborescence, puis de l'appel d'siftDown jusqu'à ce que le segment de la propriété est satisfaite de nouveau. Notez que, contrairement à buildHeap, où pour la plupart des nœuds nous appelons siftDown à partir du bas de l'arbre, nous l'appelons maintenant siftDown à partir du haut de l'arbre à chaque itération! Bien que l'arbre est en décroissance, la distance, chaque nœud peut avoir à se déplacer lors de l'appel de siftDown est proportionnelle à la hauteur de l'arbre. La hauteur de l'arbre reste constante jusqu'à ce que vous avez retiré la première moitié des nœuds (lorsque vous effacer la partie inférieure de la couche complètement). Alors pour le prochain trimestre, la hauteur est h - 1. Alors maintenant, vous obtenez une somme de

h*n/2 + (h-1)*n/4 + ... + 0 * 1.

Avis de l'interrupteur: maintenant, le zéro cas de travail correspond à un seul nœud et le h de travail cas correspond à la moitié des nœuds. Nous avons passé le groupe, où la plupart du travail est fait. En bref, le meilleur lié, nous pouvons obtenir est que nous avons besoin de O(log n) pour chaque suppression (et c'est en fait la valeur correcte pour la majorité des nœuds). Chaque suppression nécessite O(log n) le temps, de sorte que la totalité de la boucle est O(n log n) en temps.

Donc, les deux étapes de tas de tri nécessite O(n) le temps de construire le tas et O(n log n) pour supprimer chaque nœud dans l'ordre, donc la complexité est O(n log n).

Remarque: Si vous voulez faire des tas de tri en place, vous êtes probablement mieux de construire un max de tas, puis en supprimant l'élément le plus important et le placer à la fin du segment de mémoire (depuis le tas diminue, ce spot n'est plus une partie de la masse, et peut être utilisé pour stocker les articles). Fondamentalement, il trie en plaçant le plus grand élément à la fin de la liste, puis répéter jusqu'à ce que le plus petit élément est atteint.

381voto

emre nevayeshirazi Points 6245

Votre analyse est corrrect. Cependant, il n'est pas serré. Il n'est pas vraiment facile à expliquer. Vous devriez mieux lire.

Excellente analyse de l'algorithme peut être vu ici.


Cependant, l'idée principale est, en Buildheap de l'algorithme de la réelle Heapify le coût n'est pas O(logn) pour tous les éléments.

Lors de l' Heapify est appelé, le temps d'exécution dépend de la façon dont beaucoup d'un élément peut se déplacer vers le bas de l'arbre avant la fin du processus. En d'autres termes, il dépend de la hauteur de nœud. Dans le pire des cas, l'élément peut aller vers le bas tout le chemin à la feuille niveau.

Laissez-nous compter le travail fait niveau par niveau. Au dernier niveau il y a 2^(h) les nœuds, mais nous ne l'appelons Heapify sur l'un de ces ainsi, le travail est 0. Lors de la prochaine à niveau, il y a 2^(h − 1) les nœuds, et chacun d'eux peut se déplacer vers le bas de 1 niveau. À la 3ème niveau à partir du bas il y a 2^(h − 2) les nœuds, et chacun d'eux peut se déplacer vers le bas par 2 niveaux.

Comme vous pouvez le voir, pas toutes les heapify opérations sont O(logn), c'est pourquoi vous obtenez O(n).

121voto

bcorso Points 2608

Intuitivement:

"La complexité doit être en O(nLog n)... pour chaque élément, nous "heapify", il a le potentiel pour filtrer vers le bas une fois pour chaque niveau de le tas jusqu'à présent (qui est log n)."

Ce n'est pas serré, lié parce qu'elle sur des estimations des niveaux de filtrage. Si elle est construite à partir de la base, l'insertion peut prendre beaucoup moins de temps qu' O(log(n)). Le processus est comme suit:

( Étape 1 ) La première n/2 - éléments sur la ligne du bas de l'échelle. h=0, de sorte heapify n'est pas nécessaire.

( Étape 2 ) Le lendemain n/2 - éléments sur la ligne 1 vers le haut à partir du bas. 2, heapify filtres en 1 le niveau vers le bas.

( Étape i ) Le prochain - éléments dans la ligne h=1 haut à partir du bas. n/2, heapify filtres i des niveaux bas.

( Étape log(n) ) La dernière élément va dans la ligne i haut à partir du bas. h=i, heapify filtres i des niveaux bas.

AVIS: que, après la première étape, 1 éléments log(n) sont déjà dans le tas, et nous n'avons même pas besoin d'appeler heapify une fois. Aussi, notez qu'un seul élément, la racine, en fait il assume le plein h=log(n) étapes.


Théoriquement:

Le nombre Total de pas log(n) de construire un tas de la taille de l' 1/2, peut être écrit mathématiquement (à l'aide de la procédure ci-dessus):

enter image description here

La solution de la dernière sommation peut être trouvé en prenant la dérivée de la célèbre série géométrique de l'équation:

enter image description here

Enfin, en branchant (n/2) dans l'équation ci-dessus, les rendements log(n). Cela branchant dans la première équation donne:

enter image description here

Ainsi, le nombre total d'étapes est de taille N

47voto

mike__t Points 459

Il serait O(n log n) si vous avez construit le tas à plusieurs reprises par l'insertion d'éléments. Toutefois, vous pouvez créer un nouveau segment de mémoire plus efficacement par l'insertion d'éléments dans un ordre arbitraire et puis en appliquant un algorithme de "heapify" dans le bon ordre (en fonction du type de segment de cours).

Voir http://en.wikipedia.org/wiki/Binary_heap "pour l'édification d'un segment de mémoire" pour un exemple. Dans ce cas, vous avez essentiellement un travail de bas niveau de l'arbre, la permutation des nœuds parents et enfants jusqu'à ce que le tas de conditions sont remplies.

11voto

Jones Points 47

Lors de la construction d'un segment, permet de dire que vous êtes en adoptant une approche ascendante.

  1. Vous prenez chaque élément et de le comparer avec ses enfants afin de vérifier si la paire est conforme aux tas de règles. Si, par conséquent, les feuilles incluses dans le tas gratuitement. C'est parce qu'ils n'ont pas d'enfants.
  2. Déplace vers le haut, le pire scénario pour le nœud juste au-dessus de la laisse de 1 comparaison (Au max ils seront comparés à une génération d'enfants)
  3. D'aller plus loin, de leurs ascendants peuvent au maximum être comparé à deux générations d'enfants.
  4. En continuant dans la même direction, vous aurez log(n) comparaisons pour la racine, dans le pire des cas. et log(n)-1 pour ses enfants immédiats, log(n)-2 pour leurs enfants immédiats et ainsi de suite.
  5. Donc en résumé, vous arrivez sur quelque chose comme log(n) + {log(n)-1}*2 + {log(n)-2}*4 + ..... + 1*2^{(logn)-1}, qui n'est rien mais en O(n).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X