Comment la construction d'un tas peut-elle être une complexité temporelle O (n)?


494

Quelqu'un peut-il aider à expliquer comment la construction d'un tas peut être une complexité O (n)?

L'insertion d'un élément dans un segment de mémoire est O(log n), et l'insertion est répétée n / 2 fois (les autres sont des feuilles et ne peuvent pas violer la propriété du segment de mémoire). Donc, cela signifie que la complexité devrait être O(n log n), je pense.

En d'autres termes, pour chaque élément que nous "heapify", il a le potentiel d'avoir à filtrer une fois pour chaque niveau pour le tas jusqu'à présent (qui est log n niveaux).

Qu'est-ce que je rate?


Qu'entendez-vous précisément par «construire» un tas?
mfrankli

Comme vous le feriez dans un heapsort, prenez un tableau non trié et filtrez chacun des demi-éléments supérieurs jusqu'à ce qu'il soit conforme aux règles d'un tas
GBa

2
La seule chose que j'ai pu trouver était ce lien: La complexité de Buildheap semble être Θ (n lg n) - n appels à Heapify pour un coût de Θ (lg n) par appel, mais ce résultat peut être amélioré à Θ (n) cs.txstate.edu/~ch04/webtest/teaching/courses/5329/lectures/…
GBa

2
@Gba regardez cette vidéo du MIT: il explique bien comment nous obtenons O (n), avec un peu de maths youtube.com/watch?v=B7hVxCmfPtM
CodeShadow

2
Lien direct vers l'explication @CodeShadow mentionnée: youtu.be/B7hVxCmfPtM?t=41m21s
sha1

Réponses:


435

Je pense qu'il y a plusieurs questions enfouies dans ce sujet:

  • Comment l'implémentez-vous buildHeappour qu'il fonctionne en temps O (n) ?
  • Comment montrez-vous que buildHeaps'exécute en temps O (n) lorsqu'il est correctement implémenté?
  • Pourquoi cette même logique ne fonctionne-t-elle pas pour que le tri du tas s'exécute en temps O (n) plutôt qu'en O (n log n) ?

Comment l'implémentez-vous buildHeappour qu'il fonctionne en temps O (n) ?

Souvent, les réponses à ces questions se concentrent sur la différence entre siftUpet siftDown. Faire le bon choix entre siftUpet siftDownest essentiel pour obtenir des performances O (n)buildHeap , mais ne fait rien pour aider à comprendre la différence entre buildHeapet heapSorten général. En effet, les implémentations appropriées des deux buildHeapet neheapSort seront utilisées . L' opération n'est nécessaire que pour effectuer des insertions dans un segment existant, elle serait donc utilisée pour implémenter une file d'attente prioritaire à l'aide d'un segment binaire, par exemple.siftDownsiftUp

J'ai écrit ceci pour décrire le fonctionnement d'un tas max. Il s'agit du type de segment de mémoire généralement utilisé pour le tri de segment de mémoire ou pour une file d'attente prioritaire où des valeurs plus élevées indiquent une priorité plus élevée. Un tas min est également utile; par exemple, lors de la récupération d'éléments avec des clés entières dans l'ordre croissant ou des chaînes dans l'ordre alphabétique. Les principes sont exactement les mêmes; changez simplement l'ordre de tri.

La propriété de segment de mémoire spécifie que chaque nœud d'un segment de mémoire binaire doit être au moins aussi grand que ses deux enfants. En particulier, cela implique que le plus gros élément du tas est à la racine. Le filtrage et le filtrage sont essentiellement la même opération dans des directions opposées: déplacer un nœud incriminé jusqu'à ce qu'il satisfasse la propriété du tas:

  • siftDown échange un nœud trop petit avec son plus grand enfant (le déplaçant ainsi vers le bas) jusqu'à ce qu'il soit au moins aussi grand que les deux nœuds en dessous.
  • siftUp échange un nœud trop grand avec son parent (le déplaçant ainsi vers le haut) jusqu'à ce qu'il ne soit pas plus grand que le nœud au-dessus de lui.

