Escolar Documentos
Profissional Documentos
Cultura Documentos
Objetivo:
Dar una introducción a los árboles y mostrar la forma de implementar un árbol binario, ya sea en una
estructura estática (un vector) o dinámicamente (semejante a una lista ligada).
Descripción:
A lo largo de la siguiente práctica se mostrarán algunos conceptos útiles para comprender que es y para
que pudiera servir un árbol, además de que se mostrarán dos pequeños ejemplos que muestran algunos
ejemplos de utilidad para árboles binarios.
Nota:
El presente documento es una pequeña síntesis sobre árboles binarios. Se trata este
tema porque estos representan una de las estructuras de datos más importantes en
computación y estos pueden aplicarse para la solución de una gran variedad de
problemas, pero no muchas personas los ven como una buena solución puesto que los
consideran un tanto complejos. El objetivo del presente será dejar claro que es un
árbol binario y crear algunos ejemplos para tratar de tener una mayor comprensión.
Y así después cada uno de nosotros podamos definir si es conveniente aplicarlos a
la solución de algún problema o si alguna otra estructura de datos puede ser una
mejor opción. Puesto que solo veremos árboles las estructuras de datos alternativas
corren por su cuenta :-)
Desarrollo:
Conceptos y Terminología básica
Si vamos a trabajar con árboles, lo primero que tenemos que ver es que es un árbol. Aquí tenemos
algunas definiciones para árbol:
- Un árbol es una estructura de datos no lineal y homogénea en el que cada elemento puede
tener varios elementos posteriores, pero tan sólo puede tener un elemento anterior.
- Es una estructura jerárquica aplicada sobre una colección de elementos u objetos llamados
nodos; de los cuales uno es conocido como raíz. Además se crea una relación o parentesco entre
los nodos dando lugar a términos como padre, hijo, hermano, antecesor, sucesor, ancestro, etc.
- Un árbol es una estructura compuesta por un dato y varios árboles. Dado un nodo cualquiera de
la estructura, podemos considerarlo como una estructura independiente. Es decir, un nodo
cualquiera puede ser considerado como la raíz de un árbol completo.
- Son estructuras dinámicas no lineales. Dinámicas porque las estructuras de árbol pueden
cambiar durante la ejecución de un programa. No lineales, puesto que a cada elemento del árbol
pueden seguirle varios elementos. En las estructuras de datos lineales cada elemento tiene un
único elemento anterior y un único elemento posterior.
El tipo de estructura más general son los grafos. En un grafo cada elemento puede tener varios
elementos anteriores y varios elementos posteriores. Los árboles no son más que un tipo especial de
grafo en el que cada elemento puede tener varios elementos posteriores, pero tan sólo puede tener un
elemento anterior. Tanto grafos como árboles son estructuras no lineales.
Creo que la mayoría, sino es que todos, hemos utilizado los árboles cuando hemos hecho un árbol
genealógico o alguna estructura jerárquica de alguna organización. Y que me dicen de la forma en la
que organizamos la información en nuestras maquinas (se hace en una estructura de directorios y
subdirectorios en forma de árbol, para facilitar su búsqueda). Talvez sin darnos cuenta, pero hemos
manejado el concepto de árbol .
Además de la definición debemos conocer otros conceptos bastante importantes, pues estos pudieran
ser útiles a la hora de codificar un árbol:
En relación con otros nodos:
Nodo padre. Nodo que contiene un puntero al nodo actual. En un árbol un nodo solo puede
tener un nodo padre. X es padre de Y sí y solo sí el nodo X apunta a Y. También se dice que X
es antecesor de Y. En la Figura 1: B es padre de E y F.
Nodo hijo. Cualquiera de los nodos apuntados por uno de los nodos del árbol. Un nodo puede
tener varios hijos. X es hijo de Y, sí y solo sí el nodo X es apuntado por Y. También se dice que
X es descendiente directo de Y. En la Figura 1: E es hijo de B.
Hermano. Dos nodos serán hermanos si son descendientes directos de un mismo nodo. En la
Figura 1: E y F son hermanos.
En cuanto a la posición dentro del árbol:
Nodo raíz. Es el único nodo del árbol que no tiene padre. Este es el nodo que usaremos para
referirnos al árbol. En la Figura 1 A es el nodo raíz.
Nodo hoja. Nodo que no tiene hijos. Se le llama hoja o terminal a aquellos nodos que no tienen
ramificaciones (hijos). En la Figura 1 N es un nodo hoja.
Nodo interior. Es un nodo que no es raíz ni hoja. En la Figura 1 D es un nodo interior.
Existen otros conceptos que definen las características del árbol, en relación a su tamaño:
Orden. Es el número potencial de hijos que puede tener cada elemento de árbol. De este modo,
diremos que un árbol en el que cada nodo puede apuntar a otros dos es de orden dos, si puede
apuntar a tres será de orden tres, etc. Podríamos decir que nuestro árbol de ejemplo (Figura 1)
es de orden tres.
Grado. El número de hijos que tiene el elemento con más hijos dentro del árbol. En el árbol del
ejemplo, el grado es tres, ya que tanto A como D tienen tres hijos, y no existen elementos con
más de tres hijos.
Nivel. Se define para cada elemento del árbol como la distancia a la raíz, medida en nodos. El
nivel de la raíz es cero, el de sus hijos uno y así sucesivamente. En el ejemplo, el nodo D tiene
nivel 1, el nodo G tiene nivel 2, y el nodo N nivel 3.
Altura. La altura de un árbol se define como el nivel del nodo de mayor nivel. Como cada nodo
de un árbol puede considerarse a su vez como la raíz de un árbol, también podemos hablar de
altura de ramas; el máximo número de nodos que hay que recorrer para llegar de la raíz a una de
las hojas. El árbol de la Figura 1 tiene altura 3, la rama B tiene altura 2, la rama G tiene altura 1,
la N cero...
Peso. Es el número de nodos del árbol sin contar la raíz.
Camino. Es una secuencia de nodos, en el que dos nodos consecutivos cualesquiera son padre e
hijo. En el ejemplo A-D-H y A-C-G-M son caminos.
Longitud de camino. Es el número de arcos que deben ser recorridos para llegar desde la raíz
al nodo X. Por definición la raíz tiene longitud de camino 1, y sus descendientes directos
longitud de camino 2 y así sucesivamente. En nuestro árbol de ejemplo G tiene longitud de
camino 3 y N tiene longitud de camino 4.
Rama. Es el camino desde el nodo raíz a una hoja. En el ejemplo A-B-E-K y A-B-F son ramas.
A
B C D
E F G H I J
K L M N O
Los árboles tienen una gran variedad de aplicaciones. Por ejemplo, se pueden utilizar para representar
fórmulas matemáticas, para organizar adecuadamente la información, para construir un árbol
genealógico, para el análisis de circuitos eléctricos, para numerar los capítulos y secciones de un libro,
etc.
Bueno y como dijimos que lo que se vería aquí es árboles binarios pues yo creo que mejor nos vamos
centrando en estos…
Árboles binarios
Los árboles de orden dos son bastante especiales. Estos árboles se conocen también como árboles
binarios, ya que cada nodo del árbol no tendrá más de dos descendientes directos.
E R
A G O V
U
Figura 2. Ejemplo de un árbol binario
En alguna de las definiciones del inicio del documento, la tercera para ser precisos, se utiliza la
recursión para definir un árbol porque representa la forma más apropiada y porque además es una
característica inherente de los mismos. En cualquier árbol, no sólo en los binarios, si eliminamos el
nodo raíz, obtenemos dos árboles; en el caso de que sea un árbol binario. Aquel que colgaba del enlace
izquierdo del nodo raíz se denomina subárbol izquierdo y aquel que colgaba del enlace derecho se
denomina subárbol derecho. Además, en un árbol binario, todos los subárboles son también árboles
binarios.
De hecho, a partir de cualquier nodo de un árbol podemos definir un nuevo árbol sin más que
considerarlo como su nodo raíz. Por tanto, cada nodo tiene asociados un subárbol derecho y uno
izquierdo.
Existen dos tipos de árboles binarios que se consideran especiales en función de ciertas propiedades.
Estos son los siguientes:
1. Árbol binario equilibrado
2. Árbol binario completo
| altura(subárbol_izquierdo) – altura(subárbol_derecho) | ≤ 1
E R N
A O V E
N Q A
E R
A G O V
Recorrido Pre-orden (N – I – D)
En este recorrido lo primero que se obtiene o evalúa es el nodo, antes de recorrer las ramas;
posteriormente se recorre la rama izquierda y finalmente la rama derecha. El orden es Nodo – Izquierda
– Derecha (N – I – D).
El recorrido Pre-orden de nuestro árbol de la Figura 3 es: M – E – A – R – O – N – Q – V
Y el recorrido Pre-orden del árbol de la Figura 5 es: N – E – A – G – R – O – V
Recorrido In-orden (I – N – D)
En este recorrido se procesa la primera rama (rama izquierda), después el nodo y finalmente la ultima
rama (rama derecha). El orden es: Izquierda – Nodo – Derecha (I – N – D).
El recorrido In-orden de nuestro árbol de la Figura 3 es: A – E – M – N – O – Q – R – V
Y el recorrido In-orden del árbol de la Figura 5 es: A – E – G – N – O – R – V
Recorrido Post-orden (I – D – R)
En este recorrido se primero se procesan las ramas para finalmente procesar el nodo. El orden de este
recorrido es: Izquierda – Derecha – Nodo (I – D – N).
El recorrido Post-orden del árbol de la Figura 3 es: A – E – N – Q – O – V – R – M
Y el recorrido Post-orden del árbol de la Figura 5 es: A – G – E – O – V – R – N
Y como se que a la mayoría de nosotros no nos agrada mucho la teoría, aunque sea muy necesaria ,
pues vamos viendo algo práctico.
Gráficamente:
NODO
Información
Izq Der
Frecuentemente, aunque tampoco es estrictamente necesario, para hacer más fácil moverse a través del
árbol, añadiremos un puntero a cada nodo que apunte al nodo padre. De este modo podremos avanzar
en dirección a la raíz, y no sólo hacia las hojas. Si implementamos este puntero nuestro nodo estaría
compuesto por cuatro partes:
• Un enlace al padre del nodo
• La información contenida en el nodo, la cual puede ser un tipo primitivo o puede ser
cualquier tipo de objeto
• Un enlace al hijo derecho (raíz del subárbol derecho)
• Un enlace al hijo izquierdo (raíz del subárbol izquierdo)
Gráficamente:
NODO
Padre
Información
Izq Der
Los árboles pueden ser construidos con estructuras estáticas y dinámicas. Las estáticas son arreglos,
registros y conjuntos, mientras que las dinámicas están representadas por listas.
org.neos.arboles.binarios.estaticos.bean::Nodo
-informacion : Object
-padre : int
-derecho : int
-izquierdo : int
package org.neos.arboles.binarios.estaticos.beans;
public Nodo() {
this.informacion = -1;
this.derecho = -1;
this.izquierdo = -1;
}
return this.informacion.equals(info_nodo);
}
}
Como vemos nuestro nodo es una clase muy simple la cual tiene cuatro variables de instancia y los
métodos get y set de estas. Adicional a los métodos get y set contiene un método que nos será útil para
saber si nuestro nodo es igual a otro nodo, este método es: “equals(Object)”, el cual estamos
sobrescribiendo del método que proporciona Object.
Ya tenemos la clase con la cual podemos crear instancias de nuestros nodos, así que ahora vamos a
implementar la clase que representará a nuestro árbol…
Ahh pero antes de eso… Además utilizaremos una especie de Wrapper para la información que
contendrá cada nodo del árbol. Pero los nodos no contendrán objetos del Wrapper este solo nos será útil
para cuando hagamos comparaciones entre la información de los nodos.
Perdón, pero fue la única forma que se me ocurrió para hacer independiente la información de la
estructura del árbol. Así no tenemos que modificar el código del árbol si queremos que este almacene
otro tipo de información… Aunque tendíamos que redefinir nuestro Wrapper
Bueno pues pasemos a ver nuestro Wrapper para la información que almacenará nuestro árbol. Para
este ejemplo solo vamos a almacenar cadenas (pero solo para poder representar árboles como los que
vimos en los ejemplos de la teoría). De modo que quedaría de la siguiente forma.
org.neos.arboles.binarios.estaticos.bean::Informacion
-contenido : String
+equals(in info : Object) : Boolean
+compareTo(in info : Object) : int
Teóricamente, para poder trabajar con otro tipo de información lo único que debemos cambiar es el
tipo de la propiedad “contenido” y hacer que compareTo se haga la comparación de forma correcta y
en el constructor y el set de la información se haga el cast al tipo correcto, con respecto al tipo de la
variable “contenido”, ya lo único que no sabe el árbol es como comparar la información que
contendrán sus nodos, puesto que esta puede ser de cualquier tipo.
Además será un árbol ordenado; es decir, el hijo izquierdo de un nodo debe ser menor y el hijo derecho
debe ser mayor al hijo izquierdo y al nodo.
El campo que es importante conservar siempre es el nodo raíz, ya que es el nodo a partir del cual se
desarrolla el árbol y si perdemos este nodo perdemos el acceso a todo el árbol. En nuestro caso
tendremos dos variables más dentro de nuestro árbol: el vector en el que se almacenarán los nodos de
nuestro árbol y una arreglo (ArrayList) en el que almacenaremos los recorridos del árbol. De modo que
la clase que representa nuestro árbol quedará de la siguiente forma:
org.neos.arboles.binarios.estaticos::ArbolBinarioEstatico
-arbol : Nodo[]
-posicion_raiz : int
-recorrido : ArrayList
+ArbolBinarioEstatico(in num_elementos : int) : Nodo
+esArbolVacio() : Boolean
+obtenerPosicionRaiz() : int
-obtenerPosicionElemento(in info : String, in posicion _actual : int) : int
+obtenerPosicionElemento(in info : String) : int
+existeElemento(in info : String) : Boolean
+obtenerElemento(in info : String) : ArbolBinarioEstatico
+obtenerNumeroElementos(in posicion : int) : int
+obtenerPeso(in raiz : int) : int
+obtenerAltura(in raiz : int) : int
+obtenerCapacidadArbol() : int
-obtenerPosicionLibre() : int
-insertarElemento(in nuevo_nodo : ArbolBinarioEstatico, in posicion_actual : int) : Boolean
-insertarElemento(in info : String, in posicion _actual : int) : Boolean
+insertarElemento(in info : String) : Boolean
-borrarNodoHoja(in nodo : ArbolBinarioEstatico, in posicion _nodo : int) : Boolean
-borrarNodoInterior(in nodo : ArbolBinarioEstatico, in posicion _nodo : int) : Boolean
-borrarNodoRaiz(in nodo : ArbolBinarioEstatico, in posicion_nodo : int) : Boolean
+borrarElemento(in info : String) : Boolean
+recorridoPreorden(in posicion : int) : ArrayList
+recorridoInorden(in posicion : int) : ArrayList
+recorridoPostorden(in posicion : int) : ArrayList
Como podemos darnos cuenta nuestro árbol tiene varios métodos, donde algunos de estos son privados
y otros públicos. A continuación veremos una descripción de estos métodos:
Métodos públicos
public ArbolBinarioEstatico(int num_elementos) throws ExceptionDimensionInvalida
Constructor que permite crear un árbol contenido en un vector de un tamaño definido.
Parámetros:
- num_elementos: Número de elementos que podrá contener el vector.
Throws:
- ExceptionDimensionInvalida: En caso de que el tamaño del vector no sea valido.
Métodos privados
private int obtenerPosicionElemento(Object info, int posicion_actual) throws ArrayIndexOutOfBoundsException
Buscar un elemento recorriendo los elementos del árbol.
Parámetros:
- info: Información que contiene el elemento que se esta buscando.
- posicion_actual: Posición del elemento actual, con el que se compara la información.
Return:
Posición del elemento del vector que contiene la información indicada.
Throws:
- ArrayIndexOutOfBoundsException: Si la posición del elemento a buscar es -1. El nodo al que se apunta (ya sea izquierdo o
derecho) antes de llamar a esta función es nulo (NULL).
package org.neos.arboles.binarios.estaticos;
import java.util.ArrayList;
import org.neos.arboles.binarios.estaticos.beans.Informacion;
import org.neos.arboles.binarios.estaticos.beans.Nodo;
import org.neos.arboles.binarios.estaticos.constantes.ConstantesArbolBinarioEstatico;
import org.neos.arboles.binarios.estaticos.exceptions.ExceptionDesbordamientoVector;
import org.neos.arboles.binarios.estaticos.exceptions.ExceptionDimensionInvalida;
import org.neos.arboles.binarios.estaticos.exceptions.ExceptionElementoDuplicado;
import org.neos.arboles.binarios.estaticos.exceptions.ExceptionNoEncontrado;
if( obj_info_pa.equals(obj_info) ) {
return posicion_actual;
} else if( obj_info_pa.compareTo(obj_info) > 0 ) {
return obtenerPosicionElemento(info, nodo.getIzquierdo());
} else {
return obtenerPosicionElemento(info, nodo.getDerecho());
}
}
if( obj_info_r.equals(obj_info) ) {
return posicion_raiz;
} else if( obj_info_r.compareTo(obj_info) > 0 ) {
return obtenerPosicionElemento(info, nodo_raiz.getIzquierdo());
} else {
return obtenerPosicionElemento(info, nodo_raiz.getDerecho());
}
}
try {
posicion_elemento = obtenerPosicionElemento(info);
respuesta = true;
} catch(ArrayIndexOutOfBoundsException aioob_e) {
respuesta = false;
}
return respuesta;
}
try {
posicion_elemento = obtenerPosicionElemento(info);
elemento = arbol[posicion_elemento];
} catch(ArrayIndexOutOfBoundsException aioob_e) {
;
}
return elemento;
}
if(NULL != posicion) {
Nodo nodo = arbol[posicion];
num_elems += obtenerNumeroElementos(nodo.getIzquierdo());
num_elems++;
num_elems += obtenerNumeroElementos(nodo.getDerecho());
}
return (num_elems);
}
if(NULL != raiz) {
nodo = arbol[raiz];
num_h_izq = obtenerNumeroElementos(nodo.getIzquierdo());
num_h_der = obtenerNumeroElementos(nodo.getDerecho());
return peso;
}
if(NULL != raiz) {
altura = 1;
nodo = arbol[raiz];
ref_h_izq = nodo.getIzquierdo();
ref_h_der = nodo.getDerecho();
return altura;
}
return true;
} else {
return insertarElemento(info, nodo.getIzquierdo());
}
} else {
if(nodo.getDerecho() == NULL) {
posicion = obtenerPosicionLibre();
nodo.setDerecho(posicion);
return true;
} else {
return insertarElemento(info, nodo.getDerecho());
}
}
}
if( this.esArbolVacio() ) {
nuevo_nodo = new Nodo(info);
arbol[0] = nuevo_nodo;
this.posicion_raiz = 0;
se_inserto = true;
} else {
if(obtenerNumeroElementos(posicion_raiz) == arbol.length) {
throw new ExceptionDesbordamientoVector("El árbol esta lleno, no pueden agregarse
más elementos!!");
} else {
if( existeElemento(info) ) {
throw new ExceptionElementoDuplicado("El elemento ya existe dentro del
árbol!!");
} else {
se_inserto = insertarElemento(info, posicion_raiz);
}
}
}
return se_inserto;
}
if( this.esArbolVacio() ) {
throw new ExceptionNoEncontrado("El árbol esta vacío!!");
} else {
if( existeElemento(info) ) {
posicion_elem = obtenerPosicionElemento(info);
nodo = arbol[posicion_elem];
return se_borro;
}
nuevo_nodo.setPadre(posicion_actual);
arbol[posicion] = nuevo_nodo;
return true;
} else {
return insertarElemento(nuevo_nodo, nodo.getIzquierdo());
}
} else {
if(nodo.getDerecho() == NULL) {
posicion = obtenerPosicionLibre();
nodo.setDerecho(posicion);
nuevo_nodo.setPadre(posicion_actual);
arbol[posicion] = nuevo_nodo;
return true;
} else {
return insertarElemento(nuevo_nodo, nodo.getDerecho());
}
}
}
if(nodo_padre.getIzquierdo() == posicion_nodo) {
nodo_padre.setIzquierdo( nodo.getDerecho() );
} else {
nodo_padre.setDerecho( nodo.getDerecho() );
}
arbol[posicion_nodo] = null;
arbol[posicion_aux] = null;
insertarElemento(nodo, posicion_ins);
respuesta = true;
} else if(nodo.getIzquierdo() != NULL) {
nodo_padre = arbol[nodo.getPadre()];
if(posicion_nodo == nodo_padre.getDerecho()) {
nodo_padre.setDerecho( nodo.getIzquierdo() );
} else {
nodo_padre.setIzquierdo( nodo.getIzquierdo() );
}
arbol[posicion_nodo] = null;
respuesta = true;
} else {
nodo_padre = arbol[nodo.getPadre()];
if(posicion_nodo == nodo_padre.getDerecho()) {
nodo_padre.setDerecho( nodo.getDerecho() );
} else {
nodo_padre.setIzquierdo( nodo.getDerecho() );
}
arbol[posicion_nodo] = null;
respuesta = true;
}
return respuesta;
}
nodo_padre = arbol[nodo.getPadre()];
if(posicion_nodo == nodo_padre.getDerecho()) {
nodo_padre.setDerecho(NULL);
} else {
nodo_padre.setIzquierdo(NULL);
}
arbol[posicion_nodo] = null;
se_borro = true;
return se_borro;
}
posicion_aux = nodo.getIzquierdo();
nodo_aux = arbol[posicion_aux];
arbol[posicion_nodo] = null;
arbol[posicion_aux] = null;
insertarElemento(nodo, posicion_raiz);
se_borro = true;
} else if(nodo.getIzquierdo() != NULL) {
this.posicion_raiz = nodo.getIzquierdo();
nodo_aux = arbol[posicion_raiz];
nodo_aux.setPadre(NULL);
arbol[posicion_nodo] = null;
se_borro = true;
} else if(nodo.getDerecho() != NULL) {
this.posicion_raiz = nodo.getDerecho();
nodo_aux = arbol[posicion_raiz];
nodo_aux.setPadre(NULL);
arbol[posicion_nodo] = null;
se_borro = true;
} else { // no tiene ramas
arbol[posicion_raiz] = null;
posicion_raiz = NULL;
se_borro = true;
}
return se_borro;
}
if(NULL != posicion) {
Nodo nodo = arbol[posicion];
recorrido.add(nodo.getInformacion());
recorridoPreorden(nodo.getIzquierdo());
recorridoPreorden(nodo.getDerecho());
}
return recorrido;
}
if(NULL != posicion) {
Nodo nodo = arbol[posicion];
recorridoInorden(nodo.getIzquierdo());
recorrido.add(nodo.getInformacion());
recorridoInorden(nodo.getDerecho());
}
return recorrido;
}
if(NULL != posicion) {
Nodo nodo = arbol[posicion];
recorridoPostorden(nodo.getIzquierdo());
recorridoPostorden(nodo.getDerecho());
recorrido.add(nodo.getInformacion());
}
return recorrido;
}
}
Y un pequeño ejemplo en el que se muestra las funciones más importantes de nuestro árbol:
package org.neos.arboles.binarios.estaticos;
import java.util.ArrayList;
import org.neos.arboles.binarios.estaticos.exceptions.ExceptionDesbordamientoVector;
import org.neos.arboles.binarios.estaticos.exceptions.ExceptionDimensionInvalida;
import org.neos.arboles.binarios.estaticos.exceptions.ExceptionElementoDuplicado;
import org.neos.arboles.binarios.estaticos.exceptions.ExceptionNoEncontrado;
try {
arbol.insertarElemento("G");
arbol.insertarElemento("E");
arbol.insertarElemento("N");
arbol.insertarElemento("D");
arbol.insertarElemento("J");
arbol.insertarElemento("F");
arbol.insertarElemento("O");
arbol.insertarElemento("Q");
recorrido = arbol.recorridoPreorden(arbol.obtenerPosicionRaiz());
System.out.print("Recorrido Preorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print(recorrido.get(i) + " - ");
}
recorrido = arbol.recorridoInorden(arbol.obtenerPosicionRaiz());
System.out.print("\nRecorrido Inorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print(recorrido.get(i) + " - ");
}
recorrido = arbol.recorridoPostorden(arbol.obtenerPosicionRaiz());
System.out.print("\nRecorrido Postorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print(recorrido.get(i) + " - ");
}
System.err.println("\nNumero de elementos:
"+arbol.obtenerNumeroElementos(arbol.obtenerPosicionRaiz()));
System.err.println("Peso del árbol: "+arbol.obtenerPeso(arbol.obtenerPosicionRaiz()));
System.err.println("Altura del árbol: "+arbol.obtenerAltura(arbol.obtenerPosicionRaiz()));
recorrido = arbol.recorridoPreorden(arbol.obtenerPosicionRaiz());
System.out.print("\nRecorrido Preorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print(recorrido.get(i) + " - ");
}
recorrido = arbol.recorridoInorden(arbol.obtenerPosicionRaiz());
System.out.print("\nRecorrido Inorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print(recorrido.get(i) + " - ");
}
recorrido = arbol.recorridoPostorden(arbol.obtenerPosicionRaiz());
System.out.print("\nRecorrido Postorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print(recorrido.get(i) + " - ");
}
System.err.println("\nNumero de elementos:
"+arbol.obtenerNumeroElementos(arbol.obtenerPosicionRaiz()));
System.err.println("Peso del árbol: "+arbol.obtenerPeso(arbol.obtenerPosicionRaiz()));
System.err.println("Altura del árbol: "+arbol.obtenerAltura(arbol.obtenerPosicionRaiz()));
} catch(ExceptionElementoDuplicado ed_e) {
System.err.println(ed_e.getMessage());
} catch(ExceptionDesbordamientoVector dv_e) {
System.err.println(dv_e.getMessage());
} catch(ExceptionNoEncontrado ne_e) {
System.err.println(ne_e.getMessage());
}
}
}
Con las instrucciones del inicio de nuestro pequeño ejemplo (hasta antes de la marca de borrar un elemento) se crea un árbol como el
mostrado en la siguiente figura:
E N
D F J O
E O
D F J Q
Y las instrucciones “System.out.print” que están despues de la marca de borrar elemento nos ayudan a
comprobar que lo que se muestra en la Figura 12 es cierto. Lo que deben desplegar las instrucciones
despues de la marca de borrar elemento es:
Borrar elemento "N":
Recorrido Preorden: G - E - D - F - O - J - Q -
Recorrido Inorden: D - E - F - G - J - O - Q -
Recorrido Postorden: D - F - E - J - Q - O - G –
Numero de elementos: 7
Peso del árbol: 6
Altura del árbol: 2
Y como fue a mí a quien se le ocurrió la brillante idea de poner el Wrapper para la información que
contienen los nodos pues vamos a ver un ejemplo en donde nuestro nodo va a contener un objeto
Persona, el cual vamos a definir aquí. La definición de nuestra clase persona sería la que se muestra en
la Figura 13.
org.neos.beans::Persona
-nombre : Nombre
-direccion : Direccion
-telefono : String
Esta clase esta compuesta por un nombre (clase org.beans.Nombre), una dirección (clase
org.neos.beans.Direccion) y un atributo cadena llamado telefono.
Clase org.neos.beans.Nombre:
package org.neos.beans;
if( this.nombre.equals(_nombre.getNombre() )
&& paterno.equals(_nombre.getPaterno() )
&& materno.equals(_nombre.getMaterno() ) ) {
return true;
} else {
return false;
}
}
if(com_ap == 0) {
com_ap = materno.compareTo(a_mat);
if(com_ap == 0) {
return this.nombre.compareTo(nom);
} else {
return com_ap;
}
} else {
return com_ap;
}
}
}
Clase org.neos.beans.Direccion:
package org.neos.beans;
Clase org.neos.beans.Direccion:
package org.neos.beans;
return nombre.equals(_persona.getNombre() );
}
return nombre.compareTo(_persona.getNombre() );
}
return respuesta;
}
}
package org.neos.arboles.binarios.estaticos.beans;
import org.neos.beans.Persona;
if( contenido.equals(_info.getContenido()) ) {
return true;
} else {
return false;
}
}
return contenido.compareTo(_info.getContenido());
}
}
Como podemos darnos cuenta lo que tenemos que hacer si queremos cambiar el contenido de nuestro
árbol es definir la clase que nos dará la estructura de la información que queremos contener en nuestro
árbol, junto con sus métodos equals(Object) y compareTo(Object) (así como se hizo aquí con persona).
Y ya que se tenga definida la clase de nuestra información se debe importar esta en la clase
org.neos.arboles.binarios.estaticos.beans.Informacion y cambiar el tipo de nuestro contenido y el cast
de este (Lo que se encuentra marcado con amarillo en el codigo de nuestra clase Informacion) y nuestro
árbol debe seguir funcionando.
Ya que se ha definido la clase que define la información que va a contener el árbol y como se compara
esta información (en org.neos.arboles.binarios.estaticos.beans.Informacion) podemos comenzar a usar
nuestro árbol. A continuación se muestra un pequeño ejemplo:
package org.neos.arboles.binarios.estaticos;
import java.util.ArrayList;
import
org.neos.arboles.binarios.estaticos.exceptions.ExceptionDesbordamientoVector;
import org.neos.arboles.binarios.estaticos.exceptions.ExceptionDimensionInvalida;
import org.neos.arboles.binarios.estaticos.exceptions.ExceptionElementoDuplicado;
import org.neos.beans.Direccion;
import org.neos.beans.Nombre;
import org.neos.beans.Persona;
try {
direccion = new Direccion("AAA", 1, "B-1", "CCC", "DDD", "EEE");
nombre = new Nombre("Eugenio", "Flores", "Barrera");
persona = new Persona(nombre, direccion, null);
arbol.insertarElemento(persona);
nombre = new Nombre("Nayeli", "Vazquez", "Vasquez");
persona = new Persona(nombre, direccion, null);
arbol.insertarElemento(persona);
nombre = new Nombre("Hector", "Hidalgo", "Martinez");
persona = new Persona(nombre, direccion, null);
arbol.insertarElemento(persona);
nombre = new Nombre("Mario", "Hidalgo", "Matinez");
persona = new Persona(nombre, direccion, null);
arbol.insertarElemento(persona);
nombre = new Nombre("Miguel", "Flores", "Barrera");
persona = new Persona(nombre, direccion, null);
arbol.insertarElemento(persona);
recorrido = arbol.recorridoPreorden(arbol.obtenerPosicionRaiz());
System.out.print("Recorrido Preorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print((Persona)recorrido.get(i) + " - ");
}
recorrido = arbol.recorridoInorden(arbol.obtenerPosicionRaiz());
System.out.print("\nRecorrido Inorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print((Persona)recorrido.get(i) + " - ");
}
recorrido =
arbol.recorridoPostorden(arbol.obtenerPosicionRaiz());
System.out.print("\nRecorrido Postorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print((Persona)recorrido.get(i) + " - ");
}
System.err.println("\nNumero de elementos:
"+arbol.obtenerNumeroElementos(arbol.obtenerPosicionRaiz()));
System.err.println("Peso del árbol:
"+arbol.obtenerPeso(arbol.obtenerPosicionRaiz()));
System.err.println("Altura del árbol:
"+arbol.obtenerAltura(arbol.obtenerPosicionRaiz()));
} catch(ExceptionElementoDuplicado ed_e) {
System.err.println(ed_e.getMessage());
} catch(ExceptionDesbordamientoVector dv_e) {
System.err.println(dv_e.getMessage());
}
}
}
Con las instrucciones del inicio de nuestro pequeño ejemplo se crea un árbol como el mostrado en la
siguiente figura:
Flores Barrera,
Eugenio
Vazquez
Vasquez,
Nayeli
Hidalgo
Martinez,
Hugo
Hidalgo
Flores Barrera,
Martinez,
Miguel
Mario
Y para demostrar que lo que muestra la figura es cierto. Lo que debe desplegar nuestro pequeño
ejemplo es lo siguiente:
Si añadimos a los árboles la restricción de que cada elemento puede tener un solo posterior, llegamos a
las estructuras lineales, y más concretamente a las listas. Así pues, las listas no son más que un caso
particular de los árboles. En este punto, si añadimos ciertas restricciones de acceso a las listas llegamos
a las colas o a las pilas. Por lo tanto, colas pilas, son tipos particulares de listas. Así que se han
implementado una lista dinámica lo que vamos a ver debe ser fácil. Y como vamos a reciclar el código
que generamos para el árbol dentro del vector será más parecido a una lista doblemente ligada.
La estructura de nuestro nodo sigue siendo la misma que la de la Figura 7 y el mostrado en la siguiente
figura:
NODO
Padre
Información
Izq Der
Solo que en este caso las referencias hacia el padre, el hijo izquierdo y el hijo derecho son apuntadores
hacia objetos (objetos nodo iguales al mostrado en la figura), de modo que al formar nuestro árbol
dinámico se armaría una estructura más o menos parecida a la mostrada en la figura:
Padre
Información
Izq Der
Padre Padre
Información Información
Bueno, pues entonces vamos a ver la definición en Java de la clase que representará nuestro árbol. En
esta ocasión no voy a describir los métodos ya que estamos reciclando el código que se genero para el
árbol en el vector, por lo tanto es muy parecido y creo que lo entenderán para que sirve cada método si
lo relacionan con los métodos del árbol visto anteriormente :-)
package org.neos.arboles.binarios.dinamicos;
import java.util.ArrayList;
import org.neos.arboles.binarios.dinamicos.beans.Informacion;
import org.neos.arboles.binarios.dinamicos.beans.Nodo;
import org.neos.arboles.binarios.dinamicos.exceptions.ExceptionElementoDuplicado;
import org.neos.arboles.binarios.dinamicos.exceptions.ExceptionNoEncontrado;
public ArbolBinario() {
this.raiz = null;
this.recorrido = null;
}
if(null != nodo) {
return true;
} else {
return false;
}
}
if(null != nodo) {
num_elems += obtenerNumeroElementos(nodo.getIzquierdo());
num_elems++; // contabilizar el nodo visitado
num_elems += obtenerNumeroElementos(nodo.getDerecho());
}
return num_elems;
}
if(null != nodo) {
num_h_izq = obtenerNumeroElementos(nodo.getIzquierdo());
num_h_der = obtenerNumeroElementos(nodo.getDerecho());
return peso;
}
if(null != nodo) {
altura = 1;
ref_h_izq = nodo.getIzquierdo();
ref_h_der = nodo.getDerecho();
return altura;
}
return true;
} else {
return insertarElemento(info, nodo.getIzquierdo());
}
} else {
if(nodo.getDerecho() == null) {
nuevo_nodo = new Nodo(info);
nuevo_nodo.setPadre(nodo);
nodo.setDerecho(nuevo_nodo);
return true;
} else {
return insertarElemento(info, nodo.getDerecho());
}
}
}
if( this.esArbolVacio() ) {
this.raiz = new Nodo(info);
se_inserto = true;
} else {
if( existeElemento(info) ) {
throw new ExceptionElementoDuplicado("El elemento ya existe dentro del árbol!!");
} else {
se_inserto = insertarElemento(info, raiz);
}
}
return se_inserto;
}
if( this.esArbolVacio() ) {
throw new ExceptionNoEncontrado("El árbol esta vacío!!");
} else {
if( existeElemento(info) ) {
nodo = obtenerElemento(info, raiz);
if(nodo.getPadre() != null) {
if( (nodo.getIzquierdo() != null) || (nodo.getDerecho() != null) ) {
se_borro = borrarNodoInterior(nodo);
} else {
se_borro = borrarNodoHoja(nodo);
}
} else {
se_borro = borrarNodoRaiz(nodo);
}
} else {
throw new ExceptionNoEncontrado("El elemento no existe dentro del árbol!!");
}
}
return se_borro;
}
nodo_der.setPadre(nodo_padre);
nodo = null;
nodo_izq = null;
insertarElemento(nodo_c.getInformacion(), nodo_der);
respuesta = true;
} else if(nodo.getIzquierdo() != null) {
nodo_izq.setPadre(nodo_padre);
if(nodo_padre.getDerecho() == nodo) {
nodo_padre.setDerecho(nodo_izq);
} else {
nodo_padre.setIzquierdo(nodo_izq);
}
nodo = null;
respuesta = true;
} else {
nodo_der.setPadre(nodo_padre);
if(nodo_padre.getDerecho() == nodo) {
nodo_padre.setDerecho(nodo_der);
} else {
nodo_padre.setIzquierdo(nodo_der);
}
nodo = null;
respuesta = true;
}
return respuesta;
}
if(nodo_padre.getDerecho() == nodo) {
nodo_padre.setDerecho(null);
} else {
nodo_padre.setIzquierdo(null);
}
nodo = null;
se_borro = true;
return se_borro;
}
nodo = null;
nodo_izq = null;
insertarElemento(nodo_c, raiz);
se_borro = true;
} else if(nodo.getIzquierdo() != null) {
this.raiz = nodo_izq;
this.raiz.setPadre(null);
nodo = null;
se_borro = true;
} else if(nodo.getDerecho() != null) {
this.raiz = nodo_der;
this.raiz.setPadre(null);
nodo = null;
se_borro = true;
} else {
this.raiz = null;
se_borro = true;
}
return se_borro;
}
if(null != nodo) {
recorrido.add(nodo.getInformacion());
recorridoPreorden(nodo.getIzquierdo());
recorridoPreorden(nodo.getDerecho());
}
return recorrido;
}
if(null != nodo) {
recorridoInorden(nodo.getIzquierdo());
recorrido.add(nodo.getInformacion());
recorridoInorden(nodo.getDerecho());
}
return recorrido;
}
if(null != nodo) {
recorridoPostorden(nodo.getIzquierdo());
recorridoPostorden(nodo.getDerecho());
recorrido.add(nodo.getInformacion());
}
return recorrido;
}
}
Y bueno, pues el ya tan tradicional pequeño ejemplo que utiliza nuestro árbol, para demostrar que este funciona, o que por lo menos lo
funciona lo esencial.
El código de nuestro ejemplo es el código del último ejemplo del árbol contenido en el vector, solo que utilizando nuestro árbol dinámico,
por lo que utiliza las mismas clases que se usaron en el ejemplo anterior para almacenar la información.
package org.neos.arboles.binarios.dinamicos;
import java.util.ArrayList;
import org.neos.arboles.binarios.dinamicos.exceptions.ExceptionElementoDuplicado;
import org.neos.beans.Direccion;
import org.neos.beans.Nombre;
import org.neos.beans.Persona;
try {
direccion = new Direccion("AAA", 1, "B-1", "CCC", "DDD", "EEE");
nombre = new Nombre("Eugenio", "Flores", "Barrera");
persona = new Persona(nombre, direccion, null);
arbol.insertarElemento(persona);
nombre = new Nombre("Nayeli", "Vazquez", "Vasquez");
persona = new Persona(nombre, direccion, null);
arbol.insertarElemento(persona);
nombre = new Nombre("Hector", "Hidalgo", "Martinez");
persona = new Persona(nombre, direccion, null);
arbol.insertarElemento(persona);
nombre = new Nombre("Mario", "Hidalgo", "Matinez");
persona = new Persona(nombre, direccion, null);
arbol.insertarElemento(persona);
nombre = new Nombre("Miguel", "Flores", "Barrera");
persona = new Persona(nombre, direccion, null);
arbol.insertarElemento(persona);
recorrido = arbol.recorridoPreorden(arbol.obtenerPosicionRaiz());
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print((Persona)recorrido.get(i) + " - ");
}
recorrido = arbol.recorridoInorden(arbol.obtenerPosicionRaiz());
System.out.print("\nRecorrido Inorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print((Persona)recorrido.get(i) + " - ");
}
recorrido = arbol.recorridoPostorden(arbol.obtenerPosicionRaiz());
System.out.print("\nRecorrido Postorden: ");
num_elems = recorrido.size();
for(i = 0; i < num_elems; i++) {
System.out.print((Persona)recorrido.get(i) + " - ");
}
System.err.println("\nNumero de elementos:
"+arbol.obtenerNumeroElementos(arbol.obtenerPosicionRaiz()));
System.err.println("Peso del árbol: "+arbol.obtenerPeso(arbol.obtenerPosicionRaiz()));
System.err.println("Altura del árbol: "+arbol.obtenerAltura(arbol.obtenerPosicionRaiz()));
} catch(ExceptionElementoDuplicado ed_e) {
System.err.println(ed_e.getMessage());
}
}
}
Con nuestro pequeño ejemplo se crea un árbol como el mostrado en la Figura 14. Y para demostrar que lo que muestra la figura es cierto. Lo
que debe desplegar nuestro pequeño ejemplo es lo siguiente:
Recorrido Preorden: Flores Barrera, Eugenio - Vazquez Vasquez, Nayeli - Hidalgo Martinez, Hector - Flores
Barrera, Miguel - Hidalgo Matinez, Mario -
Recorrido Inorden: Flores Barrera, Eugenio - Flores Barrera, Miguel - Hidalgo Martinez, Hector - Hidalgo
Matinez, Mario - Vazquez Vasquez, Nayeli -
Recorrido Postorden: Flores Barrera, Miguel - Hidalgo Matinez, Mario - Hidalgo Martinez, Hector - Vazquez
Vasquez, Nayeli - Flores Barrera, Eugenio -
Numero de elementos: 5
Peso del árbol: 4
Altura del árbol: 3
Algo que podríamos hacer para mejorar nuestro árbol sería implementar un método que permita balancear nuestro árbol para que no
tengamos una rama más larga que otra y de esta forma tratar de optimizar búsquedas e inserciones. Algo que se me ocurre para implementar
este método es vaciar todos los elementos del árbol a un vector (en el vector los elementos estarían ordenados de menor a mayor) y después
volver a reconstruir todo el árbol tomando como raíz el elemento que se encuentra a la mitad del vector; al tomar el elemento del centro
nuestro vector se dividiría en dos partes (una izquierda, con los elementos más pequeños; y una derecha, con los elementos más grandes) por
lo tanto debemos hacer lo mismo para la parte izquierda y la parte derecha (tomar el elemento del centro como raíz) de manera recursiva
hasta terminar con ambas partes del vector. Otra opción, aun mejor, sería implementar los algoritmos para árboles equilibrados (AVL).
Otra muy buena mejora a nuestro árbol sería hacerlo independiente de la información que almacena y que siguiera teniendo la información
ordenada, para eliminar las modificaciones que tenemos que hacer a la clase “Informacion” cada ves que queremos que el árbol almacene
otro tipo de información.