In che modo costruire un heap può essere O (n) complessità temporale?


494

Qualcuno può aiutare a spiegare come può costruire un heap essere O (n) complessità?

L'inserimento di un elemento in un heap è O(log n)e l'inserimento viene ripetuto n / 2 volte (il resto sono foglie e non può violare la proprietà heap). Quindi, questo significa che la complessità dovrebbe essere O(n log n), penso.

In altre parole, per ogni elemento che "accumuliamo", ha il potenziale di dover filtrare una volta per ogni livello per l'heap finora (che è log n livelli).

Cosa mi sto perdendo?


cosa intendi esattamente con "costruire" un mucchio?
mfrankli,

Come faresti in un heapsort, prendi un array non ordinato e filtra tutti gli elementi della metà superiore fino a quando non è conforme alle regole di un heap
GBa

2
L'unica cosa che ho trovato è stato questo link: la complessità di Buildheap sembra essere Θ (n lg n) - n chiamate a Heapify al costo di Θ (lg n) per chiamata, ma questo risultato può essere migliorato a Θ (n) cs.txstate.edu/~ch04/webtest/teaching/courses/5329/lectures/…
GBa

2
@Gba guarda questo video dal MIT: spiega bene come otteniamo O (n), con un po 'di matematica youtube.com/watch?v=B7hVxCmfPtM
CodeShadow

2
Link diretto alla spiegazione @CodeShadow menzionata: youtu.be/B7hVxCmfPtM?t=41m21s
sha1

Risposte:


435

Penso che ci siano diverse domande sepolte in questo argomento:

  • Come si implementa in buildHeapmodo che funzioni in tempo O (n) ?
  • Come si mostra che buildHeapviene eseguito in tempo O (n) se implementato correttamente?
  • Perché la stessa logica non funziona per far funzionare l'heap sort in O (n) time anziché O (n log n) ?

Come si implementa in buildHeapmodo che funzioni in tempo O (n) ?

Spesso, le risposte a queste domande si concentrano sulla differenza tra siftUpe siftDown. Fare la scelta corretta tra siftUped siftDownè fondamentale per ottenere prestazioni O (n)buildHeap , ma non fa nulla per aiutare a capire la differenza tra buildHeape heapSortin generale. Infatti, adeguate implementazioni di entrambi buildHeape heapSortsaranno solo utilizzare siftDown. L' siftUpoperazione è necessaria solo per eseguire inserimenti in un heap esistente, quindi sarebbe utilizzata per implementare una coda di priorità utilizzando un heap binario, ad esempio.

Ho scritto questo per descrivere come funziona un heap max. Questo è il tipo di heap generalmente utilizzato per l'heap ordinamento o per una coda di priorità in cui valori più alti indicano una priorità più alta. È utile anche un heap minimo; ad esempio, quando si recuperano elementi con chiavi intere in ordine crescente o stringhe in ordine alfabetico. I principi sono esattamente gli stessi; cambia semplicemente l'ordinamento.