Le nombre d'opérations nécessaires siftDownet siftUpproportionnel à la distance que le nœud peut avoir à parcourir. Car siftDown, c'est la distance jusqu'au bas de l'arbre, donc siftDownc'est cher pour les nœuds en haut de l'arbre. Avec siftUp, le travail est proportionnel à la distance jusqu'au sommet de l'arbre, donc siftUpest coûteux pour les nœuds au bas de l'arbre. Bien que les deux opérations soient O (log n) dans le pire des cas, dans un tas, un seul nœud est en haut tandis que la moitié des nœuds se trouvent dans la couche inférieure. Donc , il ne devrait pas être trop surprenant que si nous devons appliquer une opération à chaque nœud, nous préférerions siftDownplus siftUp.

La buildHeapfonction prend un tableau d'éléments non triés et les déplace jusqu'à ce qu'ils satisfassent tous à la propriété du tas, produisant ainsi un tas valide. Il existe deux approches pour buildHeaputiliser les opérations siftUpet que siftDownnous avons décrites.

  1. Commencez par le haut du tas (le début du tableau) et appelez siftUpchaque élément. À chaque étape, les éléments précédemment triés (les éléments précédant l'élément actuel dans le tableau) forment un segment de mémoire valide, et le tri de l'élément suivant le place dans une position valide dans le segment de mémoire. Après avoir trié chaque nœud, tous les éléments satisfont la propriété du tas.

  2. Ou, allez dans la direction opposée: commencez à la fin du tableau et reculez vers l'avant. À chaque itération, vous tamisez un article jusqu'à ce qu'il soit au bon endroit.

Quelle implémentation buildHeapest la plus efficace?

Ces deux solutions produiront un tas valide. Sans surprise, la plus efficace est la deuxième opération qui utilise siftDown.

Soit h = log n la hauteur du tas. Le travail requis pour la siftDowndémarche est donné par la somme

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

Chaque terme de la somme a la distance maximale qu'un nœud à la hauteur donnée devra parcourir (zéro pour la couche inférieure, h pour la racine) multiplié par le nombre de nœuds à cette hauteur. En revanche, la somme pour appeler siftUpsur chaque nœud est

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

Il doit être clair que la deuxième somme est plus importante. Le premier terme seul est hn / 2 = 1/2 n log n , donc cette approche a au mieux une complexité O (n log n) .

Comment prouver que la somme de l' siftDownapproche est bien O (n) ?

Une méthode (il existe d'autres analyses qui fonctionnent également) consiste à transformer la somme finie en une série infinie, puis à utiliser la série de Taylor. Nous pouvons ignorer le premier terme, qui est zéro:

Série Taylor pour la complexité de buildHeap

Si vous ne savez pas pourquoi chacune de ces étapes fonctionne, voici une justification du processus en mots:

  • Les termes sont tous positifs, donc la somme finie doit être inférieure à la somme infinie.
  • La série est égale à une série de puissance évaluée à x = 1/2 .
  • Cette série de puissances est égale à (un temps constant) la dérivée de la série de Taylor pour f (x) = 1 / (1-x) .
  • x = 1/2 est dans l'intervalle de convergence de cette série de Taylor.
  • Par conséquent, nous pouvons remplacer la série de Taylor par 1 / (1-x) , différencier et évaluer pour trouver la valeur de la série infinie.

Puisque la somme infinie est exactement n , nous concluons que la somme finie n'est pas plus grande, et est donc O (n) .

Pourquoi le tri en tas nécessite-t-il O (n log n) ?

S'il est possible de s'exécuter buildHeapen temps linéaire, pourquoi le tri en tas requiert-il un temps O (n log n) ? Eh bien, le tri en tas se compose de deux étapes. Tout d'abord, nous faisons appel buildHeapau tableau, qui nécessite un temps O (n) s'il est implémenté de manière optimale. L'étape suivante consiste à supprimer à plusieurs reprises le plus gros élément du tas et à le placer à la fin du tableau. Parce que nous supprimons un élément du tas, il y a toujours un endroit ouvert juste après la fin du tas où nous pouvons stocker l'élément. Ainsi, le tri en tas atteint un ordre trié en supprimant successivement le prochain élément le plus grand et en le plaçant dans le tableau en commençant à la dernière position et en se déplaçant vers l'avant. C'est la complexité de cette dernière partie qui domine en tri par tas. La boucle ressemble à ceci:

for (i = n - 1; i > 0; i--) {
    arr[i] = deleteMax();
}

De toute évidence, la boucle s'exécute O (n) fois ( n - 1 pour être précis, le dernier élément est déjà en place). La complexité de deleteMaxpour un tas est O (log n) . Il est généralement implémenté en supprimant la racine (le plus grand élément restant dans le tas) et en le remplaçant par le dernier élément du tas, qui est une feuille, et donc l'un des plus petits éléments. Cette nouvelle racine violera presque certainement la propriété du tas, vous devez donc appeler siftDownjusqu'à ce que vous la remettiez dans une position acceptable. Cela a également pour effet de déplacer l'élément suivant le plus grand jusqu'à la racine. Notez que, contrairement à l' buildHeapendroit où pour la plupart des nœuds que nous appelons siftDowndepuis le bas de l'arbre, nous appelons maintenant siftDowndepuis le haut de l'arbre à chaque itération!Bien que l'arbre rétrécisse, il ne rétrécit pas assez rapidement : la hauteur de l'arbre reste constante jusqu'à ce que vous ayez supprimé la première moitié des nœuds (lorsque vous supprimez complètement la couche inférieure). Ensuite, pour le trimestre suivant, la hauteur est h - 1 . Donc, le travail total pour cette deuxième étape est

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

Remarquez le commutateur: maintenant le cas de travail zéro correspond à un seul nœud et le cas de travail h correspond à la moitié des nœuds. Cette somme est O (n log n) tout comme la version inefficace de buildHeapcelle-ci est implémentée à l'aide de siftUp. Mais dans ce cas, nous n'avons pas le choix, car nous essayons de trier et nous exigeons que le prochain élément le plus grand soit supprimé.

En résumé, le travail pour le tri de tas est la somme des deux étapes: temps O (n) pour buildHeap et O (n log n) pour supprimer chaque nœud dans l'ordre , donc la complexité est O (n log n) . Vous pouvez prouver (en utilisant certaines idées de la théorie de l'information) que pour un tri basé sur la comparaison, O (n log n) est le meilleur que vous puissiez espérer de toute façon, donc il n'y a aucune raison d'être déçu par cela ou de s'attendre à ce que le tri en tas atteigne le O (n) limité dans le temps buildHeap.


2
J'ai modifié ma réponse pour utiliser un tas max car il semble que la plupart des autres personnes s'y réfèrent et c'est le meilleur choix pour le tri de tas.
Jeremy West

28
C’est ce qui m’a rendu intuitivement clair: "un seul nœud est en haut tandis que la moitié des nœuds se trouvent dans la couche inférieure. Il ne devrait donc pas être trop surprenant que si nous devons appliquer une opération à chaque nœud, nous préférez siftDown à siftUp. "
Vicky Chijwani

3
@JeremyWest "L'une consiste à commencer en haut du tas (le début du tableau) et à appeler siftUp sur chaque élément." - Vouliez-vous commencer par le bas du tas?
aste123

4
@ aste123 Non, c'est correct comme écrit. L'idée est de maintenir une barrière entre la partie du tableau qui satisfait la propriété du tas et la partie non triée du tableau. Soit vous commencez au début en avançant et en appelant siftUpchaque élément, soit en commençant à la fin en reculant et en appelant siftDown. Quelle que soit l'approche choisie, vous sélectionnez l'élément suivant dans la partie non triée du tableau et effectuez l'opération appropriée pour le déplacer dans une position valide dans la partie ordonnée du tableau. La seule différence est la performance.
Jeremy West

2
C'est la meilleure réponse que j'aie jamais vue à n'importe quelle question dans le monde. C'était tellement bien expliqué, j'étais comme si c'était vraiment possible ... merci beaucoup.
HARSHIL JAIN

314

Votre analyse est correcte. Cependant, ce n'est pas serré.

Il n'est pas vraiment facile d'expliquer pourquoi la construction d'un tas est une opération linéaire, il vaut mieux la lire.

Une grande analyse de l'algorithme peut être vue ici .


L'idée principale est que dans l' build_heapalgorithme, le heapifycoût réel n'est pas O(log n)pour tous les éléments.

Lorsque heapifyest appelé, le temps d'exécution dépend de la distance à laquelle un élément peut descendre dans l'arborescence avant la fin du processus. En d'autres termes, cela dépend de la hauteur de l'élément dans le tas. Dans le pire des cas, l'élément peut descendre jusqu'au niveau de la feuille.

Comptons le travail effectué niveau par niveau.

Au niveau le plus bas, il y a des 2^(h)nœuds, mais nous n'en appelons heapifyaucun, donc le travail est 0. Au niveau suivant, il y a des 2^(h − 1)nœuds, et chacun peut descendre d'un niveau. Au 3e niveau à partir du bas, il y a des 2^(h − 2)nœuds, et chacun peut descendre de 2 niveaux.

Comme vous pouvez le voir, toutes les opérations de tas ne le sont pas O(log n), c'est pourquoi vous obtenez O(n).


17
C'est une excellente explication ... mais pourquoi est-ce alors que le tri en tas s'exécute dans O (n log n). Pourquoi le même raisonnement ne s'applique-t-il pas au tri en tas?
hba

49
@hba Je pense que la réponse à votre question réside dans la compréhension de cette image de cet article . Heapifyest une O(n)fois terminé, siftDownmais O(n log n)terminé siftUp. Le tri réel (extraire les éléments du tas un par un) doit donc être fait de la même siftUpfaçon O(n log n).
The111

3
J'aime vraiment l'explication intuitive de votre document externe en bas.
Lukas Greblikas

1
@hba la réponse ci-dessous par Jeremy West répond à votre question de manière plus fine et plus facile à comprendre, expliquant plus en détail la réponse du commentaire de The111 ici.
cellepo

Une question. Il me semble que les # comparaisons faites pour un nœud en hauteur à ipartir du bas d'un arbre de hauteur h doivent également faire des 2* log(h-i)comparaisons et doivent également être prises en compte @ The111. Qu'est-ce que tu penses?
Sid

94

Intuitivement:

"La complexité devrait être O (nLog n) ... pour chaque élément que nous" heapify ", il a le potentiel d'avoir à filtrer une fois pour chaque niveau pour le tas jusqu'à présent (qui est log n niveaux)."

Pas assez. Votre logique ne produit pas de limite stricte - elle surestime la complexité de chaque tas. Si elle est construite de bas en haut, l'insertion (heapify) peut être bien inférieure à O(log(n)). Le processus est le suivant:

(Étape 1) Les premiers n/2éléments vont sur la ligne inférieure du tas. h=0, donc heapify n'est pas nécessaire.

(Étape 2) Les éléments suivants vont sur la ligne 1 en partant du bas. , tassez les filtres 1 niveau plus bas.n/22h=1

(Étape i ) Les éléments suivants vont en ligne à partir du bas. , heapify filtre les niveaux vers le bas.n/2iih=ii

(Étape log (n) ) Le dernier élément monte en ligne à partir du bas. , heapify filtre les niveaux vers le bas.n/2log2(n) = 1log(n)h=log(n)log(n)

AVIS: après la première étape, 1/2les éléments (n/2)sont déjà dans le tas, et nous n'avons même pas eu besoin d'appeler heapify une fois. Notez également qu'un seul élément, la racine, engendre en fait toute la log(n)complexité.


Théoriquement:

Les étapes totales Npour créer un tas de taille npeuvent être écrites mathématiquement.

En hauteur i, nous avons montré (ci-dessus) qu'il y aura des éléments qui devront appeler heapify, et nous savons que heapify en hauteur l' est . Cela donne:n/2i+1iO(i)

entrez la description de l'image ici

La solution à la dernière sommation peut être trouvée en prenant la dérivée des deux côtés de l'équation de série géométrique bien connue:

entrez la description de l'image ici

Enfin, la connexion à x = 1/2l'équation ci-dessus donne 2. Brancher ceci dans la première équation donne:

entrez la description de l'image ici

Ainsi, le nombre total d'étapes est de taille O(n)


35

Ce serait O (n log n) si vous construisiez le tas en insérant à plusieurs reprises des éléments. Cependant, vous pouvez créer un nouveau tas plus efficacement en insérant les éléments dans un ordre arbitraire puis en appliquant un algorithme pour les "tas" dans le bon ordre (en fonction du type de tas bien sûr).

Voir http://en.wikipedia.org/wiki/Binary_heap , "Construire un tas" pour un exemple. Dans ce cas, vous travaillez essentiellement à partir du niveau inférieur de l'arborescence, en échangeant les nœuds parent et enfant jusqu'à ce que les conditions du segment soient remplies.


12

Il y a déjà de bonnes réponses mais j'aimerais ajouter une petite explication visuelle

entrez la description de l'image ici

Maintenant, regardez l'image, il y a
n/2^1 des nœuds verts avec une hauteur 0 (ici 23/2 = 12)
n/2^2 des nœuds rouges avec une hauteur 1 (ici 23/4 = 6)
n/2^3 un nœud bleu avec une hauteur 2 (ici 23/8 = 3)
n/2^4 nœuds violets de hauteur 3 (ici 23/16 = 2)
donc il y a des n/2^(h+1)nœuds pour la hauteur h
Pour trouver la complexité temporelle, nous pouvons compter la quantité de travail effectuée ou le nombre maximum d'itérations effectuées par chaque nœud
maintenant, on peut remarquer que chaque nœud peut effectuer (au plus) des itérations == hauteur du nœud

Green  = n/2^1 * 0 (no iterations since no children)  
red    = n/2^2 * 1 (heapify will perform atmost one swap for each red node)  
blue   = n/2^3 * 2 (heapify will perform atmost two swaps for each blue node)  
purple = n/2^4 * 3 (heapify will perform atmost three swaps for each purple node)   

donc pour tous les nœuds de hauteur h, le travail maximum effectué est n / 2 ^ (h + 1) * h

Maintenant, le travail total est

->(n/2^1 * 0) + (n/2^2 * 1)+ (n/2^3 * 2) + (n/2^4 * 3) +...+ (n/2^(h+1) * h)  
-> n * ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) ) 

