06BST

Anuncio
BSTs
Definición
Un árbol binario de búsqueda (binary sort tree, BST) es una estructura de datos basada en nodos
que tiene las siguientes propiedades:
 El subárbol izquierdo de un nodo sólo contienen nodos con claves menores que la clave
del nodo.
 El subárbol derecho de un nodo sólo contienen nodos con claves mayores que la clave del
nodo.
 Tanto el subárbol derecho como el izquierdo tienen que ser también arboles binarios de
búsqueda.
Datos a almacenar
Generalmente, la información que se inserta en cada nodo es un registro en vez de un único
elemento de datos. Esta estructura suele estar compuesta de una clave y de un elemento en sí
mismo. La clave se utiliza para comparar y por tanto, tiene que se posible dicha comparación.
En el caso del lenguaje de programación java, un nodo de un BST sigue la siguiente definición
public class NodoBST {
/**
* Atributo que representa la clave de un Nodo de un BST
*/
private Comparable clave;
/**
* Atributo que representa el objeto del nodo de un BST
*/
private Object elemento;
/**
* El hijo derecho de un nodo
*/
private NodoBST izquierdo;
/**
* El hijo derecho de un nodo
*/
private NodoBST derecho;
/**
* Constructor de la clase nodo
*
* @param clave
*
La clave del nodo
* @param elemento
*
El elemento del nodo
*/
public NodoBST(Comparable clave, Object elemento) {
this.clave = clave;
this.elemento = elemento;
}
/**
* Método que devuelve la clave de un nodo
*
* @return La clave del nodo
*/
public Comparable getClave() {
return clave;
}
/**
* Método que modifica la clave de un nodo
*
* @param clave
*
La nuevo clave del nodo
*/
public void setClave(Comparable clave) {
this.clave = clave;
}
/**
* Método que devuelve el elemento de un nodo
*
* @return El elemento de un nodo
*/
public Object getElemento() {
return elemento;
}
/**
* Método que que modifica el elemento del nodo
*
* @param elemento
*
El nuevo elemento del nodo
*/
public void setElemento(Object elemento) {
this.elemento = elemento;
}
/**
* Método que devuelve el hijo izquierdo de un nodo
*
* @return El hijo izquierdo del nodo
*/
public NodoBST getIzquierdo() {
return izquierdo;
}
/**
* Método que modifica el hijo izquierdo de un nodo
*
* @param izquierdo
*
El nuevo hijo izquierdo del nodo
*/
public void setIzquierdo(NodoBST izquierdo) {
this.izquierdo = izquierdo;
}
/**
* Método que devuelve el hijo derecho de un nodo
*
* @return El hijo derecho del nodo
*/
public NodoBST getDerecho() {
return derecho;
}
/**
* Método que modifica el hijo derecho de un nodo
*
* @param izquierdo
*
El nuevo hijo derecho del nodo
*/
public void setDerecho(NodoBST derecho) {
this.derecho = derecho;
}
}
La clave es comparable que es una interfaz en el lenguaje java. Un objeto de tipo comparable
tiene que implementar el método compareTo que devuelve:
 >0 si el elemento a comparar es menor que “this”
 <0 si el elemento a comparar es mayor que “this”
 0 si los objetos son iguales.