La proprietà heap specifica che ciascun nodo in un heap binario deve essere almeno grande quanto entrambi i suoi figli. In particolare, ciò implica che l'elemento più grande nell'heap è alla radice. Setacciare verso il basso e setacciare verso l'alto sono essenzialmente la stessa operazione in direzioni opposte: spostare un nodo offensivo fino a quando non soddisfa la proprietà heap:

  • siftDown scambia un nodo che è troppo piccolo con il suo figlio più grande (spostandolo così verso il basso) fino a quando non è almeno grande quanto entrambi i nodi sottostanti.
  • siftUp scambia un nodo che è troppo grande con il suo genitore (spostandolo così verso l'alto) fino a quando non è più grande del nodo sopra di esso.

Il numero di operazioni richieste siftDowned siftUpè proporzionale alla distanza che il nodo potrebbe dover spostare. Perché siftDownè la distanza dal fondo dell'albero, quindi siftDownè costoso per i nodi nella parte superiore dell'albero. Con siftUp, il lavoro è proporzionale alla distanza dalla cima dell'albero, quindi siftUpè costoso per i nodi nella parte inferiore dell'albero. Sebbene entrambe le operazioni siano O (log n) nel peggiore dei casi, in un heap, solo un nodo è nella parte superiore mentre metà dei nodi si trova nel livello inferiore. Quindi non dovrebbe essere troppo sorprendente che, se dobbiamo applicare un'operazione per ogni nodo, preferiremmo siftDownsopra siftUp.

La buildHeapfunzione accetta una matrice di elementi non ordinati e li sposta fino a quando tutti soddisfano la proprietà heap, producendo così un heap valido. Ci sono due approcci che si potrebbero adottare per buildHeapusare le operazioni siftUpe siftDownche abbiamo descritto.

  1. Inizia nella parte superiore dell'heap (l'inizio dell'array) e chiama siftUpogni elemento. Ad ogni passaggio, gli elementi precedentemente setacciati (gli elementi prima dell'articolo corrente nell'array) formano un heap valido e il set-up dell'oggetto successivo lo posiziona in una posizione valida nell'heap. Dopo aver setacciato ciascun nodo, tutti gli elementi soddisfano la proprietà heap.

  2. Oppure, andare nella direzione opposta: iniziare alla fine dell'array e spostarsi indietro verso la parte anteriore. Ad ogni iterazione, setacci un elemento verso il basso fino a quando non si trova nella posizione corretta.

Per quale implementazione buildHeapè più efficiente?

Entrambe queste soluzioni produrranno un heap valido. Non sorprende che la più efficiente sia la seconda operazione che utilizza siftDown.

Lascia che h = log n rappresenti l'altezza dell'heap. Il lavoro richiesto per l' siftDownapproccio è dato dalla somma

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

Ogni termine nella somma ha la distanza massima che un nodo alla data altezza dovrà spostare (zero per il livello inferiore, h per la radice) moltiplicato per il numero di nodi a quella altezza. Al contrario, la somma per chiamare siftUpsu ciascun nodo è

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

Dovrebbe essere chiaro che la seconda somma è maggiore. Il primo termine da solo è hn / 2 = 1/2 n log n , quindi questo approccio ha al massimo complessità O (n log n) .

Come possiamo dimostrare che la somma per l' siftDownapproccio è davvero O (n) ?

Un metodo (ci sono altre analisi che funzionano anche) è trasformare la somma finita in una serie infinita e quindi usare la serie di Taylor. Potremmo ignorare il primo termine, che è zero:

Serie di Taylor per la complessità di BuildHeap

Se non sei sicuro del motivo per cui ciascuno di questi passaggi funziona, ecco una giustificazione per il processo in parole:

  • I termini sono tutti positivi, quindi la somma finita deve essere inferiore alla somma infinita.
  • La serie è uguale a una serie di potenze valutata in x = 1/2 .
  • Quella serie di potenze è uguale (a tempi costanti) alla derivata delle serie di Taylor per f (x) = 1 / (1-x) .
  • x = 1/2 è all'interno dell'intervallo di convergenza di quella serie di Taylor.
  • Pertanto, possiamo sostituire la serie Taylor con 1 / (1-x) , differenziare e valutare per trovare il valore della serie infinita.

Poiché la somma infinita è esattamente n , concludiamo che la somma finita non è più grande, ed è quindi O (n) .

Perché l'heap sort richiede tempo O (n log n) ?

Se è possibile eseguire buildHeapin tempo lineare, perché l'ordinamento heap richiede tempo O (n log n) ? Bene, l'heap sort consiste in due fasi. Innanzitutto, chiamiamo buildHeapl'array, che richiede tempo O (n) se implementato in modo ottimale. Il passaggio successivo consiste nell'eliminare ripetutamente l'elemento più grande nell'heap e inserirlo alla fine dell'array. Poiché eliminiamo un articolo dall'heap, c'è sempre un punto aperto subito dopo la fine dell'heap in cui possiamo archiviare l'articolo. Quindi l'heap sort raggiunge un ordinamento rimuovendo in successione il prossimo oggetto più grande e inserendolo nell'array a partire dall'ultima posizione e spostandosi verso la parte anteriore. È la complessità di quest'ultima parte che domina nell'ordinamento degli heap. Il ciclo è simile al seguente:

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

Chiaramente, il ciclo esegue O (n) volte ( n - 1 per essere precisi, l'ultimo elemento è già in atto). La complessità di deleteMaxper un heap è O (log n) . In genere viene implementato rimuovendo la radice (l'elemento più grande rimasto nell'heap) e sostituendolo con l'ultimo elemento nell'heap, che è una foglia, e quindi uno degli elementi più piccoli. Questa nuova radice quasi sicuramente violerà la proprietà dell'heap, quindi devi chiamare siftDownfino a quando non la sposti in una posizione accettabile. Ciò ha anche l'effetto di spostare l'elemento successivo più grande fino alla radice. Si noti che, diversamente da buildHeapdove per la maggior parte dei nodi stiamo chiamando siftDowndalla parte inferiore dell'albero, ora stiamo chiamando siftDowndalla parte superiore dell'albero su ogni iterazione!Sebbene l'albero si stia restringendo, non si restringe abbastanza velocemente : l'altezza dell'albero rimane costante fino a quando non si è rimossa la prima metà dei nodi (quando si cancella completamente lo strato inferiore). Quindi per il prossimo trimestre, l'altezza è h - 1 . Quindi il lavoro totale per questa seconda fase è

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

Nota l'interruttore: ora il caso di lavoro zero corrisponde a un singolo nodo e il caso di lavoro h corrisponde alla metà dei nodi. Questa somma è O (n log n) proprio come la versione inefficiente di buildHeapciò è implementata usando siftUp. Ma in questo caso, non abbiamo scelta poiché stiamo cercando di ordinare e richiediamo che il prossimo articolo più grande venga rimosso successivamente.

In breve, il lavoro per l'heap sort è la somma delle due fasi: O (n) tempo per buildHeap e O (n log n) per rimuovere ciascun nodo in ordine , quindi la complessità è O (n log n) . Puoi provare (usando alcune idee della teoria dell'informazione) che per un ordinamento basato sul confronto, O (n log n) è il migliore che potresti sperare comunque, quindi non c'è motivo di essere deluso da questo o aspettarti che l'heap sort raggiunga il O (n) limite di tempo che lo buildHeapfa.


2
Ho modificato la mia risposta per utilizzare un heap massimo poiché sembra che la maggior parte delle altre persone si stiano riferendo a questo ed è la scelta migliore per l'heap sort.
Jeremy West,

28
Questo è ciò che mi ha reso intuitivamente chiaro: "solo un nodo è nella parte superiore mentre metà dei nodi si trovano nel livello inferiore. Quindi non dovrebbe essere troppo sorprendente che se dovessimo applicare un'operazione a ogni nodo, dovremmo preferisco siftDown rispetto a siftUp. "
Vicky Chijwani,

3
@JeremyWest "Uno è quello di iniziare dalla cima dell'heap (l'inizio dell'array) e chiamare siftUp su ogni elemento." - Intendevi iniziare dal fondo del mucchio?
aste123,

4
@ aste123 No, è corretto come scritto. L'idea è di mantenere una barriera tra la parte dell'array che soddisfa la proprietà heap e la parte non ordinata dell'array. All'inizio inizi a spostarti in avanti e a chiamare siftUpogni elemento oppure inizi alla fine spostandoti indietro e chiamando siftDown. Indipendentemente dall'approccio scelto, si sta selezionando l'elemento successivo nella parte non ordinata dell'array e si sta eseguendo l'operazione appropriata per spostarlo in una posizione valida nella parte ordinata dell'array. L'unica differenza è la prestazione.
Jeremy West,

2
Questa è la migliore risposta che io abbia mai visto a qualsiasi domanda nel mondo. Era così ben spiegato, ero come se fosse davvero possibile ... grazie mille.
HARSHIL JAIN il

314

La tua analisi è corretta. Tuttavia, non è stretto.

Non è davvero facile spiegare perché costruire un heap sia un'operazione lineare, dovresti leggerlo meglio.

Una grande analisi dell'algoritmo può essere vista qui .


L'idea principale è che build_heapnell'algoritmo il heapifycosto effettivo non è O(log n)per tutti gli elementi.

Quando heapifyviene chiamato, il tempo di esecuzione dipende da quanto un elemento potrebbe spostarsi verso il basso nell'albero prima che il processo termini. In altre parole, dipende dall'altezza dell'elemento nell'heap. Nel peggiore dei casi, l'elemento potrebbe scendere fino al livello foglia.

Contiamo il lavoro svolto livello per livello.

Al livello più basso, ci sono 2^(h)nodi, ma non facciamo appello heapifya nessuno di questi, quindi il lavoro è 0. Al livello successivo ci sono 2^(h − 1)nodi, e ognuno potrebbe scendere di 1 livello. Al 3 ° livello dal basso, ci sono 2^(h − 2)nodi e ognuno potrebbe spostarsi verso il basso di 2 livelli.

Come puoi vedere non tutte le operazioni di heapify sono O(log n), questo è il motivo per cui stai ottenendo O(n).


17
Questa è una grande spiegazione ... ma perché è allora che l'heap-sort funziona in O (n log n). Perché lo stesso ragionamento non si applica all'heap-sort?
hba,

49
@hba Penso che la risposta alla tua domanda risieda nel capire questa immagine da questo articolo . Heapifyè O(n)quando fatto con siftDownma O(n log n)quando fatto con siftUp. L'effettivo ordinamento (estrarre gli oggetti dall'heap uno per uno) deve essere fatto siftUpcosì O(n log n).
The111

