Download TAD Árbol - EVA FING

Document related concepts

Árbol binario de búsqueda wikipedia , lookup

Árbol AVL wikipedia , lookup

Árbol binario wikipedia , lookup

Rotación de árboles wikipedia , lookup

Recorrido de árboles wikipedia , lookup

Transcript
Programación 2
10 - TADs Arboles
1
Arboles
Una estructura árbol (árbol general) con tipo base
T es,
1. O bien la estructura vacía
2. O bien un nodo de tipo T, llamado raíz del árbol,
junto con un número finito de estructuras de árbol,
de tipo base T, disjuntas, llamadas subárboles
En un teórico anterior introdujimos algunos conceptos
acerca de árboles. A continución presentamos los
TADs Arbol Binario y Arbol Binario de Búsqueda.
Pero antes queremos ver:
¿cómo representamos árboles generales?
2
Conceptos básicos
Hijo de la raíz
del árbol
Raíz del árbol
...............
Subárboles del árbol
Los elementos se llaman habitualmente nodos del árbol.
3
Un ejemplo de árbol general:
“estructura de directorios”
/usr*
marcos*
carlos*
luis*
libro* curso* basura.c agenda.c trabajo* curso*
cap1.r cap2.r est_alg1*
discreta* logica* prog1*
………………………………………………………………………
Una aplicación: listar un directorio
(recorridos de árboles)
4
¿Cómo representamos árboles generales?
En un árbol general el número de hijos por nodo puede
variar. Luego, una idea de representación consiste en
pensar que cada nodo tiene una “lista” de árboles
asociados (sus subárboles).
Una manera de hacer esto es considerando que cada
nodo se relaciona con su “primer hijo” y con su
“siguiente hermano”, conformando una estructura de
árbol binario.
Esta forma de representación establece una
equivalencia entre árboles generales y árboles
binarios: todo árbol general puede representarse
como uno binario y, todo árbol binario corresponde a
un determinado árbol general.
5
Arbol general - Arbol binario
Cada nodo tiene
un núnero finitio
de subárboles
b
Representación
como árbol binario:
* primer hijo
* siguiente
hermano
b
c
a
d
e
d
e
f g h i j k
……………………….
c
a
f g h i j k
……………………….
6
Un TAD Arbol General particular
Considere la siguiente definición de las operaciones del TAD
ArbolesGenerales no vacíos y sin elementos repetidos, de
un tipo genérico T:
ArbolHoja: Dado un elemento genera un árbol que sólo
contiene dicho elemento (como una hoja).
 Insertar: Dados un Arbol y dos elementos v y w, inserta
a v como el primer hijo de w en el árbol (hijo más a la
izquierda), siempre que w pertenezca al árbol y v no
pertenezca al árbol. En caso contrario, la operación no
tiene efecto.
 EsArbolHoja: Dado un árbol, retorna true si, y sólo si, el
árbol es un árbol hoja (con un solo elemento).
 Pertenece: Dados un árbol y un elemento, retorna true si
y sólo si el elemento pertenece al árbol.
7
Un TAD Arbol General particular (cont.)
 Borrar: Dados un árbol y un elemento, elimina al elemento
del árbol siempre que éste pertenezca al árbol, no sea la
raíz del mismo y no tenga ningún hijo. En caso contrario, la
operación no tiene efecto.
 Otras operaciones o variantes de las anteriores…
