Professional Documents
Culture Documents
50
30
70
10 a
40
60
90
35
45
38
rbol binario de bsqueda En Java podemos automatizar el proceso de implementar un rbol binario de bsqueda diseando una clase CArbolBinB (Clase rbol Binario de Bsqueda) que proporcione los atributos y mtodos necesarios para crear cada nodo del rbol, as
Curso: Estructuras de datos Docente : Ing. Hctor Fiestas Bancayn
como para permitir el acceso a los mismos. La clase que diseamos a continuacin cubre estos objetivos.
La Clase CArbolBinB
La clase CArbolBinB que vamos a implementar incluir un atributo protegido raz para referenciar la raz del rbol y cuatro constantes pblicas relacionadas con los posibles errores que se pueden dar relativos a un nodo: CORRECTO, NO_DATOS, YA_EXISTE y NO_EXISTE. El atributo raz valdr null cuando el rbol est vaco. Asimismo, incluye una clase interna CNodo, que definir la estructura de los nodos y los mtodos indicados en la tabla siguiente: Mtodo
CArbooBinB insertar
Significado
Es el constructor. Crea un rbol vaco (null) Inserta un nodo en el rbol binario, basndose en la definicin de rbol binario de bsqueda. Tiene un parmetro que es una referencia de tipo Object al objeto a aadir. Devuelve un 0, definido por la constante CORRECTO, si la operacin de insercin se realiza satisfactoriamente: o un valor distinto de 0, definido por alguna de las constantes siguientes NO_DATOS si la raz es igual a null, o YA_EXISTE si el nodo con esos datos ya existe. Borra un nodo de un rbol binario de bsqueda. Tiene un parmetro para almacenar una referencia de tipo Object a los datos que permitirn localizar en el rbol el nodo que se desea borrar. Devuelve una referencia al rea de datos del nodo borrado o null si el rbol est vaco o no existe un nodo con esos datos. Busca un nodo determinado en el rbol. Tiene un parmetro para almacenar una referencia de tipo Object a los datos que permitirn localizar el nodo en el rbol. Devuelve una referencia al rea de datos del nodo null si el rbol est vaco o no existe un nodo con esos datos. Recorre un rbol binario utilizando la forma inorden. Tiene dos parmetros: el primero especifica la referencia al nodo a partir del cual se realizar la visita; el valor del primer parmetro slo ser tenido en cuenta si el segundo es false, porque si es true se asume que el primer parmetro es la raz del rbol. No devuelve ningn valor. Mtodo que debe ser definido por el usuario en una subclase para especificar el tipo de comparacin que e desea realizar con dos nodos del rbol. Debe de devolver un entero indicando el resultado de la comparacin (-1, 0 1 si nodo1<nodo2 , nodo1==nodo2 o nodo1>nodo2, respectivamente). Este mtodo es invocado por los mtodos insertar, borrar y buscar. Mtodo que debe ser definido por el usuario en una subclase Docente : Ing. Hctor Fiestas Bancayn
borrar
borrar
inorden
comparar
proceso
para especificar las operaciones que se desean realizar con el nodo visitado. Es invocado por el mtodo inorden. visitarInorden Mtodo sin parmetros que debe ser redefinido por el usuario en una subclase para invocar al mtodo inorden.
A continuacin se presenta el cdigo correspondiente a la definicin de la clase CArbolBinB: ///////////////////////////////////////////////////////////////// // Clase abstracta: rbol binario de bsqueda. Para utilizar los // mtodos proporcionados por esta clase, tendremos que crear // una subclase de ella y redefinir los mtodos: // comparar, procesar y visitar Inorden. // public abstract class CArbolBinB { // Atributos del rbol binario protected CNodo raz = null; // raz del rbol // Nodo de un rbol binario private class CNodo { // Atributos private Object datos; // referencia a los datos private CNodo izquierdo; // raz del subrbol izquierdo private CNodo derecho; // raz del subrbol derecho // Mtodos public CNodo() {} // constructor
// Posibles errores que se pueden dar relativos a un nodo public static final int CORRECTO = 000; public static final int NO_DATOS = 100; public static final int YA_EXISTE = 101; public static final int NO_EXISTE = 102; // Mtodos del rbol binario public CArbolBinB() {} // constructor // El mtodo siguiente debe ser redefinido en una subclase para // que permita comparar dos nodos del rbol por el atributo // que necesitemos en cada momento. public abstract int comparar(Object obj1, Object obj2); // El mtodo siguiente debe ser redefinido en una subclase para // que permita especificar las operaciones que se deseen // realizar con el nodo visitado.
Curso: Estructuras de datos Docente : Ing. Hctor Fiestas Bancayn
public abstract void procesar(Object obj); // El mtodo siguiente debe ser redefinido en una subclase para // que invoque a insertar con los argumentos deseados. public abstract void visitarInorden(); public Object buscar(Object obj) { // El mtodo buscar permite acceder a un determinado nodo. CNodo actual = raz; int nComp = 0; // Buscar un nodo que tenga asociados los datos dados por obj while ( actual != null ) { if (( nComp = comparar( obj, actual.datos )) == 0) return( actual.datos ); // CORRECTO (nodo encontrado) else if ( nComp < 0 ) // buscar en el subrbol izquierdo actual = actual.izquierdo; else // buscar en el subrbol derecho actual = actual.derecho; } return null; // NO_EXISTE } public int insertar(Object obj) { // El mtodo insertar permite aadir un nodo que an no est // en el rbol. CNodo ltimo = null, actual = raz; int nComp = 0; if ( obj == null ) return NO_DATOS; // Comienza la bsqueda para verificar si ya hay un nodo con // estos datos en el rbol while (actual != null) { if ((nComp = comparar( obj, actual.datos )) == 0) break; // se encontr el nodo else { ltimo = actual; if ( nComp < 0 ) // buscar en el subrbol izquierdo actual = actual.izquierdo; else // buscar en el subrbol derecho actual = actual.derecho; } }
Curso: Estructuras de datos Docente : Ing. Hctor Fiestas Bancayn
if ( actual == null ) // no se encontr el nodo, aadirlo { CNodo nuevoNodo = new CNodo(); nuevoNodo.datos = obj; nuevoNodo.izquierdo = nuevoNodo.derecho = null; // El nodo a aadir pasar a ser la raz del rbol total si // ste est vaco, del subrbol izquierdo de "ltimo" si la // comparacin fue menor, o del subrbol derecho de "ltimo" si // la comparacin fue mayor. if ( ltimo == null ) // rbol vaco raz = nuevoNodo; else if ( nComp < 0 ) ltimo.izquierdo = nuevoNodo; else ltimo.derecho = nuevoNodo; return CORRECTO; } // fin del bloque if ( actual == null ) else // el nodo ya existe en el rbol return YA_EXISTE;
public Object borrar(Object obj) { // El mtodo borrar permite eliminar un nodo del rbol. CNodo ltimo = null, actual = raz; CNodo marcado = null, sucesor = null; int nAnteriorComp = 0, nComp = 0; if (obj == null) return null; // NO_DATOS // Comienza la bsqueda para verificar si hay un nodo con // estos datos en el rbol. while ( actual != null ) { nAnteriorComp = nComp; // resultado de la comparacin anterior if (( nComp = comparar( obj, actual.datos )) == 0) break; // se encontr el nodo else { ltimo = actual; if ( nComp < 0 ) // buscar en el subrbol izquierdo actual = actual.izquierdo; else // buscar en el subrbol derecho actual = actual.derecho; } } // fin del bloque while ( actual != null )
if ( actual != null ) // se encontr el nodo { marcado = actual; if (( actual.izquierdo == null && actual.derecho == null )) // se trata de un nodo terminal (no tiene descendientes) sucesor = null; else if ( actual.izquierdo == null ) // nodo sin subrbol izquierdo sucesor = actual.derecho; else if ( actual.derecho == null ) // nodo sin subrbol derecho sucesor = actual.izquierdo; else // nodo con subrbol izquierdo y derecho { // Referencia al subrbol derecho del nodo a borrar sucesor = actual = actual.derecho; // Descender al nodo ms a la izquierda en el subrbol // derecho de este nodo (el de valor ms pequeo) // y hacer que el subrbol izquierdo del nodo a // borrar sea ahora el subrbol izquierdo de este nodo. while ( actual.izquierdo != null ) actual = actual.izquierdo; actual.izquierdo = marcado.izquierdo; } // Eliminar el nodo y rehacer los enlaces if ( ltimo != null ) { if ( nAnteriorComp < 0 ) ltimo.izquierdo = sucesor; else ltimo.derecho = sucesor; } else raz = sucesor; return marcado.datos;; // CORRECTO // "marcado" ser enviado a la basura
public void inorden( CNodo r , boolean nodoRaz) { // El mtodo recursivo inorden visita los nodos del rbol // utilizando la forma inorden; esto es, primero se visita // el subrbol izquierdo, despus se visita la raz, y por // ltimo, el subrbol derecho. // Si el segundo parmetro es true, la visita comienza // en la raz independientemente del primer parmetro.
Curso: Estructuras de datos Docente : Ing. Hctor Fiestas Bancayn
CNodo actual = null; if ( nodoRaz ) actual = raz; // partir de la raz else actual = r; // partir de un nodo cualquiera if ( actual != null ) { inorden( actual.izquierdo, false ); // visitar subrbol izq. // Procesar los datos del nodo visitado procesar( actual.datos ); inorden( actual.derecho, false ); // visitar subrbol dcho. } } } /////////////////////////////////////////////////////////////////
// El mtodo buscar permite acceder a un determinado nodo. CNodo actual = raz; int nComp = 0; // Buscar un nodo que tenga asociados los datos dados por obj while ( actual != null ) { if (( nComp = comparar( obj, actual.datos )) == 0) return( actual.datos ); // CORRECTO (nodo encontrado) else if ( nComp < 0 ) // buscar en el subrbol izquierdo actual = actual.izquierdo; else // buscar en el subrbol derecho actual = actual.derecho; } return null; // NO_EXISTE
while (actual != null) { if ((nComp = comparar( obj, actual.datos )) == 0) break; // se encontr el nodo else { ltimo = actual; if ( nComp < 0 ) // buscar en el subrbol izquierdo actual = actual.izquierdo; else // buscar en el subrbol derecho actual = actual.derecho; } } if ( actual == null ) // no se encontr el nodo, aadirlo { CNodo nuevoNodo = new CNodo(); nuevoNodo.datos = obj; nuevoNodo.izquierdo = nuevoNodo.derecho = null; // El nodo a aadir pasar a ser la raz del rbol total si // ste est vaco, del subrbol izquierdo de "ltimo" si la // comparacin fue menor, o del subrbol derecho de "ltimo" si // la comparacin fue mayor. if ( ltimo == null ) // rbol vaco raz = nuevoNodo; else if ( nComp < 0 ) ltimo.izquierdo = nuevoNodo; else ltimo.derecho = nuevoNodo; return CORRECTO; } // fin del bloque if ( actual == null ) else // el nodo ya existe en el rbol return YA_EXISTE;
derecha (13) en el subrbol izquierdo(13) , y si tomamos como sucesor la raz de su subrbol derecho (21), su subrbol izquierdo (13) lo ser ahora del nodo ms a la izquierda (18) en el subrbol derecho (21).
Antes Marcado
Despus En el ejemplo que muestra la figura anterior, se desciende por el rbol hasta encontrar el nodo a borrar (17). La variable actual representa la raz del subrbol en el que contina la bsqueda; inicialmente su valor es raz. La variable marcado
Curso: Estructuras de datos Docente : Ing. Hctor Fiestas Bancayn
apunta al nodo a borrar una vez localizado, el cual es sustituido por su sucesor a la derecha (21) pasando su subrbol izquierdo (13) a serlo ahora del nodo ms a la izquierda (18) en el subrbol derecho (21). Cuando finalice, la ejecucin del mtodo, el nodo que queda referenciado por marcado ser enviado a la basura, ya que marcado es una variable local y desaparecer. El proceso detallado se presenta a continuacin y contempla los casos mencionados: 1. 2. 3. 4. El nodo a borrar es un nodo terminal ; no tiene descendientes. El nodo a borrar no tiene subrbol izquierdo. El nodo a borrar no tiene subrbol derecho. El nodo a borrar tiene subrbol izquierdo y derecho.
Resumiendo, el mtodo borrar, primero localiza el nodo a borrar y lo marca (queda referenciado por marcado). Despus analiza si ste tiene descendientes y cuntos; en funcin de esto obtiene su sucesor (queda referenciado por sucesor). Finalmente, rehace los enlaces dejando fuera el nodo marcado para borrar. Dicho nodo ser enviado a la basura en el instante en que finaliza el mtodo borrar. public Object borrar(Object obj) { // El mtodo borrar permite eliminar un nodo del rbol. CNodo ltimo = null, actual = raz; CNodo marcado = null, sucesor = null; int nAnteriorComp = 0, nComp = 0; if (obj == null) return null; // NO_DATOS // Comienza la bsqueda para verificar si hay un nodo con // estos datos en el rbol. while ( actual != null ) { nAnteriorComp = nComp; // resultado de la comparacin anterior if (( nComp = comparar( obj, actual.datos )) == 0) break; // se encontr el nodo else { ltimo = actual; if ( nComp < 0 ) // buscar en el subrbol izquierdo actual = actual.izquierdo; else // buscar en el subrbol derecho actual = actual.derecho; } } // fin del bloque while ( actual != null ) if ( actual != null ) // se encontr el nodo { marcado = actual; if (( actual.izquierdo == null && actual.derecho == null )) // se trata de un nodo terminal (no tiene descendientes) sucesor = null;
Curso: Estructuras de datos Docente : Ing. Hctor Fiestas Bancayn
else if ( actual.izquierdo == null ) // nodo sin subrbol izquierdo sucesor = actual.derecho; else if ( actual.derecho == null ) // nodo sin subrbol derecho sucesor = actual.izquierdo; else // nodo con subrbol izquierdo y derecho { // Referencia al subrbol derecho del nodo a borrar sucesor = actual = actual.derecho; // Descender al nodo ms a la izquierda en el subrbol // derecho de este nodo (el de valor ms pequeo) y hacer // que el subrbol izquierdo del nodo a borrar sea ahora // el subrbol izquierdo de este nodo. while ( actual.izquierdo != null ) actual = actual.izquierdo; actual.izquierdo = marcado.izquierdo;
// Eliminar el nodo y rehacer los enlaces if ( ltimo != null ) { if ( nAnteriorComp < 0 ) ltimo.izquierdo = sucesor; else ltimo.derecho = sucesor; } else raz = sucesor; return marcado.datos;; // CORRECTO // "marcado" ser enviado a la basura
para construir el rbol. En nuestro ejemplo, vamos a ordenar los nodos del rbol por el atributo nombre de CDatos. Se trata entonces de una ordenacin alfabtica, por lo tanto, el mtodo comparar debe ser redefinido para que devuelva -1, 0 o 1 segn sea el nombre de un objeto CDatos menor, igual o mayor, respectivamente que el nombre del otro objeto con el que se compara. Pensemos ahora en el proceso que deseamos realizar con cada nodo accedido. En el ejemplo, simplemente nos limitaremos a mostrar los datos nombre y nota. Segn esto, el mtodo procesar obtendr los datos nombre y nota del objeto CDatos pasado como argumento y los mostrar. Finalmente, escribiremos el mtodo visitarInOrden para que permita recorrer en nuestro caso, el rbol en su totalidad. ///////////////////////////////////////////////////////////////// // Clase derivada de la clase abstracta CArbolBinB. Redefine los // mtodos: comparar, procesar y visitarInorden. // public class CArbolBinarioDeBusqueda extends CArbolBinB { // Permite comparar dos nodos del rbol por el atributo // nombre. public int comparar(Object obj1, Object obj2) { String str1 = new String(((CDatos)obj1).obtenerNombre()); String str2 = new String(((CDatos)obj2).obtenerNombre()); return str1.compareTo(str2); } // Permite mostrar los datos del nodo visitado. public void procesar(Object obj) { String nombre = new String(((CDatos)obj).obtenerNombre()); double nota = ((CDatos)obj).obtenerNota(); System.out.println(nombre + " " + nota); } // Visitar los nodos del rbol. public void visitarInorden() { // Si el segundo argumento es true, la visita comienza // en la raz independientemente del primer argumento. inorden(null, true); } } /////////////////////////////////////////////////////////////////
Ahora puede comprobar de una forma clara que los mtodos comparar, procesar y visitarInOrden dependen del tipo de objetos que almacenemos en el rbol que construyamos. Por esta razn no pudieron ser implementados en la clase CArbolBinB, sino que hay que implementarlos para cada caso particular. Observe que como los parmetros de los mtodos comparar y procesar son genricos, referencias de tipo Object, deben convertirse explcitamente en referencias a la clase de objetos que realmente representan; en nuestro caso a referencias a objetos de la clase CDatos, de lo contrario no tendremos acceso a los mtodos explcitos de esta clase. Cuando se declare un objeto de la clase CArbolBinarioDeBusqueda el constructor de esta clase invoca al constructor CArbolBinB de su clase base, que crear un rbol vaco (raz = null) El atributo raz apunta siempre a la raz del rbol. Finalmente, escribiremos una aplicacin Test, que utilizando la clase CArbolBinarioDeBusqueda, cree un objeto arbolb correspondiente a un rbol binario de bsqueda en el que cada nodo haga referencia a un objeto CDatos que encapsule el nombre de un alumno y la nota de una determinada asignatura cursando. Con el fin de probar que todos los mtodos proporcionados por la clase funcionan adecuadamente (piense en los mtodos heredados y en los redefinidos), la aplicacin realizar las operaciones siguientes: 1. Crear un objeto arbolb de la clase CArbolBinarioDeBusqueda. 2. Solicitar parejas de datos nombre y nota, a partir de las cuales construir los objetos CDatos que aadiremos como nodos en el arbolb. 3. Durante la construccin del rbol, permitir modificar la nota de un alumno ya existente o bien eliminarlo. Para discriminar una operacin de otra tomaremos como referencia la nueva nota; si es positiva, entenderemos que deseamos modificar la nota del alumno especificado y si es negativa, que hay que eliminarlo. 4. Finalmente, mostrar los datos almacenados en el rbol para comprobar que todo ha sucedido como esperbamos. ////////////////////////////////////////////////////////////////// // Crear un rbol binario de bsqueda // import java.util.*; public class Test { Scanner entrada = new Scanner(System.in); public static void main(String[] args) { CArbolBinarioDeBusqueda arbolbb = new CArbolBinarioDeBusqueda(); // Leer datos y aadirlos al rbol
Curso: Estructuras de datos Docente : Ing. Hctor Fiestas Bancayn
} System.out.println("\n");
String nombre; double nota; int i = 0, cod; System.out.println("Introducir datos. Finalizar con Ctrl+Z."); System.out.print("nombre: "); while ((nombre = entrada.next()) != null) { System.out.print("nota: "); nota = entrada.nextDouble(); cod = arbolbb.insertar(new CDatos(nombre, nota)); if (cod == CArbolBinarioDeBusqueda.YA_EXISTE) { // Si ya existe, distinguimos dos casos: // 1. nota nueva >= 0; cambiamos la nota // 2. nota nueva < 0; borramos el nodo if (nota >= 0) { CDatos datos = (CDatos)arbolbb.buscar(new CDatos(nombre, nota)); datos.asignarNota(nota); } else { arbolbb.borrar(new CDatos(nombre, nota)); System.out.println("nodo borrado"); } } System.out.print("nombre: ");