¿Cómo puede construir un montón ser O (n) complejidad de tiempo?

494

¿Alguien puede ayudar a explicar cómo construir un montón puede ser O (n) complejidad?

Insertar un elemento en un montón es O(log n), y el inserto se repite n / 2 veces (el resto son hojas, y no puede violar la propiedad del montón). Entonces, esto significa que la complejidad debería ser O(n log n), creo.

En otras palabras, para cada elemento que "heapificamos", tiene el potencial de tener que filtrar una vez por cada nivel para el montón hasta el momento (que es log n niveles).

¿Qué me estoy perdiendo?

GBa
fuente
¿Qué quiere decir exactamente con "construir" un montón?
mfrankli
Como lo haría en un montón, tome una matriz sin clasificar y filtre cada uno de los elementos de la mitad superior hasta que se ajuste a las reglas de un montón
GBa
2
Lo único que pude encontrar fue este enlace: la complejidad de Buildheap parece ser Θ (n lg n) - n llamadas a Heapify a un costo de Θ (lg n) por llamada, pero este resultado puede mejorarse a Θ (n) cs.txstate.edu/~ch04/webtest/teaching/courses/5329/lectures/…
GBa
2
@Gba mira este video del MIT: explica bien cómo obtenemos O (n), con un poco de matemáticas youtube.com/watch?v=B7hVxCmfPtM
CodeShadow el
2
Enlace directo a la explicación @CodeShadow mencionada: youtu.be/B7hVxCmfPtM?t=41m21s
sha1

Respuestas:

435

Creo que hay varias preguntas enterradas en este tema:

  • ¿Cómo se implementa buildHeappara que se ejecute en O (n) tiempo?
  • ¿Cómo muestra que se buildHeapejecuta en tiempo O (n) cuando se implementa correctamente?
  • ¿Por qué esa misma lógica no funciona para hacer que la ordenación del montón se ejecute en tiempo O (n) en lugar de O (n log n) ?

¿Cómo se implementa buildHeappara que se ejecute en O (n) tiempo?

A menudo, las respuestas a estas preguntas se centran en la diferencia entre siftUpy siftDown. Hacer la elección correcta entre siftUpy siftDownes fundamental para obtener el rendimiento de O (n)buildHeap , pero no ayuda a comprender la diferencia entre buildHeapy heapSorten general. De hecho, las implementaciones adecuadas de ambos buildHeapy heapSortserán sólo se utilice siftDown. La siftUpoperación solo es necesaria para realizar inserciones en un montón existente, por lo que se usaría para implementar una cola prioritaria utilizando un montón binario, por ejemplo.

He escrito esto para describir cómo funciona un montón máximo. Este es el tipo de almacenamiento dinámico que normalmente se utiliza para la ordenación del almacenamiento dinámico o para una cola de prioridad donde los valores más altos indican una prioridad más alta. Un montón mínimo también es útil; por ejemplo, al recuperar elementos con claves enteras en orden ascendente o cadenas en orden alfabético. Los principios son exactamente los mismos; simplemente cambie el orden de clasificación.

La propiedad de montón especifica que cada nodo en un montón binario debe ser al menos tan grande como sus dos hijos. En particular, esto implica que el elemento más grande en el montón está en la raíz. Desplazar hacia abajo y hacia arriba son esencialmente la misma operación en direcciones opuestas: mover un nodo ofensivo hasta que satisfaga la propiedad del montón:

  • siftDown intercambia un nodo que es demasiado pequeño con su hijo más grande (lo que lo mueve hacia abajo) hasta que sea al menos tan grande como los dos nodos debajo de él.
  • siftUp intercambia un nodo que es demasiado grande con su padre (moviéndolo hacia arriba) hasta que no sea más grande que el nodo por encima de él.

