2009-02-01 22 views
13

citando Wikipedia:Finding último elemento de una pila binaria

Es perfectamente aceptable el uso de una estructura de datos de árbol binario tradicional para implementar una pila binaria. Hay un problema con encontrar el elemento adyacente en el último nivel en el pila binaria cuando se añade un elemento que puede ser resuelto algorítmicamente ...

Alguna idea sobre cómo tal una algoritmo podría funcionar?

No pude encontrar ninguna información sobre este problema, ya que la mayoría de los montones binarios se implementan mediante matrices.

Cualquier ayuda apreciada.


Recientemente, he registrado una cuenta de OpenID y no puedo editar mi publicación inicial ni comentar las respuestas. Es por eso que estoy respondiendo a través de esta respuesta. Perdón por esto.


citando Mitch Trigo:

@Yse: es la pregunta "¿Cómo puedo encontrar el último elemento de una pila binaria"?

Sí, lo es. O para ser más precisos, mi pregunta es: "¿Cómo puedo encontrar el último elemento de un no basado en matriz montón binario?".

citando Suppressingfire:

¿Hay algún contexto en el que estás hacer esta pregunta? (Es decir, ¿hay algún problema concreto que estamos tratando de resolver ?)

Como se ha dicho anteriormente, me gustaría saber una buena manera de "encontrar el último elemento de una binaria no basado en matriz de montón "que es necesario para la inserción y eliminación de nodos.

citando Roy:

Parece más comprensible para mí sólo tiene que utilizar un binario estructura normal del árbol (utilizando un PROOT y el Nodo definido como [datos, pLeftChild, pRightChild]) y agregue dos punteros adicionales (pInsertionNode y pLastNode). pInsertionNode y pLastNode se actualizarán durante las subrutinas de inserción y eliminación para mantenerlos actualizados cuando cambien los datos dentro de la estructura.Este da O (1) acceso al punto de inserción y al último nodo de la estructura.

Sí, esto debería funcionar. Si no me equivoco, podría ser un poco complicado encontrar el nodo de inserción y el último nodo, cuando sus ubicaciones cambian a otro subárbol debido a una eliminación/inserción. Pero voy a intentar esto.

citando Zach Scrivena:

¿Qué tal la realización de una búsqueda en profundidad ...

Sí, esto sería un buen enfoque. Voy a probar eso también.

Todavía me pregunto, si hay una manera de "calcular" las ubicaciones del último nodo y el punto de inserción. La altura de un montón binario con N nodos se puede calcular tomando el registro (de la base 2) de la potencia más pequeña de dos que sea más grande que N. Quizás también sea posible calcular el número de nodos en el nivel más profundo. Entonces tal vez fue posible determinar cómo se debe atravesar el montón para llegar al punto de inserción o al nodo para su eliminación.

+0

@Yse: es su pregunta "¿Cómo puedo encontrar el último elemento de un montón binario"? –

+0

Usa 2 montones, o (peor porque pierde O (1) inserción de avarage) un montón binario y haz un seguimiento de los más grandes y pequeños: http://stackoverflow.com/questions/7878622/can-we-use-binary-search -tree-to-simulate-heap-operation –

Respuesta

1

¿Qué tal si realizas un depth-first search, visitando al niño de la izquierda antes que al niño de la derecha para determinar la altura del árbol? A partir de entonces, la primera hoja que encuentre con una profundidad más corta, o un padre con un niño desaparecido indicaría dónde debe colocar el nuevo nodo antes de "burbujear".


El enfoque primero en profundidad de búsqueda (DFS) anterior no supone que se conoce el número total de nodos en el árbol. Si esta información está disponible, entonces podemos "zoom-in" rápidamente al lugar deseado, haciendo uso de las propiedades de árboles binarios completos:

Vamos N será el número total de nodos en el árbol y H sea la altura del árbol.

Algunos valores de (N, H) son (1,0), (2,1), (3,1), (4,2), ..., (7,2) , (8, 3). La fórmula general relativa los dos es H = ceil [log2 (N 1)] - 1. Ahora, solamente N dado, queremos atravesar desde la raíz hasta la posición para el nuevo nodo, en el menor número de pasos, es decir, sin ningún "retroceso". primero se calcula el número total de nodos M en un árbol binario perfecto de altura H = ceil [log2 (N 1)] - 1, que es M = 2^(H 1) - 1.

