En informática , un montón binomial es una estructura de datos que actúa como una cola de prioridad . Es un ejemplo de un montón fusionable (también llamado montón combinable), ya que admite la fusión de dos montones en tiempo logarítmico. Se implementa como un montón similar a un montón binario pero utilizando una estructura de árbol especial que es diferente de los árboles binarios completos utilizados por los montones binarios. [1] Los montones binomiales fueron inventados en 1978 por Jean Vuillemin . [1] [2]
Un montón binomial se implementa como un conjunto de árboles binomiales (compárese con un montón binario , que tiene la forma de un solo árbol binario ), que se definen recursivamente de la siguiente manera: [1]
Un árbol binomial de orden tiene nodos y una altura . El nombre proviene de la forma: un árbol binomial de orden tiene nodos en profundidad y un coeficiente binomial . Debido a su estructura, un árbol binomial de orden se puede construir a partir de dos árboles de orden uniendo uno de ellos como el hijo más a la izquierda de la raíz del otro árbol. Esta característica es fundamental para la operación de fusión de un montón binomial, que es su principal ventaja sobre otros montones convencionales. [1] [3]
Un montón binomial se implementa como un conjunto de árboles binomiales que satisfacen las propiedades del montón binomial : [1]
La primera propiedad garantiza que la raíz de cada árbol binomial contenga la clave más pequeña del árbol. De ello se deduce que la clave más pequeña de todo el montón es una de las raíces. [1]
La segunda propiedad implica que un montón binomial con nodos consta de, como máximo, árboles binomiales, donde es el logaritmo binario . El número y los órdenes de estos árboles están determinados de forma única por el número de nodos : hay un árbol binomial por cada bit distinto de cero en la representación binaria del número . Por ejemplo, el número decimal 13 es 1101 en binario, , y, por lo tanto, un montón binomial con 13 nodos constará de tres árboles binomiales de órdenes 3, 2 y 0 (véase la figura siguiente). [1] [3]
La cantidad de formas diferentes en que los elementos con claves distintas se pueden organizar en un montón binomial es igual al divisor impar más grande de . Para estos números son
Si los elementos se insertan en un montón binomial en un orden uniformemente aleatorio, cada una de estas disposiciones es igualmente probable. [3]
Debido a que ninguna operación requiere acceso aleatorio a los nodos raíz de los árboles binomiales, las raíces de los árboles binomiales se pueden almacenar en una lista enlazada , ordenadas por orden creciente del árbol. Debido a que el número de hijos de cada nodo es variable, no funciona bien que cada nodo tenga enlaces separados a cada uno de sus hijos, como sería común en un árbol binario ; en cambio, es posible implementar este árbol utilizando enlaces desde cada nodo a su hijo de orden más alto en el árbol, y a su hermano del siguiente orden más pequeño que él. Estos punteros de hermano se pueden interpretar como los siguientes punteros en una lista enlazada de los hijos de cada nodo, pero con el orden opuesto a la lista enlazada de raíces: del orden más grande al más pequeño, en lugar de viceversa. Esta representación permite que dos árboles del mismo orden se vinculen entre sí, formando un árbol del siguiente orden más grande, en tiempo constante. [1] [3]
La operación de fusión de dos montones se utiliza como subrutina en la mayoría de las demás operaciones. Una subrutina básica dentro de este procedimiento fusiona pares de árboles binomiales del mismo orden. Esto se puede hacer comparando las claves en las raíces de los dos árboles (las claves más pequeñas en ambos árboles). El nodo raíz con la clave más grande se convierte en un hijo del nodo raíz con la clave más pequeña, aumentando su orden en uno: [1] [3]
función mergeTree(p, q) si p.root.key <= q.root.key devuelve p.addSubTree(q) de lo contrario devuelve q.addSubTree(p)
Para fusionar dos montones de manera más general, las listas de raíces de ambos montones se recorren simultáneamente de una manera similar a la del algoritmo de fusión , en una secuencia desde órdenes más pequeños de árboles a órdenes más grandes. Cuando solo uno de los dos montones que se fusionan contiene un árbol de orden , este árbol se mueve al montón de salida. Cuando ambos montones contienen un árbol de orden , los dos árboles se fusionan en un árbol de orden de modo que se cumpla la propiedad de montón mínimo. Más adelante puede ser necesario fusionar este árbol con algún otro árbol de orden en uno de los dos montones de entrada. En el transcurso del algoritmo, examinará como máximo tres árboles de cualquier orden, dos de los dos montones que fusionamos y uno compuesto por dos árboles más pequeños. [1] [3]
función merge(p, q) mientras no (p.end() y q.end()) árbol = mergeTree(p.currentTree(), q.currentTree())
si no es heap.currentTree().empty() árbol = mergeTree(árbol, montón.currentTree())
montón.addTree(árbol) montón.siguiente(); p.siguiente(); q.siguiente()
Como cada árbol binomial en un montón binomial corresponde a un bit en la representación binaria de su tamaño, existe una analogía entre la fusión de dos montones y la suma binaria de los tamaños de los dos montones, de derecha a izquierda. Siempre que se produce un acarreo durante la suma, esto corresponde a una fusión de dos árboles binomiales durante la fusión. [1] [3]
El recorrido de cada árbol binomial durante la fusión solo involucra raíces, por lo que el tiempo empleado en la mayoría de los casos es de orden y, por lo tanto, el tiempo de ejecución es . [1] [3]
La inserción de un nuevo elemento en un montón se puede realizar simplemente creando un nuevo montón que contenga solo este elemento y luego fusionándolo con el montón original. Debido a la fusión, una sola inserción lleva tiempo . Sin embargo, esto se puede acelerar utilizando un procedimiento de fusión que acorta la fusión después de que llega a un punto donde solo uno de los montones fusionados tiene árboles de orden mayor. Con esta aceleración, a lo largo de una serie de inserciones consecutivas, el tiempo total para las inserciones es . Otra forma de decirlo es que (después de la sobrecarga logarítmica para la primera inserción en una secuencia) cada inserción sucesiva tiene un tiempo amortizado de (es decir, constante) por inserción. [1] [3]
Una variante del montón binomial, el montón binomial sesgado , logra un tiempo de inserción constante en el peor de los casos mediante el uso de bosques cuyos tamaños de árboles se basan en el sistema numérico binario sesgado en lugar de en el sistema numérico binario. [4]
Para encontrar el elemento mínimo del montón, hay que buscar el mínimo entre las raíces de los árboles binomiales. Esto se puede hacer a tiempo, ya que sólo hay raíces de árboles para examinar. [1]
Al utilizar un puntero al árbol binomial que contiene el elemento mínimo, el tiempo para esta operación se puede reducir a . El puntero debe actualizarse al realizar cualquier operación que no sea la de encontrar el mínimo. Esto se puede hacer a tiempo por actualización, sin aumentar el tiempo de ejecución asintótico total de ninguna operación. [ cita requerida ]
Para eliminar el elemento mínimo del montón, primero busque este elemento, quítelo de la raíz de su árbol binomial y obtenga una lista de sus subárboles hijos (que son cada uno de ellos árboles binomiales, de órdenes distintos). Transforme esta lista de subárboles en un montón binomial separado reordenándolos del orden más pequeño al más grande. Luego, fusione este montón con el montón original. Dado que cada raíz tiene como máximo hijos, crear este nuevo montón lleva tiempo . Fusionar montones lleva tiempo , por lo que toda la operación de eliminación del mínimo lleva tiempo . [1]
función deleteMin(montón) min = montón.árboles().primero() para cada corriente en heap.trees() si current.root < min.root entonces min = current para cada árbol en min.subTrees() tmp.addTree(árbol) montón.removeTree(min) fusionar(montón, temporal)
Después de disminuir la clave de un elemento, puede llegar a ser más pequeña que la clave de su padre, violando la propiedad de montón mínimo. Si este es el caso, intercambie el elemento con su padre, y posiblemente también con su abuelo, y así sucesivamente, hasta que la propiedad de montón mínimo ya no se viole. Cada árbol binomial tiene una altura de como máximo , por lo que esto lleva tiempo. [1] Sin embargo, esta operación requiere que la representación del árbol incluya punteros desde cada nodo a su padre en el árbol, lo que complica un poco la implementación de otras operaciones. [3]
Para eliminar un elemento del montón, disminuya su clave a infinito negativo (o equivalentemente, a un valor menor que cualquier elemento en el montón) y luego elimine el mínimo en el montón. [1]
Aquí se muestran las complejidades temporales [5] de varias estructuras de datos de montón. La abreviatura am. indica que la complejidad dada se amortiza, de lo contrario es una complejidad del peor caso. Para el significado de " O ( f )" y " Θ ( f )", consulte la notación Big O. Los nombres de las operaciones suponen un montón mínimo.