El número de operaciones requeridas siftDowny siftUpes proporcional a la distancia que el nodo puede tener que moverse. Para siftDown, es la distancia a la parte inferior del árbol, por lo que siftDownes costoso para los nodos en la parte superior del árbol. Con siftUp, el trabajo es proporcional a la distancia hasta la parte superior del árbol, por lo que siftUpes costoso para los nodos en la parte inferior del árbol. Aunque ambas operaciones son O (log n) en el peor de los casos, en un montón, solo un nodo está en la parte superior, mientras que la mitad de los nodos se encuentran en la capa inferior. Por lo tanto, no debería sorprendernos demasiado que si tenemos que aplicar una operación a cada nodo, preferiríamos siftDownterminar siftUp.

La buildHeapfunción toma una matriz de elementos sin clasificar y los mueve hasta que todos satisfagan la propiedad del montón, produciendo así un montón válido. Hay dos enfoques que uno podría tomar para buildHeapusar las operaciones siftUpy siftDownque hemos descrito.

  1. Comience en la parte superior del montón (el comienzo de la matriz) y llame siftUpa cada elemento. En cada paso, los elementos previamente seleccionados (los elementos anteriores al elemento actual en la matriz) forman un montón válido, y al seleccionar el siguiente elemento hacia arriba lo coloca en una posición válida en el montón. Después de seleccionar cada nodo, todos los elementos satisfacen la propiedad del montón.

  2. O bien, vaya en la dirección opuesta: comience al final de la matriz y avance hacia el frente. En cada iteración, tamiza un elemento hacia abajo hasta que esté en la ubicación correcta.

¿Qué implementación buildHeapes más eficiente?

Ambas soluciones producirán un montón válido. Como era de esperar, la más eficiente es la segunda operación que utiliza siftDown.

Deje que h = log n represente la altura del montón. El trabajo requerido para el siftDownenfoque viene dado por la suma

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

Cada término en la suma tiene la distancia máxima que un nodo a la altura dada tendrá que moverse (cero para la capa inferior, h para la raíz) multiplicado por el número de nodos a esa altura. En contraste, la suma para llamar siftUpa cada nodo es

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

Debe quedar claro que la segunda suma es mayor. El primer término solo es hn / 2 = 1/2 n log n , por lo que este enfoque tiene complejidad en el mejor de los casos O (n log n) .

¿Cómo demostramos que la suma del siftDownenfoque es de hecho O (n) ?

Un método (hay otros análisis que también funcionan) es convertir la suma finita en una serie infinita y luego usar la serie Taylor. Podemos ignorar el primer término, que es cero:

Serie de Taylor para la complejidad de buildHeap

Si no está seguro de por qué funciona cada uno de esos pasos, aquí hay una justificación del proceso en palabras:

  • Todos los términos son positivos, por lo que la suma finita debe ser menor que la suma infinita.
  • La serie es igual a una serie de potencia evaluada en x = 1/2 .
  • Esa serie de potencia es igual a (un tiempo constante) la derivada de la serie de Taylor para f (x) = 1 / (1-x) .
  • x = 1/2 está dentro del intervalo de convergencia de esa serie de Taylor.
  • Por lo tanto, podemos reemplazar la serie de Taylor con 1 / (1-x) , diferenciar y evaluar para encontrar el valor de la serie infinita.

Dado que la suma infinita es exactamente n , concluimos que la suma finita no es mayor y, por lo tanto, es O (n) .

¿Por qué la ordenación del montón requiere tiempo O (n log n) ?

Si es posible ejecutar buildHeapen tiempo lineal, ¿por qué la ordenación en montón requiere tiempo O (n log n) ? Bueno, la ordenación del montón consta de dos etapas. Primero, llamamos buildHeapa la matriz, que requiere tiempo O (n) si se implementa de manera óptima. La siguiente etapa es eliminar repetidamente el elemento más grande en el montón y ponerlo al final de la matriz. Debido a que eliminamos un artículo del montón, siempre hay un espacio abierto justo después del final del montón donde podemos almacenar el artículo. Por lo tanto, la ordenación en montón logra un orden ordenado eliminando sucesivamente el siguiente elemento más grande y colocándolo en la matriz comenzando en la última posición y avanzando hacia el frente. Es la complejidad de esta última parte la que domina en el montón. El bucle se ve así:

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