Si N == M, entonces nuestro árbol es perfecta , y el nuevo nodo debe añadirse en un nuevo nivel.Esto significa que simplemente podemos realizar un DFS (izquierda antes a la derecha) hasta que lleguemos a la primera hoja ; el nuevo nodo se convierte en el hijo izquierdo de esta hoja. Fin de la historia.

Sin embargo, si N < M, entonces todavía hay plazas vacantes en el último nivel de nuestro árbol, y el nuevo nodo debe añadirse a la vacante más a la izquierda. El número de nodos que ya se encuentran en el último nivel de nuestro árbol es (N - 2^H + 1). Esto significa que el nuevo nodo ocupa el lugar X = (N - 2^H + 2) desde la izquierda, en el último nivel.

Ahora, para llegar desde la raíz, tendrá que hacer los giros correctos (L vs R) en cada nivel para que termine en el punto X en el último nivel. En la práctica, usted determinaría los giros con un poco de cálculo en cada nivel. Sin embargo, creo que la tabla siguiente se muestra el panorama general y los patrones relevantes sin quedar empantanados en la aritmética (que pueden reconocer esto como una forma de arithmetic coding para una distribución uniforme):

0 0 0 0 0 X 0 0 <--- represents the last level in our tree, X marks the spot! 
     ^
L L L L R R R R <--- at level 0, proceed to the R child 
L L R R L L R R <--- at level 1, proceed to the L child 
L R L R L R L R <--- at level 2, proceed to the R child 
     ^     (which is the position of the new node) 
      this column tells us 
      if we should proceed to the L or R child at each level 

EDIT: Se ha añadido una descripción de cómo llegar al nuevo nodo en el menor número de pasos suponiendo que conocemos la cantidad total de nodos en el árbol.

17

Básicamente, la declaración citada se refiere al problema de resolver la ubicación para la inserción y eliminación de elementos de datos en y desde el montón. Para mantener "la propiedad de forma" de un montón binario, el nivel más bajo del montón siempre debe llenarse de izquierda a derecha sin dejar nodos vacíos. Para mantener los tiempos medios de inserción y eliminación de O (1) para el montón binario, debe poder determinar la ubicación para la siguiente inserción y la ubicación del último nodo en el nivel más bajo para usar para eliminar el nodo raíz, ambos en tiempo constante.

Para un montón binario almacenado en una matriz (con su estructura de datos compactada implícita como se explica en la entrada de Wikipedia), esto es fácil. Simplemente inserte el miembro de datos más nuevo al final de la matriz y luego "burbuja" en su posición (siguiendo las reglas de montón). O reemplace la raíz con el último elemento de la matriz "burbujeando hacia abajo" para eliminar. Para los montones en el almacenamiento de matriz, la cantidad de elementos en el montón es un puntero implícito al lugar donde se insertará el siguiente elemento de datos y dónde encontrar el último elemento que se utilizará para eliminación.

Para un montón binario almacenado en una estructura en árbol, esta información no es tan obvia, pero dado que es un árbol binario completo, se puede calcular. Por ejemplo, en un árbol binario completo con 4 elementos, el punto de inserción siempre será el hijo correcto del hijo izquierdo del nodo raíz. El nodo que se utilizará para la eliminación siempre será el hijo izquierdo del hijo izquierdo del nodo raíz. Y para cualquier tamaño de árbol arbitrario dado, el árbol siempre tendrá una forma específica con puntos de inserción y eliminación bien definidos. Como el árbol es un "árbol binario completo" con una estructura específica para cualquier tamaño dado, es muy posible calcular la ubicación de la inserción/eliminación en el tiempo O (1). Sin embargo, la trampa es que incluso cuando sabes dónde está estructuralmente, no tienes idea de dónde estará el nodo en la memoria. Por lo tanto, debe atravesar el árbol para llegar al nodo determinado, que es un proceso O (log n) que hace que todas las inserciones y eliminaciones sean un mínimo de O (log n), rompiendo el comportamiento O (1) que se desea normalmente. Cualquier búsqueda ("primero en profundidad", o alguna otra) será al menos O (log n) también debido al problema transversal observado y, por lo general, O (n) debido a la naturaleza aleatoria del montón semiorreginado.