3
Mi piace molto la spiegazione intuitiva del tuo documento esterno in fondo.
Lukas Greblikas,

1
@hba la risposta in basso di Jeremy West affronta la tua domanda in modo più dettagliato e di facile comprensione, spiegando ulteriormente la risposta del commento di The111 qui.
cellepo,

Una domanda. Mi sembra che i confronti # effettuati per un nodo in altezza idal fondo di un albero di altezza h debbano anche fare 2* log(h-i)confronti e dovrebbero essere presi in considerazione anche @ The111. Cosa ne pensi?
Sid,

94

Intuitivamente:

"La complessità dovrebbe essere O (nLog n) ... per ogni elemento che" accumuliamo ", ha il potenziale per dover filtrare una volta per ogni livello per l'heap finora (che è log n livelli)."

Non proprio. La tua logica non produce un limite stretto: sopravvaluta la complessità di ogni heapify. Se costruito dal basso verso l'alto, l'inserimento (heapify) può essere molto inferiore a O(log(n)). Il processo è il seguente:

(Passaggio 1) I primi n/2elementi vanno nella riga inferiore dell'heap. h=0, quindi heapify non è necessario.

(Passaggio 2) Gli elementi successivi vanno nella riga 1 dal basso. , heapify filtri 1 livello in basso.n/22h=1

