Docsity
Docsity

Prepara tus exámenes
Prepara tus exámenes

Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity


Consigue puntos base para descargar
Consigue puntos base para descargar

Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium


Orientación Universidad
Orientación Universidad


Arboles java estructuras de datos dinamicos, Resúmenes de Estructuras de Datos y Algoritmos

Arboles javal resumen apuntes etx

Tipo: Resúmenes

2016/2017

Subido el 23/11/2017

mary-alejandra-carre
mary-alejandra-carre 🇨🇴

1 documento

1 / 31

Toggle sidebar

Esta página no es visible en la vista previa

¡No te pierdas las partes importantes!

bg1
ESTRUCTURAS DE DATOS ÁRBOLES 143
TEMA 4.
ÁRBOLES
4.1. CONCEPTOS GENERALES.
Un árbol es una estructura de datos ramificada (no lineal) que puede representarse
como un conjunto de nodos enlazados entre por medio de ramas. La información
contenida en un nodo puede ser de cualquier tipo simple o estructura de datos.
Los árboles permiten modelar diversas entidades del mundo real tales como, por
ejemplo, el índice de un libro, la clasificación del reino animal, el árbol genealógico de un
apellido, etc.
La figura 4.1. muestra un ejemplo de estructura en árbol (la numeración de los nodos
es arbitraria). Se entiende por “topología” de un árbol a su representación geométrica.
1
2
3
4
5
6
7
8
9
10
11
12
Figura 4.1. Ejemplo de árbol.
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f

Vista previa parcial del texto

¡Descarga Arboles java estructuras de datos dinamicos y más Resúmenes en PDF de Estructuras de Datos y Algoritmos solo en Docsity!

ESTRUCTURAS DE DATOS ÁRBOLES 143

TEMA 4.

ÁRBOLES

4.1. CONCEPTOS GENERALES.

Un árbol es una estructura de datos ramificada (no lineal) que puede representarse

como un conjunto de nodos enlazados entre sí por medio de ramas. La información

contenida en un nodo puede ser de cualquier tipo simple o estructura de datos.

Los árboles permiten modelar diversas entidades del mundo real tales como, por

ejemplo, el índice de un libro, la clasificación del reino animal, el árbol genealógico de un

apellido, etc.

La figura 4.1. muestra un ejemplo de estructura en árbol (la numeración de los nodos

es arbitraria). Se entiende por “topología” de un árbol a su representación geométrica.

1

2 3 4

5 6 7 8 9

10 11 12

Figura 4.1. Ejemplo de árbol.

144 ÁRBOLES ESTRUCTURAS DE DATOS

Una definición formal es la siguiente:

Un árbol es una estructura de datos base que cumple una de estas dos condiciones:

Es una estructura vacía, o

Es un nodo de tipo base que tiene de 0 a N subárboles disjuntos entre sí.

Al nodo base, que debe ser único, se le denomina raíz y se establece el convenio de

representarlo gráficamente en la parte superior.

En un árbol se representa una relación jerárquica a partir del nodo raíz en sentido

vertical descendente, definiendo niveles

1

. El nivel del nodo raíz es 1.

Desde la raíz se puede llegar a cualquier nodo progresando por las ramas y

atravesando los sucesivos niveles estableciendo así un camino. En la figura 4.1. el nodo 7

está a nivel 3 y la secuencia de nodos 4, 8 y 11 constituye un (sub)camino.

Se dice que un nodo es antecesor de otro cuando ambos forman parte de un camino y

el primero se encuentra en un nivel superior (numeración más baja) al del segundo

(numeración más alta). En el ejemplo anterior el nodo 4 es antecesor del 11. Por el

contrario, el nodo 11 es descendiente del 4.

La relación entre dos nodos separados de forma inmediata por una rama se denomina

padre/hijo. En el ejemplo de la figura 4.1., el nodo 5 es hijo del nodo 2 y, recíprocamente,

el nodo 2 es padre del nodo 5. En un árbol un padre puede tener varios hijos pero un hijo

solo puede tener un padre.

Se denomina grado al número de hijos de un nodo. Por ejemplo, en la figura 4.1. el

nodo 4 tiene grado 3 y el nodo 7 tiene grado 0.

Se dice que un nodo es hoja cuando no tiene descendientes (grado 0).

Se establecen los siguientes atributos para un árbol:

Altura / profundidad / nivel: La mayor altura / profundidad / nivel de sus nodos. La