El truco es ser capaz tanto de calcular y referencia aquellos inserción/puntos de deleción en tiempo constante, ya sea mediante el aumento de la estructura de datos ("enhebrar" el árbol, como se menciona en el artículo de Wikipedia) o el uso adicional punteros.

La implementación que me parece más fácil de entender, con poca memoria y sobrecarga de codificación adicional, es simplemente usar una estructura de árbol binario simple normal (usando pRoot y Node definidos como [data, pParent, pLeftChild, pRightChild]) y agrega dos punteros adicionales (pInsert y pLastNode). pInsert y pLastNode se actualizarán durante las subrutinas de inserción y eliminación para mantenerlos actualizados cuando cambien los datos dentro de la estructura. Esta implementación le da a O (1) acceso tanto al punto de inserción como al último nodo de la estructura y debe permitir la preservación del comportamiento general de O (1) tanto en la inserción como en la eliminación. El costo de la implementación es de dos punteros adicionales y un pequeño código extra en las subrutinas de inserción/eliminación (también conocido como mínimo).

EDITAR: pseudocódigo añadido para un O (1) insertar()

Aquí es pseudo código para una subrutina de inserto que es O (1), en promedio:

define Node = [T data, *pParent, *pLeft, *pRight] 

void insert(T data) 
{ 
    do_insertion(data); // do insertion, update count of data items in tree 

    # assume: pInsert points node location of the tree that where insertion just took place 
    # (aka, either shuffle only data during the insertion or keep pInsert updated during the bubble process) 

    int N = this->CountOfDataItems + 1;  # note: CountOfDataItems will always be > 0 (and pRoot != null) after an insertion 

    p = new Node(<null>, null, null, null);  // new empty node for the next insertion 

    # update pInsert (three cases to handle) 
    if (int(log2(N)) == log2(N)) 
     {# #1 - N is an exact power of two 
     # O(log2(N)) 
     # tree is currently a full complete binary tree ("perfect") 
     # ... must start a new lower level 
     # traverse from pRoot down tree thru each pLeft until empty pLeft is found for insertion 
     pInsert = pRoot; 
     while (pInsert->pLeft != null) { pInsert = pInsert->pLeft; } # log2(N) iterations 
     p->pParent = pInsert; 
     pInsert->pLeft = p; 
     } 
    else if (isEven(N)) 
     {# #2 - N is even (and NOT a power of 2) 
     # O(1) 
     p->pParent = pInsert->pParent; 
     pInsert->pParent->pRight = p; 
     } 
    else 
     {# #3 - N is odd 
     # O(1) 
     p->pParent = pInsert->pParent->pParent->pRight; 
     pInsert->pParent->pParent->pRight->pLeft = p; 
     } 
    pInsert = p; 

    // update pLastNode 
    // ... [similar process] 
} 

Así, insert (T) es O (1) en promedio: exactamente O (1) en todos los casos, excepto cuando el árbol debe incrementarse en un nivel cuando es O (log N), lo que ocurre en cada log N inserciones (suponiendo que no hay eliminaciones) . La adición de otro puntero (pLeftmostLeaf) podría hacer insert() O (1) para todos los casos y evitar el posible caso patológico de inserción alternada & en un árbol binario completo completo. (Agregar pLeftmost se deja como un ejercicio [es bastante fácil].)

+0

Comprende el problema, pero el argumento O (1) que proporciona es incorrecto. Por ejemplo, ¿cómo se actualiza pInsertionNode después de una inserción? – user51568

+0

@ stefan.ciobaca: estoy de acuerdo en que @Roy no ha abordado ese problema. Sin embargo, es fácil con la adición de más punteros, por ejemplo, un par en cada nodo que "enhebre" a través de los niveles del árbol. (En términos de la matriz, cada elemento de la matriz señala uno hacia adelante y uno hacia atrás.) –