Se pide:
a) Especifique en C++ el TAD ArbolesGenerales.
b) Implemente el TAD ArbolesGenerales. Considere la
representación de árboles generales basada en árboles
binarios, como una estructura de datos (“primer hijo” –
“siguiente hermano”).
8
Otras variantes…
• Qué cambiaría en la especificación si el árbol
general permitiera elementos repetidos, siempre y
cuando no sean hermanos?
La noción de ruta absoluta (como en los file
systems) sería útil?
• Y si se permitieran elementos repetidos en
cualquier parte del árbol?
La noción de identificadores “1.3.4.1” (como en los
manejadores de versiones) sería útil?
• Cómo podría ser la especificación de un árbol
general con posiciones implícitas (rutas relativas
en vez de rutas absolutas en el caso de los file
9
systems)
TAD Arbol Binario (AB)
El tipo abstracto de datos Arbol Binario de
elementos de tipo Etype especifica la noción de un
conjunto finito de nodos tal que:
 o bien es vacío
 o consiste de un elemento de Etype (llamado
raíz) y dos árboles binarios de tipo Etype
disjuntos llamados subárbol izquierdo y
derecho de la raíz
10
Arbol Binario (AB)
11
Operaciones del TAD AB
(una alternativa...)
Se definen seis operaciones asociadas al TAD AB.
Constructoras: Vacio y CreoAB.
La primera operación permite construír un árbol vacio.
Dados un elemento de tipo Etype y dos árboles binarios,
CreoAB construye un árbol no vacío.
Predicado: EsVacio.
Esta operación es usada para testear si un árbol es o no
vacío.
Selectoras: Raiz, SubArbIzq y SubArbDer.
Estas operaciones permiten seleccionar la raíz, el
subárbol izquierdo y el derecho, respectivamente, de
un árbol no vacío.
12
Operaciones binarias y árboles binarios
Consideremos la fórmula 4 + 5 + 7.
El cálculo de izquierda a derecha de esta expresión
primero efectúa la suma de 4 y 5, obteniendo el
valor 9, y luego se calcula la suma de 9 y 7,
obteniendo el valor final 16.
El siguiente árbol binario refleja la estructura del
cómputo + ( + (4 , 5) , 7)
+
4
+
5
7
13
Operaciones binarias y árboles binarios (cont)
El siguiente árbol binario refleja la estructura de la
expresión 1 + 2 * 3
1
+
2
*
3
Esta estructura describe un cálculo de derecha a
izquierda de la expresión ya que las reglas de
precedencia de los operadores aritméticos indican
que la multiplicación debe efectuarse antes que la
suma.
14
Operaciones binarias y árboles binarios (cont)
El siguiente árbol binario refleja la estructura de la
expresión a + (x + y + z) * 3
+
'a'
*
+
3
+
'z'
'x'
'y’
En este caso la parentización fuerza una mezcla de
computaciones izquierda-derecha y derechaizquierda. Además algunas de las hojas son
caracteres que identifican los nombres que forman
15
parte de la expresión original.
Operaciones binarias y árboles binarios (cont)
Notar que cada operador es la raíz de un árbol
cuyos subárboles son no vacíos y naturalmente
también reflejan la estructura de una expresión.
No es este el caso de los valores e identificadores.
Cómo quedaría expresado este último árbol si
usamos los constructores del TAD AB para
elementos de un tipo char, por ejemplo?
Qué problemas genera la codificación y
decodificación de los datos si los elementos son
char, por ejemplo?
16
Operaciones binarias y árboles binarios (cont)
Notar que si el tipo de los elementos es refinado
como char (podría también ser *char) tenemos
una disminución en el nivel de abstracción.
Al procesar un árbol de expresiones es bastante
natural pensar que las acciones a tomar van a ser
fuertemente guiadas por los elementos que
ocurren en las raíces de los subárboles que
componen al árbol principal.
La uniformización de la representación de estos
elementos implica un esfuerzo extra de
procesamiento (discriminación y conversión).
17
Operaciones binarias y árboles binarios (cont)
Si las hojas fueran construídas de tal forma que se
pudiera directamente recuperar no solamente la
información que almacenan sino además qué tipo
de información es, el procesamiento de estos
elementos se vería simplificado.
Una posible solución es refinar el tipo T de los
elementos a una unión discriminada de la forma:
typedef int Valor; typedef char Operador;
typedef char Ident;
union E {Valor Val; Operador Op; Ident Id;};
enum TipoExpr {EsVal, EsOpComp, EsId};
struct T {E exp; TipoExpr tipo;};
18
Operaciones binarias y árboles binarios (cont)
Sin embargo, la anterior modificación no soluciona
el problema de redundancia de información que
se genera al construír el nodo correspondiente a
un valor o a un identificador. Esto queda claro por
ejemplo al crear un valor:
´3´
La construcción de la hoja que denota el valor 3
tiene que reflejar el hecho de que estamos
construyendo un nodo del árbol binario.
19
Operaciones binarias y árboles binarios (cont)
Un punto importante en el proceso de abstraer la
noción de expresión aritmética lineal a la de una
forma particular de árbol es el de reflejar que
estamos considerando componentes que poseen
estructuras diferentes y que proveen a su vez
distintos tipos de información.
Este análisis es el que motiva la decisión que hemos
tomado al definir el TAD Expre de expresiones:
20
TAD Expre
// Constructores
Expre ValorExpre(Valor Val)
// Crea una expresión atómica a partir de un valor
Expre VariableExpre(Ident id)
/* Crea una expresión atómica a partir de un
identificador de variable */
Expre OperExpre (Expre exp1, Expre exp2,
Operador op)
/* Crea una expresión compuesta aplicando un operador
a dos expresiones */
21
TAD Expre
// Discriminador (predicado)
TipoExpr QueTipoExpre(Expre e)
// retorna el tipo de la expresión
// Selectoras
Expre SubExpreIzq(Expre e)
/* pre-condición: la expresión es compuesta.
Retorna la sub expresión izquierda */
Expre SubExpreDer(Expre e)
/* pre-condición: la expresión es compuesta.
Retorna la sub expresión derecha */
22
TAD Expre
Operador MainOper(Expre e)
/* pre-condición: expre es una expresión compuesta
Retorna el operador principal */
Valor DarValor(Expre e)
/* pre-condición: la expresión es simple (un valor)
Retorna el valor que construye esa expresión */
Ident DarIdent(Expre e)
/* pre-condición: la expresión es un Identificador
Retorna el identificador */
void DestruirExpre(Expre & e)
/* destruye la expresión e y libera la memoria asociada */
23
Especificación de un ABB
#ifndef _ABB_H
#define _ABB_H
struct RepresenatcionABB;
typedef RepresentacionABB* ABB;
void crearABB (ABB &abb);
/* Devuelve en abb el arbol vacio. */
bool agregarABB (T t, ABB &abb);
/* Agrega el elemento t en abb y devuelve 'true' si t no esta
en abb. En otro caso no hace nada y devuelve 'false'. */
bool esVacioABB (ABB abb);
/* Devuelve 'true' si abb es vacio, 'false' en otro caso. */
T valorABB (ABB abb);
/* Devuelve el valor de la raiz de abb.
Precondicion: ! esVacioABB(abb). */
24
Especificación de un ABB
ABB arbolIzquierdo (ABB abb);
/* Devuelve el subarbol izquierdo de abb.
Precondicion: ! esVacioABB(abb). */
ABB arbolDerecho (ABB abb);
/* Devuelve el subarbol derecho de abb.
Precondicion: ! esVacioABB(abb).*/
void destruirABB (ABB &abb);
/* Libera toda la memoria ocupada por abb.*/
#endif /* _ABB_H */
25
Implementación de un ABB
#include "ABB.h"
#include <stddef.h>
#include <assert.h>
struct RepresentacionABB
{
RepresentacionABB* der;
RepresentacionABB* izq;
T valor;
};
void crearABB (ABB & abb)
{
abb = NULL;
}
bool esVacioABB (ABB abb)
{
return (abb == NULL);
}
26
Implementación de un ABB
bool agregarABB (T t, ABB &abb)
{
bool result;
if (esVacioABB (abb)) {
abb = new RepresentacionABB;
abb->der = NULL;
abb->izq = NULL;
abb->valor = t;
result = true;
}
else if (esMayor (abb->valor, t))
{
result = agregarABB (t, abb->izq);
}
else if (esMenor (abb->valor, t))
{
result = agregarABB (t, abb->der);
}
else result = false;
return result;
}
27
Implementación de un ABB
T valorABB (ABB abb)
{
assert (!esVacioABB (abb));
return abb->valor;
}
ABB
{
}
ABB
{
}
arbolIzquierdo (ABB abb)
assert (!esVacioABB (abb));
return abb->izq;
arbolDerecho (ABB abb)
assert (!esVacioABB (abb));
return abb->der;
28
Implementación de un ABB
void destruirABB (ABB &abb)
{
if(abb != NULL)
{
destruirABB(abb->izq);
destruirABB(abb->der);
delete(abb);
}
}
29
ABB - Análisis
Consideremos un AB (o ABB) completo:
n = 1 + 21 + 22 + … + 2h = 2h+1 - 1
Luego, la altura es O(log2 n) en el caso de un árbol
completo, pero O(n) de un árbol arbitrario. Por qué?
Qué podemos concluir de éste análisis sobre la
eficiencia en tiempo de ejecución de las operaciones
sobre un ABB ?
30
ABB - Análisis
Insertar, Buscar y Borrar tienen tiempo de ejecución
proporcional a log2 n (siempre se recorre un sólo
camino del árbol) si el árbol es completo pero,
la eficiencia puede caer a orden n si el árbol se
degenera (el caso extermo es una lista).
1
2
3
…..
n
31
ABB - Análisis
Si bien el orden de tiempo de ejecución promedio de
las operaciones citadas para ABB’s es O(log2 n),
el peor caso es O(n).
La idea es entonces tratar de trabajar con ABB’s
que sean “completos”, o al menos que estén
“balanceados”…
Tratando de refinar esta idea es que surgen los AVL
32
Arboles AVL
• Un árbol AVL (Adelson-Velskii y Landis) es una ABB
con una condición de equilibrio. Debe ser fácil
mantener la condición de equilibrio, y ésta asegura
que la profundidad del árbol es O(log (n)).
• La condición AVL: para cada nodo del árbol, la altura
de los subárboles izquierdo y derecho puede diferir a
lo más en 1 (hay que llevar y mantener la información
de la altura de cada nodo, en el registro del nodo).
33
Arboles AVL
• Así, todas las operaciones sobre árboles se
pueden resolver en O (log(n)), con la posible
excepción de la inserción (si se usa eliminación
perezosa - recomposición AVL tardía).
AVL Tree
AVL Tree
34
Arboles AVL (cont)
• Cuando se hace una inserción es necesario
actualizar toda la información de equilibrio para
los nodos en el camino a la raíz. La razón de que
la inserción sea potencialmente dificíl es que
insertar un nodo puede violar la propiedad de
árbol AVL.
6
6
2
9
2
9
1
4 8
1
4
3
3
5
ABB y AVL
ABB pero no AVL
35
Arboles AVL: rotaciones
• Al insertar (según la inserción ABB) el 7 en el AVL
anterior se viola la propiedad AVL para el nodo 9.
• Si éste es el caso, se tiene que restaurar la
propiedad AVL antes de dar por culminado el
proceso de inserción. Resulta que esto se puede
hacer siempre con una modificación al árbol,
conocida como rotación (simple y doble).
36
Arboles AVL (cont)
l
k
A
B
Rotación simple
C
A
k
l
B
C
•Ambos son ABB’s ?
•Qué implica una rotación con una implementación
dinámica del árbol ?
•Qué punteros cambian ?
•Qué pasa con las alturas de los subárboles A, B y C ?
•Coinciden los recorridos In-order de ambos árboles ?
37
Arboles AVL (cont)
• Insertar los elementos 1 al 7 a partir de un AVL
vacio.
• Luego, insertar los elementos 8 al 15 en sentido
inverso.
• NOTA: al insertar el 14, una rotación simple no
recompone la propiedad AVL.
• Cuando el elemento insertado se encuentra entre
los valores correspondientes a los nodos del árbol
a rotar (por violación de la propiedad AVL), una
rotación simple no recompone la propiedad AVL.
• Solución: usar una rotación doble, que involucra 4
subárboles en vez de 3.
38
Arboles AVL: rotación doble
l
k
A
D
m
B
l
A
m
B
Izq-Der
Der-Izq
k
C
k
A
C
D
A
m
B
l
C
m
l
B
D
k
C
D
39
Arboles AVL: inserción
• Completar la inserción del 14 al 8 en sentido
inverso.
• Algoritmo de inserción: insertar como en los
ABB y luego iniciar en el nodo insertado y subir en
el árbol, actualizando la información de equilibrio
en cada nodo del camino. Acabamos si se llega a
la raíz sin haber encontrado ningún nodo
desiquilibrado. Si no, se aplica una rotación
(simple o doble, según corresponda) al primer
nodo desiquilibrado que se encuentre, se ajusta
su equilibrio y ya está (no necesitamos llegar a la
raíz, salvo que se use eliminación perezosa).
40
Rebalanceando árboles AVL
Inserciones del tipo ABB que no conducen a un
árbol AVL
– 4 casos
1
2
– 1 y 4 son similares
– 2 y 3 son similares
3
4
41
Rebalanceando árboles AVL
Caso 1 solucionado por rotación simple
– Caso 4 es similar
42
Rebalanceando árboles AVL
Caso 2 necesita una rotación doble
– Caso 3 es similar
43
Arboles AVL: supresión
• Algoritmo de supresión: el algoritmo de
eliminación que preserva la propiedad AVL para
cada nodo del árbol es más complejo.
En general, si las eliminaciones son relativamente
poco frecuentes, puede utilizarse una estrategia
de eliminación perezosa.
• La pertenencia es igual que para los ABB.
• Por más detalles y el código de las operaciones
para una implementación de AVL con estructuras
dinámicas ver el libro de Mark Allen Weiss.
44