Claramente, el ciclo se ejecuta O (n) veces ( n - 1 para ser precisos, el último elemento ya está en su lugar). La complejidad de deleteMaxpara un montón es O (log n) . Normalmente se implementa eliminando la raíz (el elemento más grande que queda en el montón) y reemplazándolo con el último elemento del montón, que es una hoja y, por lo tanto, uno de los elementos más pequeños. Esta nueva raíz seguramente violará la propiedad del montón, por lo que debe llamar siftDownhasta que la vuelva a colocar en una posición aceptable. Esto también tiene el efecto de mover el siguiente elemento más grande hasta la raíz. Tenga en cuenta que, en contraste con buildHeapdónde para la mayoría de los nodos que estamos llamando siftDowndesde la parte inferior del árbol, ¡ahora estamos llamando siftDowndesde la parte superior del árbol en cada iteración!Aunque el árbol se está encogiendo, no se encoge lo suficientemente rápido : la altura del árbol se mantiene constante hasta que haya eliminado la primera mitad de los nodos (cuando despeja la capa inferior por completo). Luego, para el próximo trimestre, la altura es h - 1 . Entonces, el trabajo total para esta segunda etapa es

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

Observe el cambio: ahora el caso de trabajo cero corresponde a un solo nodo y el caso de trabajo h corresponde a la mitad de los nodos. Esta suma es O (n log n) al igual que la versión ineficiente buildHeapque se implementa usando siftUp. Pero en este caso, no tenemos otra opción ya que estamos tratando de ordenar y requerimos que el siguiente elemento más grande se elimine a continuación.

En resumen, el trabajo para la ordenación del montón es la suma de las dos etapas: O (n) tiempo para buildHeap y O (n log n) para eliminar cada nodo en orden , por lo que la complejidad es O (n log n) . Puede probar (usando algunas ideas de la teoría de la información) que, para una clasificación basada en la comparación, O (n log n) es lo mejor que podría esperar de todos modos, por lo que no hay razón para decepcionarse o esperar que la clasificación en montón logre O (n) límite de tiempo que buildHeapsí.

Jeremy West
fuente
2
Edité mi respuesta para usar un montón máximo, ya que parece que la mayoría de las otras personas se refieren a eso y es la mejor opción para el tipo de montón.
Jeremy West
28
Esto es lo que me dejó intuitivamente claro: "solo un nodo está en la parte superior, mientras que la mitad de los nodos se encuentran en la capa inferior. Por lo tanto, no debería ser demasiado sorprendente que si tenemos que aplicar una operación a cada nodo, lo haríamos prefiero siftDown sobre siftUp ".
Vicky Chijwani
3
@JeremyWest "Uno es comenzar en la parte superior del montón (el comienzo de la matriz) y llamar a siftUp en cada elemento". - ¿Querías comenzar desde el fondo del montón?
aste123
44
@ aste123 No, es correcto como está escrito. La idea es mantener una barrera entre la parte de la matriz que satisface la propiedad del montón y la parte no ordenada de la matriz. Usted comienza al principio avanzando y llamando siftUpa cada elemento o comienza al final retrocediendo y llamando siftDown. No importa qué enfoque elija, está seleccionando el siguiente elemento en la parte no ordenada de la matriz y está realizando la operación adecuada para moverlo a una posición válida en la parte ordenada de la matriz. La única diferencia es el rendimiento.
Jeremy West
2
Esta es la mejor respuesta que he visto a cualquier pregunta en el mundo. Estaba tan bien explicado, pensé que es realmente posible ... muchas gracias.
HARSHIL JAIN
314