+0

En realidad, dado un tamaño de árbol conocido para un árbol binario completo, puede calcular en qué lugar del árbol estarán los siguientes nodos de inserción/eliminación y pasar a ellos en O (1) * promedio * tiempo [límite superior O (log n)] (usando básicamente el mismo análisis que el que determina el O (1) promedio de inserción + tiempo de burbuja para montones). – rivy

0

¡Solución en caso de que no tenga referencia para el padre! Para encontrar el lugar correcto para el próximo nodo que tiene 3 casos de manejar

  • caso (1) Nivel de árbol es completa log2 (N)
  • el caso (2) número de nodos del árbol es aún
  • caso (3) número de nodos del árbol es impar

insertar:

void Insert(Node root,Node n) 
{ 
Node parent = findRequiredParentToInsertNewNode (root); 
if(parent.left == null) 
parent.left = n; 
else 
parent.right = n; 
} 

Encuentra el padre del nodo con el fin de insertarlo

void findRequiredParentToInsertNewNode(Node root){ 

Node last = findLastNode(root); 

//Case 1 
if(2*Math.Pow(levelNumber) == NodeCount){ 
    while(root.left != null) 
     root=root.left; 
    return root; 
} 
//Case 2 
else if(Even(N)){ 
    Node n =findParentOfLastNode(root ,findParentOfLastNode(root ,last)); 
return n.right; 
} 
//Case 3 
else if(Odd(N)){ 
    Node n =findParentOfLastNode(root ,last); 
return n; 
} 

} 

Para encontrar el último nodo que necesita para realizar una BFS (búsqueda en anchura) y obtener el último elemento en la cola

Node findLastNode(Node root) 
{ 
    if (root.left == nil) 
     return root 

    Queue q = new Queue(); 

    q.enqueue(root); 
    Node n = null; 

    while(!q.isEmpty()){ 
     n = q.dequeue(); 
    if (n.left != null) 
     q.enqueue(n.left); 
    if (n.right != null) 
     q.enqueue(n.right); 
     } 
    return n; 
} 

Encuentra el padre del último nodo con el fin de establecer el nodo a NULL en caso de sustitución con la raíz en el caso de la eliminación

Node findParentOfLastNode(Node root ,Node lastNode) 
{ 
    if(root == null) 
     return root; 

    if(root.left == lastNode || root.right == lastNode) 
     return root; 

    Node n1= findParentOfLastNode(root.left,lastNode); 
    Node n2= findParentOfLastNode(root.left,lastNode); 

    return n1 != null ? n1 : n2; 
} 
4

Usted podría utilizar la representación binaria del tamaño del binario Montón para encontrar la ubicación del último nodo en O (log N). El tamaño podría ser almacenado e incrementado, lo que tomaría O (1) vez. El concepto fundamental detrás de esto es la estructura del árbol binario.

Supongamos que nuestro tamaño de almacenamiento dinámico es 7. La representación binaria de 7 es, "111".Ahora, recuerde omitir siempre el primer bit. Entonces, ahora nos quedamos con "11". Lea de izquierda a derecha. El bit es '1', por lo tanto, vaya al secundario correcto del nodo raíz. Entonces, la cadena que queda es "1", el primer bit es "1". Por lo tanto, vuelve al hijo correcto del nodo en el que te encuentras. Como ya no tiene bits para procesar, esto indica que ha llegado al último nodo. Entonces, el trabajo en bruto del proceso es eso, convierta el tamaño del montón en bits. Omita el primer bit. De acuerdo con el bit del extremo izquierdo, vaya al elemento secundario derecho del nodo actual si es '1', y al secundario izquierdo del nodo actual si es '0'.

Como siempre, hasta el final del árbol binario, esta operación siempre toma el tiempo O (log N). Este es un procedimiento simple y preciso para encontrar el último nodo.

Puede que no lo entiendas en la primera lectura. Intente trabajar este método en el papel para diferentes valores de Binary Heap, estoy seguro de que obtendrá la intuición detrás de él. Estoy seguro de que este conocimiento es suficiente para resolver su problema. Si desea obtener más explicaciones con las cifras, puede consultar mi blog.