altura del árbol de la figura 4.1. es 4 (la alcanzan sus nodos 10, 11 y 12).

Amplitud / Anchura: El número de nodos del nivel más poblado. En el ejemplo, 5

(nivel 3).

Grado: el mayor de los grados de los nodos. En el ejemplo, 3 (nodos 1 y 4).

1

Los términos “altura” o “profundidad” son sinónimos del de nivel.

146 ÁRBOLES ESTRUCTURAS DE DATOS

4.2. ÁRBOLES BINARIOS.

Se definen como árboles de grado 2. Esto es, cada nodo puede tener dos, uno o

ningún hijo. Al tratarse como mucho de dos hijos, cada uno de ellos puede identificarse

como hijo izquierdo o hijo derecho.

4.2.1. Implementación física.

El gráfico de un árbol es una representación conceptual cuya implementación física

admite diversas posibilidades condicionadas, en primer lugar, por el dispositivo de

almacenamiento del mismo (memoria principal o memoria externa). A los efectos del curso

nos ocuparemos exclusivamente de la memoria principal en donde puede optarse por dos

filosofías principales:

Estructuras de datos estáticas, normalmente matrices.

Estructuras de datos dinámicas

En cualquier caso, la representación física de un árbol requiere contemplar tres

componentes:

La clave (simple o compuesta).

Dos punteros, indicando las ubicaciones respectivas de los nodos hijo izquierdo e

hijo derecho

2

Ejemplo. La figura 4.3. representa un árbol binario que podría implementarse

físicamente según se ilustra en las figuras 4.4. (estructura estática) ó 4.5. (estructura

dinámica).

15

25 20

10 5

45

Figura 4.3. Ejemplo de árbol binario.

Clave 15 20 5 25 45 10 Hijo izquierdo 3 5 4 * * * Hijo derecho 1 2 * * * *

Figura 4.4. Ejemplo de árbol binario. Implementación estática.

2

Deberá establecerse un convenio para indicar la inexistencia de hijo(s).

ESTRUCTURAS DE DATOS ÁRBOLES 147

Puntero al árbol

raíz 20 15

25 * *

10 * * 5 *

45 * *

Figura 4.5. Ejemplo de árbol binario. Implementación dinámica.

4.2.2. Algoritmos básicos con árboles binarios

3

Para la utilización de árboles binarios es necesario definir las clases NodoArbol y

Arbol siguiendo la sintaxis siguiente:

public class NodoArbol {

public NodoArbol (int dato) { clave = dato;

iz = null;

de = null; }

public int clave; public NodoArbol iz, de;

}

public class Arbol {

public NodoArbol raiz;

public Arbol () { raiz = null;

}

}

4.2.2.1.Recorridos.

Se entiende por recorrido el tratamiento realizado para acceder a los diferentes nodos

de un árbol. El recorrido puede afectar a la totalidad de los nodos del árbol (recorrido

completo), por ejemplo si se desea conocer el número de nodos, o finalizar anticipadamente

en función de determinada/s circunstancia/s, por ejemplo al encontrar el valor de una clave

determinada.

En cualquier caso, el recorrido se puede realizar basándose en las siguientes

modalidades:

3

Los siguientes algoritmos se han desarrollado en Java considerando la implementación del árbol por medio

de una estructura dinámica. A efectos de la realización de prácticas se utiliza la sintaxis del lenguaje Java.

ESTRUCTURAS DE DATOS ÁRBOLES 149

o Postorden. Se desciende recursivamente por la rama izquierda, al alcanzar el

final de dicha rama, se retorna y se desciende por la rama derecha. Cuando se

alcanza el final de la rama derecha, se procesa la clave. La exploración en

postorden del árbol de la figura 4.3.a. daría el siguiente resultado: 25, 10, 45, 5,

Este recorrido es el menos utilizado. Se utiliza para liberar memoria, o bien

cuando la información de los niveles más profundos del árbol afecta a la

información buscada.

En general, el algoritmo de recorrido en postorden es el siguiente:

// Escribe las claves del árbol binario en postorden. static void postOrden (NodoArbol arbol) { if (arbol != null) { postOrden (arbol.iz); // Izquierda postOrden (arbol.de); // Derecha System.out.print (arbol.clave + " ") ; // Nodo } } public void postOrden () { postOrden (raiz); }

Ejemplo de algoritmo de recorrido en profundidad: desarrollar un método estático

(sumaClaves) que obtenga la suma de las claves del árbol

