Árboles balanceados

Anuncio
Árboles balanceados
Habíamos visto que se puede demostrar por inducción que en un árbol completo el número de nodos totales es
(2 ** h+1) –1. Esto nos indica que las cosas que se hacen en árboles son de orden log n pero sólo en el caso en
que el árbol esté lleno. En el peor de los casos las cosas pueden ser lineales O(n). ¿Cómo poder garantizar,
entonces que se mantenga un balanceo? Cada vez que debido a una inserción o eliminación el árbol se
desbalancee, reubicar los nodos de modo que, manteniendo el invariante de un árbol de búsqueda binaria,
se trate de mantener la altura del árbol. Ej:
El primer tipo de árboles balanceados fue el AVL (Adelson Velskii Landis). No son frecuentemente implementados,
ya que hay otros mejores, pero las ideas que hay detrás de ellos se ven en los demás tipos de árboles balanceados. Se
trata de incluir otra condición más en el invariante de modo de asegurar que la búsqueda sea O(log n): LO más
simple sería requerir que para un árbol AVL la altura de su subárbol derecho sea igual a la de su subárbil izquierdo.
Recordemos que los árboles se definen recursivanmete, por lo tanto esto se debería cumplir para cada nodo. Esto es
sin embargo muy restrictivo ya que implicaría que todo árbol AVL debería ser completo además. La definición de
árboles AVL es entonces algo más relajada:
Def: Un árbol AVL es un árbol binario con la propiedad adicional que para cualquier nodo
En el árbol la altura de su subárbol izquierdo y de su subárbol derecho difieren a lo más en 1.
Esta condición asegura que el árbol sólo tendrá altura logarítmica. Para probar esto necesitamos mostrar que un árbol
de altura H tiene por lo menos C**H nodos para alguna constante H>1. En otras palabras, si el mínimo número de
nodos en un árbol es exponencial a su altura, entonces la máxima altura de un árbol con N elementos es dada por Log
en base C de N. Esto se puede probar con los números de Fibonacci:
Sea S(H) un árbol AVL con altura H y con el mínimo de elementos para esa altura. Entonces S(0) = 1 y S(1) = 2.
Ahora, por la condición de un árbol AVL sabemos que un árbol AVL mínimo de altura H tiene como hijos uno
mínimo de altura H-1 y otro mínimo de altura H-2, ya que el desbalanceo puede ser a lo más de 1. Del dibujo
podemos ver que la cantidad de nodos de este árbol es S(H) = S(H-1) + S(H-2) + 1. Ahora los números de Fibonacci
eran F(N) = F(N-1)+F(N-2) con F(0) = 1 y F(1) = 1. Corrigiendo: S(H) = F(H+3). Ahora, se sabe que el fibonacci de
un número i es alrededor de (K**i)/sqrt(5) con K alrededor de 1.618 (o sea > 1). Consecuentemente un árbol AVL de
altura H tiene a lo menos (gruesamente estimando) K**(H+3)/sqrt(5), por lo cual la altura para un árbol AVL
mínimo es logarítmica con respecto al número de nodos. Esto implica que las operaciones sobre un árbol AVL están
acotadas logaritmicamente.
Pero esto no se logra gratis, el costo es la complicación de las operaciones insertar y eliminar ya que estas son las que
pueden desbalancear un árbol. Una observación clave es que es que después de una inserción sólo los nodos que
estaban en el recorrido desde la raíz hasta el lugar de inserción pueden resultar desblanceados. Al volver
recursivamente hacia la raíz después de haber insertado o eliminado un nodo es posible encontrar nodos cuyo nuevo
balance viole el principio de árbol AVL.
Para hacer más fácil este control, los nodos de un árbol AVL además de tener la información normal (el elemento)
tiene además un número de balanceo que es la diferencia de alturas entre el árbol izquierdo y el árbol derecho
(altura(izq) – atura(der)).
Veamos los casos de inserción que pueden desbalancear un árbol AVL. Un nodo X podria necesitar ser
rebalanceado si se inserta un nuevo nodo:
1234-
en el subárbol izquierdo del hijo izquierdo de X
en el subárbol derecho de hijo izquierdo
en el subárbol izquierdo del hijo derecho
en el subárbol derecho del hijo derecho
Caso 1
Caso 4
COMO ESTÁN LOS
INDICADORES DE BALANCE ?
Caso 2
Caso 3
COMO ESTÁN LOS
INDICADORES DE BALANCE ?
En el caso 1 y 4 se habla de un nodo externo causando el desbalanceo. En el caso 2 y 3 de uno interno.
Para resolver el caso 1 y 4 de los nodos externos se hace lo que se llama rotación simple (ej pag 513).
Static NodoArbol rotHijoIzq(NodoArbol p) {
NodoArbol q = p.izq;
p.izq = q.der;
q.der = p; p.bal = 0; q.bal = 0;
return q;
}
Static NodoArbol rotHijoDer(NodoArbol p) {
NodoArbol q = p.der;
p.der = q.izq;
q.izq = p; p.bal = 0; q.bal = 0;
return q;
}
Para los casos 2 y 3 de los nodos internos se hace la llamada rotación doble (ej pag 515)
P
R
Q
Q
R
D
A
A
B
P
B
C
D
C
Static NodoArbol dobleConIzq(NodoArbol p) {
NodoArbol q = p.izq, r = q.der;
q.der = r.izq; p.izq = r.der;
r.izq = q; r.der = p;
if (r.bal > 0) { // B era mas alto que C
q.bal = 0; r.bal = 1; p.bal = -1;
}
}
Inserción en AVL.
La manera más simple es un algoritmo recursivo: Para insertar un elemento X hacemos un algoritmo recursivo
(como el conocido para binarios) que inserta en el lugar preciso. El subárbol formado por el nodo recién creado es
1más alto que el antiguo así que debe informar de esto a su padre para que recalcule el factor de balance. Si al
calcularlo el padre ve que se mantiene el balance, está todo en orden pero debe informar a su padre que creció en uno
para que este recalcule también. Si en algún momento se ve un desbalance que no se puede tolerar se realiza la
rotación necesaria. Desde ese momento no es necesario informar de crecimiento de árbol hacia arriba ya que se
volvió la altura original.
ARBOLES ROJO-NEGRO
Tienen la gracia de que se puede balancear mientras se va hacia abajo (no se necesita el paso de vuelta hacia arriba
por lo cual no se necesita hacerlo en forma recursiva). Como resultado la implementación es más simple y rápida que
el AVL. Los rojo-negro tienen las siguientes características:
1234-
Cada nodo está coloreado rojo o negro
La raíz es negra
Si un nodo es rojo, los hijos deben ser negros (no se permiten dos rojos seguidos en un path)
Cualquier path desde la raíz hasta una referencia null debe contener el mismo número de nodos negros
Ver ejemplo en pág 517.
Se puede demostrar por inducción que si todo path desde la raíz hasta una referencia null tiene B nodos negros,
entonces tiene que haber por lo menos (2**B)-1 nodos negros en el árbol. Más aún, como la raíz es negra y no
pueden haber dos nodos consecutivos rojos en el path, la altura de un árbol rojo-negro es a lo más 2*log(N+1)
(intercalemos un nodo rojo en alguno de los path cada nodo negro). Consecuentemente, la búsqueda está garantizada
de ser logarítmica.
Inserción, primera aproximación: botton up
Recordemos que todo nodo nuevo se inserta como una hoja en un árbol. Si la coloreamos negra entonces de seguro
estaremos violando la propiedad 4 ya que habrá un path a una referencia null con un nodo negra más (en realidad 2).
Insertemos la hoja nueva de color rojo. Si el padre era negro está todo bien y hasta aquí llega el problema. Si el padre
ya era rojo entonces estamos en problemas, pero con rotaciones y cambios de colores es posible solucionar esto.
Hay varios casos a considerar (y sus espejos) si el padre era rojo:
Primero consideremos que el tio era negro o null. Si llamamos x al nuevo nodo, p al padre, a al abuelo y t al tio
podemos hacer una rotación y un cambio de colores que nos dejará todo en orden de nuevo. Sólo p y x pueden ser
rojos en este caso porque si a fuera rojo hubiese una situación no válida ANTES de insertar. Ahora podemos usar la
terminología de los árboles AVL y decir que el nodo nuevo x puede ser externo o interno. Si es externo una rotación
simple y un cambio de color de p y de a arreglan la cosa:
a
p
p
t
C
D
E
a
A
t
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
x
x
B
A
C
B
D
E
Aunque x es una hoja y en ese caso t es null, hemos dibujado un caso más general para usar esto más adelante.
Para el caso de una inserción como nodo interno: rotación doble.
a
x
t
x
B
C
p
a
t
X
X
X
X
X
X
X
X
X
X
A
X
X
X
X
X
X
X
X
X
X
p
A
B
C
D
E
Antes de continuar tenemos que estar seguros que esto funciona también para el caso general (en que X no es hoja).
Primero podemos ver que se mantienen las condiciones de rojo-negro intercalados y también la de los nodos negros
en los paths ya que se conservo la cantidad de nodos negros (dos a la derecha, 1 a la izquierda). También se puede
ver que las raíces de A,B,C y D deberían haber sido originalmente negras (o null) si todo estaba en orden antes de
aparecer X.
OK, hasta aquí, pero ¿qué pasa si el tío era rojo? Bueno, se ve que esto también se puede arreglar:
a
p
p
t
C
D
E
a
A
t
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
x
x
B
A
C
B
D
a
x
t
x
B
p
a
t
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
X
p
A
E
A
B
C
C
D
E
Aunque todo esto parece funcionar queda una pregunta: ¿qué pasa si el padre de a era originalmente rojo? Bueno,
aquí podemos hacer los mismos análisis que hemos hecho hasta ahora. Esta vez sí que tiene sentido tener en cuenta
lo del caso general, o sea que estamos tratando con nodos internos.
Inserción top-down en árboles rojo-negro
Para evitar la necesidad de tener que estar rotando el árbol cuando se vaya hacia arriba de vuelta de haber hecho una
inserción podemos usar un truco cuando vayamos hacia abajo de modo de asegurar que cuando se llega al lugar que
se debe insertar, el tío nunca será rojo por lo cual bastará con insertar una hoja roja y hacer a lo más una rotación
(simple o doble).
El procedimiento se basa en lo siguiente: mientras se va hacia abajo, si vemos un nodo x que tiene dos hijos rojos
cambiamos x a rojo y hacemos los hijos negros.
X
X
Es claro que el número de nodos negros bajo x permanece inalterado. El problema es que si el padre de x era rojo
tenemos dos rojos consecutivos. En teste caso podemos aplicar las rotaciones simple o doble vistas anteriormente, en
las cuales había un tío negro, dependiendo de dónde se encuentra x (estas no suben un nodo rojo así que no pará
más). Pero qué pasa si el tío es rojo ! ahí si se pasa un nodo rojo hacia arriba !. Esto no puede pasar ya que en el viaje
hacia abajo si se encontró que el abuelo tenía dos hijos rojos se cambiaron a negros.
Ejemplo: cuadros 18.34 (pág 517) se quiere poner el 45cuando bajamos vemos que el 50 tiene dos hijos rojos. Esto
se cambia (ver cuadro 18.39 en pág. 520). Esto produce que el 60 y el 50 queden sucesivamente rojos. Se hace una
rotación simple (ver cuadro 18.40 en pagina del lado) y luego se puede poner el 45 sin problemas.
Descargar