(Passaggio i ) Gli elementi successivi vanno in fila dal basso. , heapify filtra i livelli in basso.n/2iih=ii

(Step log (n) ) L'ultimo elemento va in fila dal basso. , heapify filtra i livelli in basso.n/2log2(n) = 1log(n)h=log(n)log(n)

AVVISO: che dopo il primo passo, 1/2gli elementi (n/2)sono già nell'heap e non abbiamo nemmeno bisogno di chiamare heapify una volta. Si noti inoltre che solo un singolo elemento, la radice, comporta effettivamente la piena log(n)complessità.


teoricamente:

I passaggi totali Nper creare un mucchio di dimensioni npossono essere scritti matematicamente.

A altezza i, abbiamo dimostrato (sopra) che ci saranno gli elementi che hanno bisogno di chiamare heapify, e sappiamo heapify all'altezza è . Questo da:n/2i+1iO(i)

inserisci qui la descrizione dell'immagine

La soluzione all'ultima somma può essere trovata prendendo la derivata di entrambi i lati dell'equazione della serie geometrica ben nota:

inserisci qui la descrizione dell'immagine

Infine, inserendo x = 1/2i rendimenti dell'equazione sopra 2. Inserendolo nella prima equazione si ottiene:

inserisci qui la descrizione dell'immagine