4

static int sumaClaves (NodoArbol arbol) {

int resul;

if (arbol != null) resul = arbol.clave + sumaClaves (arbol.iz) + sumaClaves (arbol.de); else resul = 0; return resul;

}

static int sumaClaves (Arbol a) {

return sumaClaves (a.raiz);

}

Como ejercicio se propone al alumno la realización de un algoritmo que cuente los

nodos que tiene un árbol, utilizando uno de los tres recorridos en profundidad

propuestos.

4

Puede realizarse indistintamente con cualquiera de las modalidades de recorrido, en la solución propuesta se

hace un recorrido en preorden.

150 ÁRBOLES ESTRUCTURAS DE DATOS

Recorrido en amplitud. Implica un acceso a las claves recorriendo cada nivel de

izquierda a derecha y descendiendo al siguiente. Por ejemplo, la exploración en

amplitud del árbol de la figura 4.3.a. daría el siguiente resultado: 15, 25, 20, 10, 5,

Para la implementación del recorrido en amplitud se requiere la utilización de la

técnica iterativa así como de la disponibilidad de una estructura de datos auxiliar: TAD cola

de nodos de árbol:

class NodoCola {

NodoArbol dato; NodoCola siguiente; // Constructores NodoCola (NodoArbol elemento, NodoCola n) { dato = elemento; siguiente = n; }

}

Por ejemplo, el algoritmo siguiente permite obtener un listado de las claves de un

árbol binario recorrido en amplitud:

static void listarAmplitud (NodoArbol arbol) {

NodoArbol p; Cola c = new TadCola ();

p = arbol;

if (p != null)

c.encolar (p); while (! c.colaVacia ()) {

p = c.desencolar (); System.out.print (p.clave + " ");

if (p.iz != null) c.encolar (p.iz);

if (p.de != null)

c.encolar (p.de); }

} public void listarAmplitud () {

listarAmplitud (raiz);

}

La idea, básicamente, consiste en encolar en la cola de punteros las direcciones

(referencias) de los posibles hijos de cada nodo procesado (su dirección se encuentra en la

variable p ).

A continuación, en cada iteración se extrae el primer elemento de la cola que se

corresponde con el nodo actual del árbol. Si su hijo izquierdo y/o su hijo derecho son

distintos de null se encolan, y el proceso terminará cuando la cola se encuentre vacía.

152 ÁRBOLES ESTRUCTURAS DE DATOS

