Estructuras de Datos y Algoritmos. Curso 2004/05 Tema 6

Anuncio
Estructuras de Datos y Algoritmos. Curso 2004/05
Tema 6. Árboles y Árboles Binarios
Mabel Galiano
Natividad Prieto
Departamento de Sistemas Informáticos y Computación
Escuela Técnica Superior de Informática Aplicada
Índice
1. Introducción a las Estructuras Jerárquicas: Árboles
2
1.1. Conceptos generales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2. Árboles Binarios: definición, propiedades y tipos de Recorrido . . . . . . . . . . 4
1.3. Representación Contigua de un Árbol Binario Completo . . . . . . . . . . . . . 8
1.4. Representación Enlazada de un Árbol Binario General: el concepto de Nodo Binario 9
2. Implementación en Java de un Árbol Binario General
9
2.1. La clase ArbolBinario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2. La clase NodoBinario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Objetivos y Bibliografı́a
El objetivo general de este tema es introducir y estudiar el Árbol Binario como la estructura jerárquica básica a partir de la cual se obtienen tanto el Montı́culo Binario o Heap como el
Árbol Binario de Búsqueda, que se presentarán más tarde en la asignatura como posibles Implementaciones de alguno de los Modelos avanzados, bien Cola de Prioridad o bien Diccionario.
Para ello, a partir de los conceptos generales asociados al Árbol, se definirá el Árbol Binario, sus
propiedades y operaciones tanto básicas como de Recorrido (In-Orden, Pre-Orden, Post-Orden
y Por Niveles). Además, se presentarán sus dos Representaciones posibles: contigua para un
Árbol Binario Completo, que se utilizará para representar un Montı́culo, y enlazada para un
Árbol Binario general, que será el fundamento de la Representación de los Árboles Binarios de
Búsqueda y en función de la cual se diseñará en Java la clase ArbolBinario que representa un
Árbol Binario general mediante un único Enlace a su Nodo raı́z, esto es como una estructura
Enlazada de Objetos de la clase NodoBinario.
Como bibliografı́a básica de este tema se utilizarán los siguientes capı́tulos y apartados del
libro de Weiss, M.A. Estructuras de datos en Java: el apartado 5 del capı́tulo 6 para la
introducción de las estructuras de Árbol y Árbol Binario y los apartados del 1 al 3 del capı́tulo
17 para lo referente a las clases Java ArbolBinario y NodoBinario.
1
1.
Introducción a las Estructuras Jerárquicas: Árboles
En ocasiones los Datos de una Colección mantienen relaciones de tipo Jerárquico que no es
posible expresar mediante una Representación Lineal de la Colección, como la Lista Enlazada Indirecta o el simple array estudiadas en el tema anterior. Por ejemplo, son claramente
jerárquicas las relaciones que mantienen los distintos directorios, y ficheros, en los que se organiza la información de un Sistema Operativo o los operadores y operandos que conforman
una expresión aritmética a evaluar durante la compilación o interpretación de un programa.
En estos casos, la forma más natural de representar la Colección es la de Árbol, la estructura
Jerárquica por excelencia; la siguiente figura muestra gráficamente el Árbol que representa la
Colección de Directorios de las prácticas de EDA a partir del directorio home de un usuario:
$HOME
EDA
libJava
estructurasDeDatos
jerarquicos
lineales
progJava
util
misFiguras
bancoDeModems
hashing
En cuanto a la expresión aritmética (((a*b)+(c+d))*(e-f)), en notación infija y parentizada, se muestra a continuación el Árbol que la puede representar:
*
+
−
*
a
e
+
b
c
f
d
Muy importante también para los propósitos de la asignatura resulta destacar que la estructura en Árbol, además de representar las relaciones jerárquicas existentes entre los Datos
de una Colección, puede favorecer la Búsqueda Dinámica caracterı́stica de los Modelos Cola
de Prioridad y Diccionario en tiempos sublineales, que como es sabido no consigue su Representación Lineal. Por ejemplo, si se desea representar la Colección de Palabras mes, mesa,
meta, metro, coro y corona cualquier estructura Lineal requiere un tiempo de Búsqueda del
orden del número de Palabras que la forman, mientras que el Árbol de Palabras (Trie) que se
muestra a continuación sólo exige un tiempo proporcional a la longitud de la Palabra buscada:
m
c
e
o
t
s
a
r
o
r
a
o
n
a
2
Otro ejemplo significativo de Búsqueda Dinámica eficiente es el que se consigue con Árboles
como el que muestra la siguiente figura y que representa la Colección de Integer 100, 50,
200, 25, 75, 150, 300 y 180:
100
50
200
75
25
300
150
180
En este Árbol, cara a favorecer cualquier proceso de Búsqueda Binaria (insertar, borrar o
buscar un Dato), los Integer de la Colección se disponen de la siguiente forma ordenada: el
primero, el 100, ocupa la Raı́z del Árbol y a partir de ésta, cada Dato menor que el primero se
sitúa en la parte izquierda del Árbol y cada Dato mayor en la derecha; además, para cada subÁrbol o parte de éste, el proceso se repite. Por ejemplo, el Dato 75 al ser menor que 100 pero
mayor que 50 se sitúa a la izquierda de la Raı́z, ocupada por 100, y a la derecha del sub-Árbol
cuya Raı́z ocupa 50; con ello bastarán 2 comparaciones para encontrar el Integer 200 en el
Árbol. Árboles de este tipo, denominados Árboles Binarios de Búsqueda, se estudiarán más
adelante con detalle para conocer bajo qué condiciones especı́ficas garantizan una Búsqueda
Dinámica con coste logarı́tmico.
1.1.
Conceptos generales
Un Árbol es una estructura Jerárquica que se puede definir por medio de un conjunto de
Nodos, uno de los cuales es distinguido como la Raı́z del Árbol, y un conjunto de Aristas tal
que cualquier Nodo H, a excepción de la Raiz, está conectado por medio de una Arista a un
único Nodo P; se dice entonces que P es el Padre de H y H es un Hijo de P. Si un Nodo no
tiene ningún Hijo se denomina Hoja; excluidas las Hojas, al resto de Nodos del Árbol, los que
tienen algún Hijo, se les denomina Internos. Para ejemplificar las definiciones realizadas, en la
siguiente figura se muestra un Árbol con 14 Nodos etiquetados con las Letras de la A a la N:
A
B
D
C
E
F
I
M
G
J
H
K
L
N
Figura 1: Árbol Ejemplo con 14 Nodos
Nótese que el Nodo etiquetado como A es la Raı́z del Árbol y B y C son sus Hijos; el Nodo
etiquetado con C es el Padre de los Nodos F, G y H y el Nodo I tiene dos Hijos etiquetados como
M.Galiano,N.Prieto
3
M y N. Asimismo, los Nodos con las etiquetas D, M, N, F, J, K y L son Hojas y el resto Nodos
Internos.
Caminos entre Nodos: Profundidad, Nivel y Altura de un Nodo
Un Árbol se caracteriza porque desde la Raı́z a cada uno de los Nodos hay un Camino único
cuya Longitud viene dada por el número de Aristas que lo componen. Por ejemplo, en el Árbol
de la Figura 1 el Camino desde la Raı́z hasta la Hoja etiquetada como N es A-B-E-I-N y tiene
longitud 4.
A partir de la Longitud de un Camino se definen la Profundidad, el Nivel y la Altura de
un Nodo y un Árbol. Se denomina Profundidad de un Nodo a la Longitud del Camino que va
desde la Raı́z hasta él; por tanto, la Profundidad de la Raı́z es 0. Además, se dice que todos
los Nodos que están a la misma Profundidad están en el mismo Nivel, siendo cero el de la
Raı́z. Por ejemplo, en la Figura 1 los Nodos D, E, F, G y H ocupan el Nivel 2, pues todos ellos
tienen la misma Profundidad, también 2. Esta misma figura permite también observar que la
representación gráfica que se hace de un Árbol es precisamente por Niveles, empezando por el
de la Raı́z, el cero, hasta el que ocupan las Hojas. Finalmente, la Altura de un Nodo se define
como la Longitud del Camino que va desde dicho Nodo hasta la Hoja más profunda bajo él.
En base a esta definición, la Altura de un Árbol es la de su Nodo Raı́z, 4 la del Árbol de la
Figura 1.
Relaciones entre Nodos: Tamaño de un Nodo
Utilizando la nomenclatura tı́pica de la Genealogı́a, a los Nodos de un Árbol que tienen el
mismo Padre se les denomina Hermanos; ası́, en el Árbol de la Figura 1 los Nodos F, G y H
son Hermanos porque tienen el mismo Padre, el Nodo etiquetado con C. Asimismo, si hay un
Camino desde el Nodo u hasta el Nodo v, se dice que u es un Ascendiente de v y que v es un
Descendiente de u. Si u = v, entonces u es un Ascendiente Propio de v y v es un Descendiente
Propio de u; ası́, en el Árbol ejemplo son Ascendientes Propios del Nodo etiquetado con N los
Nodos I, E, B y A y son Descendientes propios de C los Nodos F, G, H, J, K y L.
En base a la nomenclatura que se acaba de introducir, el Tamaño de un Nodo se define
como el número de Descendientes que tiene y el de un Árbol como el de su Nodo Raı́z. Por
ejemplo, el Tamaño del Nodo C de la Figura 1 es 7, el del Nodo B es 6 y el del Árbol, i.e. el de
su Nodo Raı́z, es 14.
Definición Recursiva de un Árbol
Una definición de Árbol alternativa a la presentada es la recursiva. Según ésta, un Árbol
es bien un Árbol vacı́o o bien un Nodo Raı́z y cero o más sub-Árboles no vacı́os, cada una de
cuyas Raı́ces está conectada por medio de una Arista con la Raı́z del Árbol.
Lo interesante de esta definición es que permite identificar el concepto de Nodo con el de
Árbol: un Nodo define el Árbol del que es Raı́z. Será al hablar de Árboles Binarios cuando se
explotará esta identificación.
1.2.
Árboles Binarios: definición, propiedades y tipos de Recorrido
Un Árbol Binario es un Árbol en el que ningún Nodo puede tener más de dos Hijos, que por
ello pueden ser distinguidos como Hijo Izquierdo e Hijo Derecho. Nótese cómo el Árbol ejemplo
que se ha venido utilizando hasta ahora, Figura 1, no es un Árbol Binario: tiene un Nodo, el
etiquetado con C, que tiene 3 Hijos; sin embargo, el que muestra la siguiente figura sı́ lo es y
4
tiene 7 Nodos etiquetados: con A la Raı́z, con B la Raı́z de su Hijo Izquierdo y con C la de su
Hijo Derecho (que a su vez, nótese, sólo tiene Hijo Izquierdo, que es el Nodo F).
A
B
D
C
E
F
G
En la siguiente figura aparece un Árbol Binario que sólo difiere del que se acaba de presentar
en que su Nodo F es Hijo Derecho, y no Izquierdo, de C:
A
B
D
C
E
F
G
Como se puede demostrar fácilmente por Inducción, en un Árbol Binario de Altura H el
número máximo de Nodos del Nivel i es 2i , 0 ≤ i ≤ H. A partir de este resultado y en base
a las definiciones previamente realizadas se pueden establecer los siguientes:
1. su número máximo de Nodos es i = 0 ... H 2i = 2H+1 − 1;
2. su número máximo de Hojas es (2H+1 − 1) − ( i = 0 ... H-1 2i ) = 2H ;
3. su número máximo de Nodos Internos es (2H+1 − 1) − (2H ) = 2H − 1.
Árbol Binario Lleno y Completo
Un Árbol Binario de Altura H es Lleno si tiene todos sus Niveles completos, esto es si en
cada Nivel i, 0 ≤ i ≤ H, tiene exactamente 2i Nodos. Por ejemplo, el Árbol Binario de Altura
3 que muestra la siguiente figura es Lleno:
A partir de esta definición, en base a las propiedades expuestas para un Árbol Binario,
es inmediato deducir que en un Árbol Binario Lleno de Tamaño N y Altura H se cumplen las
siguientes propiedades: H = log2 N y N = 2H+1 − 1.
Asimismo, se dice que un Árbol Binario es Completo si tiene todos sus Niveles completos a
excepción quizás del último que, entonces, debe de tener situados todos sus Nodos, las Hojas
del Árbol, tan a la izquierda como sea posible; por ejemplo, el Árbol Binario que aparece en la
siguiente figura es un Árbol Binario Completo:
M.Galiano,N.Prieto
5
Nótese que mientras un Árbol Binario Lleno es Completo no es cierta la afirmación inversa,
por lo que para la Altura y Tamaño de un Árbol Binario Completo se cumplen, respectivamente, las siguientes desigualdades: H ≤ log2 N y 2H ≤ N ≤ 2H+1 − 1.
A modo de resumen, y para su uso posterior en la asignatura, conviene destacar ahora que
la relación expresada entre la Altura H y el Número de Nodos N de un Árbol Binario Completo
(o Lleno) le confiere una caracterı́stica muy importante: es Equilibrado, pues los N Datos de
una Colección se distribuyen en él de forma que la longitud de su Camino más largo, i.e. H, es
a lo sumo log2 N.
Definición Recursiva de Árbol Binario
De forma análoga a la empleada para un Árbol, y como se expresa gráficamente en la
siguiente figura, un Árbol Binario se define recursivamente bien como un Árbol Binario vacı́o o
bien como un Nodo Raı́z y dos sub-Árboles Binarios, uno Izquierdo y otro Derecho, que pueden
ser vacı́os.
RAIZ
HIJO IZQUIERDO
HIJO DERECHO
Figura 2: Representación gráfica de un Árbol Binario definido recursivamente
Esta definición resulta de gran utilidad para lograr implementar un Árbol Binario General,
por lo que se empleará repetidamente en el resto del tema. En primer lugar, al identificar los
conceptos de Árbol y Nodo, permite que un Árbol Binario se represente mediante un Enlace a
su Nodo (Binario) Raı́z y que, por tanto, las operaciones sobre un Árbol Binario sean operaciones que efectivamente se realizan, se lanzan, sobre su Nodo Raı́z. En segundo lugar, aunque
igualmente importante, proporciona una descomposición recursiva del Árbol Binario en base
a la cual es posible diseñar fácil y naturalmente la mayorı́a de sus operaciones; a modo de
ejemplo, obsérvese que el Tamaño o número de Nodos del Árbol Binario que muestra la Figura
2 se define recursivamente bien como cero si el Árbol Binario es vacı́o y sino como la suma del
Tamaño de su Raı́z, esto es 1, y la del Tamaño de sus Hijos Izquierdo y Derecho.
Operaciones de un Árbol Binario
A continuación se clasifican en base a su coste las operaciones que se pueden realizar sobre un
Árbol Binario, o equivalentemente sobre su Nodo (Binario) Raı́z; al presentarlas se describirán
6
de manera somera, reservando para la última sección de este tema la definición de sus perfiles
y los detalles de su implementación en Java:
1. Operaciones Básicas o de coste del orden de una constante. Agrupadas por su funcionalidad, éstas son: dos constructoras, la de un Árbol Binario vacı́o y la de una de sus Hojas,
i.e. la de un Árbol con un único Nodo Raı́z compuesto por un Dato y dos Hijos vacı́os; una
modificadora, denominada unión, que dados dos Árboles Binarios arBinIzq y arbinDer
y un Dato x obtiene como resultado un Árbol cuya Raı́z contiene a x y cuyos Hijos son
arBinIzq y arbinDer; finalmente, la última operación Básica es la tı́pica consultora que
comprueba si un Árbol Binario está vacı́o.
2. Operaciones de Recorrido cuyo coste es lineal con el Número de Nodos del Árbol visitados;
en particular, según el orden en que se visitan los Nodos durante un Recorrido se pueden
establecer los siguientes tipos:
de Recorrido en Anchura en el que los Nodos se visitan Nivel a Nivel y, dentro
de un Nivel, siempre de izquierda a derecha. En base a esta definición es claro
que la forma más natural de describirlas es la iterativa, función que más tarde se
explicitará durante el diseño del método Java imprimirPorNiveles, que a modo
de toString obtiene en formato texto el Recorrido en Anchura de los Nodos de un
Árbol Binario.
de Recorrido en Profundidad en el que los Nodos se visitan bajando por las Ramas
del Árbol y empezando siempre por la situada más a la izquierda; es por ello que,
al contrario que lo que ocurrı́a en el Recorrido en Anchura, la forma más natural de
describirlo es la recursiva. Nótese que el cálculo del Tamaño y la Altura de un Árbol
Binario suponen un Recorrido en Profundidad de éste, lo mismo que la obtención
de la representación en formato texto de sus Nodos (de nuevo toString pero en
Profundidad) o la de su copia o la de su imagen simétrica o ...
Teniendo en cuenta que un Nodo se identifica con el Árbol del que es Raı́z, según
la naturaleza de la operación a realizar cada Nodo puede ser visitado durante un
Recorrido en Profundidad de cualquiera de las tres formas siguientes: bien antes que
los Hijos Izquierdo y Derecho o en Pre-Orden, bien después que éstos o en PostOrden, bien después que el Hijo Izquierdo y antes que el Derecho o en In-Orden.
Conviene mencionar ahora que también son posibles los Recorridos en Profundidad
simétricos a los tres indicados, i.e. en los que se visita primero el Hijo Derecho y
después el Izquierdo.
Aplicando las definiciones de los cuatro tipos de Recorrido expuestos al Árbol Binario
que muestra la siguiente figura,
A
B
D
C
E
F
G
I
H
J
K
las diferentes secuencias de Nodos que se visitan durante cada uno de ellos son:
M.Galiano,N.Prieto
7
en In-Orden: D-B-E-H-A-F-C-J-I-K-G
en Pre-Orden: A-B-D-E-H-C-F-G-I-J-K
en Post-Orden: D-H-E-B-F-J-K-I-G-C-A
Por Niveles: A-B-C-D-E-F-G-H-I-J-K
Es más, nótese que si en lugar de Caracteres el Árbol Binario representa una expresión
aritmética, sus Recorridos en In-Orden, Pre-Orden y Post-Orden corresponden, respectivamente, a las notaciones Infija, Prefija y Postfija de dicha expresión.
Cuestión: utilizando como Árbol ejemplo el que se acaba de mostrar, determı́nese qué tipo
de Recorrido en Profundidad requieren el cálculo de su Altura, Tamaño y la obtención
de un duplicado de él o copia.
1.3.
Representación Contigua de un Árbol Binario Completo
Como ya se ha indicado, un Árbol Binario Completo es un tipo especial de Árbol Binario
pues en él los Datos de una Colección se distribuyen de forma equilibrada. Además de esta
caracterı́stica, se presenta en esta sección una propiedad igualmente importante, estructural,
que será utilizada en el siguiente tema para realizar la Implementación eficiente de la EDA Cola
de Prioridad: para representar un Árbol Binario Completo sin ambigüedad alguna basta con
almacenar el resultado de su Recorrido por Niveles en un array.
1
13
2
3
21
4
5
24
8
0
6
31
9
65
10
26
16
7
19
68
32
13
21
16
24
31
19
68
65
26
32
1
2
3
4
5
6
7
8
9
10
Especı́ficamente, como muestra la figura, el Dato que ocupa la Raı́z del Árbol se sitúa en la
posición 1 del array (la cero se deja libre por motivos que se explicarán más tarde), el primer
Dato del primer Nivel en la 2, el segundo Dato del primer Nivel en la 3 y ası́ sucesivamente
hasta que situar el último Dato, el N-ésimo si N es el Tamaño del Árbol, del Nivel H, el último
si H es la Altura del Árbol.
Con una Representación como la descrita, que recibe el nombre de Implı́cita, no se necesita
espacio adicional alguno para representar los dos Hijos del Árbol, sólo el del array que
almacena sus Datos y un ı́ndice que indica su talla actual. En efecto, si la i-ésima componente
del array, 1 ≤ i ≤ N, representa el i-ésimo Nodo (Raı́z) de su Recorrido por Niveles entonces:
su Hijo Izquierdo se encuentra en la posición 2 ∗ i, siempre que 2 ∗ i ≤ N;
su Hijo Derecho se encuentra en la posición 2 ∗ i + 1, si 2 ∗ i + 1 ≤ N;
su Nodo Padre está en la posición i2 si i = 1, ya que la Raı́z del Árbol es el único
Nodo que no tiene Padre; si lo tuviera, ocuparı́a la posición cero del array, por lo que,
precisamente, ésta debe dejarse libre.
Además de su destacada eficiencia, una ventaja adicional de la Representación Implı́cita es
que simplifica las operaciones de Recorrido del Árbol.
8
1.4.
Representación Enlazada de un Árbol Binario General: el concepto
de Nodo Binario
Como ya se ha indicado, la definición recursiva de un Árbol Binario permite identificar
cualquiera de sus Nodos con los (sub)Árboles Binarios de los que son Raı́z; de donde resulta
directo establecer que un Árbol Binario General sea representado únicamente mediante un
Enlace a su Nodo Raı́z. Para que la identificación sea completa y congruente, la Representación
de un Nodo Binario, es decir de un Nodo de un Árbol Binario, debe constar de tres elementos:
un Objeto que representa el Dato que en él se sitúe y un Enlace al Nodo Binario que es Raı́z
de su Hijo Izquierdo y otro al Nodo Binario Raı́z de su Hijo Derecho, como se muestra en la
siguiente figura:
NodoBinario
dato
izq
der
Figura 3: Estructura de un Nodo Binario
Nótese que mientras los Nodos de una Lista Enlazada Indirecta sólo contienen un Enlace al
siguiente, una Representación Enlazada de un Árbol Binario requiere dos Enlaces, tantos como
Hijos tiene el Árbol que representa.
2.
Implementación en Java de un Árbol Binario General
A partir de las operaciones y Representación definidas previamente, en esta última sección
se desarrollan las dos clases Java que implementan un Árbol Binario General: ArbolBinario
y NodoBinario. Mientras que la primera se declarará pública, por si se quisiera utilizar aposteriori en aplicaciones tı́picas de los Árboles Binarios, la segunda es sólo friendly pues, como
se recordará, sólo debe resultar visible a las clases que se desarrollarán en temas posteriores
como posibles implementaciones de los Modelos Avanzados Cola de Prioridad y Diccionario;
ambas se ubicarán en el paquete jerarquicos del proyecto estructurasDeDatos de libJava.
2.1.
La clase ArbolBinario
La clase Java ArbolBinario que se presenta a continuación representa un Árbol Binario
General mediante un único Enlace a su Nodo Binario Raı́z; por ello consta de un único atributo
NodoBinario raiz sobre el que la gran mayorı́a de sus métodos (tamanyo, altura, etc.) se
limitan a lanzar o invocar el correspondiente método de NodoBinario.
Además de su simplicidad, resulta conveniente señalar dos detalles más presentes en la
codificación de los métodos de ArbolBinario. En primer lugar, obsérvese que la instrucción
principal de su método unir, raiz = new NodoBinario(x, arBin1.raiz, arBin2.raiz), no
se llega a ejecutar si los subÁrboles parámetro arBin1 y arBin2 son iguales; si lo son, se
lanza y propaga la Excepción UnionNoPermitida. Además, en caso de ejecutarse se evita el
posible aliasing entre dichos subÁrboles parámetro y los Hijos del Árbol resultado de la unión:
si no se ejecutase arBin1.raiz = null cuando arBin1 != null, tanto arbin1.raiz como
this.raiz.izq apuntarı́an al mismo Nodo, lo mismo que arbin2.raiz y this.raiz.der si no
M.Galiano,N.Prieto
9
se ejecutara arBin2.raiz = null cuando arBin2 != null. En segundo lugar, los métodos de
Recorrido siguen dos estilos de diseño distintos al lanzar el método homónimo de NodoBinario:
tamanyo, altura y duplicar lo hacen invocando a un método estático mientras que los
distintos imprimir invocan a un método dinámico; como se puede observar ahora, y también
en su implementación en NodoBinario, la única diferencia entre ambos tipos de métodos radica
en cómo tratan el caso de un Nodo o (sub)Árbol vacı́o.
package jerarquicos;
import excepciones.*;
public class ArbolBinario {
protected NodoBinario raiz;
public ArbolBinario() { raiz = null;}
public ArbolBinario(Object x) { raiz = new NodoBinario(x);}
public void unir(Object x, ArbolBinario arBin1, ArbolBinario arBin2)
throws UnionNoPermitida {
if( arBin1.raiz == arBin2.raiz && arBin1.raiz != null )
throw new UnionNoPermitida("Los dos subÁrboles son iguales; unión imposible !!!");
this.raiz = new NodoBinario( x, arBin1.raiz, arBin2.raiz );
/** Para evitar el aliasing, desreferenciar los subÁrboles utilizados en la unión */
if( this != arBin1 ) arBin1.raiz = null;
if( this != arBin2 ) arBin2.raiz = null;
}
public boolean esVacio() { return ( this.raiz == null ); }
public int tamanyo() { return NodoBinario.tamanyo(raiz);}
public int altura() { return NodoBinario.altura(raiz); }
public void duplicar(ArbolBinario original){
if ( original.raiz != null ) this.raiz = original.raiz.duplicar();
}
public String imprimirPostOrden(){
if ( this.raiz != null ) return raiz.imprimirPostOrden(); else return "*";
}
public String imprimirPreOrden(){
if ( this.raiz != null )
return raiz.imprimirPreOrden(); else return "*";
}
public String imprimirInOrden(){
if ( this.raiz != null ) return raiz.imprimirInOrden(); else return "*";
}
public String imprimirPorNiveles(){
if ( this.raiz != null ) return raiz.imprimirPorNiveles(); else return "*";
}
}
2.2.
La clase NodoBinario
La clase Java NodoBinario representa un Nodo de un Árbol Binario, por lo que sus atributos
serán tres (según muestra la Figura 3): Object dato, que representa el Dato (de tipo genérico)
que contiene el Nodo y dos Enlaces NodoBinario izq, der que representan, respectivamente,
a sus Hijos Izquierdo y Derecho. Nótese también que en NodoBinario se re-utiliza el software
desarrollado para implementar una Cola: el interfaz Cola del paquete modelos y la clase que lo
implementa ArrayCola del paquete lineales; la razón es que su método imprimirPorNiveles
implementa un Recorrido en Anchura de un Nodo que sólo se puede realizar iterativamente, y
no en base a su descomposición recursiva. Para ello, como ejemplifica el código propuesto, se
debe utilizar como estructura auxiliar una Cola que inicialmente contiene el Nodo a recorrer
por Niveles; mientras queden elementos en ella, el tratamiento que sistemáticamente recibe
su primer elemento o Nodo encolado es: tratar el Dato a él asociado, eliminarlo y, si no son
10
vacı́os, encolar sus Hijos empezando por el Izquierdo. Obsérvese que es al tratar la Cola cuando,
indirectamente, se realiza el Recorrido iterativo por Niveles un Nodo.
package jerarquicos;
import modelos.*;
import lineales.*;
final class NodoBinario {
Object dato; NodoBinario izq, der;
NodoBinario(Object x) {this(x, null, null); }
NodoBinario(Object x, NodoBinario izquierdo, NodoBinario derecho) {
dato = x; izq = izquierdo; der = derecho;
}
static int tamanyo(NodoBinario actual) {
if ( actual == null ) return 0;
else return 1 + tamanyo(actual.izq) + tamanyo(actual.der);
}
static int altura(NodoBinario actual) {
if ( actual == null) return -1;
else return (Math.max(altura(actual.izq), altura(actual.der)) + 1);
}
NodoBinario duplicar(){
NodoBinario raiz = new NodoBinario(this.dato);
if( this.izq != null ) raiz.izq = this.izq.duplicar( );
if( this.der != null ) raiz.der = this.der.duplicar( );
return raiz;
}
String imprimirPostOrden(){
String res = "";
if( this.izq != null ) res += izq.imprimirPostOrden( );
if( this.der != null ) res += der.imprimirPostOrden( );
res += dato.toString()+"\n";
return res;
}
String imprimirInOrden(){
String res = "";
if( this.izq != null ) res += this.izq.imprimirInOrden( );
res += dato.toString()+"\n";
if( this.der != null ) res += this.der.imprimirInOrden( );
return res;
}
String imprimirPreOrden(){
String res = dato.toString()+"\n";
if( this.izq != null ) res += this.izq.imprimirPreOrden( );
if( this.der != null ) res += this.der.imprimirPreOrden( );
return res;
}
String imprimirPorNiveles(){
String res = "";
Cola q = new ArrayCola();
q.encolar(this);
while ( !q.esVacia() ){
NodoBinario nodoActual = (NodoBinario)(q.desencolar());
res += nodoActual.dato.toString()+"\n";
if ( nodoActual.izq != null ) q.encolar(nodoActual.izq);
if ( nodoActual.der != null ) q.encolar(nodoActual.der);
}
return res;
}
}
M.Galiano,N.Prieto
11
Ejercicios propuestos:
1. Se desean añadir a la clase ArbolBinario nuevos métodos para realizar las siguientes
operaciones:
obtener el número de Hojas de un Árbol Binario;
obtener el Árbol Binario que es la imagen especular de uno dado;
visualizar la información asociada a los Nodos del Nivel k de un Árbol Binario;
comprobar si algún Nodo de un Árbol Binario contiene un Objeto dado x;
obtener el Nivel en el que está el Nodo que contiene un Objeto dado x.
Diséñense como invocaciones a sus homónimos de NodoBinario, estáticos o dinámicos
según resulte más conveniente.
2. Modifı́quese el método imprimirPorNiveles() de ArbolBinario para que muestre en
lı́neas diferentes los Nodos de cada Nivel.
3. Impleméntense de forma iterativa los métodos imprimirPreOrden(), imprimirInOrden()
y imprimirPostOrden() de ArbolBinario.
4. Diséñese un método que obtenga el Padre de un Nodo de un Árbol Binario e indı́quese
su coste Temporal ¿Se podrı́a simplificar modificando la estructura de los Nodos?
12
Descargar