Pertanto, il numero totale di passaggi è di dimensioni O(n)


35

Sarebbe O (n log n) se si crea l'heap inserendo ripetutamente elementi. Tuttavia, è possibile creare un nuovo heap in modo più efficiente inserendo gli elementi in ordine arbitrario e quindi applicando un algoritmo per "accumularli" nell'ordine corretto (a seconda del tipo di heap ovviamente).

Vedi http://en.wikipedia.org/wiki/Binary_heap , "Costruire un mucchio" per un esempio. In questo caso si lavora essenzialmente dal livello inferiore dell'albero, scambiando i nodi padre e figlio fino a quando le condizioni dell'heap sono soddisfatte.


12

Ci sono già delle ottime risposte, ma vorrei aggiungere una piccola spiegazione visiva

inserisci qui la descrizione dell'immagine

Ora, dai un'occhiata all'immagine, ci sono
n/2^1 nodi verdi con altezza 0 (qui 23/2 = 12)
n/2^2 nodi rossi con altezza 1 (qui 23/4 = 6)
n/2^3 nodo blu con altezza 2 (qui 23/8 = 3)
n/2^4 nodi viola con altezza 3 (qui 23/16 = 2)
quindi ci sono n/2^(h+1)nodi per altezza h
Per trovare la complessità del tempo consente di contare la quantità di lavoro svolto o il massimo numero di iterazioni eseguite da ciascun nodo
ora si può notare che ogni nodo può esegue (al massimo) iterazioni == altezza del nodo

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)   

quindi per tutti i nodi con altezza h il massimo lavoro svolto è n / 2 ^ (h + 1) * h

Ora il lavoro totale svolto è

->(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) ) 

ora per qualsiasi valore di h , la sequenza

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

non supererà mai 1
Pertanto la complessità temporale non supererà mai O (n) per la creazione di heap


7

Come sappiamo che l'altezza di un heap è log (n) , dove n è il numero totale di elementi. Rappresentiamolo come h
   Quando eseguiamo un'operazione di heapify, gli elementi all'ultimo livello ( h ) non si sposteranno nemmeno di un singolo passo.
   Il numero di elementi al secondo ultimo livello ( h-1 ) è 2 h-1 e possono spostarsi al massimo 1 livello (durante l'heapify).
   Analogamente, per l' i esimo , livello abbiamo 2 i elementi che possono muoversi hi posizioni.

Pertanto numero totale di mosse = 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
questa è la serie AGP , per risolvere questa divisione di entrambi i lati di 2
                                               S / 2 = 2 h {1/2 2 + 2/2 3 + ... h / 2 h + 1 } --------------------------------- ---------------- 2
sottraendo l'equazione 2 da 1 si ottiene
                                               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 }