public void juntar (int dato, Arbol a1, Arbol a2) { if (a1.raiz == a2.raiz && a1.raiz != null) { System.out.println ("no se pueden mezclar, t1 y t2 son iguales") ; return; } // Crear el nodo nuevo raiz = new NodoArbol (dato, a1.raiz, a2.raiz) ; // Borrar los árboles a1 y a if (this != a1) a1.raiz = null; if (this != a2) a2.raiz = null; }

En el siguiente ejemplo, se muestra como generar el árbol de la figura 4.6:

public static void main (String [] args) { Arbol a1 = new Arbol (1) ; Arbol a3 = new Arbol (3) ; Arbol a5 = new Arbol (5) ; Arbol a7 = new Arbol (7) ; Arbol a2 = new Arbol (); Arbol a4 = new Arbol (); Arbol a6 = new Arbol ();

a2.juntar (2, a1, a3) ; a6.juntar (6, a5, a7) ; a4.juntar (4, a2, a6) ;

}

Figura 4.6. Ejemplo de árbol binario.

4.2.2.4.Tratamiento de hojas.

En este tipo de algoritmos se trata de evaluar la condición de que un nodo del árbol

sea una hoja:

(arbol.iz == null) && (arbol.de == null).

Por ejemplo, el siguiente método de tipo entero ( cuentaHojas ) devuelve como

resultado el número de hojas del árbol que se le entrega como argumento.

4

2 6

1 3 5 7

ESTRUCTURAS DE DATOS ÁRBOLES 153

static int cuentaHojas (NodoArbol arbol) {

int resul = 0;

if (arbol != null) if ((arbol.iz == null) && (arbol.de == null))

resul = 1; else resul = cuentaHojas (arbol.iz) + cuentaHojas (arbol.de);

return resul;

}

static int cuentaHojas (Arbol a) {

return cuentaHojas (a.raiz);

}

4.2.2.5. Procesamiento con constancia del nivel.

Determinados tratamientos requieren conocer el nivel en que se encuentran los nodos

visitados, para lo cual es necesario conocer el nivel actual.

Recorrido en profundidad.

Cuando se va a realizar un tratamiento en profundidad, pasamos como argumento

(por valor) el nivel actual. En cada llamada recursiva (por la derecha o por la izquierda) se

incrementa el nivel en una unidad. La inicialización de dicho argumento (a 1) se realiza en

la primera llamada (no recursiva).

Por ejemplo, el siguiente método ( clavesNiveles ) recorre un árbol en preorden

5

mostrando en la pantalla el valor de las diferentes claves así como el nivel en que se

encuentran.

static void clavesNiveles (NodoArbol arbol, int n) { if (arbol != null) { System.out.println ("Clave: " + arbol.clave + " en el nivel: " + n); clavesNiveles (arbol.iz, n+1); clavesNiveles (arbol.de, n+1); } } static void clavesNiveles (Arbol a) { clavesNiveles (a.raiz, 1); }

Recorrido en amplitud.

El algoritmo explicado para el recorrido en amplitud en el apartado 3.2.2.2., permite

recorrer el árbol en amplitud, sin embargo, no se tiene constancia del nivel en que se

encuentran los nodos a los que se accede.

En caso de que esto sea necesario debería modificarse, bien la estructura de la cola

añadiendo el nivel, o bien el algoritmo visto anteriormente de manera que su estructura sea

una iteración anidada en dos niveles.

5

La modalidad de recorrido es indiferente.

ESTRUCTURAS DE DATOS ÁRBOLES 155

(controlado por medio de la variable contador ) es necesario conocer a priori su amplitud

(variable actual ) y según se va explorando dicho nivel se cuenta el número de hijos del

siguiente nivel (variable siguiente ). Al iniciar el proceso del siguiente nivel se pasa a la

variable actual el último valor encontrado en la variable siguiente.

A continuación se muestra como ejemplo una variante del ejemplo anterior.

static void listarAmplitudNiveles (NodoArbol arbol) {

NodoArbol p; Cola c = new TadCola ();

int actual, siguiente, contador, altura;

altura = 0; siguiente = 1;

p = arbol;

if (p != null ) c.encolar (p);

while (!c.colaVacia()) { actual = siguiente;

siguiente = 0; contador = 1;

altura++; while (contador <= actual) {

p = c.desencolar ();

System.out.println ("clave: " + p.clave + " nivel: " + altura); contador++;

if (p.iz != null) { c.encolar (p.iz);

siguiente++;

} if (p.de != null) {

c.encolar (p.de); siguiente++;

} }

}

} public void listarAmplitudNiveles (){ listarAmplitudNiveles (raiz); }

156 ÁRBOLES ESTRUCTURAS DE DATOS

4.2.3. Ejemplo.

A continuación se desarrolla un método que intenta verificar si dos árboles son

iguales o no.

static boolean iguales (NodoArbol a, NodoArbol b) {

boolean resul ;

if ((a == null) && (b == null)) resul = true;

else if ((a == null) || (b == null)) resul = false;

else if (a.clave == b.clave)

resul = iguales(a.iz, b.iz) && iguales (a.de, b.de); else resul = false;

return resul; }

static boolean iguales (Arbol a1, Arbol a2) {

return iguales (a1.raiz, a2.raiz); }

158 ÁRBOLES ESTRUCTURAS DE DATOS

En caso de no buscar una clave determinada el recorrido del árbol binario de

búsqueda no ofrece ninguna particularidad respecto al árbol binario genérico, pudiendo

realizarse dicho recorrido en cualquier modalidad: orden central, preorden o postorden, así

como en amplitud.

No obstante, cuando se desea recuperar el conjunto de claves del árbol ordenadas, el

recorrido deberá ser en orden central (de izquierda a derecha para secuencia ascendente y

de derecha a izquierda para secuencia descendente).

A continuación se muestra como ejemplo el método booleano esBusqueda que

devuelve true si el arbol binario , que se pasa como argumento, es de búsqueda y false en

caso contrario. Consiste en verificar que la secuencia de claves es ascendente, por lo que

habrá que hacer un recorrido en orden central (de izquierda a derecha) y comparar el valor

de cada clave con la anterior ( ant ) que, debidamente inicializada, deberá pasarse como

argumento en las sucesivas llamadas recursivas. Es necesario identificar el primer elemento

de la lista (no tiene elemento anterior con el que comparar la clave), con una variable

auxiliar booleana ( primero ), inicializada a true desde fuera de el método.

static class Busqueda {

static int ant; static boolean primero = true;

static boolean esBusqueda (NodoArbol arbol) { boolean resul;

if (arbol == null) resul = true; else { resul = esBusqueda (arbol.iz); if (primero) primero = false; else if (arbol.clave <= ant) resul = false; if (resul) { ant = arbol.clave; resul = esBusqueda(arbol.de); } } return resul; }

}

static boolean esBusqueda (Arbol a) {

return Busqueda.esBusqueda(a.raiz); }

ESTRUCTURAS DE DATOS ÁRBOLES 159

4.3.2. Algoritmos de modificación.

4.3.2.1. Inserción.

Se trata de crear un nuevo nodo en la posición que le corresponda según el criterio de

árbol binario de búsqueda. A continuación se muestra el algoritmo.

static NodoArbol insertar (NodoArbol arbol, int dato) { NodoArbol resul = arbol; if (arbol != null) if (arbol.clave < dato) arbol.de = insertar (arbol.de, dato); else if (arbol.clave > dato) arbol.iz = insertar (arbol.iz, dato); else System.out.println ("la clave ya existe"); else resul = new NodoArbol (dato); return resul; } public void insertar (int dato) { raiz = insertar (raiz, dato); }

4.3.2.2. Eliminación.

La eliminación de un nodo en un árbol binario de búsqueda implica una

reorganización posterior del mismo con el objeto de que una vez eliminado el nodo el árbol

mantenga su naturaleza de búsqueda. Para ello se procede de la manera siguiente:

Figura 4.8. Árbol binario de búsqueda

Primero localizamos el nodo a eliminar. Una vez encontrado, tenemos tres posibles

casos:

1. Tenemos que borrar un nodo hoja: procedemos a borrarlo directamente. Por ejemplo, si

en el árbol de la figura 4.11, queremos borrar la clave 13, el resultado sería la figura

Figura 4.9. Borrado de un nodo hoja en un árbol binario de búsqueda

15

10 20

2 12 17 25

1 7 13 18 23

15

10 20

2 12 17 25

1 7 18 23

ESTRUCTURAS DE DATOS ÁRBOLES 161

static class Eliminar { static NodoArbol eliminar2Hijos (NodoArbol arbol, NodoArbol p) { NodoArbol resul; if (arbol.de != null) { resul = arbol; arbol.de = eliminar2Hijos (arbol.de, p); } else { p.clave = arbol.clave; resul = arbol.iz; } return resul; } static NodoArbol eliminarElemento (NodoArbol arbol, int elem) { NodoArbol p;

if (arbol != null) if (arbol.clave > elem) arbol.iz = eliminarElemento (arbol.iz, elem); else if (arbol.clave < elem) arbol.de = eliminarElemento (arbol.de, elem); else { p = arbol; if (arbol.iz == null) arbol= arbol.de; else if (arbol.de == null) arbol = arbol.iz; else arbol.iz = eliminar2Hijos (arbol.iz, p); } else System.out.println ("la clave buscada no existe"); return arbol; } } public void eliminar (int elem) { raiz = Eliminar.eliminarElemento (raiz, elem); }

4.3.3. Ejemplo.

Dado un árbol binario de búsqueda y una lista enlazada por medio de punteros,

implementar un algoritmo en Java que, recibiendo al menos un árbol y una lista ordenada

ascendentemente, determine si todos los elementos del árbol están en la lista.

Implementaremos un método booleano con el arbol y la lista como argumentos.

Como la lista está ordenada ascendentemente, y el árbol binario es de búsqueda,

realizaremos un recorrido del árbol en orden central, parando en el momento en que

detectemos algún elemento que existe en el árbol, pero no en la lista.

162 ÁRBOLES ESTRUCTURAS DE DATOS

static boolean estaContenido (NodoArbol arbol, NodoLista lista) {

boolean seguir, saltar;

if (arbol == null) seguir = true; else { seguir = estaContenido (arbol.iz, lista); if (seguir && (lista != null)) if (arbol.clave < lista.clave) seguir = false; else { saltar = true; while ((lista != null) && saltar) if (arbol.clave == lista.clave) saltar = false; else lista = lista.sig; if (!saltar) seguir = estaContenido (arbol.de, lista.sig); else seguir = false; } else seguir = false; } return seguir;

}

static boolean estaContenido (Arbol a, Lista l) {

return estaContenido (a.raiz, l.inicio);

}