Tu análisis es correcto. Sin embargo, no es apretado.

No es realmente fácil explicar por qué construir un montón es una operación lineal, es mejor que lo lea.

Un gran análisis del algoritmo se puede ver aquí .


La idea principal es que en el build_heapalgoritmo el heapifycosto real no es O(log n)para todos los elementos.

Cuando heapifyse llama, el tiempo de ejecución depende de qué tan lejos se pueda mover un elemento hacia abajo en el árbol antes de que finalice el proceso. En otras palabras, depende de la altura del elemento en el montón. En el peor de los casos, el elemento podría descender hasta el nivel de la hoja.

Cuentemos el trabajo realizado nivel por nivel.

En el nivel más bajo, hay 2^(h)nodos, pero no llamamos heapifya ninguno de estos, por lo que el trabajo es 0. En el siguiente nivel hay 2^(h − 1)nodos, y cada uno puede moverse hacia abajo en 1 nivel. En el 3er nivel desde la parte inferior, hay 2^(h − 2)nodos, y cada uno puede moverse hacia abajo en 2 niveles.

Como puede ver, no todas las operaciones de heapify son O(log n), por eso está obteniendo O(n).

emre nevayeshirazi
fuente
17
Esta es una gran explicación ... pero ¿por qué es entonces que la ordenación del montón se ejecuta en O (n log n). ¿Por qué no se aplica el mismo razonamiento a la ordenación del montón?
hba
49
@hba Creo que la respuesta a su pregunta radica en comprender esta imagen de este artículo . Heapifyes O(n)cuando termina siftDownpero O(n log n)cuando termina siftUp. Por lo tanto, la clasificación real (extraer elementos del montón uno por uno) debe realizarse de esta siftUpmanera O(n log n).
The111
3
Realmente me gusta la explicación intuitiva de su documento externo en la parte inferior.
Lukas Greblikas
1
@hba la respuesta a continuación por Jeremy West aborda su pregunta con más detalle y fácil de entender, explicando más detalladamente la respuesta al comentario de The111 aquí.
cellepo
Una pregunta. Me parece que las # comparaciones hechas para un nodo en altura idesde la parte inferior de un árbol de altura h también deben hacer 2* log(h-i)comparaciones y deben tenerse en cuenta también @ The111. ¿Qué piensas?
Sid
94

Intuitivamente

"La complejidad debería ser O (nLog n) ... para cada elemento que" heapify ", tiene el potencial de tener que filtrar una vez por cada nivel para el montón hasta ahora (que son niveles log n)".

No exactamente. Su lógica no produce un límite estrecho: sobreestima la complejidad de cada heapify. Si se construye de abajo hacia arriba, la inserción (heapify) puede ser mucho menor que O(log(n)). El proceso es el siguiente:

(Paso 1) Los primeros n/2elementos van en la fila inferior del montón. h=0, por lo que heapify no es necesario.

(Paso 2) Los siguientes elementos van en la fila 1 desde abajo. , heapify filtra 1 nivel abajo.n/22h=1

(Paso i ) Los siguientes elementos van en fila hacia arriba desde la parte inferior. , heapify filtra los niveles hacia abajo.n/2iih=ii

( Registro de pasos (n) ) El último elemento va en fila hacia arriba desde la parte inferior. , heapify filtra los niveles hacia abajo.n/2log2(n) = 1log(n)h=log(n)log(n)

AVISO: después del paso uno, 1/2los elementos (n/2)ya están en el montón, y ni siquiera tuvimos que llamar a heapify una vez. Además, tenga en cuenta que solo un elemento, la raíz, en realidad incurre en toda la log(n)complejidad.


Teóricamente:

Los pasos totales Npara construir un montón de tamaño nse pueden escribir matemáticamente.

En altura i, hemos demostrado (arriba) que habrá elementos que deben llamarse heapify, y sabemos que heapify en altura es . Esto da:n/2i+1iO(i)