ora 1/2 + 1/2 2 + 1/2 3 + ... + 1/2 h sta diminuendo GP la cui somma è inferiore a 1 (quando h tende all'infinito, la somma tende a 1). In ulteriore analisi, prendiamo un limite superiore per la somma che è 1.
Questo dà S = 2 h + 1 {1 + h / 2 h + 1 }
                    = 2 h + 1 + h
                    ~ 2 h + h
come h = log (n) , 2 h = n

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


6

Durante la creazione di un heap, supponiamo che stai adottando un approccio dal basso verso l'alto.

  1. Prendi ogni elemento e lo confronti con i suoi figli per verificare se la coppia è conforme alle regole dell'heap. Quindi, quindi, le foglie vengono incluse gratuitamente nell'heap. Questo perché non hanno figli.
  2. Spostandosi verso l'alto, lo scenario peggiore per il nodo proprio sopra le foglie sarebbe 1 confronto (al massimo verrebbero confrontati con una sola generazione di bambini)
  3. Salendo più in alto, i loro genitori immediati possono al massimo essere paragonati a due generazioni di bambini.
  4. Continuando nella stessa direzione, avrai i confronti log (n) per il root nello scenario peggiore. e log (n) -1 per i suoi figli immediati, log (n) -2 per i loro figli immediati e così via.
  5. Quindi riassumendo tutto, si arriva su qualcosa come log (n) + {log (n) -1} * 2 + {log (n) -2} * 4 + ..... + 1 * 2 ^ {( logn) -1} che non è altro che O (n).

2

In caso di costruzione dell'heap, partiamo dall'altezza, logn -1 (dove logn è l'altezza dell'albero di n elementi). Per ogni elemento presente all'altezza 'h', andiamo all'altezza massima fino a (logn -h) in basso.

    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

Inserimenti successivi possono essere descritti da:

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

Per approssimazione storno n! =~ O(n^(n + O(1))), quindiT =~ O(nlog(n))

Spero che questo aiuti, il modo ottimale O(n) è usare l'algoritmo build heap per un determinato set (l'ordinamento non ha importanza).


1

Fondamentalmente, il lavoro viene eseguito solo su nodi non foglia durante la creazione di un heap ... e il lavoro svolto è la quantità di scambio verso il basso per soddisfare la condizione di heap ... in altre parole (nel peggiore dei casi) la quantità è proporzionale all'altezza del nodo ... tutto sommato la complessità del problema è proporzionale alla somma delle altezze di tutti i nodi non foglia ... che è (2 ^ h + 1 - 1) -h-1 = nh-1 = Su)


1

@bcorso ha già dimostrato la prova dell'analisi della complessità. Ma per il bene di coloro che stanno ancora imparando l'analisi della complessità, devo aggiungere:

La base dell'errore originale è dovuta a un'interpretazione errata del significato dell'istruzione, "l'inserimento in un heap richiede tempo O (log n)". L'inserimento in un heap è effettivamente O (log n), ma devi riconoscere che n è la dimensione dell'heap durante l'inserimento .

Nel contesto dell'inserimento di n oggetti in un heap, la complessità dell'ith insertion è O (log n_i) dove n_i è la dimensione dell'heap al momento dell'inserimento i. Solo l'ultimo inserimento ha una complessità di O (log n).


1

Supponiamo che tu abbia N elementi in un heap. Quindi la sua altezza sarebbe Log (N)

Ora si vuole inserire un altro elemento, quindi la complessità sarebbe: Log (N) , dobbiamo confrontare fino in fondo UP alla radice.

Ora hai N + 1 elementi e altezza = Log (N + 1)

Utilizzando la tecnica di induzione si può dimostrare che la complessità dell'inserzione sarebbe ∑logi .

Ora usando

log a + log b = log ab

Questo semplifica: ∑logi = log (n!)

che in realtà è O (NlogN)

Ma

stiamo facendo qualcosa di sbagliato qui, come in tutti i casi non raggiungiamo la cima. Quindi, mentre eseguiamo la maggior parte delle volte, possiamo scoprire che non stiamo andando nemmeno a metà dell'albero. Quindi, questo limite può essere ottimizzato per avere un altro limite più stretto usando la matematica fornita nelle risposte sopra.

Questa realizzazione mi è venuta dopo un dettaglio e una sperimentazione su Heaps.


0

Mi piace molto la spiegazione di Jeremy West .... Un altro approccio che è davvero facile da capire è dato qui http://courses.washington.edu/css343/zander/NotesProbs/heapcomplexity

