Explora Libros electrónicos
Categorías
Explora Audiolibros
Categorías
Explora Revistas
Categorías
Explora Documentos
Categorías
INTRODUCCIÓN
Recordar el TDA Cola, cuyo modelo es:
quitarPrimero
COLA
insertar
primero
DESARROLLO
En muchos casos el TDA Cola no resuelve algunos problemas de espera, pues aquí siempre la técnica
de acceso es FIFO; pero, en ocasiones, interesa que se atiendan no en el orden en que llegan sino en
base a cierta prioridad que tengan.
Modelo
Una cola de prioridad es una estructura de datos que mantiene un conjunto de elementos, cada uno de
los cuales es asociado con un valor, llamado llave o prioridad.
Así, una cola de prioridad puede verse como una cola a cuyos elementos se le ha asignado una
prioridad, de forma que el orden en que los elementos son procesados sigue la siguiente regla: el
elemento con mayor prioridad (o de menor prioridad) es procesado primero.
En estos casos, para el procesamiento lo que nos interesa es la prioridad que estamos considerando; por
lo tanto, al almacenar los datos éste será el valor que tendremos en cuenta.
Consideraremos la prioridad como un número entero; de tal manera que, mientras menor sea el número
la prioridad será más alta.
eliminarMin,
insertar buscarMin
Cola de
Prioridad
Sólo es accesible el elemento mínimo
2
Este tipo de cola de prioridad se conoce como cola de prioridad mínima (min-priority queue), pero
también existe la cola de prioridad máxima (max-priority queue).
Estas colas de prioridad pueden ser utilizadas en los hospitales para atender pacientes, los que deben
ser atendidos según el grado de gravedad que tengan. En computación se utilizan mucho en sistemas
operativos multiprocesamiento, pues se le da una prioridad a cada proceso que se ejecuta y en
dependencia de ésta se asigna el procesador.
Operaciones
Las operaciones básicas sobre una Cola de Prioridad Mínima se relacionan a continuación, según las
características de una interface para el lenguaje Java:
A continuación, se presenta la Interfaz de las colas de prioridad con sus operaciones básicas:
//Interfaz ColaPrioridad
public interface ColaPrioridad <E>
{
void insertar( E x );
void introducir(E x);
E buscarMin( ) throws ColaVacia;
E eliminarMin( ) throws ColaVacia;
void vaciar( );
boolean esVacia( );
}
Implementaciones
Hay varias formas de implementar una cola de prioridad:
Lista enlazada. Pueden seguirse dos estrategias:
- Se hacen las inserciones al inicio (O(1)) y se recorre la lista para eliminar el mínimo (O(N)).
- Se inserta de tal manera que la lista quede ordenada (O(N)) y se elimina siempre el primero
(O(1)).
Si se usa uno de estos dos casos, se debe implementar de manera de no permitir operaciones que no
sean necesarias en una cola de prioridad.
2
3
1. Propiedad estructural. Un montículo es un árbol binario completo; es decir, está totalmente
lleno con la excepción del nivel inferior, que debe llenarse de izquierda a derecha.
Ejemplo 1:
A
B C
D E F G
H I J
Si el nodo J fuera hijo derecho de E, el árbol ya no estaría completo, pues faltaría un nodo.
Por tratarse de un árbol binario completo, debemos tener en cuenta que:
a) Su altura es a lo sumo log2 N. Esto significa que podemos esperar un comportamiento
logarítmico en el caso peor, si restringimos los cambios en la estructura a los caminos de la
raíz a una hoja.
b) No se necesitan referencias a los hijos izquierdo y derecho, ya que se puede representar sin
ambigüedad almacenando su recorrido por niveles en un vector. Colocaremos la raíz en la
posición 1 para reservar la posición 0 para cierta información que veremos después.
En el ejemplo 1, el montículo puede almacenarse en un vector, de la forma siguiente:
0 1 2 3 4 5 6 7 8 9 10 11 12
A B C D E F G H I J
Observe que, si el elemento en la posición i del arreglo tiene hijo izquierdo, éste está en la
posición 2i y el derecho en la 2i+1 y el padre en i/2 . Por ejemplo, el elemento C ocupa la
posición 3, su hijo izquierdo F ocupa la 6, su hijo derecho G ocupa la 7 y su padre A ocupa la
posición 1.
De esta forma, si la raíz tuviera padre estaría en la posición 0. Precisamente, se reserva esta
posición para colocar un elemento falso que sirva como padre de la raíz, lo que simplificará
algunas operaciones. A este elemento se le llama centinela.
3
4
13 13
21 16 21 16
24 31 19 68 6 31 19 68
65 26 32 65 26 32
Montículo No es montículo
Podemos también establecer la propiedad del orden de manera que cada elemento del montículo sea
mayor o igual que sus dos hijos; en este caso estaríamos en presencia de un montículo máximo y la
operación de eliminar el elemento que está en la raíz del montículo sería eliminarMax. En esta clase
nos referiremos siempre a un montículo mínimo.
Inserción:
Para insertar un elemento x creamos un “hueco” en la siguiente posición disponible, ya que de otra
forma el árbol no será completo. Si lo podemos insertar en el hueco sin violar la propiedad de orden,
todo está resuelto. En otro caso, se desliza el elemento que está en el lugar del nodo padre del hueco,
subiendo así el hueco hacia la raíz. Seguimos este proceso hasta que se pueda colocar x en el hueco.
0 1 2 3 4 5 6 7 8 9 10 11
13 21 16 24 31 19 68 65 26 32
13
65 26 32
0 1 2 3 4 5 6 7 8 9 10 11
13 21 16 24 31 19 68 65 26 32
x=14; hueco 11
4
5
13
65 26 32 31
0 1 2 3 4 5 6 7 8 9 10 11
13 21 16 24 19 68 65 26 32 31
vector[11] = 31; hueco 11/2 = 5
13
65 26 32 31
0 1 2 3 4 5 6 7 8 9 10 11
13 16 24 21 19 68 65 26 32 31
vector[5] = 21; hueco 5/2 = 2
0 1 2 3 4 5 6 7 8 9 10 11
13 14 16 24 21 19 68 65 26 32 31
Esta estrategia se conoce como filtrado ascendente o reflotamiento, porque el nuevo elemento sube
hasta encontrar la posición adecuada.
En caso necesario, se puede duplicar el tamaño del vector.
Ejemplo 3: Insertar en un montículo vacío las llaves: 150, 80, 40, 30, 10, 70, 110 y 100.
10
0 1 2 3 4 5 6 7 8 9
150
80 150 30 70
40 150 80
30 40 80 150
10 30 80 150 40 100 40 80 110
10 30 70 150 40 80 110
10 30 70 100 40 80 110 150
150
5
6
Observe que, si efectuamos N operaciones seguidas de insertar, obtenemos una complejidad O(N logN).
Analicemos si es posible ser más eficiente, introduciendo los N nodos en el árbol, sin ocuparnos de
mantener el ordenamiento del montículo, y solo en el momento que sea necesario extraer el nodo con
mayor prioridad, efectuar el ordenamiento.
Veamos que sucede al introducir en un árbol vacío las llaves del ejemplo 3, teniendo en cuenta solo la
propiedad de la estructura, pero no así la de ordenación.
En este caso, obtenemos el árbol siguiente, en el que los elementos 150 y 80 violan la propiedad de
ordenación:
150
0 1 2 3 4 5 6 7 8 9
150 80 40 30 10 70 110 100
80 40
30 10 70 110
100
El costo de introducir N nodos en un árbol es O(N), si logramos ahora construir un montículo a partir
del árbol binario completo, en un tiempo lineal, habremos obtenido una mayor eficiencia.
Reconstruir el montículo.
La propiedad de ordenación es inherente a los nodos que tienen hijos; por tanto, se comienza
ordenando por el último nodo que tiene hijos; es decir por el que ocupa la posición tamaño/2 y se va
retrocediendo hasta llegar a la raíz. Si un nodo es menor o igual que sus hijos, entonces está bien
posicionado. Por el contrario, si es mayor que alguno de sus hijos, se debe hundir hasta que se cumpla
la propiedad de ordenamiento. Su lugar lo ocupa el menor de sus hijos.
Del árbol anterior se obtiene el montículo siguiente:
10
0 1 2 3 4 5 6 7 8 9
150 80 40 30 10 70 110 100 ok
150 80 40 30 10 70 110 100 ok 30 40
150 80 40 30 10 70 110 100 hundir
150 10 40 30 80 70 110 100 hundir
10 150 40 30 80 70 110 100 hundir 100 80 70 110
10 30 40 150 80 70 110 100 hundir
10 30 40 100 80 70 110 150 ok
150
Observe que no coincide exactamente con el que se obtuvo ordenando a medida que se insertaba, pero
es también válido.
Cuando restablecemos el montículo, después de introducir N nodos en el árbol vacío, debemos analizar
si se encuentran en la posición correcta los N/2 nodos que tienen hijos. Si alguno no lo está, debemos
hundirlo hasta encontrar su posición.
En el texto, pág. 564-568 se demuestra que ambos procesos (recorrer los N/2 nodos y hundir los que
sean necesario) tiene, en total, un costo lineal.
6
7
Eliminación
Encontrar el mínimo es sencillo: siempre está en la raíz del árbol, la parte más difícil es eliminarlo.
Cuando se retira el mínimo se crea un hueco en la raíz. Como el tamaño del montículo se reduce en
uno, la propiedad estructural obliga a que el último nodo (X) debe moverse a alguna otra parte. Si se
puede colocar X en el hueco habremos terminado -esto solo es posible si el tamaño del árbol es 2 ó 3,
¿por qué?- de lo contrario, se sube para el hueco el menor de sus dos hijos y el hueco se empuja hacia
abajo. Este paso se realiza hasta que X se pueda colocar en el hueco.
Ejemplo 2. Si en el montículo final del ejemplo 2 se hace una eliminación queda:
0 1 2 3 4 5 6 7 8 9 10 11
14 16 24 21 19 68 65 26 32 31
hueco 1; 31 > 14 y 31 > 16
65 26 32 31
0 1 2 3 4 5 6 7 8 9 10 11
14 16 24 21 19 68 65 26 32 31
hueco 2; 31 > 24 y 31 > 21
14
16
El 31 no se puede ubicar en
el hueco. Se sube el menor
de los hijos: 21
24 21 19 68
65 26 32 31
0 1 2 3 4 5 6 7 8 9 10 11
14 21 16 24 19 68 65 26 32 31
hueco 5; 31 < 32
7
8
14
65 26 32 31
0 1 2 3 4 5 6 7 8 9 10 11
14 21 16 24 31 19 68 65 26 32
14
21 16
24 31 19 68
65 26 32
Esta técnica se conoce como filtrado descendente o hundimiento. Es fácil ver que esta es una
operación logarítmica en el caso peor.
8
9
private Object [] vector; //el vector del montículo
private static final int CAPACIDAD = 11;//tamaño del vector con centinela
Constructor
public MonticuloBinario(Object infNeg) //infNeg un valor menor o igual q
todos los demás.
{
tamActual = 0;
ordenado = true;
obtenerVector(CAPACIDAD);
vector[ 0] = infNeg;
}
Reserva de memoria
private void obtenerVector(int nuevoTamMax)//reserva memoria para el vector
{
vector=new Object[nuevoTamMax+1];// una posición extra para el centinela
}
9
10
{
ordenado = false; // se ha introducido un elemento sin ordenar
}
}
10
11
private void hundir(int hueco) {
//coloca el elemento que está en la posición hueco en el lugar
//correcto dentro del montículo, empujándolo hacia abajo
int hijo;
Object tmp = vector[hueco];
for (; hueco * 2 <= tamActual; hueco = hijo) {
hijo = hueco * 2;
E x = (E) vector[hijo + 1];
if (hijo != tamActual && x.compareTo((E) vector[hijo]) < 0) {
hijo++;
}
E y = (E) vector[hijo];
if (y.compareTo((E) tmp) < 0) {
vector[hueco] = vector[hijo];
} else {
break;
}
}
vector[hueco] = tmp;
}
CONCLUSIONES
Hemos estudiado los Montículos, un tipo de árbol con propiedades de estructura y ordenación que
permiten implementar el TDA Cola de Prioridad.
- Se puede implementar usando un simple vector.
- Soporta insertar y eliminarMin en tiempo logarítmico respecto al número de elementos
existentes
- Se puede lograr un tiempo lineal al introducir un conjunto de elementos antes de realizar
alguna operación de acceso.
11