1. Qué son las colas de prioridad
Una cola de prioridad es una estructura de datos que permite al menos las siguientes dos
operaciones: insertar, que añade elementos a la cola, y eliminar mínimo, que busca, devuelve y
elimina el elemento mínimo de la cola.
La implantación de la cola de prioridad en la biblioteca de clases de DSTool se realiza mediante un
montículo binario.
Para que una estructura sea un montículo binario debe cumplir dos propiedades:
Un montículo es un árbol binario completamente lleno, con la posible excepción del nivel más
bajo, el cual se rellena de izquierda a derecha. Estos árboles se denominan árboles binarios
completos.
Todo nodo debe ser menor que todos sus descendientes. Por lo tanto, el mínimo estará en la
raíz y su búsqueda y eliminación se podrá realizar rápidamente.
Los árboles binarios completos son muy regulares, lo que permite que los montículos puedan ser
representados mediante simples arrays sin necesidad de punteros. En una representación con arrays
los hijos de un elemento colocado en la posición i estarían en las posiciones 2i y 2i+1 ( hijo izquierdo e
hijo derecho respectivamente), y el padre en la posición E( i/2 ) (parte entera de i/2 ).
En el siguiente ejemplo:
En el árbol se puede apreciar claramente los hijos de B ( D y E) y su padre ( A). A través de la
representación con un array se podría obtener la misma información:
Nodo B en la posición 2.
Hijo izquierdo: posición 2*2 = 4. Luego el hijo izquierdo es D.
Hijo derecho: posición 2*2+1 = 5. Luego el hijo derecho es E.
Padre: posición E( 2/2) = 0. Luego el padre es A.
Operaciones básicas de las colas de prioridad
Las dos operaciones básicas de las colas de prioridad son insertar y eliminar mínimo. Vamos a conocer
su funcionamiento.
2. Insertar
La inserción en una cola de prioridad puede ser algo muy simple o una operación compleja.
Inicialmente se inserta el nuevo elemento en la primera posición libre de la cola de prioridad, en el
montículo representado arriba se insertaría como el hijo derecho del nodo C. En el caso de que el
nuevo elemento fuese mayor que su padre la operación de inserción habría finalizado, ya que seguiría
manteniendo la condición de orden de los montículos. Por supuesto este era el caso simple...
La complejidad se produce cuando el nodo insertado es menor que su padre. Si repasamos las
condiciones para que una estructura sea un montículo binario observaremos que es necesario que
cada nodo sea menor que sus hijos, por lo que si dejásemos la estructura así, con el nuevo nodo en la
última posición, esa estructura ya no sería un montículo.
Para solucionar este problema lo que se realiza es un intercambio entre padre e hijo. Con este
intercambio ya estaría solucionado el problema, pero ¿qué sucede si el nuevo nodo (que se ha
intercambiado con el padre) es aún más pequeño que su nuevo padre?. Pues la respuesta es simple,
se volvería a realizar un nuevo intercambio del nuevo nodo con su nuevo padre, prolongándose estos
intercambios hasta que el nuevo nodo sea mayor que sus padre o hasta que el nuevo nodo llegue a la
raíz.
Vamos a ver este caso con un ejemplo gráfico:
Sobre el montículo inicial, representado por los nodos negros, insertamos un nuevo elemento con
valor 3, representado por el nodo rojo.
Como vemos, si se dejase en esa posición el nuevo nodo, se violaría la condición de orden del
montículo, ya que el nodo 8 no es menor que todos sus descendientes ( 3 es menor que 8). Por lo
tanto se realiza un intercambio entre 8 y 3.
3. La situación ahora entre el nodo 8 y el nodo 3 es correcto. Perfecto. Pero si observamos un poco más
arriba, veremos que el nodo padre del nuevo nodo, el 4, viola de nuevo la condicón de orden del
montículo. Por lo tanto volvemos a intercambiar posiciones entre 3 y 4.
Ahora sí que ya podemos decir que el montículo es correcto, ya que el nuevo nodo 3 sí que es mayor
que la raíz 1.
Véase que la condición de orden sólo obliga a que un nodo sea menor que sus descendientes, no a
que sea menor que los nodos que estén en un nivel inferior. Esto se puede apreciar con el hijo
izquierdo de 2 ( valor 5) y el nodo con valor 4 situado en el mismo nivel. Nodo 4 es menor que nodo 5
y está en un nivel inferior. Esto no importa, el montículo es correcto.
Eliminar mínimo
La operación eliminar mínimo consta de dos partes, la búsqueda del mínimo y su eliminación. La
búsqueda es fácil, el mínimo es siempre la raíz. Eliminarlo es lo complejo.
Al eliminar el mínimo se crea un hueco en la raíz, este hueco se cubrirá con el menor de uno de los
siguientes nodos, sus hijos o el último nodo del montículo. Al realizar este trasvase, se creará un
nuevo hueco en el lugar del nodo que promocionó a la raíz, excepto si el nodo que promocionó fue el
último del montículo. Para cubrir este nuevo hueco se vuelve a realizar la misma acción, se cubre con
4. el menor de sus hijos o con el último nodo si éste es más pequeño. Este procedimiento se repetirá
recursivamente hasta que el hueco sea cubierto por el último nodo del montículo original, caso que se
producirá cuando dicho nodo sea menor que los nodos hijo del hueco o cuando el hueco esté situado
en un nodo hoja.
Este proceso se apreciará mejor gráficamente:
Al eliminar el mínimo (1), se crea un hueco en la raíz que será cubierto por el menor de los siguientes
nodos: hijo izquierdo (2), hijo derecho (3), último nodo (4). En este caso el nodo que promociona será
el 2.
Para ocupar el nuevo nodo se tienen en cuenta los siguientes nodos: hijo izquierdo (5), hijo derecho
(8), último nodo (4). El mínimo es el 4, por lo tanto es el que ocupa el hueco y se acaba la operación
de inserción. La estructura obtenida es un montículo correcto.
Otras operaciones
En una cola de prioridad el resto de operaciones son secundarias y cada cola puede implementar unos
métodos distintos. Por esta razón no entraré en otras operaciones.
Únicamente comentar que las colas de prioridad pueden ser también de máximos, caso inverso
totalmente al visto en esta página. Es importante también advertir que en un montículo de mínimos la
operación eliminar máximo no es ni mucho menos tan eficiente como la operación eliminar mínimo, ya
5. que habría que realizar un recorrido completo del árbol para encontrar el máximo. De la misma forma
sucede con la operación eliminar mínimo en un montículo de máximos.
Insertar
El proceso de inserción en una tabla hash es muy simple y sencillo. Sobre el elemento que se desea
insertar se aplica la función de dispersión. El valor obtenido tras la aplicación de esta función será el
índice de la tabla en el que se insertará el nuevo elemento.
Veamos este proceso con un ejemplo. Sobre la siguiente tabla hash se desea introducir un nuevo
elemento, la cadena azul. Sobre este valor se aplica la función de dispersión, obteniendo el índice 2.
El resultado de la inserción sería el siguiente:
En el caso de que se produzca una colisión al tratar de insertar el nuevo elemento, el procedimiento
será distinto en función del tipo de hash con el que se esté tratando.
Encadenamiento separado: El nuevo elemento se añadirá al final de la lista que se inicia en la
posición indicada por el valor que retornó la función de dispersión.
Direccionamiento abierto: En este caso, se busca una nueva posición en la que almacenar el
nuevo valor.
6. Borrar
El borrado en una tabla hash es muy sencillo y se realiza de forma muy eficiente. Una vez indicada la
clave del objeto a borrar, se procederá a eliminar el valor asociado a dicha clave de la tabla.
Esta operación se realiza en tiempo constante, sin importar el tamaño de la tabla o el número de
elementos que almacene en ese momento la estructura de datos. Esto es así ya que al ser la tabla una
estructura a la que se puede acceder directamente a través de las claves, no es necesario recorrer
toda la estructura para localizar un elemento determinado.
Si sobre la tabla resultante de la inserción del elemento azul realizamos el borrado del
elemento negro, la tabla resultante sería la siguiente:
Otras operaciones
Una de las principales operaciones que se pueden realizar en las tablas hash es la redispersión. La
redispersión se suele realizar cuando el factor de carga(número de elementos / capacidad de la tabla)
de la tabla supera cierto umbral.
La redispersión consiste en pasar todos los elementos de la tabla original a una nueva tabla de un
tamaño mayor. De esta forma, se reduce el factor de carga de la tabla