Download Arboles AVL - Universidad de La Serena

Document related concepts

Árbol AVL wikipedia , lookup

Rotación de árboles wikipedia , lookup

Transcript
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
Arboles AVL
Objetivo: Lo substancial de este informe se centra en poder reconocer los
árboles AVL, por otra parte superar el problema generado por la inserción en los
árboles de Búsqueda Binaria que podían degenerar en una lista proporcional a
los datos ingresados y por otra reforzar los conceptos adquiridos en Estructuras
de Datos, como al mismo tiempo practicar su implementación con la ayuda del
lenguaje de programación Java.
Introducción: Una de las primeras estructuras de datos de árboles de búsqueda
“equilibrados” que consideraremos son los árboles AVL ( que llevan el nombre
de sus autores Adelson-Velskii-Landis), existen otros tipos de estructuras
similares, tales como los árboles Red-Black y otros. En este informe se presenta
la definición de los árboles AVL, con ejemplos. Se hace una estimación del
número de nodos en el peor árbol AVL de altura h. Se revisa, en particular, el
método de Insertar, con ejemplos. Además se muestra la implementación en
Java de cada uno de los componentes, tales como Nodo, Arbol AVL y las
Rotaciones, como el medio para lograr la implementación de Inserción en los
árboles AVL. Finalmente, se hace una simulación de la implementación y se
interpreta la salida, junto con mencionar algunas mejoras, u otras
especificaciones que se le podrían realizar, como por ejemplo considerar que se
ingresan string y no enteros, que son los datos en que se basa la presente
implementación.
Definición: Un árbol AVL es un árbol binario de búsqueda en el que las alturas
de los subarboles izquierdos y derecho de cualquier nodo difieren a lo sumo en
1. Esta restricción impuesta sobre la altura de los subarboles de un árbol AVL se
le conoce como “propiedad de los árboles AVL”, y debe ser cumplida por todos y
cada uno de los nodos del árbol.
Ejemplos:
(1) Basado en que los árboles AVL son árboles binarios es que a partir de un árbol vacío se han
insertado los datos originando el árbol de la Fig. 1, el cual deja de tener la propiedad AVL. Pues,
aunque los nodos 2, 4, 10, la posean considerando sus respectivos subarboles, notamos que en
este caso la raíz, es decir el nodo 8 no posee la propiedad de los árboles AVL pues la altura del
subárbol izquierdo no difiere en a lo sumo 1 con el subárbol derecho, (en particular el subárbol
izquierdo tiene altura 3, mientras que el subárbol derecho tiene altura 1. ( De allí la marca que se
considera como –2 bajo el nodo 8 y –1 bajo el nodo 4.
8
4
2
-1
-2
10
6
1
Fig.1
____________________________________________________________________
Area de Computación, Universidad de La Serena
1
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
Los valores –2 y –1 se conocen como "Balance" de los nodos 8 y 4
respectivamente. H registra los valores de las alturas de los subárboles, los
que deben ser -1 (balance_izq), 0 (balanceado) y +1 (balance_der).
(2) En este otro caso, todos los nodos satisfacen la condición de balance, de manera que es un
árbol AVL.
4
2
1
8
3
6
10
Fig. 2
5
7
Sea T(h) cualquier árbol AVL que contenga N(h) nodos, con h>=2. Como T(h) es
un árbol AVL, y supongamos que tenga el menor número de nodos, entonces
uno de los subárboles de la raíz debe tener altura h-1 y el otro deberá tener
altura h-2, de manera que el número de nodos de un árbol AVL en el caso peor
de altura h viene dado por: (Ver (2), para mayor información)
N(h) = 1 + N(h-1) + N(h-2).
Si suponemos que N(h) = 0 cuando h <0, entonces los primeros valores de esta
relación de recurrencia son: 1, 2, 4, 7, 12, 20, ...etc.
En general, N(h) = F(h+3) – 1, donde F(i) es el i-ésimo número de Fibonacci.
(Ver (4) , pág.36-37).
Tomando logaritmo en base (1 + 5)/2), se tiene que h = log (1 + 5)/2) N(h) –3.
Como N(h) es el número de nodos en el peor árbol AVL de altura h, se
desprende del análisi anteriorque la altura de cualquier árbol AVL de n-nodos es
O(log(n)).
Métodos de Consulta
Al igual que como se hizo para los árboles de búsqueda binaria podemos
realizar consultas en un árbol AVL. A saber, Insertar y Eliminar, y otros métodos
que no resultan tan directos como en los árboles de búsqueda binaria, por el
problema de balanceo que se genera.
Ejemplos:
Dado el siguiente árbol T de búsqueda binaria
8
4
2
10
6
Fig 3.:
____________________________________________________________________
Area de Computación, Universidad de La Serena
2
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
1) En este árbol, son insertados los nodos con claves 9 y 11. Generándose el árbol T1 :
8
4
2
10
6
9
11
Fig. 4.:
2) Basado en T, son insertados los nodos con claves 1, 3, 5 y 7. Generándose el árbol T2:
8
4
2
-1
-2
10
6
1
Fig. 5:
Ya al insertar la clave „1“ el árbol pierde la propiedad de AVL.. De aquí el aplicar una doble
rotación.
4
2
8
1
6
10
Arbol obtenido luego de insertar las claves 3, 5 y 7.
4
2
1
8
3
6
5
10
7
Fig. 6: La inserción de las claves „3“, „5“ y „7“ no hacen perder la propiedad AVL.
____________________________________________________________________
Area de Computación, Universidad de La Serena
3
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
La propiedad de AVL se pierde si H = +2 resp. -2. Esto se remedia mediante
rotaciones.
Rotaciones
a) Los árboles de Fig. 7 contienen los mismos elementos y son ambos árboles
de búsqueda binaria. Primero, en ambos casos k1 < k2, segundo, todos los
elementos en los subárboles X son menores que k1 en ambos árboles, tercero,
todos los elementos en el subárbol Z son mayores que k2. Finalmente todos los
elementos en el subárbol Y están entre k1 y k2. La conversión de uno de ellos al
otro se conoce como „Rotación simple“, que significa en lo substancial cambiar
la estructura del árbol. Las figuras muestran también las variantes simetricas.
k2
k1
k1
k2
Z
Y
X
Y
Z
X
k1
k2
k2
k1
X
Y
X
Y
Z
Z
Fig. 7: Rotaciones Simples
Representación en Java
Un árbol AVL se representa de la misma manera que un árbol binario de
búsqueda, esto es con nodos que contienen punteros a su padre y a sus hijos
izquierdo y derecho, sin embargo, un nodo ahora debe almacenar un campo
adicional que indica la altura o balance del nodo.
// Descripción de un nodo para un árbol AVL
class Nodo_Avl
{ // Instancias
protected Nodo_Avl izq;
// hijo Izquierdo
protected Nodo_Avl der;
// hijo derecho
protected int altura;
// altura
public Comparable datos;
// elementos
____________________________________________________________________
Area de Computación, Universidad de La Serena
4
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
// Constructores
public Nodo_Avl(Comparable datElem)
{
this(datElem, null, null );
}
public Nodo_Avl( Comparable datElem, Nodo_Avl ib, Nodo_Avl db )
{
datos = datElem;
izq = ib;
der = db;
balance = 0;
}
}
/*
Este método puede ser llamado solamente si k2 tiene un hijo
izquierdo, realizando una rotación entre el nodo k2, tal como lo muestra
la figura 7. Además, actualiza la altura, asignando la nueva raíz a k2.
*/
private static Nodo_Avl RotacionSimpleIzq(Nodo_Avl k2)
{
Nodo_Avl k1 = k2.izq;
k2.izq = k1.der;
k1.der = k2;
k2.altura = max( altura( k2.izq ), altura( k2.der ) ) + 1;
k1.altura = max( altura( k1.izq ), k2.altura ) + 1;
return k1;
}
b) Existen situaciones en donde el desbalanceo es generado por un nodo que es
insertado en el árbol que está contenido en el subárbol de el medio( es decir Y) y
que al mismo tiempo como los otros arboles tienen idéntica altura.
El caso es fácil de chequear y la solución es llamada “Rotación Doble”, la cual es
muy similar a la rotación simple salvo que ahora se ven involucrados 4
subárboles en vez de 3.
k3
k2
k1
k1
k3
D
k2
B
A
C
A
B
D
C
Fig.8: Rotación Izq-Der, Rotación Doble.
____________________________________________________________________
Area de Computación, Universidad de La Serena
5
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
K3
k2
K1
A
k3
k1
k2
D
B
C
A
B
D
C
Fig.9: Rotación Der-Izq, Rotación Doble.
/*
Rotación Doble, basada en Fig. 8: Este método solo puede ser usadosi
k3 tiene hijo izquierdo y los hijos de k3 tienen hijo derecho. Esta
rotación se conoce como rotación izq-der. Actualiza la altura, y su
raíz.
*/
private static Nodo_Avl DobleRotacionIzq_Der(Nodo_Avl k3)
{ /* Rotación entre k1 y k2*/
k3.izq = RotationSimpleIzq( k3.izq);
return RotationSimpleDer( k3 );
}
Ejemplos:
4
2
1
6
3
5
k3 7
k1 15
k2 14
Rotación der-Izq
4
2
1
6
3
5
k2 14
k3 7
k1 15
____________________________________________________________________
Area de Computación, Universidad de La Serena
6
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
Insertar 13
4
2
1
k3
3
6
5
k1 14
k2 7
15
13
Rotación der-Izq
4
2
1
k2
3
k3
5
6
7
k1 14
13
15
Ud. podrá verificar que cualquier desbalanceo causado por una inserción en un
árbol AVL puede ser realizada por una Rotación Doble o Simple, (Ver (1)).
Ahora, respecto a la eficiencia de esta TDA mencionemos que almacenar la
información de la altura, que en este caso son suficientes con +1, 0 y –1, es de
gran utilidad
/* Método para calcular la altura de un nodo en un árbol AVL.
*/
private static int altura( Nodo_Avl b)
{
return b == null ? -1 : b.altura;
}
Entonces recordemos que para Insertar un nodo con la clave „x“ en un árbol
AVL, el valor „x“ se inserta recursivamente en el subarbol correspondiente, tal
como en los árboles de búsqueda binario. En el caso que la altura del subárbol
no cambie, la inserción concluye. En caso contrario es necesario utilizar según
sea el caso, Rotación Simple o Rotación Doble.
____________________________________________________________________
Area de Computación, Universidad de La Serena
7
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
Implementación de la Inserción en los árboles AVL
Archivo Nodo_Avl.java
(Dr. Eric Jeltsch F.)
// Declaracion de la clase Nodos para los elementos en los arboles AVL.
class Nodo_Avl
{
// Instancias
protected Nodo_Avl izq;
protected Nodo_Avl der;
protected int altura;
public Comparable datos;
//
//
//
//
hijo izquierdo
hijo derecho
altura
los datos como elementos del arbol avl
// Constructores
public Nodo_Avl(Comparable datElem)
{
this(datElem, null, null );
}
public Nodo_Avl( Comparable datElem, Nodo_Avl ib, Nodo_Avl db )
{
datos = datElem;
izq = ib;
der = db;
altura = 0;
}
}
Archivo Arbol_Avl.java
(Dr. Eric Jeltsch F.)
// Métodos Típicos para los Arboles de Busqueda Binaria
// Constructor: Inicializar la raíz con null
//
// *** algunos métodos usuales para los arboles***
// void insertar( x )
--> inserta x
// void eliminar( x )
--> elimina x
// Comparable hallar( x )
--> hallar un Elemento que corresponde a x
// Comparable hallarMin( ) --> Entrega el Elemento más pequeño
// Comparable hallarMax( ) --> Entrega el Elemento más grande
// boolean esVacio( )
--> Return true si es vacio; else false
// void hacerVacio( )
--> Eliminar todos
// void printArbol( )
--> Salida de los datos en una sucesión ordenada
// void salidaArbolBinario()
--> Salida de los datos girados en 90
grados.
/*
* Comparaciones se basan en el método compareTo.(REPASAR)
*/
public class Arbol_Avl
{
/* Raiz del Arbol */
private Nodo_Avl raiz;
/*
* Constructor por defecto
*/
public Arbol_Avl( )
{
____________________________________________________________________
Area de Computación, Universidad de La Serena
8
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
raiz = null;
}
/*
* Insertar: Duplicados son ignorados.
* x es el dato a ser insertado.
*/
public void insertar(Comparable x )
{
raiz = insertar( x, raiz );
}
/*
* Eliminar un nodo del Arbol. Caso que x no este,
* nada ocurre.
* Si x esta, es eliminado.
*/
//no esta la implementación......
/*
* Determinar el elemento más pequeño en el arbol..
* Devuelve: el dato más pequeño o null,
* en el caso que el arbol este vacio.
* Analogamente se podría determinar el más grande elemento en el
arbol
*/
//no esta implementado.....
/*
* Eliminar el arbol.
*/
//no esta implementado....
/*
* Test, si el arbol esta vacio o no.
* devuelve true, caso de vacio; sino false.
*/
public boolean esVacio( )
{
return raiz == null;
}
/*
* Entregar el contenido del árbol en una sucesion ordenada.
*/
public void printArbol( )
{
if( esVacio( ) )
System.out.println( "Arbol vacio" );
else
printArbol( raiz );
}
/*
* Salida de los elementos del arbol binario rotados en 90 grados
*/
public void salidaArbolBinario()
{
if( esVacio() )
System.out.println( "Arbol vacio" );
else
salidaArbolBinario(raiz,0);
}
/*
____________________________________________________________________
Area de Computación, Universidad de La Serena
9
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
* Metodo interno para tomar un nodo del arbol.
* Parametro b referencia al nodo del arbol.
* Devuelve los elementos o null,
* caso de b sea null.
*/
private Comparable elementAt(Nodo_Avl b )
{
return b == null ? null : b.datos;
}
/*
* Metodo Interno para agregar o insertar un nodo en un subarbol.
* x es el elemento a agregar.
* b es el correspondiente nodo raiz.
* Devuelve la nueva raiz del respectivo subarbol.
*/
private Nodo_Avl insertar(Comparable x, Nodo_Avl b)
{
if( b == null )
b = new Nodo_Avl(x, null, null);
else if (x.compareTo( b.datos) < 0 )
{
b.izq = insertar(x, b.izq );
if (altura( b.izq ) - altura( b.der ) == 2 )
if (x.compareTo( b.izq.datos ) < 0 )
b = RotacionSimpleIzq(b);
else
b = RotacionDobleIzq_Der(b);
}
else if (x.compareTo( b.datos ) > 0 )
{
b.der = insertar(x, b.der);
if( altura(b.der) - altura(b.izq) == 2)
if( x.compareTo(b.der.datos) > 0 )
b = RotacionSimpleDer(b);
else
b = RotacionDobleDer_Izq(b);
}
else
; // Duplicados; no hace nada
b.altura = max( altura( b.izq ), altura( b.der ) ) + 1;
return b;
}
/*
* Metodo Interno para determinar el dato más pequeño.
* b es la raiz.
* Devuelve: Nodo con el elemento mas pequeño.
*/
private Nodo_Avl hallarMin(Nodo_Avl b)
{
if (b == null)
return b;
while(b.izq != null )
b = b.izq;
return b;
}
/*
* Analogamente al anterior pero el más grande.
*/
____________________________________________________________________
Area de Computación, Universidad de La Serena
10
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
private Nodo_Avl hallarMax(Nodo_Avl b )
{
if (b == null)
return b;
while (b.der != null)
b = b.der;
return b;
}
/*
* Metodo interno para determinar un dato.
* x es el dato buscado
* b es la raiz
* Devuelve: Nodo con el correspondiente dato.
*/
private Nodo_Avl hallar(Comparable x, Nodo_Avl b)
{
while( b != null )
if (x.compareTo( b.datos) < 0 )
b = b.izq;
else if( x.compareTo( b.datos ) > 0 )
b = b.der;
else
return b;
// paso
return null;
// no paso nada
}
/*
* Metodo Interno para devolver los datos de un subarbol en una
sucesion ordenada.
* b es la raiz.
*/
private void printArbol(Nodo_Avl b)
{
if( b != null )
{
printArbol( b.izq );
System.out.println( b.datos );
printArbol( b.der );
}
}
/*
* salida del arbol binario rotado en 90 Grados
*/
private void salidaArbolBinario(Nodo_Avl b, int nivel)
{
if (b != null)
{
salidaArbolBinario(b.izq, nivel + 1);
for (int i = 0; i < nivel; i++)
{
System.out.print(' ');
}
System.out.println(b.datos);
salidaArbolBinario(b.der, nivel + 1);
}
}
/*
* Salida: altura de los nodos, o -1, en el caso null.
*/
private static int altura(Nodo_Avl b)
____________________________________________________________________
Area de Computación, Universidad de La Serena
11
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
{
return b == null ? -1 : b.altura;
}
/*
* Salida: Maximum entre lhs y rhs.
*/
private static int max( int lhs, int rhs )
{
return lhs > rhs ? lhs : rhs;
}
/*
* Rotacion Simple Izquierda(simetrica a Rotacion Simple Derecha).
* Para los arboles AVL, esta es una de las simples rotaciones.
* Actualiza la altura, devuelve la nueva raiz.
*/
private static Nodo_Avl RotacionSimpleIzq(Nodo_Avl k2)
{
Nodo_Avl k1 = k2.izq;
k2.izq = k1.der;
k1.der = k2;
k2.altura = max( altura( k2.izq ), altura( k2.der ) ) + 1;
k1.altura = max( altura( k1.izq ), k2.altura ) + 1;
return k1;
}
/*
* Rotación Simple Derecha.
*/
private static Nodo_Avl RotacionSimpleDer(Nodo_Avl k1)
{
Nodo_Avl k2 = k1.der;
k1.der = k2.izq;
k2.izq = k1;
k1.altura = max( altura( k1.izq ), altura( k1.der ) ) + 1;
k2.altura = max( altura( k2.der ), k1.altura ) + 1;
return k2;
}
/*
* Rotacion doble: primero hijo izquierdo con su hijo derecho
* entonces nodo k3 con el nuevo hijo izquierdo.
* para los arboles AVL, esta es una doble rotación
* actualiza alturas, entrega nueva raiz.
*/
private static Nodo_Avl RotacionDobleIzq_Der(Nodo_Avl k3)
{
k3.izq = RotacionSimpleDer( k3.izq );
return RotacionSimpleIzq( k3 );
}
/*
* rotacion doble: primero hijo derecho
* con su hijo izquierdo; luego nodo k1 con nuevo hijo derecho.
* Para los AVL, esta es una doble rotación.
* actualiza alturas, entrega nueva raiz.
*/
private static Nodo_Avl RotacionDobleDer_Izq(Nodo_Avl k1)
{
k1.der = RotacionSimpleIzq(k1.der);
return RotacionSimpleDer(k1);
}
}
____________________________________________________________________
Area de Computación, Universidad de La Serena
12
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
archivo Arbol_AvlTest.java
(Dr. Eric Jeltsch F.)
public class Arbol_AvlTest
{
// Programa Test
public static void main(String [] args)
{
Arbol_Avl b1 = new Arbol_Avl();
Arbol_Avl b2 = new Arbol_Avl();
for (int i = 0; i < 7; i++) //
{
Integer r = new Integer(i);
b1.insertar(r);
}
System.out.println("Arbol girado en 90 grados");
b1.salidaArbolBinario();
for (int i = 0; i < 10; i++)
{
// Genera un número entre 0 y 100
Integer r = new Integer((int)(Math.random()*100));
b2.insertar(r);
}
System.out.println("Arbol girado en 90 grados");
b2.salidaArbolBinario();
System.out.println("Travesia en Inorden(Izq-Raiz-Der)");
b2.printArbol();
}
}
Salida que se genera:
Arbol girado en 90 grados
0
1
2
3
4
5
6
Arbol girado en 90 grados
4
14
35
39
52
64
74
75
77
Travesia en Inorden(Izq-Raiz-Der)
4
14
35
39
52
64
74
75
77
____________________________________________________________________
Area de Computación, Universidad de La Serena
13
Diseño y Análisis de Algoritmos con Java
Dr.Eric Jeltsch F.
Interpretación de la salida
Arbol girado en 90 grados
0
1
2
3
4
5
6
La implementación de los árboles AVL, así como su salida están basados en los
ejemplos y materiales entregados en (3).
3
1
0
5
2
4
6
Propuestas: Una mejora factible de considerar en la implementación del método
insertar es considerar que los elementos a ingresar son String o caracteres,
además de considerar el factor de balance y la nueva raíz que se obtiene. Como
se muestra en el ejemplo siguiente.
caracter que desea insertar al arbol-AVL (Borrar: \n): a
a insertado
AVL con balanceo:
a(0)
Caracter que desea insertar al arbol-AVL (Borrar: \n): b
b insertado
AVL con balanceo:
b(0)
a(1)
c insertado
AVL con balanceo:
c(0)
b(0)
a(0)
Bibliografía:
(1) Mark Allen Weiss , "Data Structures and Algorithm Analysis (in Java)",
Addison_Wesley, 1999.
(2) Gregory Heileman, “Estructuras de Datos, Algoritmos y Programación
Orientada a Objetos”, Mc Graw Hill, 1997.
(3) Documentos “POO(2003)” y “apresto a UML”, en la dirección Web
http://dns.uls.cl/~ej/java-2003/Labor2003/Praxis3Web/practica3.htm
(4) Cormen, Leiserson, Rivest, "Introduction to algorithms", McGraw-Hill, 1990.
____________________________________________________________________
Area de Computación, Universidad de La Serena
14