maintenant pour toute valeur de h , la séquence

-> ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) ) 

ne dépassera jamais 1
Ainsi, la complexité temporelle ne dépassera jamais O (n) pour la construction du tas


7

Comme nous savons que la hauteur d'un tas est log (n) , où n est le nombre total d'éléments. Représentons-le comme h
   Lorsque nous effectuons une opération de tas, les éléments du dernier niveau ( h ) ne bougeront même pas un seul étape.
   Le nombre d'éléments à l'avant-dernier niveau ( h-1 ) est de 2 h-1 et ils peuvent se déplacer à max 1 niveau (pendant le tas).
   De même, pour le i e , niveau nous avons 2 i éléments qui peuvent se déplacer salut positions.

Donc nombre total de mouvements = S = 2 h * 0 + 2 h-1 * 1 + 2 h-2 * 2 + ... 2 0 * h

                                               S = 2 h {1/2 + 2/2 2 + 3/2 3 + ... h / 2 h } ----------------------- -------------------------- 1
il s'agit de la série AGP , pour résoudre cette division des deux côtés par 2
                                               S / 2 = 2 h {1/2 2 + 2/2 3 + ... h / 2 h + 1 } --------------------------------- ---------------- 2 en
soustrayant l'équation 2 de 1 donne
                                               S / 2 = 2 h { 1/2 + 1/2 2 + 1/2 3 + ... + 1 / 2 h + h / 2 h + 1 }
                                               S = 2 h + 1 { 1/2 + 1/2 2 + 1/2 3 + ... + 1/2 h + h / 2 h + 1 }