poiché buildheap dipende dall'uso dipende dall'heapify e viene utilizzato l'approccio di shiftdown che dipende dalla somma delle altezze di tutti i nodi. Quindi, per trovare la somma dell'altezza dei nodi che è data da S = somma da i = 0 a i = h di (2 ^ i * (hi)), dove h = logn è l'altezza dell'albero che risolve s, otteniamo s = 2 ^ (h + 1) - 1 - (h + 1) poiché, n = 2 ^ (h + 1) - 1 s = n - h - 1 = n- logn - 1 s = O (n), e quindi la complessità di buildheap è O (n).


0

"Il limite di tempo lineare di build Heap, può essere mostrato calcolando la somma delle altezze di tutti i nodi nell'heap, che è il numero massimo di linee tratteggiate. Per l'albero binario perfetto di altezza h contenente N = 2 ^ ( h + 1) - 1 nodi, la somma delle altezze dei nodi è N - H - 1. Quindi è O (N). "


0

Prova di O (n)

La dimostrazione non è elaborata, e piuttosto semplice, ho dimostrato solo il caso di un albero binario completo, il risultato può essere generalizzato per un albero binario completo.


0

Otteniamo il tempo di esecuzione per la build dell'heap capendo la mossa massima che ogni nodo può eseguire. Quindi abbiamo bisogno di sapere quanti nodi ci sono in ogni riga e quanto lontano può andare ogni nodo.

A partire dal nodo radice ogni riga successiva ha il doppio dei nodi rispetto alla riga precedente, quindi rispondendo con che frequenza possiamo raddoppiare il numero di nodi fino a quando non abbiamo più nodi, otteniamo l'altezza dell'albero. O in termini matematici l'altezza dell'albero è log2 (n), n essendo la lunghezza dell'array.

Per calcolare i nodi in una riga partiamo dal retro, sappiamo che n / 2 nodi sono in fondo, quindi dividendo per 2 otteniamo la riga precedente e così via.

Sulla base di questo otteniamo questa formula per l'approccio Siftdown: (0 * n / 2) + (1 * n / 4) + (2 * n / 8) + ... + (log2 (n) * 1)

Il termine nell'ultima parantesi è l'altezza dell'albero moltiplicato per l'unico nodo che è alla radice, il termine nella prima parantesi sono tutti i nodi nella riga inferiore moltiplicati per la lunghezza che possono percorrere, 0. Stessa formula in smart: inserisci qui la descrizione dell'immagine

Matematica

Riportando n abbiamo 2 * n, 2 può essere scartato perché è una costante e quindi abbiamo il peggior tempo di esecuzione dell'approccio Siftdown: n.


-6

pensi che stai facendo un errore. Dai un'occhiata a questo: http://golang.org/pkg/container/heap/ Costruire un heap non è O (n). Tuttavia, l'inserimento è O (lg (n). Presumo che l'inizializzazione sia O (n) se si imposta una dimensione heap b / c, l'heap deve allocare spazio e impostare la struttura dei dati. nell'heap quindi sì, ogni inserto è lg (n) e ci sono n elementi, quindi ottieni n * lg (n) come hai affermato


2
no non è stretto. analisi più rigorosa dei rendimenti di heap di build O (n)
emre nevayeshirazi

sembra che sia una stima. La citazione nell'articolo a cui ha fatto riferimento è "L'intuizione è che la maggior parte delle chiamate all'heapify sono su cumuli molto brevi" Tuttavia, questo sta facendo alcune ipotesi. Presumibilmente, per un grande ammasso, lo scenario peggiore sarebbe comunque O (n * lg (n)), anche se di solito potresti avvicinarti a O (n). Ma potrei sbagliarmi
Mike Schachter il

Sì, questa è anche la mia risposta intuitiva, ma riferimenti come lo stato di Wikipedia "Heaps con n elementi possono essere costruiti dal basso verso l'alto in O (n)."
GBa

1
Stavo pensando a una struttura di dati completamente ordinata. Ho dimenticato le proprietà specifiche di un heap.
Mike Schachter,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.