Normalmente, las claves de los nodos BSTs son datos primitivos como int, doublé, etc, que
teniendo en cuenta las restricciones de java, han de ser representados por sus clases envoltorio,
Integer, Double, etc. Estos tipos de datos son comparables por definición y no es necesario
reimplementar su método compareTo.
Definición del tipo BST
Un BST se define por su nodo raíz y por nada más. A partir de ese nodo, se pueden realizar las
operaciones básicas del BST siempre comenzando con el nodo raíz.
public class BST {
/**
* Atributo que representa el elemento raiz del BST
*/
private NodoBST raiz;
/**
* Método que permite el acceso al nodo raiz
* del BST. Se utilizará para hacer el recorrido
* de un árbol desde un main
* @return El nodo raiz del BST
*/
public NodoBST getRaiz() {
return raiz;
}
Operaciones a realizar en un árbol
Se pueden realizar las siguientes operaciones en un árbol:
1. Inserción
2. Búsqueda
3. Eliminación
Inserción de un elemento en un árbol
Para insertar un elemento lo primero que se hace es buscar el sitio para insertar ese elemento. En
otras palabras, utilizando la comparación de claves se busca el sitio apropiado para el elemento,
que será aquél que sea nulo. Se comienza con la raíz, si el elemento es menor se continua con el
sub-árbol izquierdo y si es mayor con el subárbol derecho. Este proceso se repita hasta que se
encuentra un sub-árbol que sea nulo y, por consiguiente, el lugar en el que insertar el nodo
Por ejemplo, supongamos que tenemos el siguiente árbol:
Y queremos insertar el elemento con clave 4. El proceso comenzaría comparando la clave 4 con
la clave de la raíz que es 2 y, por tanto, menor que la clave de la raíz. Como la clave a insertar es
mayor que la raíz, comparamos con la raíz del sub-árbol derecho, esto es, 5. En este caso, la
clave a insertar es menor y, por lo tanto, procesamos el sub-árbol izquierdo. Comparamos la
clave 4 con la clave 3 y es mayor por lo que tenemos que procesar el sub-árbol derecho, que no
existe. Hemos encontrado la posición del elemento con clave 4.
El código java para realizar este proceso es el siguiente:
/**
* Método que inserta un elemento en el BST (requiere una clave)
Es un
* método concha
*
* @param clave
*
La clave del elemento
* @param elemento
*
El elemento en si mismo
*/
public void insertar(Comparable clave, Object elemento) {
// Devuelve false si se produce error por existir elemento
con la misma
// clave
// Llamada al método recursivo
this.raiz = insertarRec(this.raiz, clave, elemento);
}
/**
* Método recursivo que inserta un elemento en el BST (requiere
una clave)
*
* @param actual
*
El recorrido actual
* @param clave
*
La clave del elemento
* @param elemento
*
El elemento en si mismo
* @return false si se produce error por existir elemento con la
misma clave
*/
private NodoBST insertarRec(NodoBST actual, Comparable clave,
Object elemento) {
// Si hemos llegado a null es que tenemos un hueco para
insertar el nodo
// ES EL CASO BASE
if (actual == null) {
// Creamos el nodo
actual = new NodoBST(clave, elemento);
} else if (actual.getClave().compareTo(clave) < 0) {
// Si el recorrido actual es más pequeño que lo que
queremos
// insertar entonces lo insertaremos por
// su hijo derecho
actual.setDerecho
(insertarRec(actual.getDerecho(), clave, elemento));
} else {
// Si el recorrido actual es más grande que lo que
queremos insertar
// entonces lo insertaremos por
// su hijo izquierdo
actual.setIzquierdo
(insertarRec(actual.getIzquierdo(), clave,
elemento));
}
return actual;
}
Búsqueda de un elemento dentro de un árbol
Para buscar un elemento lo primero que se hace es buscar su sitio para recuperar ese elemento.
En otras palabras, utilizando la comparación de claves se busca el elemento. Se comienza con la
raíz, si el elemento es menor se continua con el sub-árbol izquierdo y si es mayor con el subárbol
derecho. Este proceso se repita hasta que se encuentra un elemento con clave igual. En ese caso
se recupera el elemento del nodo.
Por ejemplo, supongamos que tenemos el siguiente árbol:
Y queremos buscar el elemento con clave 4. El proceso comenzaría comparando la clave 4 con la
clave de la raíz que es 2 y, por tanto, menor que la clave de la raíz. Como la clave a insertar es
mayor que la raíz, comparamos con la raíz del sub-árbol derecho, esto es, 5. En este caso, la
clave a insertar es menor y, por lo tanto, procesamos el sub-árbol izquierdo. Comparamos la
clave 4 con la clave 3 y es mayor por lo que tenemos que procesar el sub-árbol derecho, que
contiene el elemento con clave 4.
El código java para realizar esta operación es el siguiente
/**
* Método que devuelve un objeto dada la clave
*
* @param clave
*
La clave del objeto a buscar
* @return El objeto de la clave
*/
public Object get(Comparable clave) {
// Llamada al método recursivo
return getRec(this.raiz, clave);
}
/**
* Método que devuelve un objeto dada la clave.
*
* @param actual
*
El recorrido actual
* @param clave
*
La clave del objeto a buscar
* @return El objeto de la clave
*/
private Object getRec(NodoBST actual, Comparable clave) {
// Si hemos llegado a null es que no existe ese elemento
if (actual == null) {
// devolvemos null
return null;
}
// Si no es nulo
else {
// Comprobamos la comparación
int comp = clave.compareTo(actual.getClave());
// Si son iguales es que lo hemos encontrado
if (comp == 0) {
// devolvemos el objeto del nodo
return actual.getElemento();
} else if (comp > 0) {
// Si el recorrido actual es más pequeño que
lo que queremos
// encontrar entonces lo buscaremos por
// su hijo derecho
return getRec(actual.getDerecho(), clave);
} else {
// Si el recorrido actual es más grande que lo
que queremos
// encontrar
// entonces lo buscaremos por
// su hijo izquierdo
return getRec(actual.getIzquierdo(), clave);
}
}
}
/**
* Método que devuelve un objeto dada la clave
*
* @param clave
*
La clave del objeto a buscar
* @return El objeto de la clave
*/
public Object getIter(Comparable obj) {
// Empezamos por la raiz
NodoBST act = raiz;
// Hasta el fin del árbol
while (act != null) {
// Comprobamos la comparación
int comp = obj.compareTo(act.getClave());
// Si son iguales lo devolvemos
if (comp == 0) {
return act.getElemento();
}
// Si es menor vamos por la rama izquierda
else if (comp < 0) {
act = act.getIzquierdo();
}
// Si es mayor vamos por la rama derecha
else {
act = act.getDerecho();
}
}
return null;
}
El método devolverá el objeto con la clave que se busca o null si ese elemento no existe en el
BST.
Borrado de un elemento dentro de un árbol
Para borrar un elemento lo primero que se hace es buscar su sitio para borrar ese elemento. Se
utiliza la comparación de claves se busca el elemento. Se comienza con la raíz, si el elemento es
menor se continua con el sub-árbol izquierdo y si es mayor con el sub-árbol derecho. Una vez
encontrado el elemento podemos encontrar 3 diferentes opciones:
1. Que el nodo no tenga hijos:
En este caso eliminamos el nodo sin más operaciones
2. Que el nodo tenga sólo un hijo:
En este caso se elimina el nodo y el hijo ocupa su lugar
3. Que tenga ambos hijos:
El nodo con el que sustituye el nodo a eliminar puede ser el nodo menor del sub-árbol
derecho o el nodo mayor del sub-árbol izquierdo
El código java para esta operación es el siguiente.
/**
* Método para borrar un elemento de un árbol
*
* @param clave
*
La clave del elemento a borrar
* @return Devuelve si lo ha borrado o no
*/
public void borrar(Comparable clave) {
// Llamada al método recursivo
this.raiz = borrarRec(this.raiz, clave);
}
/**
* Método para borrar un elemento de un árbol
*
* @param actual
*
El recorrido actual
* @param clave
*
La clave del elemento a borrar * @return Devuelve
si lo ha
*
borrado o no
*/
private NodoBST borrarRec(NodoBST actual, Comparable clave) {
// Obtenemos el valor de la comparación
int comp = clave.compareTo(actual.getClave());
// Si es mayor vamos por la derecha
if (comp > 0) {
actual.setDerecho(borrarRec(actual.getDerecho(),
clave));
}
// Si es menor vamos por la izquierda
else if (comp < 0) {
actual.setIzquierdo(borrarRec(actual.getIzquierdo(),
clave));
}
// Si es igual lo borramos
else {
// Si no tiene hijos
if (actual.getIzquierdo() == null
&& actual.getDerecho() == null) {
actual = null;
}
// Si tiene hijo izquierdo
else if (actual.getDerecho() == null) {
actual = actual.getIzquierdo();
}
// Si tiene hijo derecho
else if (actual.getIzquierdo() == null) {
actual = actual.getDerecho();
}
// Si tiene ambos hijos
else {
// Si el sucesor está enseguida
if (actual.getDerecho().getIzquierdo() ==
null) {
actual.getDerecho().setIzquierdo(actual.getIzquierdo());
actual = actual.getDerecho();
} else {
// Si no recorremos por la rama
izquierda del hijo derecho
// para encontrar el más a la izquierda
// Además, guardamos su padre para hacer
las modificaciones
// pertinentes
NodoBST sucesor, anteriorSucesor =
actual.getDerecho();
while
(anteriorSucesor.getIzquierdo().getIzquierdo() != null) {
anteriorSucesor =
anteriorSucesor.getIzquierdo();
}
sucesor =
anteriorSucesor.getIzquierdo();
anteriorSucesor.setIzquierdo(sucesor.getDerecho());
anteriorSucesor.setIzquierdo(actual.getIzquierdo());
sucesor.setDerecho(actual.getDerecho());
actual = sucesor;
}
}
}
return actual;
}
Recorrido por los elementos de un árbol
Además de las operaciones que se han mencionado, se puede recorrer un árbol para realizar un
proceso con cada uno de los elementos del mismo.
En concreto, existen 3 tipos de recorridos que vamos a ver:
 Recorrido en pre-orden: Se realiza el proceso de un nodo y luego se visita el hijo
izquierdo y el hijo derecho. Código java:
/**
* Método que recorre en preorden un árbol
*/
public void recorrerPreOrder() {
this.recorrerPreOrderRec(this.raiz);
}
/**
* Método que recorre en preorden un árbol
*
* @param nodoRecorrido
*
El nodo actual
*/
private void recorrerPreOrderRec(NodoBST nodoRecorrido) {
if (nodoRecorrido != null) {
System.out.println(nodoRecorrido.getElemento());
recorrerPreOrderRec(nodoRecorrido.getIzquierdo());
recorrerPreOrderRec(nodoRecorrido.getDerecho());
}
}
 Recorrido en post-orden: Se visita el hijo izquierdo y el hijo derecho y luego se procesa el
nodo. Código java:
/** Método que recorre en postorden un árbol
*/
public void recorrerPostOrder() {
this.recorrerPostOrderRec(this.raiz);
}
/**
* Método que recorre en postorden un árbol
*
* @param nodoRecorrido
*
El nodo actual
*/
private void recorrerPostOrderRec(NodoBST nodoRecorrido) {
if (nodoRecorrido != null) {
recorrerPostOrderRec(nodoRecorrido.getIzquierdo());
recorrerPostOrderRec(nodoRecorrido.getDerecho());
System.out.println(nodoRecorrido.getElemento());
}
}
 Recorrido en in-orden: Se visita el hijo izquierdo, luego se procesa el nodo y, finalmente,
se procesa el hijo derecho. Código java:
/**
* Método que recorre en orden un árbol
*/
public void recorrerInOrder() {
this.recorrerInOrderRec(this.raiz);
}
/**
* Método recursivo que recorre en orden un árbol
*
* @param nodoRecorrido
*
El nodo actual
*/
private void recorrerInOrderRec(NodoBST nodoRecorrido) {
if (nodoRecorrido != null) {
recorrerInOrderRec(nodoRecorrido.getIzquierdo());
System.out.println(nodoRecorrido.getElemento());
recorrerInOrderRec(nodoRecorrido.getDerecho());
}
}
Descargar