Tabla de contenidos
Instruir al alumno en la utilización de árboles binarios de búsqueda.
Un árbol binario de búsqueda es un árbol binario ordenado de modo que para cada nodo n, todas las claves de los nodos del subárbol izquierdo son menores que la clave de n y todas las del subárbol derecho son mayores (supondremos que no existen repeticiones).
En esta práctica implementaremos un árbol binario de búsqueda mediante las clases BSTree
(que representa el árbol) y la clase auxiliar BSNode
, de modo que:
Un árbol BSTree
contiene la referencia a su nodo raíz.
Un nodo BSNode
contiene la clave de comparación, la información a almacenar y las referencias a los nodos que forman los subárboles izquierdo y derecho.
Tal como se representa en la figura siguiente:
Observa que con esta estructura, un árbol vacío es aquél que tiene su nodo raíz a null
(sin embargo, el objeto árbol en sí no es null
). A continuación, implementa:
Define los atributos de las clases BSTree
y BSNode
, de acuerdo a la estructura representada en la figura anterior.
Proporciona un constructor, sin parámetros, para la clase BSTree
que inicialice un árbol vacío (es decir, con la raíz igual a null
).Proporciona un constructor para la clase auxiliar BSNode
. Este constructor debe recibir como parámetros la clave de búsqueda asociada ese nodo y la información a almacenar. Los subárboles hijos, izquierdo y derecho, del nodo se inicializarán a null
.
Para cada clase, proporciona los métodos get
y set
necesarios para acceder a sus atributos.
Programa el método boolean isEmpty()
que comprueba si el árbol está vacío.
Programa el método public Object search(Comparable key)
que devuelve el objeto almacenado en el nodo cuya clave asociada coincide con la que se pasa como parámetro.
Programa el método public void insert(Comparable key, Object info)
que introduce un nuevo dato en el árbol. Para ello, debe crear un nuevo nodo en el lugar apropiado, que contenga la clave y el objeto que se pasan como parámetros. Si el árbol ya contiene un nodo con la clave indicada, se sobreescribe la información almacenada en el objeto.
Programa el método void printPreOrden()
que presenta por salida estándar todas las entradas del árbol, siguiendo el recorrido pre-orden.
Ahora que ya tienes experiencia en el manejo de árboles vamos a implementar una agenda de direcciones utilizando un árbol binario de búsqueda para facilitar las búsquedas:
Programa la clase DirectoryEntry
que almacena los datos asociados a cada entrada de la agenda: Nombre, Apellidos, Dirección, Teléfono, Móvil and Correo electrónico.
Programa la clase Directory
, que utiliza un árbol binario de búsqueda para almacenar los datos de una agenda electrónica. La clave de búsqueda por la que se ordenan los datos de la agenda es el nombre completo, según el formato: Apellidos, Nombre
Agrega a la clase Directory
el método void insert(DirectoryEntry data)
que introduce los datos que se pasan como parámetros en la agenda. Recuerda que debes concatenar los apellidos y el nombre para generar la clave de búsqueda asociada. Agrega a la clase Directory
el método void insert()
que introduce una nueva entrada en la agenda con los datos que se introduzcan por teclado.
Agrega a la clase Directory
el método DirectoryEntry search(String fullName)
que permite recuperar los datos de una determinada entrada a partir del nombre completo (apellidos y nombre).
Programa el método void printInOrder()
que presenta el contenido de la agenda por salida estándar, en orden alfabético (recorrido in-orden).
Instruir al alumno en la implementación y utilización de árboles con un número variable de hijos.
En este ejercicio se utilizará la definición recursiva de árbol para diseñar una clase Java que implemente un árbol N-ario.
Recuerda que se necesitan dos clases, NTree y NNode, para poder modelar adecuadamente el árbol vacío.
Crea la clase NTree
basándote en la definición recursiva anterior. Programa un constructor sin parámetros que inicialice un árbol vacío. Programa un constructor adicional que reciba como parámetro el elemento (de la clase Object
) a almacenar en el nodo raíz.
Crea la clase NNode
basándote en la definición recursiva anterior, que encapsule la información a almacenar en el nodo y un Vector
con las referencias a los hijos. Programa un constructor que reciba como parámetro el elemento (de la clase Object
) a almacenar en el nodo. Programa también los correspondientes métodos accesores (get y set).
Programa el método size
que devuelve el tamaño (número de nodos) del árbol.
Programa el método preorder
que imprime los elementos del árbol siguiendo un recorrido preorden.
Necesitarás métodos auxiliares para construir y recorrer el árbol, como int getNumberOfChildren()
, void addChild(NNode child)
, void addChildAt(int index, NNode child)
, NNode getChildAt(int index)
, y otros.
Profundizar en el manejo de árboles mediante montículos binarios, implementando una cola de prioridad.
Un montículo binario es un árbol binario completo ordenado de modo que el dato almacenado en un determinado nodo es menor que los datos almacenados en sus hijos. Esta característica hace que podamos implementarlo usando un vector, garantizando que su profundidad es logarítmica.
Al ser un árbol binario completo puede representarse fácilmente de
forma lineal, almacenando su recorrido por niveles (por ejemplo en un
Vector
). Dado un elemento situado en la posición
i
, su hijo izquierdo estará situado en la posición
2i+1
y su hijo derecho en la posición
2i+2
. De forma simétrica, su padre estará en la posición
(i-1)/2
. Observa que necesitamos tener también el número de elementos del
árbol para comprobar si un nodo existe antes de intentar acceder a su
posición. Observe la siguiente figura:
Teniendo en cuenta la descripción anterior:
Programa la clase
MonticuloBinario
que representa un montículo binario, tal como se ha definido
previamente, utilizando internamente un
Vector
para almacenar los datos. Define los atributos de la clase y
proporciona un constructor que inicializa un montículo vacío (no
recibe parámetros). Observa que cada elemento del montículo
almacena dos datos: la prioridad del elemento y el objeto
almacenado. Programa la clase auxiliar
ElementoColaPrioridad
que encapsula esta información
Agrega a la clase
MonticuloBinario
los siguientes métodos:
int tamaño();
, que devuelve el número de elementos y
boolean isVacio();
, que indica si está vacío el árbol
Agrega a la clase
MonticuloBinario
el método
void insertar(int prioridad, Object info);
que almacena un nuevo el elemento manteniendo las propiedades de
estructura y orden del montículo.
Para insertar un elemento
X
en el montículo, primero debemos añadir un nuevo nodo al árbol.
Este nodo debe agregarse al final de la estructura para garantizar
que continúa siendo completo. Una vez agregado el nuevo elemento en
la última posición, hay que comprobar que se mantiene la propiedad
de ordenación del montículo. Para ello, hay que comprobar si el
nuevo hijo es mayor que su padre. En ese caso, el montículo está
ordenado y termina el proceso. Si el nuevo dato es menor que el
almacenado en el nodo padre, hay que intercambiarlos y repetir el
proceso de comprobación hasta que el dato llegue a un nodo en que
se cumpla la propiedad del montículo (padre menor o igual que hijo)
o bien a la raíz.
Agrega a la clase
MonticuloBinario
el método
Object extraer();
que devuelve el objeto almacenado en la raíz, es decir, con mayor
prioridad, y reorganiza el montículo de modo que se mantengan sus
propiedades de estructura y orden. Recuerda que las prioridades más
altas se corresponden con los valores más pequeños, siendo 1 la
prioridad máxima.
Devolver el objeto buscado es trivial, puesto que las propiedades del montículo garantizan que se encuentra en la raíz.
La dificultad de este método consiste en reorganizar posteriormente la estructura, puesto que el nodo raíz puede tener dos hijos. Para ello hay que realizar el proceso inverso al "reflotamiento" realizado al insertar, "hundiendo" el "hueco" generado hasta la posición adecuada.
Para ocupar el lugar que ha quedado vacío, hay que seleccionar el menor de los dos hijos del nodo (si existen). El elemento hijo pasa a ocupar la posición del padre, de modo que el hueco baja un nivel. El proceso se repite sucesivamente hasta llegar a una hoja (un nodo que no tenga hijos).
El árbol debe seguir siendo completo, de modo que el último elemento pasa a la posición en que ha quedado el hueco.
Agrega a la clase
MonticuloBinario
el método
print()
, que imprime por pantalla los elementos almacenados junto con su
prioridad. Los elementos deben presentarse en orden según su
posición en el montículo.
Un nodo
NodoBB
contiene la clave de comparación, la información a almacenar y las
referencias a los subárboles izquierdo y derecho.
Modifica la definición de la clase
MonticuloBinario
de modo que implemente el interfaz
ColaConPrioridad
.
interface PriorityQueue {
static int MAX_PRIORITY = 1;
/**
* Insert the indicated object in the correct position in the queue, depending
* on its priority. Priority is declared with a natural number (> 0). The
* bigger is this number, the lower is the element priority associated with
* the number. The value 1 corresponds to elements with maximum priority.
*/
void insert(int priority, Object info);
/**
* Extract the object with highest priority out of the queue (that is, the one
* with the minimum priority) In case that several elements have the same high
* priority, the first introduced (FIFO) will be returned. If queue is empty,
* null is returned.
*/
Object extract();
/**
* Returns the number of elements in the queue.
*/
int size();
/**
* Indicates if the queue is empty.
*/
boolean isEmpty();
}
Utilizando la clase
MonticuloBinario
desarrollada en los apartados anteriores, completa la clase
GestorColaImpresion
que gestiona los trabajos a imprimir mediante una cola con
prioridad:
public class PrintQueueManager {
PriorityQueue queue;
/**
* Initialize the manager, empty.
*/
public PrintQueueManager() {
// to be completed by the student
}
/**
* Adds a new work to print, with the indicated priority. *
*/
public void print(int priority, String file) {
// to be completed by the student
}
/**
* Processes (prints out of the printer) the first work of the queue, with the
* highest priority, reorganizing the data structure.
*/
public void process() {
// to be completed by the student
}
}
Observa que el atributo que almacena la cola se declara de tipo
interfaz
ColaConPrioridad
. La justificación es que el código sea lo más genérico posible. De
este modo, si en algún momento se decide utilizar otra
implementación de la cola en vez de un montículo, los cambios serán
mínimos. Mantén esta filosofía en el código que desarrolles
utilizando siempre que sea posible el tipo definido por el interfaz
y evitando (en la medida de lo posible) las referencias a la clase
que lo implementa.