ingrese la descripción de la imagen aquí

La solución a la última suma se puede encontrar tomando la derivada de ambos lados de la conocida ecuación de series geométricas:

ingrese la descripción de la imagen aquí

Finalmente, conectando x = 1/2con la ecuación anterior se obtiene 2. Al conectar esto a la primera ecuación se obtiene:

ingrese la descripción de la imagen aquí

Por lo tanto, el número total de pasos es de tamaño O(n)

bcorso
fuente
35

Sería O (n log n) si construye el montón insertando elementos repetidamente. Sin embargo, puede crear un nuevo montón de manera más eficiente insertando los elementos en orden arbitrario y luego aplicando un algoritmo para "heapificarlos" en el orden correcto (dependiendo del tipo de montón, por supuesto).

Ver http://en.wikipedia.org/wiki/Binary_heap , "Construyendo un montón" para un ejemplo. En este caso, básicamente trabaja desde el nivel inferior del árbol, intercambiando los nodos padre e hijo hasta que se cumplan las condiciones de almacenamiento dinámico.

mi____t
fuente
12

Ya hay algunas respuestas geniales, pero me gustaría agregar una pequeña explicación visual

ingrese la descripción de la imagen aquí

Ahora, eche un vistazo a la imagen, hay
n/2^1 nodos verdes con altura 0 (aquí 23/2 = 12)
n/2^2 nodos rojos con altura 1 (aquí 23/4 = 6)
n/2^3 nodo azul con altura 2 (aquí 23/8 = 3)
n/2^4 nodos púrpura con altura de 3 (aquí 23/16 = 2)
por lo que no son n/2^(h+1)nodos para la altura h
para encontrar la complejidad del tiempo permite contar el volumen de trabajo realizado o no máximo de iteraciones realizadas por cada nodo
ahora se puede notar que cada nodo puede realizar (casi) iteraciones == altura 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)   

entonces, para cualquier nodo con altura h, el trabajo máximo realizado es n / 2 ^ (h + 1) * h

Ahora el trabajo total realizado es

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

ahora para cualquier valor de h , la secuencia

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

nunca excederá 1
Por lo tanto, la complejidad del tiempo nunca excederá O (n) para construir el montón

Julkar9
fuente
7

Como sabemos que la altura de un montón es log (n) , donde n es el número total de elementos.    Vamos a representarlo como h
Cuando realizamos la operación de heapify, los elementos en el último nivel ( h ) no se moverán ni un solo paso.
   El número de elementos en el segundo último nivel ( h-1 ) es de 2 h-1 y se pueden mover al máximo nivel 1 (durante el heapify).
   Del mismo modo, para el i ésimo , nivel tenemos 2 i elementos que pueden moverse hi posiciones.

Por lo tanto, número total de movimientos = 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
esta es la serie AGP , para resolver esto divida ambos lados entre 2
                                               S / 2 = 2 h {1/2 2 + 2/2 3 + ... h / 2 h + 1 } --------------------------------- ---------------- 2
restando la ecuación 2 de 1 da
                                               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 }
ahora 1/2 + 1/2 2 + 1/2 3 + ... + 1/2 h está disminuyendo GP cuya suma es menor que 1 (cuando h tiende al infinito, la suma tiende a 1). En un análisis posterior, tomemos un límite superior en la suma que es 1.
Esto da S = 2 h + 1 {1 + h / 2 h + 1 }
                    = 2 h + 1 + h
                    ~ 2 h + h
como h = log (n) , 2 h = n

Por lo tanto S = n + log (n)
T (C) = O (n)

Tanuj Yadav
fuente
6