maintenant 1/2 / 1 + 2 2 + 1/2 3 + ... + 1/2 h diminue GP dont la somme est inférieure à 1 (quand h tend vers l'infini, la somme tend vers 1). Dans une analyse plus approfondie, prenons une limite supérieure sur la somme qui est 1.
Cela donne S = 2 h + 1 {1 + h / 2 h + 1 }
                    = 2 h + 1 + h
                    ~ 2 h + h
comme h = log (n) , 2 h = n

Donc S = n + log (n)
T (C) = O (n)


6

Lors de la construction d'un tas, disons que vous adoptez une approche ascendante.

  1. Vous prenez chaque élément et le comparez avec ses enfants pour vérifier si la paire est conforme aux règles du tas. Par conséquent, les feuilles sont incluses gratuitement dans le tas. C'est parce qu'ils n'ont pas d'enfants.
  2. En se déplaçant vers le haut, le pire scénario pour le nœud juste au-dessus des feuilles serait une comparaison (au maximum, ils seraient comparés à une seule génération d'enfants)
  3. En montant plus haut, leurs parents immédiats peuvent au maximum être comparés à deux générations d'enfants.
  4. En poursuivant dans la même direction, vous aurez des comparaisons log (n) 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 d'autre que O (n).

2

En cas de construction du tas, on part de la hauteur, logn -1 (où logn est la hauteur de l'arbre de n éléments). Pour chaque élément présent à la hauteur «h», nous descendons jusqu'à la hauteur maximale (logn -h).

    So total number of traversal would be:-
    T(n) = sigma((2^(logn-h))*h) where h varies from 1 to logn
    T(n) = n((1/2)+(2/4)+(3/8)+.....+(logn/(2^logn)))
    T(n) = n*(sigma(x/(2^x))) where x varies from 1 to logn
     and according to the [sources][1]
    function in the bracket approaches to 2 at infinity.
    Hence T(n) ~ O(n)

1

Les insertions successives peuvent être décrites par:

T = O(log(1) + log(2) + .. + log(n)) = O(log(n!))

Par approximation étourneau n! =~ O(n^(n + O(1))), doncT =~ O(nlog(n))

J'espère que cela vous aidera, la façon optimale O(n)est d'utiliser l'algorithme de tas de construction pour un ensemble donné (l'ordre n'a pas d'importance).


1

Fondamentalement, le travail est effectué uniquement sur les nœuds non-feuilles lors de la construction d'un tas ... et le travail effectué est la quantité de swaps vers le bas pour satisfaire la condition de tas ... en d'autres termes (dans le pire des cas), la quantité est proportionnelle à la hauteur du nœud ... dans l'ensemble la complexité du problème est proportionnelle à la somme des hauteurs de tous les nœuds non-feuilles..qui est (2 ^ h + 1 - 1) -h-1 = nh-1 = Sur)


1

@bcorso a déjà démontré la preuve de l'analyse de complexité. Mais pour le bien de ceux qui apprennent encore l'analyse de complexité, j'ai ceci à ajouter:

La base de votre erreur d'origine est due à une mauvaise interprétation de la signification de la déclaration, "l'insertion dans un segment de mémoire prend du temps O (log n)". L'insertion dans un tas est bien O (log n), mais vous devez reconnaître que n est la taille du tas lors de l'insertion .

Dans le contexte de l'insertion de n objets dans un tas, la complexité de la ième insertion est O (log n_i) où n_i est la taille du tas comme à l'insertion i. Seule la dernière insertion a une complexité de O (log n).


1

Supposons que vous ayez N éléments dans un tas. Alors sa hauteur serait Log (N)

Maintenant , vous voulez insérer un autre élément, la complexité serait la suivante: Log (N) , nous devons comparer tout le chemin UP à la racine.

Vous avez maintenant N + 1 éléments et height = Log (N + 1)

En utilisant la technique d' induction , il peut être prouvé que la complexité de l'insertion serait ∑logi .

Maintenant en utilisant

log a + log b = log ab

Cela simplifie: ∑logi = log (n!)

qui est en fait O (NlogN)

Mais

nous faisons quelque chose de mal ici, car dans tous les cas, nous n'atteignons pas le sommet. Par conséquent, lors de l'exécution la plupart du temps, nous pouvons constater que nous n'allons même pas à mi-chemin dans l'arbre. D'où, cette limite peut être optimisée pour avoir une autre limite plus stricte en utilisant les mathématiques données dans les réponses ci-dessus.

Cette réalisation m'est venue après un détail et une expérimentation sur Heaps.


0

J'aime vraiment l'explication de Jeremy West .... une autre approche qui est vraiment facile à comprendre est donnée ici http://courses.washington.edu/css343/zander/NotesProbs/heapcomplexity

depuis, buildheap dépend de l'utilisation dépend de heapify et l'approche de rétrogradation est utilisée qui dépend de la somme des hauteurs de tous les nœuds. Donc, pour trouver la somme de la hauteur des nœuds qui est donnée par S = sommation de i = 0 à i = h de (2 ^ i * (hi)), où h = logn est la hauteur de l'arbre à résoudre s, nous obtenons s = 2 ^ (h + 1) - 1 - (h + 1) puisque, n = 2 ^ (h + 1) - 1 s = n - h - 1 = n- logn - 1 s = O (n), et donc la complexité du buildheap est O (n).


0

"La limite de temps linéaire de la génération de tas, peut être affichée en calculant la somme des hauteurs de tous les nœuds du tas, qui est le nombre maximal de lignes en pointillés. Pour l'arbre binaire parfait de hauteur h contenant N = 2 ^ ( h + 1) - 1 nœuds, la somme des hauteurs des nœuds est N - H - 1. Ainsi, c'est O (N). "


0

Preuve d'O (n)

La preuve n'est pas sophistiquée, et assez simple, je n'ai prouvé que le cas pour un arbre binaire complet, le résultat peut être généralisé pour un arbre binaire complet.


0

Nous obtenons le temps d'exécution pour la construction du tas en déterminant le déplacement maximum que chaque nœud peut prendre. Nous devons donc savoir combien de nœuds sont dans chaque ligne et jusqu'où peut aller chaque nœud.

En partant du nœud racine, chaque ligne suivante a le double des nœuds que la ligne précédente, donc en répondant à quelle fréquence pouvons-nous doubler le nombre de nœuds jusqu'à ce qu'il ne reste plus de nœuds, nous obtenons la hauteur de l'arbre. Ou en termes mathématiques, la hauteur de l'arbre est log2 (n), n étant la longueur du tableau.

Pour calculer les nœuds dans une ligne, nous partons de l'arrière, nous savons que n / 2 nœuds sont en bas, donc en divisant par 2 nous obtenons la ligne précédente et ainsi de suite.

Sur cette base, nous obtenons cette formule pour l'approche Siftdown: (0 * n / 2) + (1 * n / 4) + (2 * n / 8) + ... + (log2 (n) * 1)

Le terme dans la dernière paranthèse est la hauteur de l'arbre multipliée par le nœud qui se trouve à la racine, le terme dans la première paranthèse correspond à tous les nœuds de la rangée du bas multipliés par la longueur qu'ils peuvent parcourir, 0. Même formule en smart: entrez la description de l'image ici

Math

En ramenant le n, nous avons 2 * n, 2 peut être écarté car c'est une constante et nous avons le pire cas d'exécution de l'approche Siftdown: n.


-6

pensez que vous faites une erreur. Jetez un œil à ceci: http://golang.org/pkg/container/heap/ Construire un tas n'est pas O (n). Cependant, l'insertion est O (lg (n). Je suppose que l'initialisation est O (n) si vous définissez une taille de segment b / c, le segment doit allouer de l'espace et configurer la structure de données. Si vous avez n éléments à placer dans le tas alors oui, chaque insert est lg (n) et il y a n éléments, donc vous obtenez n * lg (n) comme indiqué u


2
non ce n'est pas serré. analyse plus
stricte des

il semble que ce soit une estimation. La citation dans l'article auquel il a fait référence est: «L'intuition est que la plupart des appels à l'accumulation sont sur des tas très courts». Cependant, cela fait certaines hypothèses. Vraisemblablement, pour un gros tas, le pire des cas serait toujours O (n * lg (n)), même si vous pouvez généralement vous rapprocher de O (n). Mais je peux me tromper
Mike Schachter

Oui, c'est aussi ma réponse intuitive, mais des références telles que wikipedia indiquent que "des tas avec n éléments peuvent être construits de bas en haut dans O (n)".
GBa

1
Je pensais à une structure de données entièrement triée. J'ai oublié les propriétés spécifiques d'un tas.
Mike Schachter
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.