Espero que mi respuesta te haya ayudado, si es así, házmelo saber ...! ☺

+1

¡Bienvenido a Stackoverflow! Gracias por su respuesta, intente incluir la respuesta como su nombre dice * Responda * aquí, no en el enlace externo que tal vez no esté disponible en el futuro. –

+0

Sí señor ... Sé que ... Si tuviera que explicarlo, sería grande y tedioso, así que di un enlace a mi blog ... ¿Hay alguna forma más sencilla de responder, señor ...? (Dado que ya tengo el contenido) ... –

+1

He puesto el contenido principal en la respuesta señor ... Aunque puedo asegurarle que el enlace se mantendrá, incluso si el enlace falla de alguna manera, creo que la pregunta sigue siendo respondió señor ... ¿Es esto aceptable ...? –

7

Mi primera vez para participar en el desbordamiento de pila.

Sí, la respuesta anterior de Zach Scrivena (Dios no sé cómo referirse adecuadamente a otras personas, lo siento) es correcta. Lo que quiero agregar es una forma simplificada si se nos da el conteo de nodos.

La idea básica es:

Dado el recuento N de nodos en este árbol binario completo, realice el cálculo "N% 2" y empujar los resultados en una pila. Continúe con el cálculo hasta que N == 1. Luego muestre los resultados. El resultado es 1 significa correcto, 0 significa izquierdo. La secuencia es la ruta desde la raíz hasta la posición objetivo.

Ejemplo:

El árbol ahora tiene 10 nodos, quiero insertar otro nodo en la posición 11. Como ruta que?

11 % 2 = 1 --> right (the quotient is 5, and push right into stack) 
5 % 2 = 1 --> right (the quotient is 2, and push right into stack) 
2 % 2 = 0 --> left  (the quotient is 1, and push left into stack. End) 

Aparece la pila: izquierda -> derecha -> derecha. Este es el camino desde la raíz.

0

Sé que este es un hilo viejo, pero estaba buscando una respuesta a la misma pregunta. Pero no podía darme el lujo de hacer una solución o (log n) ya que tenía que encontrar el último nodo miles de veces en unos pocos segundos. Tenía un algoritmo O (log n) pero mi programa se estaba rastreando debido a la cantidad de veces que realizó esta operación. Así que después de pensarlo mucho finalmente encontré una solución para esto. No estoy seguro de si alguien es interesante.

Esta solución es O (1) para la búsqueda. Para la inserción es definitivamente menor que O (log n), aunque no puedo decir que sea O (1).

Solo quería agregar que si hay interés, también puedo proporcionar mi solución. La solución es agregar los nodos en el montón binario a una cola. Cada nodo de cola tiene indicadores frontales y posteriores. Seguimos agregando nodos al final de esta cola de izquierda a derecha hasta que llegamos al último nodo en el montón binario. En este punto, el último nodo en el montón binario estará en la parte posterior de la cola. Cada vez que necesitamos encontrar el último nodo, dequeuemos desde la parte posterior, y el penúltimo ahora se convierte en el último nodo en el árbol. Cuando queremos insertar, buscamos hacia atrás desde la parte posterior para el primer nodo donde podemos insertarlo y ponerlo allí. No es exactamente O (1) pero reduce drásticamente el tiempo de ejecución.

+0

La inserción en la cola tiene O (n) el peor tiempo de ejecución del caso y puede ser muy lenta en la práctica. Tal vez funcionó rápido en su caso porque sus datos tenían una distribución particular (por ejemplo, los nuevos datos ya están clasificados). – mefathy

+0

@mefathy: Sí, los datos estaban ordenados en mi programa. Y tendría una peor inserción de O (n) en la cola. Pero a medida que N aumenta las posibilidades de caminar por toda la cola también se reduce porque el nodo donde la inserción necesita producirse estará más cerca de la parte posterior (o profundidades más cerca de la parte inferior del árbol). Pensaría que O (n) sería verdadero si la inserción está más cerca de la raíz. – Dayanidhi

Cuestiones relacionadas