Mientras construye un montón, digamos que está adoptando un enfoque ascendente.

  1. Toma cada elemento y lo compara con sus elementos secundarios para verificar si el par se ajusta a las reglas del montón. Por lo tanto, las hojas se incluyen en el montón de forma gratuita. Eso es porque no tienen hijos.
  2. Moviéndose hacia arriba, el peor de los casos para el nodo justo encima de las hojas sería 1 comparación (como máximo se compararían con solo una generación de niños)
  3. Más adelante, sus padres inmediatos pueden compararse como máximo con dos generaciones de niños.
  4. Continuando en la misma dirección, tendrá comparaciones log (n) para la raíz en el peor de los casos. y log (n) -1 para sus hijos inmediatos, log (n) -2 para sus hijos inmediatos, etc.
  5. En resumen, llegas a algo como log (n) + {log (n) -1} * 2 + {log (n) -2} * 4 + ..... + 1 * 2 ^ {( logn) -1} que no es más que O (n).
Jones
fuente
2

En caso de construir el montón, comenzamos desde la altura, logn -1 (donde logn es la altura del árbol de n elementos). Para cada elemento presente en la altura 'h', vamos a max upto (logn -h) height down.

    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)
Kartik Goyal
fuente
1

Las inserciones sucesivas se pueden describir mediante:

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

Por aproximación de estornino n! =~ O(n^(n + O(1))), por lo tantoT =~ O(nlog(n))

Espero que esto ayude, la forma óptima O(n)es usar el algoritmo de acumulación de compilación para un conjunto dado (el orden no importa).

Tomer Shalev
fuente
1

Básicamente, el trabajo se realiza solo en nodos no hoja mientras se construye un montón ... y el trabajo realizado es la cantidad de intercambio hacia abajo para satisfacer la condición del montón ... en otras palabras (en el peor de los casos), la cantidad es proporcional a la altura del nodo ... en general, la complejidad del problema es proporcional a la suma de las alturas de todos los nodos no hoja ... que es (2 ^ h + 1 - 1) -h-1 = nh-1 = En)

Shubham Jindal
fuente
1

@bcorso ya ha demostrado la prueba del análisis de complejidad. Pero por el bien de aquellos que aún están aprendiendo análisis de complejidad, tengo esto que agregar:

La base de su error original se debe a una interpretación errónea del significado de la declaración, "la inserción en un montón lleva tiempo O (log n)". La inserción en un montón es de hecho O (log n), pero debe reconocer que n es el tamaño del montón durante la inserción .

En el contexto de insertar n objetos en un montón, la complejidad de la inserción i-ésima es O (log n_i) donde n_i es el tamaño del montón como en la inserción i. Solo la última inserción tiene una complejidad de O (log n).

N.Vegeta
fuente
1

Supongamos que tiene N elementos en un montón. Entonces su altura sería Log (N)

Ahora desea insertar otro elemento, a continuación, la complejidad sería la siguiente: log (n) , tenemos que comparar todo el camino arriba hasta la raíz.

Ahora tiene N + 1 elementos & height = Log (N + 1)

Usando la técnica de inducción se puede demostrar que la complejidad de la inserción sería ∑logi .

Ahora usando

log a + log b = log ab

Esto se simplifica a: ∑logi = log (n!)

que en realidad es O (NlogN)

Pero

Aquí estamos haciendo algo mal, ya que en todos los casos no llegamos a la cima. Por lo tanto, mientras ejecutamos la mayoría de las veces, podemos encontrar eso, no vamos ni a la mitad del árbol. Por lo tanto, este límite puede optimizarse para tener otro límite más estricto mediante el uso de las matemáticas dadas en las respuestas anteriores.

Me di cuenta de esto después de un detalle y una experimentación en Heaps.

Fooo
fuente
0

Realmente me gusta la explicación de Jeremy West ... otro enfoque que es realmente fácil de entender se da aquí http://courses.washington.edu/css343/zander/NotesProbs/heapcomplexity

dado que buildheap depende del uso depende de heapify y se utiliza el enfoque shiftdown que depende de la suma de las alturas de todos los nodos. Entonces, para encontrar la suma de la altura de los nodos que viene dada por S = suma de i = 0 a i = h de (2 ^ i * (hi)), donde h = logn es la altura del árbol que resuelve s, obtenemos s = 2 ^ (h + 1) - 1 - (h + 1) ya que, n = 2 ^ (h + 1) - 1 s = n - h - 1 = n- log - 1 s = O (n), y entonces la complejidad de buildheap es O (n).

Nitish Jain
fuente
0

"El límite de tiempo lineal del montón de compilación se puede mostrar calculando la suma de las alturas de todos los nodos en el montón, que es el número máximo de líneas discontinuas. Para el árbol binario perfecto de altura h que contiene N = 2 ^ ( h + 1) - 1 nodos, la suma de las alturas de los nodos es N - H - 1. Por lo tanto, es O (N) ".

sec3
fuente
0

Prueba de O (n)

La prueba no es elegante, y bastante sencilla, solo probé el caso de un árbol binario completo, el resultado puede generalizarse para un árbol binario completo.

Yi Y
fuente
0

Obtenemos el tiempo de ejecución para la construcción del montón determinando el movimiento máximo que cada nodo puede tomar. Por lo tanto, necesitamos saber cuántos nodos hay en cada fila y qué tan lejos de ellos puede ir cada nodo.

Comenzando desde el nodo raíz, cada fila siguiente tiene el doble de nodos que la fila anterior, por lo que al responder con qué frecuencia podemos duplicar el número de nodos hasta que no nos quede ninguno, obtenemos la altura del árbol. O en términos matemáticos, la altura del árbol es log2 (n), siendo n la longitud de la matriz.

Para calcular los nodos en una fila comenzamos desde la parte posterior, sabemos que n / 2 nodos están en la parte inferior, por lo que al dividir por 2 obtenemos la fila anterior y así sucesivamente.

En base a esto obtenemos esta fórmula para el enfoque Siftdown: (0 * n / 2) + (1 * n / 4) + (2 * n / 8) + ... + (log2 (n) * 1)

El término en la última parálisis es la altura del árbol multiplicado por el nodo que está en la raíz, el término en la primera parálisis son todos los nodos en la fila inferior multiplicados por la longitud que pueden viajar, 0. Misma fórmula en smart: ingrese la descripción de la imagen aquí

Matemáticas

Trayendo de nuevo la n tenemos 2 * n, 2 puede descartarse porque es una constante y tada tenemos el peor tiempo de ejecución del enfoque Siftdown: n.

Max Tromp
fuente
-6

Creo que estás cometiendo un error. Eche un vistazo a esto: http://golang.org/pkg/container/heap/ Construir un montón no es O (n). Sin embargo, la inserción es O (lg (n). Supongo que la inicialización es O (n) si establece un tamaño de almacenamiento dinámico b / c, el almacenamiento dinámico debe asignar espacio y configurar la estructura de datos. Si tiene n elementos para colocar en el montón, entonces sí, cada inserción es lg (n) y hay n elementos, por lo que obtienes n * lg (n) como dijiste

Mike Schachter
fuente
2
No, no está apretado. análisis más estricto de los rendimientos del montón de compilación O (n)
emre nevayeshirazi
Parece que es una estimación. La cita en el artículo al que hizo referencia es "La intuición es que la mayoría de las llamadas al heapify están en montones muy cortos" Sin embargo, esto está haciendo algunas suposiciones. Presumiblemente, para un montón grande, el peor de los casos sería O (n * lg (n)), incluso si por lo general podría acercarse a O (n). Pero podría estar equivocado
Mike Schachter
Sí, esa también es mi respuesta intuitiva, pero referencias como el estado de Wikipedia "Los montones con n elementos se pueden construir de abajo hacia arriba en O (n)".
GBa
1
Estaba pensando en una estructura de datos completamente ordenada. Olvidé las propiedades específicas de un montón.
Mike Schachter