Download Cap_4_daa09(tdaII) - Universidad de La Serena

Document related concepts

Árbol binario wikipedia , lookup

Recorrido de árboles wikipedia , lookup

Árbol binario de búsqueda wikipedia , lookup

Árbol AVL wikipedia , lookup

Rotación de árboles wikipedia , lookup

Transcript
Diseño y Análisis de Algoritmos. (DAA-2009)
CAPITULO 4 – Estructuras dinámicas de datos
Contenidos:
1. Arbol (Tree)
2. Arbol Búsqueda Binaria(ABB), Heap,
3. AVL,
4. Splay Tree,
5. Red-Black Tree,
6. AA-Tree,
7. B-tree, y otros.
Dr. Eric Jeltsch F.
Introducción
En esta sección se presentan los árboles que son un tipo de dato abstracto más adecuado para el
tratamiento de grandes cantidades de información, las aplicaciones de los mismos son muy
diversas, así por ejemplo, se usan para el almacenamiento y búsqueda de información ya que
pueden establecerse diversas formas en las que el tiempo medio de las operaciones de búsqueda es
del orden de log(n), lo que es bastante bueno comparado por ejemplo, con las listas enlazadas en la
búsqueda de alguna información. Otros ámbitos de aplicación son en la implementación del sistema
de archivos en los sistemas operativos, manejo de archivos en las base de datos. En este segmento
veremos una diversidad de estructuras de datos dinámicas y propiedades de los árboles Binario,
ABB, árboles AVL, Splay, y Red-Black, entre otros. En donde lo sustantivo será el reconocer las
estructuras, manejar sus propiedades de “equilibrio” a través de las “rotaciones”, su performance,
implementación y aplicaciones.
Además, como ya hemos visto, muchas aplicaciones requieren de un conjunto dinámico que
soporte solamente las operaciones de Diccionario, a saber, Insert, Search, y Delete. Consideremos
en este contexto el problema de cómo recuperar eficientemente el registro de alumnos. Dada una
clave buscada K, que corresponde a un alumno en particular y una tabla T, que consiste de 5.000
alumnos. ¿Cómo organizar T, de manera que la búsqueda de la clave K sea eficiente como sea
posible?
Una forma: usar un registro implementado a través de un array en orden numérico ascendente sobre
las claves y usar posteriormente búsqueda binaria para localizar el registro.
a) Búsqueda binaria toma aproximadamente (log2 (5001)-1) –comp. (ó 11.3), sobre el promedio, si
cada clave es igualmente probable a ser usada.
b) Otra posibilidad es usar o dejar los registros en un AVL, de acuerdo al número del alumno. En
este caso, sobre el promedio, la búsqueda en un AVL es (log 25000 + 0.25) – comp. (ó 12.5 ).
Sin embargo, usando la técnica hashing doble se puede afirmar que almacenando 5000 alumnos en
una tabla T que tiene espacio para 6.000 alumnos reduce el nº promedio de comparaciones
necesarias para localizar a algún alumno, realizando efectivamente (2.15)-comp. De manera, que la
técnica del Hashing permite recuperar la información casi 4 veces más rápido ó eficiente que las
otras estructuras. Sin embargo, no todo es del tipo Diccionario lo que se puede hacer con la
información.
Arbol
Un árbol es una estructura no lineal formada por un conjunto de nodos y un conjunto de ramas o
arcos. De un punto de vista más técnico digamos que los árboles se basan en el concepto de nodo,
que corresponde a cualquier tipo cuyos elementos son registros formados por un campo de datos y
un número dado de punteros, tal como se vieron en las listas enlazadas.
_____________________________________________________________________________
Escuela de Ingeniería en Computación, Universidad de La Serena.
1
Diseño y Análisis de Algoritmos. (DAA-2009)
Mundo Real
Mundo Virtual
Dr. Eric Jeltsch F.
Mundo de la Fantasía
Ahora, si los nodos están rotulados con el fin de almacenar un tipo de información, tenemos una
gran variedad de árboles. En general, digamos que en un árbol existe un nodo especial denominado
raíz. Así mismo, un nodo del que sale alguna rama, recibe el nombre de nodo de bifurcación o nodo
rama y un nodo que no tiene ramas recibe el nombre de nodo terminal o nodo hoja, tal como lo
muestra la siguiente figura.
De un modo más formal, diremos que un árbol es un conjunto finito de uno o más nodos tales que:
a) Existe un nodo especial llamado raíz del árbol, y
b) los nodos restantes están agrupados en n > 0 conjuntos disjuntos A1, .., An donde cada uno de los
cuales es a su vez un árbol que recibe el nombre de subárbol.
Evidentemente, la definición dada es recursiva; es decir, hemos definido un árbol como un conjunto
de árboles. De la definición se desprende, que cada nodo de un árbol es la raíz de algún subárbol
contenido en la totalidad del mismo. El número de ramas de un nodo recibe el nombre de grado del
nodo. El nivel de un nodo respecto al nodo raíz se define diciendo que la raíz tiene nivel 0 y
cualquier otro nodo tiene un nivel igual a la distancia de ese nodo al nodo raíz. El máximo de los
niveles se denomina profundidad o altura del árbol. Es útil limitar los árboles en el sentido de que
cada nodo sea a lo sumo de grado 2. De esta forma cabe distinguir entre subárbol izquierdo y
subárbol derecho de un nodo. Los árboles así formados, se denominan árboles binarios. Un árbol
binario es un conjunto finito de nodos que consta de un nodo raíz que tiene dos subárboles binarios
denominados subárbol izquierdo y subárbol derecho.
Para motivar su utilidad digamos que los diagramas de Venn y su anidación de paréntesis (A (B (D
( I ), E, F (J, K )), C (G, H ( L )))), puede ser representada a través de árbol.
_____________________________________________________________________________
Escuela de Ingeniería en Computación, Universidad de La Serena.
2
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Una de las aplicaciones típicas de los árboles binario es la representación de expresiones
algebraicas, debido a que los operadores que intervienen son operadores binarios. La figura
siguiente nos muestra un árbol que corresponde a la expresión aritmética: (a+b*c)/(d-e/f)
Una combinación de los dos anteriores surge al considerar (A1 ((A 2 ) ( A3 ))) (((A4 ) (A 5 ))A6 ), que
corresponde a la forma óptima de parentizar un producto de matrices de algún orden el que se puede
representar por el árbol binario siguiente
El árbol binario es una estructura de datos muy útil cuando el tamaño de la estructura no se conoce,
y se necesita acceder a sus elementos ordenadamente, la velocidad de búsqueda es importante o el
orden en el que se insertan los elementos es casi aleatorio. En definitiva, un árbol binario es una
colección de objetos (nodos del árbol) cada uno de los cuales contiene datos o una referencia a los
datos, una referencia a su subárbol izquierdo y una referencia a su subárbol derecho. Según lo
expuesto, la estructura de datos representativa de un nodo puede ser de la forma siguiente:
Si el número de nodos en un árbol de orden t es N, entonces un árbol completo de altura h contiene:
h
(1) N   t
i 1
i 1
th 1

t 1
En particular, un árbol binario (t = 2) contiene N  2 h  1 nodos. Esto nos dice que para un árbol
binario de altura h = 3, se tienen 7 nodos. Tal como se ve en la siguiente figura, estamos frente a un
árbol binario completo y el otro que no lo es
_____________________________________________________________________________
Escuela de Ingeniería en Computación, Universidad de La Serena.
3
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
En general, un árbol del tipo como la figura también es considerado completo, pero no “full”.
Mientras que el de enfrente No es completo. Ya que, el sentido de “completitud”, es considerado
que se van completando por niveles de arriba hacia abajo y de izquierda a derecha.
Clasificación de los árboles
A causa del gran significado que poseen los árboles es que se hace necesaria una clasificación,
tanto en la forma, como los datos que son almacenados en los árboles, así como en la forma de
buscar un tipo de información, y de recorrer los nodos. En general, los datos o información se
encuentra en los nodos, de aquí que consideremos en forma particular los árboles binarios y la
forma de cómo disponer su información, generando un tipo de árbol llamado árbol de búsqueda
binaria.
ABB(árboles de búsqueda binaria)
Las claves o datos son dispuestas de la siguiente manera: los datos menores a la izquierda y los
mayores a la derecha. En la siguiente figura se puede constatar fácilmente la posición en la cual se
encuentra algún dato en particular.
40
30
50
20
11
39
24
37
60
44
40
41
45
62
65
Arboles de Decisión(o árbol de búsqueda o de comparaciones)
Este tipo de árboles es aplicado a los algoritmos el cual se obtiene al trazar de principio a fin la
acción del algoritmo, representando cada comparación como un vértice del árbol. En los árboles
binarios se da la situación de dos comparaciones, las que se pueden codificar por:
- 0 : Decisión para el hijo izquierdo
- 1 : Decisión para el hijo derecho
En el ejemplo, se muestra el árbol de comparaciones para la búsqueda secuencial, en donde
debemos resaltar los „cajones„ como nodos externos o especiales y los nodos en donde se encuentra
la información, generándose así un nuevo tipo de árbol, llamado árbol extendido.
_____________________________________________________________________________
Escuela de Ingeniería en Computación, Universidad de La Serena.
4
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
En este sentido, el árbol de comparaciones cambia radicalmente su rotulación si incorporamos la
comparación <, >, o =, tal como se ve en la siguiente figura, cuando n =10, en la cual se combinan
dos comparaciones a fin de obtener una comparación de tres vías para cada paso, así el árbol se ve
más compacto, en comparación con el árbol que considera las comparaciones  a la izquierda y > a
la derecha.
Supongamos que deseamos ordenar tres datos A, B y C. La siguiente figura muestra un árbol de
decisión posible para resolver este problema. Los nodos internos del árbol representan
comparaciones y los nodos externos representan salidas emitidas por el programa.
Todo árbol de decisión con H hojas tiene al menos altura log2 H, y la altura del árbol de decisión es
igual al número de comparaciones que se efectúan en el peor caso. En un árbol de decisión para
ordenar n datos se tiene que H=n!, y por lo tanto se tiene que todo algoritmo que ordene n datos
mediante comparaciones entre llaves debe hacer al menos log2 n! comparaciones en el peor caso.
Usando la aproximación de Stirling, se puede demostrar que log2 n! = n log2 n + O(n), por lo cual la
cota inferior es de O(n log n).
Basado en la forma de rotular los nodos, podríamos clasificar los árboles en árboles orientados a los
nodos y árboles orientados a las hojas, para distinguirlos digamos que los primeros son árboles en
_____________________________________________________________________________
Escuela de Ingeniería en Computación, Universidad de La Serena.
5
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
donde los datos se encuentran en los nodos del árbol, mientras que los otros son árboles en donde
los datos se encuentran solamente en las hojas. Convengamos que, tal como la figura anterior si el
árbol es ampliado con nodos especiales, de manera que todos los subárboles están completos, es
decir todos los apuntadores sin nodos descendientes apuntan a un nodo especial, se habla de un
árbol extendido. En este contexto, se define la longitud de camino X como el número de arcos que
deben ser recorridos para llegar desde la raíz al nodo X. por definición la raíz tiene longitud de
camino 1, sus descendientes directos 2… Por otra parte, la longitud de camino interno de un árbol,
es la suma de todas las longitudes de trayectoria de sus nodos, y se calcula por medio de la
siguiente fórmula.
h
LCI   ni * i
i 1
donde i = nivel del árbol, h = altura del árbol, ni = número de nodos en el nivel i. Por otra parte, la
media de la longitud de camino, se calcula por la formula LCI /N, donde N es el número de nodos.
En este grafo el LCI = 1*1 + 2*2 + 5*3 + 4*4 = 36.
Otra forma de clasificación es considerar árboles optimal estáticos u optimal dinámicos, los
primeros significa que el árbol debe ser construido nuevamente, mientras que el otro se construye
durante el ingreso o al agregar los datos. El objetivo final en ambos casos es lograr una razonable
estructura de almacenamiento, aunque esta situación en general globalmente no se pueda lograr,
pero sí localmente. En ambos casos, se evitan árboles degenerados, que son los árboles que
degeneran en listas lineales o ramas, de ahí que surjan las “rotaciones” como una forma de evitar
este tipo de situaciones.
Los árboles sirven también para representar una jerarquía, tal como lo muestra el siguiente ejemplo,
respecto de la representación de expresiones aritméticas. Por ejemplo, para la expresión
(A  B / C) ( D  E F) se puede representar por el siguiente árbol:
*
+
-
A
/
B
D
C
*
E
F
Relación de Orden y Representación
En cada nodo, se da una situación de orientación y jerarquerización en los árboles binarios: Toda
clave al lado derecho(izq) de los subarboles son mayores(menores) a la clave del nodo. Con ayuda
_____________________________________________________________________________
Escuela de Ingeniería en Computación, Universidad de La Serena.
6
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
de esta relación de orden se generan árbol que sirven para buscar, borrar o hallar algún elemento en
particular.
La búsqueda de un elemento se realiza desde la raíz hasta el nodo en donde se encuentra la clave,
en caso de existir, en caso contrario no existe el dato.
1. Las dos claves son iguales, la que se busca y la que tiene el nodo: el elemento es encontrado.
2. La clave buscada es pequeña: el elemento buscado se encuentra solamente en los subarboles
izquierdo.
3. La clave buscada es mayor: el elemento buscado se encuentra solamente en los subarboles
derecho.
Este proceso se realiza hasta que la clave es encontrada. Notar que la estructura y crecimiento de
los árboles binarios son a través de una relación de orden, por tal motivo se generan varios árboles
con rotulaciones distintas. Por ejemplo, se dan las 3 claves 1, 2 y 3, tan solo con estas podemos
generar distintos árboles binarios respetuosos del orden antes descrito, por ejemplo
existiendo 6 distintas formas de rotularlos y por ende 6 árboles binarios distintos. En general, se
demuestra que n-elementos generan n! formas.
_____________________________________________________________________________
Escuela de Ingeniería en Computación, Universidad de La Serena.
7
Diseño y Análisis de Algoritmos. (DAA-2009)
Operaciones
Dr. Eric Jeltsch F.
Veamos como generar un árbol de búsqueda binaria a partir del árbol vacío, al cual se le agregan
los datos (12, 7, 15, 5, 13).
Inserción en un ABB
1) comparar la clave a insertar con la raíz del árbol. Si es mayor, debe avanzar hacia el subarbol
derecho. Si es menor, debe avanzar hacia el subarbol izquierdo. Repetir sucesivamente 1), hasta que
se cumpla alguna de las siguientes condiciones:
a)
el subarbol derecho es vacío, o el subarbol izquierdo es vacío; en cuyo caso se procede a
insertar el elemento en el lugar que le corresponda.
b)
la clave que se quiere insertar es igual a la raíz del árbol; en cuyo caso no se realiza la
inserción.
Por ejemplo, insertar 120, 87 y 130, en ese orden a partir del árbol vacío.
Eliminación en un ABB
Se refiere a eliminar un Nodo con una determinada clave, suponiendo que el elemento ha sido
encontrado. Existen varias situaciones, entre ellas están:
a)
Si el elemento a borrar es terminal u hoja, simplemente se elimina.
b)
Si el elemento a borrar tiene un solo descendiente, entonces tiene que sustituirse por ese
descendiente.
_____________________________________________________________________________
Escuela de Ingeniería en Computación, Universidad de La Serena.
8
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
c)
Si el elemento a borrar tiene los 2 descendientes, entonces se tiene que sustituir por el nodo
que se encuentra más a la izquierda en el subárbol derecho o por el nodo que se encuentra más a la
derecha en el subárbol izquierdo.
Ejemplo:
Veamos como va quedando el ABB, luego de realizar una serie de eliminaciones.
Para cuando se eliminen los valores 2 y 6. Se tiene (caso a)
_____________________________________________________________________________
Escuela de Ingeniería en Computación, Universidad de La Serena.
9
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
a) 13(caso b)
d) 15 (caso b)
e) 5 (caso a)
f) 12 (caso c)
Dado el árbol búsqueda binaria. Se desea eliminar el 12, ¿como queda el ABB.?
_____________________________________________________________________________ 10
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Recorrido y orden en Arboles Binario
El principio de recorrer un árbol binario, determina un orden sobre el conjunto de nodos. Existen 3
posibilidades o principios de como recorrer un árbol binario, ellos son la lectura en Inorden,
Preorden y Posorden.
Inorden
Orden IRD
(1) Recorrer el subárbol izquierdo en INORDEN
(2) Visitar la raíz
(3) Recorrer el subárbol derecho en INORDEN
Orden DRI
(1) Recorrer el subárbol derecho en INORDEN
(2) Visitar la raíz
(3) Recorrer los subárbol derecho en INORDEN
Los orden IRD y DRI son uno inverso del otro. El orden IRD, se llama orden simétrico
Preorden
Orden RID
(1) Visitar la raíz
(2) Recorrer el subárbol izquierdo en PREORDEN
(3) Recorrer el subárbol derecho en PREORDEN
Orden RDI
(1) Visitar la raíz
(2) Recorrer el subárbol derecho en PREORDEN
(3) Recorrer el subárbol izquierdo en PREORDEN
En general se visita la raíz antes de los dos subárboles.
Posorden
Orden IDR
(1) Recorrer el subárbol izquierdo en POSTORDEN
(2) Recorrer el subárbol derecho en POSTORDEN
_____________________________________________________________________________ 11
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
(3) Visitar la raíz
Dr. Eric Jeltsch F.
Primero se visitan los subárboles y luego la raíz.
Orden DIR
(1) Recorrer el subárbol derecho en POSTORDEN
(2) Recorrer el subárbol izquierdo en POSTORDEN
(3) Visitar la raíz
Primero se visitan los subárboles y luego la raíz.
Por ejemplo, se dan una serie de árboles binarios, y la idea es describir su recorrido en las formas
antes definidas.
1.-
2.
Este ejemplo nos muestra una estructura de árbol (representación de una estructura jerarquica de
una expresión aritmética). Esta representación „arbórea“ es en particular muy útil para la traducción
de una expresión en lenguaje de máquina. Desde la estructura anterios se pueden representar
fácilmente las distintas formas de una expresión aritmética. Entregando de esta manera el recorrido
en "Posorden" como la notación Postfija, y en "Preorden" la notación Prefija".
3.
_____________________________________________________________________________ 12
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
4.
Aplicaciones de los recorridos
Con ayuda de los recorridos antes descritos se pueden determinar algunas otras operaciones sobre
los árboles. Por ejemplo, determinar el número de hojas en el árbol, entregar la altura del árbol,
copiar el árbol, borrar el árbol, descripción gráfica de un árbol de búsqueda binaria. En un árbol de
búsqueda binario se puede dar la siguiente situación, la que se interpreta como un árbol de
búsqueda binario que degenero en una lista lineal, derivando en que la búsqueda de algún elemento
en particular resulta tan costoso como buscarlo en forma exhaustiva, de aquí que es importante
evitar que se genere una situación de este tipo.
Para ello, están los „árboles perfectamente balanceados“ que evitan que se de una situación como la
descrita a continuación, de manera de obtener una forma de balanceo que en definitiva facilita la
búsqueda de algún elemento, pues no se encuentra a una profundidad tan alejado de la raíz. Se
verifica que el árbol de la derecha es árbol AVL, que luego lo veremos en detalle.
A continuación se muestra un simple algoritmo que genera un árbol con las condiciones de
balanceo.
(1) ordenar las claves en una sucesión ordenada en forma creciente
(2) es conocido el número de Objetos (claves) que se deben tener.
_____________________________________________________________________________ 13
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Se consideran dos tipos de recorrido: recorrido en profundidad y recorrido en anchura o a nivel.
Puesto que los árboles no son secuenciales como las listas, hay que buscar estrategias alternativas
para visitar todos los nodos. Dada la siguiente figura:
Figura 1
- Recorridos en profundidad:
Recorrido en preorden: consiste en visitar el nodo actual (visitar puede ser simplemente mostrar la
clave del nodo por pantalla), y después visitar el subárbol izquierdo y una vez visitado, visitar el
subárbol derecho. Es un proceso recursivo por naturaleza. Si se hace el recorrido en preorden del
árbol de la figura 1 las visitas serían en el orden siguiente: a,b,d,c,e,f.
Recorrido en inorden u orden central: se visita el subárbol izquierdo, el nodo actual, y después se
visita el subárbol derecho. En el ejemplo de la figura 1 las visitas serían en este orden: b,d,a,e,c,f.
private void printTree(Nodo_ABB b)
{
if(b != null)
{
printTree(b.izq);
System.out.print(b.dat);
System.out.print(' ');
printTree( b.der );
}
}
Recorrido en postorden: se visitan primero el subárbol izquierdo, después el subárbol derecho, y
por último el nodo actual. En el ejemplo de la figura 1 el recorrido quedaría así: d,b,e,f,c,a.
La ventaja del recorrido en postorden es que permite borrar el árbol de forma consistente. Es decir,
si visitar se traduce por borrar el nodo actual, al ejecutar este recorrido se borrará el árbol o
subárbol que se pasa como parámetro. La razón para hacer esto es que no se debe borrar un nodo y
después sus subárboles, porque al borrarlo se pueden perder los enlaces, y aunque no se perdieran
se rompe con la regla de manipular una estructura de datos inexistente. Una alternativa es utilizar
una variable auxiliar, pero es innecesario aplicando este recorrido.
- Recorrido en amplitud:
_____________________________________________________________________________ 14
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Consiste en ir visitando el árbol por niveles. Primero se visitan los nodos de nivel 1 (como mucho
hay uno, la raíz), después los nodos de nivel 2, así hasta que ya no queden más. Si se hace el
recorrido en amplitud del árbol de la figura una visitaría los nodos en este orden: a,b,c,d,e,f.
Construcción de un árbol binario
Hasta el momento se ha visto la declaración y recorrido de un árbol binario. Sin embargo no se ha
estudiado ningún método para crearlos. A continuación se estudia un método para crear un árbol
binario que no tenga claves repetidas partiendo de su recorrido en preorden e inorden, almacenados
en sendos arrays.
Antes de explicarlo se recomienda al lector que lo intente hacer por su cuenta, es sencillo cuando
uno es capaz de construir el árbol viendo sus recorridos pero sin haber visto el árbol terminado.
Partiendo de los recorridos preorden e inorden del árbol de la figura 1 puede determinarse que la
raíz es el primer elemento del recorrido en preorden. Ese elemento se busca en el array inorden. Los
elementos en el array inorden entre izq y la raíz forman el subárbol izquierdo. Asimismo los
elementos entre der y la raíz forman el subárbol derecho. Por tanto se tiene este árbol:
A continuación comienza un proceso recursivo. Se procede a crear el subárbol izquierdo, cuyo
tamaño está limitado por los índices izq y der. La siguiente posición en el recorrido en preorden es
la raíz de este subárbol. Queda esto:
El subárbol b tiene un subárbol derecho, que no tiene ningún descendiente, tal y como indican los
índices izq y der. Se ha obtenido el subárbol izquierdo completo de la raíz a, puesto que b no tiene
subárbol izquierdo:
_____________________________________________________________________________ 15
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Después seguirá construyéndose el subárbol derecho a partir de la raíz a.
Aplicación:
Se tiene un archivo de texto ASCII. Para este propósito puede servir cualquier libro electrónico de
la librería Gutenberg o Cervantes, que suelen tener varios cientos de miles de palabras. El objetivo
es clasificar todas las palabras, es decir, determinar que palabras aparecen, y cuantas veces aparece
cada una. Palabras como 'niño'-'niña', 'vengo'-'vienes' etc, se consideran diferentes para simplificar
el problema. Se pide, escribir un programa, que recibiendo como entrada un texto, realice la
clasificación descrita anteriormente.
Nótese que el empleo de una lista enlazada ordenada no es una buena solución. Si se obtienen hasta
20.000 palabras diferentes, por decir un número, localizar una palabra cualquiera puede ser, y en
general lo será, muy costoso en tiempo. Se puede hacer una implementación por pura curiosidad
para evaluar el tiempo de ejecución, pero no merece la pena.
La solución pasa por emplear un árbol binario de búsqueda para insertar las claves. El valor de
log(20.000) es aproximadamente de 14. Eso quiere decir que localizar una palabra entre 20.000
llevaría en el peor caso unos 14 accesos. El contraste con el empleo de una lista es simplemente
abismal. Por supuesto, como se ha comentado anteriormente el árbol no va a estar perfectamente
equilibrado, pero nadie escribe novelas manteniendo el orden lexicográfico (como un diccionario)
entre las palabras, asi que no se obtendrá nunca un árbol muy degenerado. Lo que está claro es que
cualquier evolución del árbol siempre será mejor que el empleo de una lista.
Implementación en Java
Por último, veamos un ejemplo o propuesta de implementación de los árboles ABB. Se muestra una
implementación (No genérica, es decir no usa “Generics”), pero si usa la interface Comparable y
otras que vimos en programación orientada a objetos. La propuesta se muestra en dos archivos, uno
de ellos es Arbol_ABB.java y el otro Arbol_ABBTest.java.
class Nodo_ABB
{
// Instancia (variables)
protected Nodo_ABB izq;
protected Nodo_ABB der;
public Comparable dat;
// sub arbol izq
// sub arbol derecho
// datos de los nodos
// Constructor
public Nodo_ABB(Comparable datElement)
_____________________________________________________________________________ 16
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
{
this(datElement, null, null );
}
public Nodo_ABB(Comparable datElement, Nodo_ABB i, Nodo_ABB d)
{
dat
= datElement;
izq = i;
der = d;
}
public Nodo_ABB getIzq()
{
return izq;
}
public Nodo_ABB getDer()
{
return der;
}
}
public class Arbol_ABB
{
/* la raíz del árbol */
private Nodo_ABB root;
/*
* Constructor.
*/
public Arbol_ABB()
{
root = null;
}
/*
* agregar un elemento en el ABB
* los valores duplicados son ignorados
*/
public void insert( Comparable x )
{
root = insert(x, root);
}
/*
* eliminar de un ABB. En caso que no este, no pasa nada
*/
public void remove( Comparable x )
{
root = remove(x, root);
}
/*
* hallar el menor elemento del ABB
*/
public Comparable findMin()
{
return elementAt(findMin(root));
}
/*
* hallar el más grande elemento en el ABB
*/
public Comparable findMax()
{
return elementAt(findMax(root));
}
/*
* hallar un elemento (dato) en el ABB
*/
_____________________________________________________________________________ 17
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
public Comparable find(Comparable x)
{
return elementAt(find(x, root));
}
/*
* Hacer el ABB vacío
*/
public void makeEmpty()
{
root = null;
}
/*
* Test, para saber si el ABB esta vacio
*/
public boolean isEmpty()
{
return root == null;
}
/*
* salida de los elementos en orden
*/
public void printTree()
{
if( isEmpty( ) )
System.out.println( "ABB esta vacio" );
else printTree( root );
}
/*
* salida de los elementos, pero deben interpretarse (90 Grad rotados)
*/
public void salidaABB()
{
if( isEmpty() )
System.out.println( "ABB vacio" );
else
salidaABB( root,0 );
}
private Comparable elementAt( Nodo_ABB b )
{
return b == null ? null : b.dat;
}
//insertar
private Nodo_ABB insert(Comparable x, Nodo_ABB b)
{
if( b == null )
b = new Nodo_ABB( x, null, null );
else if( x.compareTo( b.dat ) < 0 )
b.izq = insert( x, b.izq );
else if( x.compareTo( b.dat ) > 0 )
b.der = insert( x, b.der );
else
; // Duplicado, no hace nada
return b;
}
//eliminar
private Nodo_ABB remove(Comparable x, Nodo_ABB b)
{
if( b == null )
return b;
// no encontrado, no hace nada.
if( x.compareTo(b.dat) < 0 )
b.izq = remove(x, b.izq );
else if( x.compareTo(b.dat) > 0 )
_____________________________________________________________________________ 18
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
b.der = remove( x, b.der );
else if( b.izq != null && b.der != null ) // dos hijos
{
b.dat = findMin(b.der).dat;
b.der = remove(b.dat, b.der);
}
else
b = ( b.izq != null ) ? b.izq : b.der;
return b;
}
//hallar el min
private Nodo_ABB findMin(Nodo_ABB b)
{
if (b == null)
return null;
else if( b.izq == null)
return b;
return findMin(b.izq );
}
//hallar el max
private Nodo_ABB findMax( Nodo_ABB b)
{
if( b != null )
while( b.der != null )
b = b.der;
return b;
}
//buscar un elemento
private Nodo_ABB find(Comparable x, Nodo_ABB b)
{
if(b == null)
return null;
if( x.compareTo(b.dat ) < 0)
return find(x, b.izq);
else if( x.compareTo(b.dat) > 0)
return find(x, b.der);
else
return b;
// hallado!
}
//imprimir de una forma .. inorden
private void printTree(Nodo_ABB b)
{
if(b != null)
{
printTree(b.izq);
System.out.print(b.dat);
System.out.print(' ');
printTree( b.der );
}
}
/*
* salida, pero el arbol esta rotado en 90 Grad.(puede mejorarse)
*/
private void salidaABB(Nodo_ABB b, int paso)
{
if (b != null)
{
salidaABB(b.izq, paso + 1);
for (int i = 0; i < paso; i++)
{
System.out.print(' ');
}
_____________________________________________________________________________ 19
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
System.out.println(b.dat);
salidaABB(b.der, paso + 1);
}
}
}
public class Arbol_ABB_Test
{
// Test o pruebas
public static void main( String [] args )
{
// Test Nr. 1
Arbol_ABB b = new Arbol_ABB();
final int NUMERO = 4000;
final int ESPACIO = 37;
System.out.println(
"Probando....");
for( int i = ESPACIO; i != 0; i = (i + ESPACIO) % NUMERO)
b.insert( new Integer( i ) );
for(int i = 1; i < NUMERO; i+= 2 )
b.remove( new Integer( i ) );
if (NUMERO < 40)
b.printTree( );
if( ((Integer)(b.findMin( ))).intValue( ) != 2 ||
((Integer)(b.findMax( ))).intValue( ) != NUMERO - 2 )
System.out.println( "fallas en FindMin o FindMax !" );
for( int i = 2; i < NUMERO; i+=2 )
if( ((Integer)(b.find( new Integer( i ) ))).intValue( ) != i )
System.out.println( "hallar fallas!" );
for( int i = 1; i < NUMERO; i+=2 )
{
if( b.find( new Integer( i ) ) != null )
System.out.println( "encontrar error!" );
}
// Test Nr.2
Arbol_ABB b1 = new Arbol_ABB();
for (int i = 0; i < 10; i++)
{
// genera nº entre 0 y 100
Integer r = new Integer((int)(Math.random()*100));
b1.insert(r);
}
System.out.println("recorrido en Inorden");
b1.printTree();
System.out.println();
System.out.println("representacion del arbol, rotar en 90 Grados");
b1.salidaABB();
System.out.print("el menor valor: ");
System.out.print(((Integer)(b1.findMin())).intValue());
System.out.println();
System.out.print("el mayor valor: ");
System.out.print(((Integer)(b1.findMax())).intValue());
System.out.println();
for (int i = 0; i < 10; i++)
{
// genera nº entre 0 y 100
Integer r = new Integer((int)(Math.random()*100));
if ( b1.find(r) != null )
{
b1.remove( r );
}
// else System.out.println(r.intValue() + " no encontrado.");
_____________________________________________________________________________ 20
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
}
b1.salidaABB();
// Test Nr. 3
Arbol_ABB b2 = new Arbol_ABB();
for (int i = 0; i < 20; i++)
{
String s = "nº random " + (int)(Math.random() * 100);
b2.insert(s);
}
b2.printTree(); // aparecen ordenados
}
}
Arboles AVL
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-VelskiiLandis), 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. Lo substancial de este informe se centra en poder superar el
problema generado por la inserción en los ABB que podían degenerar en una lista proporcional a
los datos ingresados y por otra reforzar los conceptos adquiridos en Estructuras de Datos.
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.
_____________________________________________________________________________ 21
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Ejemplo:
Los tres árboles que se muestran son del tipo AVL, mientras que los de enfrente no lo son.
Ejemplo:
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 sub-arboles, 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.
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).
En este otro caso, todos los nodos satisfacen la condición de balance, de manera que es un árbol
AVL, como muestra Fig. 2
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)
_____________________________________________________________________________ 22
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
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
1) En este árbol, son insertados los nodos con claves 9 y 11. Generándose el árbol T1 :
2) Basado en T, son insertados los nodos con claves 1, 3, 5 y 7. Generándose el árbol T2:
_____________________________________________________________________________ 23
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Ya al insertar la clave „1“ el árbol pierde la propiedad de AVL.. De aquí el aplicar una doble
rotación.
Arbol obtenido luego de insertar las claves 3, 5 y 7.
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. La figura 7 muestra las variantes simétricas y la doble rotación.
Fig. 7
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
_____________________________________________________________________________ 24
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
protected Nodo_Avl izq;
protected Nodo_Avl der;
protected int altura;
public Comparable datos;
Dr. Eric Jeltsch F.
// hijo Izquierdo
// hijo derecho
// altura
// elementos
// 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.
Fig.8: Rotación Izq-Der, Rotación Doble y en forma similar Rotación Der-Izq, Rotación Doble.
/*
Rotación Doble, basada en Fig. 8: Este método solo puede ser usado si 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 );
}
_____________________________________________________________________________ 25
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Ejemplo:
Dr. Eric Jeltsch F.
Rot.der-izq
En este último árbol se inserta la clave 13, quedando
Rot.der-izq
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.
Implementación de los árboles AVL
//En archivo Arbol_AvlTest.java
public class Arbol_AvlTest
{
// Programa Test
public static void main(String [] args)
_____________________________________________________________________________ 26
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
{
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();
}
}
//En archivo Arbol_Avl.java
/*
* Comparaciones se basan en el método compareTo.(REPASAR lo visto en POO)
*/
public class Arbol_Avl
{
/* Raiz del Arbol */
private Nodo_Avl raiz;
/*
* Constructor por defecto
*/
public Arbol_Avl( )
{
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......(Tarea)
/*
* 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
*/
_____________________________________________________________________________ 27
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
//no esta implementado.....(Tarea)
/*
* Eliminar el arbol.
*/
//no esta implementado....(Tarea)
/*
* 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);
}
/*
* 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
_____________________________________________________________________________ 28
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
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.
*/
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
}
// recorrido en Inorden
_____________________________________________________________________________ 29
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
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)
{
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;
_____________________________________________________________________________ 30
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
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);
}
}
//En archivo Nodo_Avl.java
// Declaración de la clase Nodos para los elementos en los arbol 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;
}
}
_____________________________________________________________________________ 31
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
La implementación de los árboles AVL, así como su salida están basados en los ejemplos dados.
Para interpretar la salida considerar que se debe girar el árbol resultante de los datos 0, 1, 2, 3, 4, 5,
6, ya que los otros son al azar.
Propuestas de mejora: 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.
carácter que desea insertar al arbol-AVL (Borrar: \n): a
a insertado
AVL con balanceo:
a(0)
carácter 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)
_____________________________________________________________________________ 32
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
b(0)
a(0)
Dr. Eric Jeltsch F.
Arboles Splay
Introducción: Como una forma de introducir el tema, digamos que se desea implementar un
sistema de software posible de llevar la atención a los pacientes de un hospital o consultorio, en el
cual (dependiendo de su dolencia) se supone que cualquier paciente no tiene igual probabilidad de
ser atendido. Por otra parte, cuando un paciente deja el hospital, es posible que su ficha clínica se
vuelva menos activa, aunque es posible que eventualmente se consulte ella para hacer un historial
clínico del paciente. Por otra parte, si después de un tiempo se vuelve a atender, se volverá activo de
nuevo. Y finalmente, no es posible archivarlos cuando no están activos, pues pueden ocurrir nuevas
atenciones, especialmente en pacientes crónicos, cuyas consultas pueden ser muy frecuentes en
alguna época del año. La pregunta que surge es entonces que tipo de estructura de dato es la más
adecuada en este tipo de situaciones. Digamos que si usamos ABB o AVL, los registros más
antiguos irían a las hojas del árbol y por consiguiente sería más costoso su acceso. La pregunta es
como traerlos “de abajo” sin grandes costos “hacia arriba”. Recordar que “más arriba “, significa
que los accesos al disco son menos costosos que si los datos se encontraran en las hojas en un árbol
de gran profundidad. En todo caso cualquier esfuerzo que se haga para traerlos repercutirá en el
total. Lo sustantivo de esta nueva estructura de dato es reducir el tiempo total consumido
accediendo a los elementos, en donde son trasladados los que son accedidos más frecuentemente
hacia la raíz del árbol, donde pueden ser encontrados más rápidamente.
En un sentido más técnico, los árboles ABB, AVL y Red-Black, la memoria de accesos a los
elementos no es utilizada explícitamente; el objetivo es, en cambio, asegurar que el árbol nunca se
desequilibre, garantizando de este modo que cualquier acceso nunca requiere más tiempo que
O(logn). Con los árboles Splay no nos preocupamos tanto del costo de un acceso individual, que
puede ser tan malo como O(n); en su lugar queremos asegurar que este tipo de comportamiento no
puede producirse repetidamente. Este informe se centra en poder reconocer los árboles Splay y por
otra reforzar los conceptos adquiridos en Estructuras de Datos, como al mismo tiempo practicar su
implementación y aplicación con la ayuda del lenguaje de programación Java.
Definición: Un árbol Splay es un árbol binario de búsqueda en el que todas las operaciones sobre el
conjunto dinámico se implementan utilizando un esquema de reestructuración llamado biselación.
La forma en la que un nodo es “rotado” hasta la raíz debe ser considerado dentro de los siguientes
esquemas o patrones. Supongamos que se está realizando una biselación en el nodo v, cuyos nodos
padre y abuelo (si existen) están etiquetados por p y g respectivamente. Los 3 casos siguientes
poseen una variante simétrica.
Caso1(zig): Si p es la raíz del árbol y v es su hijo izquierdo, realizar una operación rotar-derecha(p)
tal como se muestra en la siguiente figura. En donde p es la raíz. Este proceso concluye cuando v
haya alcanzado la raíz.
Caso2(zig-zig): Si p no es la raíz del árbol, sino que tanto v como p son hijos izquierdos, realizar la
operación rotar-derecha(g) seguido de rotar-derecha(p), tal como se muestra en la siguiente figura.
En donde p es la raíz. Este proceso concluye cuando v haya alcanzado la raíz.
_____________________________________________________________________________ 33
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Caso3(zig-zag): Si p no es la raíz del árbol, sino que p es un hijo izquierdo y v es un hijo derecho,
realizar la operación rotar-izquierda(p) seguido de rotarderecha(g), tal como se muestra en la
siguiente figura. Este proceso concluye cuando v haya alcanzado la raíz.
Ejemplo: Consideremos que sucede después de haber accesado k1 sobre el árbol dado. El acceso al
nodo k1 es a través de la línea punteada.
Primeramente realizamos una rotación simple entre k1 y su padre (k2), obteniendo el siguiente
árbol,
Luego rotamos entre k1 y k3, obteniendo el siguiente árbol
_____________________________________________________________________________ 34
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Finalmente debemos realizar dos rotaciones más, para obtener que k1 alcance la raíz.
Ejemplo:
Se muestra un ejemplo respecto de la búsqueda de una clave y las distintas operaciones a realizar.
_____________________________________________________________________________ 35
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Ejemplo:
En este caso se busca el 2. Splay(2) es genérico en término que intervienen una serie de
operaciones. ¿Cuales son ellas?.
Desgraciadamente, se puede apreciar que a través de las rotaciones el nodo k3 quedo situado en los
niveles más inferiores, si lo comparamos con la posición que tenía antes.
Notar que si supiéramos las frecuencias de acceso a cada elemento, podríamos armar un árbol en el
que los elementos más accedidos estén más cerca de la raíz. De allí entonces el considerar este tipo
de estructura árbol Splay, como una buena opción para cuando se tenga una distribución de
probabilidades.
Buscar: Supongamos que estamos buscando un elemento con clave k. Dado que un árbol biselado
satisface la propiedad de los ABB para buscar un elemento es que se puede aplicar la misma
técnica. Si se encuentra un nodo v conteniendo un elemento con clave k, el árbol es biselado en v y
se devuelve el elemento. Si, la búsqueda es fallida, entonces el último nodo examinado antes de
alcanzar un puntero nulo es biselado y se devuelve el valor nulo.
Insertar: Se usa el método estándar de los ABB. Esto es, comenzamos buscando el punto de
inserción; si se encuentra un puntero nulo se añade el nuevo nodo al árbol en este punto. A
continuación el nuevo nodo es biselado. No obstante, si ya existe en el árbol un elemento con la
clave que estamos buscando se bisela ese nodo.
Eliminar: Esta es la operación mas complicada dado que involucra dos biselaciones. Primero, el
nodo que queremos eliminar debe ser encontrado (si no se puede encontrar, entonces es biselado el
último nodo encontrado). Asumamos que la clave que estamos buscando se encuentra en el nodo v.
Entonces v es biselado a la raíz y luego eliminado, tal como lo muestra la figura siguiente.
Esto nos deja con dos árboles biselados separados. La propiedad de los ABB asegura que toda clave
de I es menor que toda clave de D. Para unir estos dos árboles de nuevo reorganizamos uno de los
árboles, sea I, en donde se ejecuta la operación Máximo sobre él. Esto causa que el predecesor en
inorden de v, denotado v‘, sea llevado a la raíz de I. Ahora como v’ contiene la clave máxima de I
_____________________________________________________________________________ 36
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
no tendrá hijo derecho y la operación se completa haciendo que la raíz de D sea hijo derecho de v’,
tal como lo muestra la figura siguiente.
Si se desea en cambio reorganizar D, debería ejecutarse Mínimo sobre D antes de añadirle I.
Implementación de los árboles Splay
class NodoArbolBinario
{
// variables Instancias
protected NodoArbolBinario izq;
protected NodoArbolBinario der;
public Comparable datos;
// subarbol izquierdo
// subarbol derecho
// datos contenidos en el Nodo
// Constructor
public NodoArbolBinario(Comparable datosElement)
{
this(datosElement, null, null );
}
public NodoArbolBinario(Comparable datosElement, NodoArbolBinario iz,
NodoArbolBinario de)
{
datos
= datosElement;
izq
= iz;
der
= de;
}
public void insertar (Comparable x)
{
if (x.compareTo(datos) > 0)
// luego a la derecha
{
if (der == null)
der = new NodoArbolBinario(x);
else der.insertar(x);
}
else // sino izquierda
{
if (izq == null)
izq = new NodoArbolBinario(x);
else izq.insertar(x);
}
}
public NodoArbolBinario getIzq()
{
return izq;
}
public NodoArbolBinario getDer()
{
return der;
}
}
/*
* Implementacion de un proceso top-down sobre los arboles Splay.
_____________________________________________________________________________ 37
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
* Las comparaciones utilizan el método compareTo().
*/
public class ArbolSplay
{
private NodoArbolBinario raiz;
private static NodoArbolBinario Nodonull;
{
Nodonull = new NodoArbolBinario( null );
Nodonull.izq = Nodonull.der = Nodonull;
}
private static NodoArbolBinario nuevoNodo = null;
// en diversos formas de inserción sera utilizado
private static NodoArbolBinario ini = new NodoArbolBinario(null);
/*
* Constructor.
*/
public ArbolSplay( )
{
raiz = Nodonull;
}
/*
* manipulando la raiz
*/
public NodoArbolBinario buscarRaiz()
{
return raiz;
}
/*
* Insertar.
* Parametro x es el elemento a agregar.
*/
public void insertar( Comparable x )
{
if( nuevoNodo == null )
nuevoNodo = new NodoArbolBinario( null );
nuevoNodo.datos = x;
if( raiz == Nodonull )
{
nuevoNodo.izq = nuevoNodo.der = Nodonull;
raiz = nuevoNodo;
}
else
{
raiz = splay( x, raiz );
if( x.compareTo( raiz.datos ) < 0 )
{
nuevoNodo.izq = raiz.izq;
nuevoNodo.der = raiz;
raiz.izq = Nodonull;
raiz = nuevoNodo;
}
else if( x.compareTo( raiz.datos ) > 0 )
{
nuevoNodo.der = raiz.der;
nuevoNodo.izq = raiz;
raiz.der = Nodonull;
raiz = nuevoNodo;
}
_____________________________________________________________________________ 38
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
else return;
}
nuevoNodo = null;
//
}
/*
* Remover.
* Parametro x es el elemento a eliminar.
*/
public void remover( Comparable x )
{
NodoArbolBinario nuevoArbol;
// Caso que x es encontrado, deja x en la raíz
raiz = splay( x, raiz );
if( raiz.datos.compareTo( x ) != 0 )
return;
// Elemento no encontrado; no hace nada
if( raiz.izq == Nodonull )
nuevoArbol = raiz.der;
else
{
// Hallar el máximmo en el subarbol izquierdo
nuevoArbol = raiz.izq;
nuevoArbol = splay( x, nuevoArbol );
nuevoArbol.der = raiz.der;
}
raiz = nuevoArbol;
}
/*
* Determinar el elemento más pequeño del árbol.
* Devuelve: el elemento más pequeño, o null caso vacío.
*/
public Comparable hallarMin( )
{
if( esVacio( ) ) return null;
NodoArbolBinario ptr = raiz;
while( ptr.izq != Nodonull ) ptr = ptr.izq;
raiz = splay( ptr.datos, raiz );
return ptr.datos;
}
/*
* Determinar el elemento más grande del árbol.
* Devuelve: el elemento más grande, o null, caso vacío.
*/
public Comparable hallarMax( )
{
if (esVacio( )) return null;
NodoArbolBinario ptr = raiz;
while( ptr.der != Nodonull ) ptr = ptr.der;
raiz = splay( ptr.datos, raiz );
return ptr.datos;
}
/*
* Determina un elemento en el árbol.
* Parametro x contiene el elemento buscado.
* Devuelve: El elemento adecuado o null, caso vacío
*/
public Comparable hallar( Comparable x )
{
raiz = splay( x, raiz );
if (raiz.datos.compareTo( x ) != 0) return null;
return raiz.datos;
}
_____________________________________________________________________________ 39
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
/*
* Hacer el arbol vacío.
*/
public void hacerVacio( )
{
raiz = Nodonull;
}
/*
* Comprobar si el árbol es vacío
* Devuelve true caso vacío, en otro caso false.
*/
public boolean esVacio( )
{
return raiz == Nodonull;
}
/*
* Entrega el contenido del árbol según un cierto formato u orden.
*/
public void printArbol( )
{
if (esVacio( ))
System.out.println( "Arbol vacio" );
else
printArbol( raiz );
}
/*
* Salida del arbol binario rotado en 90 Grados
*/
public void salidaArbolBinario(NodoArbolBinario b, int nivel)
{
if (b != b.izq)
{
salidaArbolBinario(b.izq, nivel + 3);
for (int i = 0; i < nivel; i++)
{
System.out.print(' ');
}
System.out.println(b.datos.toString());
salidaArbolBinario(b.der,nivel + 3);
}
}
/*
* Metodo Interno para llevar a cabo un "top down" para splay.
* El último Nodo encontrado en el árbol es la nueva raíz.
* Parametro x es elemento objetivo, para el proceso Splaying o "biselado".
* Parameter b es la raíz del subárbol, luego de llevado a cabo el Splaying.
* Devuelve de subarbol.
*/
private NodoArbolBinario splay( Comparable x, NodoArbolBinario t )
{
NodoArbolBinario izqArbolMax, derArbolMin;
ini.izq = ini.der = Nodonull;
izqArbolMax = derArbolMin = ini;
Nodonull.datos = x;
// garantiza coincidencia
for( ; ; )
if( x.compareTo( t.datos ) < 0 )
{
if( x.compareTo( t.izq.datos ) < 0 )
t = rotacion_con_HijoIzq( t );
if( t.izq == Nodonull ) break;
// a la derecha
derArbolMin.izq = t;
derArbolMin = t;
_____________________________________________________________________________ 40
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
t = t.izq;
}
else if( x.compareTo( t.datos ) > 0 )
{
if( x.compareTo( t.der.datos ) > 0 )
t = rotacion_con_HijoDer( t );
if( t.der == Nodonull ) break;
// a la izquierda
izqArbolMax.der = t;
izqArbolMax = t;
t = t.der;
}
else break;
izqArbolMax.der = t.izq;
derArbolMin.izq = t.der;
t.izq = ini.der;
t.der = ini.izq;
return t;
}
/*
* Rotacion NodoArbolBinario con descendiente a la izquierda.
*/
static NodoArbolBinario rotacion_con_HijoIzq(NodoArbolBinario k2)
{
NodoArbolBinario k1 = k2.izq;
k2.izq = k1.der;
k1.der = k2;
return k1;
}
/*
* Rotacion NodoArbolBinario con descendiente a la derecha.
*/
static NodoArbolBinario rotacion_con_HijoDer(NodoArbolBinario k1)
{
NodoArbolBinario k2 = k1.der;
k1.der = k2.izq;
k2.izq = k1;
return k2;
}
private void printArbol( NodoArbolBinario b )
{
if( b != b.izq )
{
printArbol(b.izq);
System.out.println(b.datos.toString( ));
printArbol(b.der);
}
}
}
import java.io.*;
public class ArbolSplayTest
{
public static void main( String [ ] args )
{
ArbolSplay b = new ArbolSplay();
String lineaEntrada = null;
System.out.println("Ingrese Datos");
BufferedReader entrada = null;
entrada = new BufferedReader( new InputStreamReader(System.in));
try {
_____________________________________________________________________________ 41
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
int numero;
do
{
System.out.println("Numero ? ");
lineaEntrada = entrada.readLine();
try {
numero = Integer.parseInt(lineaEntrada);
b.insertar(new Integer(numero));
b.salidaArbolBinario(b.buscarRaiz(),2);
}
catch (NumberFormatException ne)
{ break; }
} while (lineaEntrada != "");
}
catch (IOException ioe)
{
System.out.println("Entrada en main()");
}
System.out.println("Hallar el elemento mas pequeño");
b.hallarMin();
b.salidaArbolBinario(b.buscarRaiz(),2);
System.out.println("Hallar el elemento más grande");
b.hallarMax();
b.salidaArbolBinario(b.buscarRaiz(),2);
}
}
Una forma de visualizar su funcionamiento le ingresamos las claves 1, 2, 3, 4, 5.
cuya salida debería interpretarse como el árbol que se adjunta. Más aún si ahora, en este momento
pulsa <enter> podrá obtener en la raíz el más grande y el más pequeño de los elementos.
_____________________________________________________________________________ 42
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
cuyos árboles Splay son los que se muestran.
Arbol Red-Black
Introducción: Supongamos que se desea tener un Buscador de Direcciones de Email, el cual
almacena nombres y direcciones de e-mail en un archivo map basado en la clase java.util.TreeMap
definida en el JDK 1.2. La clase TreeMap crea una estructura de datos llamada "red-black tree", en
el cual, los datos son almacenados con una clave y un valor, es decir, el nombre es la clave y la
dirección e-mail el valor.
Cuando añadimos una entrada al archivo map, introducimos tanto un nombre (la clave) como una
dirección e-mail (el valor). De manera que, podemos buscar o borrar una dirección e-mail
introduciendo sólo un nombre. El nombre no puede ser null porque es una clave. Si un usuario
intenta introducir un nombre null, la aplicación lanza una excepción y muestra una página de error.
Esta es una aplicación en donde se utilizan la estructura de datos Red-Black para organizar los
datos.
Un árbol rojo-negro es un árbol binario extendido, en donde los nodos pueden ser ramas u hojas.
Los nodos hojas son los nodos que hay al final de una línea, mientras que los nodos ramas son los
nodos más grandes que conectan con dos o más líneas. Los nodos se almacenan en una estructura
compensada en el árbol, usando las siguientes condiciones:
_____________________________________________________________________________ 43
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
1. Cada nodo tiene dos hijos o es una hoja.
2. Cada nodo está coloreado en rojo en negro.
3. Cada nodo hoja está coloreado en negro.
4. Si un nodo es rojo, sus dos hijos son negros.
5. Cada camino desde el raíz hasta una hoja contiene el mismo número de nodos negros.
Ejemplo
Aquí vemos un Red-Black válido, y otro que no lo es.
Todo nodo es ya sea red (circulo) ó black (cuadrado)
• La raíz es black,
• Toda hoja (leaf ) es black, a pesar que no se vean,
• Si un nodo es red, entonces ambos hijos son black
- dos nodos red pueden no ser adyacentes.
- Pero si un nodo es black, sus hijos pueden ser red o black,
• Para todo nodo, todos los caminos (paths) desde él a sus descendientes contienen el mismo nº de
nodos black.
Observar que todo AVL tree es también un Red-Black tree, sin embargo no todo Red-Black tree es
necesariamente un AVL. Notar que una de la diferencia fundamental es que los AVL tree tienen
altura de log(n), mientras que los Red-Black tree tienen altura menor o igual a 2log(n+1).
La ventaja de un árbol Red_Black en el contexto de Buscador de Direcciones de E-mail es que
podemos crear un archivo map que almacena datos en orden ascendente (ordenados por claves) y
que tiene tiempos de búsqueda rápidos.
Una relación interesante de los Red-Black con otro tipo de árbol es con los árbol 2-3-4, los que
pueden representarse como árboles binarios.
La idea comprende, representar 3-nodos y 4-nodos como árbol binario, los que a través de arcos
„rojos“ pueden ser unidos tal como lo muestra la figura,
_____________________________________________________________________________ 44
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Ejemplo:
Transformar el árbol siguiente en un Red-Black
Desde un punto de vista más técnico, digamos que la librería Collection posee un “map”, que
corresponde a una colección de datos, cuya naturaleza consta de dos partes:
– una clave única, la que “maps” al valor correspondiente
– un valor, el cual puede no ser única.
Un “map” de alumnos, en donde la clave, que es única (nº ID del alumno) y el resto es el valor del
alumno (ficha asociada a él, o el promedio de notas de un curso).
Llamaremos altura negra ( black-height ó bh(x) ) de un nodo x, al número de nodos negros desde x
a una hoja, no incluyendo a x. Se definirá como altura negra de un RB-Tree a la altura negra de su
raíz. Se asumirá que la raíz de un RB-Tree es negra. La altura negra del RB-Tree de la Figura 1, es
3.
_____________________________________________________________________________ 45
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
= red
Dr. Eric Jeltsch F.
= black
40
3
0
20
100
2
3
10
30
60
120
1
1
2
2
0
0
0
0
50
80
110
1
2
1
0
0
70
90
1
1
0
0
0
0
140
2
0
0
130
160
1
1
0
150
170
1
0
1
0
0
0
Algunos números de interés.
a) Cualquier árbol Red-Black, con raíz x, tiene al menos n = 2bh(x) – 1 nodos internos, donde bh(x)
es la altura negra del nodo x.
b) En un árbol Red-Black, al menos la mitad de los nodos sobre cualquier camino desde la raíz a la
hoja debe ser negro.
Con esto se puede probar que un árbol Red-Black con n-nodos internos tiene altura h  2log(n -1).
En efecto, basado en b), podemos inferir que si h es la altura de un árbol Red-Black, entonces bh(x)
 h/2, luego n  2h/2 – 1, n –1  2h/2, log(n-1)  h/2 y finalmente 2 log(n-1)  h. En consecuencia
este resultado establece que la altura de los árboles Red-Black es O(log(n)). De donde se tiene que
el costo de inserción es
 O(log(n)) para descender al punto de inserción,
 O(1), para hacer la inserción y
 O(log(n)) para ascender.
Así como también se puede afirmar que la búsqueda esta garantizada que sea bajo logN.
INSERTAR
La inserción es igual que el insertado en un ABB, pero con la propiedad de que el nodo insertado
siempre va ser rojo. Luego de tener insertado el nodo en el árbol se rompe la propiedad Red-Black
si y solo si el padre es rojo, si el padre es negro no existen problemas. Sin embargo, existen una
serie de casos a estudiar. Por lo pronto digamos que insertar en un arbol RED-BLACK es de costo
Ο(log n).
Ejemplo
A continuación se muestra una sucesión de inserciones a partir de un Red-Black inicialmente vacío.
Los datos a insertar son 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15.
,
,
,
,
_____________________________________________________________________________ 46
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
,
,
Y a continuación se muestra la salida para una propuesta de implementación que usa la interface
Comparable y la interpretación que se le debe dar a la salida para relacionarlo con los árboles RedBlack, es decir, es un par ordenado (a, b) en donde la primera componente es el valor ingresado y la
segunda componente es ya sea 1(black) o 0(red).
,
,
_____________________________________________________________________________ 47
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
,
,
,
,
,
.
_____________________________________________________________________________ 48
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
/*
* Implementación de un Arbol_RB.
* Las comparaciones están basadas en el método compareTo.
*/
public class Arbol_RB
{
private Nodo_RB base;
// nodo básico
private static Nodo_RB nullNode;
{
nullNode = new Nodo_RB(null);
nullNode.izq = nullNode.der = nullNode;
}
static final int BLACK = 1;
// Black debe ser 1
static final int RED
= 0;
// Para "insertar" se necesitan
private static Nodo_RB actual;
private static Nodo_RB padre;
private static Nodo_RB relac1; //relac1 es la relación entre los nodos
_____________________________________________________________________________ 49
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
private static Nodo_RB relac2;//relac1 es la relación entre los nodos
/*
* Construcción del Arbol.
* negInf es un valor, el que es menor o igual a todos los otros valores.
*/
public Arbol_RB(Comparable negInf)
{
base = new Nodo_RB(negInf);
base.izq = base.der = nullNode;
}
/*
* agregar en el RB. Duplicados no serán considerados.
* "item" es el dato que se agrega.
*/
public void insert(Comparable item)
{
actual = padre = relac1 = base;
nullNode.dat = item;
while( actual.dat.compareTo( item ) != 0 )
{
relac2 = relac1;
relac1 = padre;
padre = actual;
actual = item.compareTo(actual.dat ) < 0 ? actual.izq : actual.der;
// Probar si dos hijos R; en caso de si, fijarlo
if( actual.izq.color == RED && actual.der.color == RED )
reOrientar( item );
}
// campo para agregar, en caso de
if( actual != nullNode )
return;
actual = new Nodo_RB( item, nullNode, nullNode );
// colgar de su padre
if( item.compareTo( padre.dat) < 0 )
padre.izq = actual;
else
padre.der = actual;
reOrientar( item );
}
/*
* eliminar no esta implementado
* x es el dato a eliminar.
*/
public void remove( Comparable x )
{
System.out.println("Eliminar no esta implementado.");
}
/*
* hallar el min elemento en el RB.
* devuelve el elemento más pequeño o null, en caso vacío.
*/
public Comparable findMin( )
{
if (isEmpty( ))
return null;
Nodo_RB itr = base.der;
while( itr.izq != nullNode )
itr = itr.izq;
return itr.dat;
}
/*
* hallar el elemento más grande en el RB.
_____________________________________________________________________________ 50
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
* devuelve el elemento más grande, o null, en caso vacío.
*/
public Comparable findMax( )
{
if (isEmpty( ))
return null;
Nodo_RB itr = base.der;
while( itr.der != nullNode )
itr = itr.der;
return itr.dat;
}
/*
* hallar un elemento en el RB.
* x es el elemento buscado.
* devuelve el elemento respectivo o null,en el caso que no sea encontrado.
*/
public Comparable find(Comparable x)
{
nullNode.dat = x;
actual = base.der;
for( ; ; )
{
if( x.compareTo( actual.dat ) < 0 )
actual = actual.izq;
else if( x.compareTo( actual.dat ) > 0 )
actual = actual.der;
else if( actual != nullNode )
return actual.dat;
else
return null;
}
}
/*
* hacer el RB vacío.
*/
public void makeEmpty( )
{
base.der = nullNode;
}
/*
* Test, o si el RB esta vacío o no.
* devuelve true, en el caso vacío; en otro caso false.
*/
public boolean isEmpty( )
{
return base.der == nullNode;
}
/*
* salida del RB en orden.
*/
public void printTree( )
{
if( isEmpty() )
System.out.println("Arbol vacío");
else
printTree( base.der );
}
/*
* uno de los métodos de lectura inorden.
* b es la raíz del RB.
*/
private void printTree(Nodo_RB b)
{
_____________________________________________________________________________ 51
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
if (b != nullNode )
{
printTree(b.izq);
System.out.println(b.dat );
printTree(b.der);
}
}
/*
* salida del RB con una rotacion de 90 Grados.
*/
public void salidaArbol_RB()
{
salidaArbol_RB(base.der,0);
}
private void salidaArbol_RB(Nodo_RB b, int nSpace)
{
if (b != nullNode)
{
salidaArbol_RB(b.izq,nSpace += 6);
for (int i = 0; i < nSpace; i++)
System.out.print(" ");
System.out.println(b.dat + " " + b.color);
salidaArbol_RB(b.der, nSpace);
}
}
/*
* rutina que permite en el caso de que un nodo tenga 2 hijos
* los lleva a cambiar de color y rotar.
* item contiene el dato a incorporar.
*/
private void reOrientar(Comparable item)
{
// cambiar color
actual.color = RED;
actual.izq.color = BLACK;
actual.der.color = BLACK;
if (padre.color == RED)
// Rotación es necesaria
{
relac1.color = RED;
if ( (item.compareTo( relac1.dat) < 0 ) !=
(item.compareTo( padre.dat) < 0 ) )
padre = rotacion(item, relac1); // parte la doble rotación
actual = rotacion(item, relac2);
actual.color = BLACK;
}
base.der.color = BLACK; // hace la raíz negra
}
/*
* rutina para una simple o doble rotación.
* "item" es el dato a reOrientar.
* "padre" es el "padre" de la raíz de los sub arboles rotados.
* devuelve: la raíz de los sub arboles rotados.
*/
private Nodo_RB rotacion(Comparable item, Nodo_RB padre)
{
if (item.compareTo(padre.dat) < 0)
return padre.izq = item.compareTo( padre.izq.dat) < 0 ?
rotacionCon_descen_izq(padre.izq) :
// LL
rotacionCon_descen_der(padre.izq) ; // LR
else
return padre.der = item.compareTo(padre.der.dat) < 0 ?
rotacionCon_descen_izq(padre.der) : // RL
rotacionCon_descen_der(padre.der); // RR
_____________________________________________________________________________ 52
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
}
/*
* Rotación Nodo_AB con descendiente a la izq.
*/
static Nodo_RB rotacionCon_descen_izq( Nodo_RB k2 )
{
Nodo_RB k1 = k2.izq;
k2.izq = k1.der;
k1.der = k2;
return k1;
}
/*
* Rotación Nodo_AB con descendiente a la der.
*/
static Nodo_RB rotacionCon_descen_der(Nodo_RB k1)
{
Nodo_RB k2 = k1.der;
k1.der = k2.izq;
k2.izq = k1;
return k2;
}
}
import java.io.*;
public class Arbol_RBTest
{
public static void main(String[ ] args)
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader
ein = new BufferedReader(isr);
String entrada_datos
= null;
Arbol_RB b = new Arbol_RB(new Integer(Integer.MIN_VALUE));
System.out.println("Ingrese los datos: ");
while (true)
{
try {
entrada_datos = ein.readLine();
int a = Integer.parseInt(entrada_datos);
if (a == 0) break;//no admite al cero como entrada
b.insert(new Integer(a));
b.salidaArbol_RB();
}
catch(IOException e)
{
System.out.println(e.toString());
System.exit(0);
}
}
b.printTree();
b.salidaArbol_RB();
}
}
Ejemplo:
Veamos como se relacionan las claves "10 85 15", con la propuesta de implementación de árbol
red-black.
_____________________________________________________________________________ 53
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Estado de inicio
Dr. Eric Jeltsch F.
public Arbol_RB(Comparable negInf)
{
base = new Nodo_RB(negInf);
base.izq = base.der = nullNode;
}
Veamos que pasa con el árbol RB luego de la declaración b.insert(new Integer(10)).
Prosigamos con la declaración b.insert(new Integer(85)).
_____________________________________________________________________________ 54
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Y, veamos que pasa con b.insert(new Integer(15))
Dr. Eric Jeltsch F.
Notar que „Rotación“ es necesaria! Sobre el padre, se tiene padre = rotacion(item,relac1), de
donde padre.der = rotacionCon_descen_izq(padre.der).
Luego de esta rotación continúa otra rotación y esta vez sobre, actual = rotacion(item,
relac2) y padre.der = rotacionCon_descen_der(padre.der)
_____________________________________________________________________________ 55
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Estado Final
Dr. Eric Jeltsch F.
Eliminación:
La eliminación de un dato en un árbol RED-BLACK consta de dos pasos, primero se procede con el
mismo método de eliminación en un ABB normal, es decir buscar el elemento a eliminar y
remplazarlo por el mayor de sus descendientes reduciendo el problema a la eliminación de una hoja;
luego se procede a restablecer las propiedades para el balanceo de un RED-BLACK. Sin embargo,
existen una serie de casos a estudiar.
Arboles AA
Introducción: La implementación de los árboles Red-Black es demasiado extenso, en particular la
gran cantidad de casos que se dan en el borrado. Los AA-tree permiten limitar en cierto modo lo
realizado por los Red-Black.
Ejemplo:
A partir de un árbol AA, primitivamente vacío se deben agregar los siguientes elementos: 10, 85 15,
70, 20, 60, 30, 50, 65, 80, 90, 40, 5, 55, 35, y en este sentido ver su resultado.
_____________________________________________________________________________ 56
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
_____________________________________________________________________________ 57
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
_____________________________________________________________________________ 58
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
que corresponde a la siguiente salida según la implementación siguiente
class Nodo_AA
{
public Comparable dat;
protected Nodo_AA izq;
protected Nodo_AA der;
protected int nivel;
// Constructores
Nodo_AA(Comparable datElement)
{
this(datElement, null, null);
}
Nodo_AA(Comparable datElement, Nodo_AA i, Nodo_AA d)
{
dat = datElement;
izq = i;
der = d;
nivel = 1;
}
}
public class Arbol_AA
{
private Nodo_AA raiz;
private static Nodo_AA nullNode;
{
nullNode = new Nodo_AA(null);
_____________________________________________________________________________ 59
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
nullNode.izq = nullNode.der = nullNode;
nullNode.nivel = 0;
}
private static Nodo_AA borrarNodo;
private static Nodo_AA ultNodo;
/*
* Constructor.
*/
public Arbol_AA( )
{
raiz = nullNode;
}
public void insert(Comparable x)
{
raiz = insert(x, raiz);
}
public void remove( Comparable x )
{
borrarNodo = nullNode;
raiz = remove(x, raiz);
}
public Comparable findMin( )
{
if (isEmpty( ))
return null;
Nodo_AA zgr = raiz;
while (zgr.izq != nullNode )
zgr = zgr.izq;
return zgr.dat;
}
public Comparable findMax( )
{
if (isEmpty( ))
return null;
Nodo_AA zgr = raiz;
while (zgr.der != nullNode)
zgr = zgr.der;
return zgr.dat;
}
public Comparable find(Comparable x )
{
Nodo_AA actual = raiz;
nullNode.dat = x;
for( ; ; )
{
if (x.compareTo(actual.dat) < 0 )
actual = actual.izq;
else if (x.compareTo(actual.dat) > 0 )
actual = actual.der;
else if (actual != nullNode)
return actual.dat;
else
return null;
}
}
_____________________________________________________________________________ 60
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
public void makeEmpty()
{
raiz = nullNode;
}
public boolean isEmpty( )
{
return raiz == nullNode;
}
public void printTree()
{
if (isEmpty( ))
System.out.println("Arbol vacio");
else
printTree(raiz);
}
public void salidaArbol_AA()
{
salidaArbol_AA(raiz,0);
}
private Nodo_AA insert(Comparable x, Nodo_AA b)
{
if (b == nullNode)
b = new Nodo_AA(x, nullNode, nullNode);
else if (x.compareTo(b.dat) < 0 )
b.izq = insert(x, b.izq);
else if (x.compareTo(b.dat) > 0 )
b.der = insert(x, b.der);
else
return b;
b = skew(b);
b = split(b);
return b;
}
private Nodo_AA remove(Comparable x, Nodo_AA b)
{
if( b != nullNode )
{
// paso 1: buscar, ubicar ultNodo y borrarNodo
ultNodo = b;
if( x.compareTo( b.dat ) < 0 )
b.izq = remove( x, b.izq );
else
{
borrarNodo = b;
b.der = remove( x, b.der );
}
// Paso 2: si x es el actual, es eliminado
if( b == ultNodo )
{
if( borrarNodo == nullNode || x.compareTo( borrarNodo.dat ) != 0 )
return b;
// si no es encontrado, no pasa nada.
borrarNodo.dat = b.dat;
b = b.der;
}
// Paso 3: En otro caso, Rebalanciar
else
_____________________________________________________________________________ 61
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
if( b.izq.nivel < b.nivel - 1 || b.der.nivel < b.nivel - 1 )
{
if( b.der.nivel > --b.nivel )
b.der.nivel = b.nivel;
b = skew( b );
b.der = skew( b.der );
b.der.der = skew( b.der.der );
b = split( b );
b.der = split( b.der );
}
}
return b;
}
private void printTree(Nodo_AA b)
{
if (b != b.izq)
{
printTree( b.izq );
System.out.println( b.dat.toString( ) );
printTree(b.der );
}
}
private void salidaArbol_AA(Nodo_AA b, int nSpace)
{
if (b != b.izq)
{
salidaArbol_AA(b.izq,nSpace += 6);
for (int i = 0; i < nSpace; i++)
System.out.print(" ");
System.out.println(b.dat + ", " + b.nivel);
salidaArbol_AA(b.der, nSpace);
}
}
private Nodo_AA skew(Nodo_AA b)
{
if (b.izq.nivel == b.nivel )
b = rotacionCon_descen_izq(b);
return b;
}
private Nodo_AA split(Nodo_AA b)
{
if (b.der.der.nivel == b.nivel )
{
b = rotacionCon_descen_der(b);
b.nivel++;
}
return b;
}
static Nodo_AA rotacionCon_descen_izq(Nodo_AA k2)
{
Nodo_AA k1 = k2.izq;
k2.izq = k1.der;
k1.der = k2;
return k1;
}
static Nodo_AA rotacionCon_descen_der(Nodo_AA k1)
{
_____________________________________________________________________________ 62
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Nodo_AA k2 = k1.der;
k1.der = k2.izq;
k2.izq = k1;
return k2;
}
}
import java.io.*;
public class Arbol_AATest
{
public static void main(String[ ] args)
{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader
ein = new BufferedReader(isr);
String entrada_datos
= null;
Arbol_AA b = new Arbol_AA();
System.out.println("Ingrese los datos: ");
while (true)
{
try {
entrada_datos = ein.readLine();
int a = Integer.parseInt(entrada_datos);
if (a == 0) break;
b.insert(new Integer(a));
b.salidaArbol_AA();
}
catch(IOException e)
{
System.out.println(e.toString());
// System.exit(0);
}
}
// b.printTree();
b.salidaArbol_AA();
}
}
Ejemplo
Dado el árbol AA,
Se pide,
a)
Agregar la clave 2
b)
Agregar la clave 45
_____________________________________________________________________________ 63
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
- Punteros a la izquierda son eliminados a través de una rotación derecha, tal como se ve en la figura
(llamada “skew”)
Por ejemplo,
- punteros consecutivos a la derecha se eliminan a través de una rotación izquierda simple,
(llamada, “split”)
Por ejemplo, agregar la clave 45.
„split“ en el nodo con clave 35
_____________________________________________________________________________ 64
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
„skew“ en el nodo con clave 50
Dr. Eric Jeltsch F.
„split“ en el nodo con clave 40
Finalmente, se obtiene el siguiente árbol, luego de realizar „skew“ en el nodo 70 y „split“ en el
nodo 30
Arboles B-Tree
Introducción: Pensar en establecer una suerte de balanceo resulta a la postre un costo muy superior
al de los árboles binarios, sin embargo, los árboles Red-Black proponen una buena forma de
optimizar en términos de ahorro en las Rotaciones, sin embargo, para aquellos árboles de grado
mayor que 2, resulta un tanto más complicado buscar la forma de optimizarlos, de allí entonces que
la opción de usar los árbol AA resultan una buena alternativa.
Otro tipo de árboles, llamados árboles multicaminos o M-Way son también muy utilizados en la
construcción y mantenimiento de árboles de búsqueda con gran cantidad de datos o nodos, y por
tanto, usualmente almacenados en memoria secundaria, en donde en este caso el acceso al disco, así
como la rapidez para encontrar la información, es un problema no menor.
Como ya se menciono, en los almacenamientos en memoria secundaria el costo más significativo
viene dado por el acceso a este tipo de memoria, muy superior al tiempo necesario para acceder a
cualquier posición de memoria principal. Entonces al tratarse de un dispositivo periférico, y por
tanto, de acceso lento, resulta primordial minimizar en lo posible el número de accesos. Por
ejemplo, para almacenar un millón de elementos en memoria secundaria, con un árbol binario, el
_____________________________________________________________________________ 65
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
6
número promedio de accesos es del orden log 210 aprox. 20 accesos, en donde AVL o Red-Black
no mejoran substancialmente este record.
Sin embargo los árboles multicaminos mejoran este número de accesos, pues se organizan de tal
manera que un árbol se subdivide en subárboles y éstos se representan como unidades a las que se
accede simultáneamente, estos subárboles se les llama páginas, cada acceso a página requiere un
único acceso a memoria secundaria. De manera que si almacenamos 100 nodos por página se
necesitarán en promedio log 100106 aprox. 3 accesos a memoria secundaria para buscar en un millón
de elementos. No obstante esta forma de distribuir la información no garantiza que el árbol se
degenere, es decir en el peor de los casos, los accesos serán 104. Nuevamente se hace
imprescindible un criterio de crecimiento armónico que garantice cierto equilibrio, que se logra en
las aplicaciones en Base de Datos.
Los datos como ya mencionamos son almacenados sobre el disco en bloques, (blocks) con un
número fijo de bytes. Todo bloque por otro lado puede tener muchos registros (record). Un registro
corresponde al dato almacenado en el nodo exterior o interior, un nodo por registro. Ahora, el
tiempo requerido para acceder al bloque es relativamente largo, pues el cabezal debe desplazarse
sobre el cilindro apropiado y el bloque debe entonces rotar con alguna velocidad para posicionarse
bajo el cabezal. Una vez que el disco esta posicionado sobre el bloque, el bloque entero es accesado
(leer o escribir). El dato más pequeño que puede ser accesado sobre el disco es un bloque. Veamos a
continuación una breve introducción de los datos más relevantes en la organización y acceso a la
información guardada en una base de datos.
Organización Física de los Sistemas de Base de Datos.
La organización física de una base de datos es un tópico extenso y se aborda en detalle,
principalmente en la asignatura Base de Datos, y digo principalmente pues también se trata en
sistema operativo y sistemas de software. Sin embargo, el rendimiento general de un sistema de
base de datos se determina en gran medida por las estructuras de datos físicas usadas y por la
eficiencia con la cual el sistema trabaja sobre las mismas.
Aunque los usuarios no siempre deban tener conocimiento de los detalles del diseño físico de la
base de datos, sin embargo Ud. deberían saber que éstos afectan al rendimiento, un factor de gran
importancia en la satisfacción del usuario con el sistema de base de datos. Una pregunta nos asalta,
¿Podría el usuario obtener la información deseada en el formato apropiado y en un tiempo
conveniente?. Esta última frase, "tiempo conveniente", puede expresarse generalmente como tiempo
de respuesta aceptable. La "información deseada" y el "formato apropiado" no se afectan mucho por
la organización física de la base de datos, pero el tiempo de respuesta sí. El tiempo de respuesta es
el tiempo transcurrido entre la iniciación de una operación sobre la base de datos y la disponibilidad
del resultado. Un tiempo de respuesta lento es la queja más frecuente que expresan los usuarios de
los sistemas de bases de datos, posiblemente debido a que es lo que se observa más fácilmente.
Un buen diseño físico de la base de datos almacenaría datos de forma que puedan recuperarse,
actualizarse y manipularse en el mínimo tiempo posible. En este segmento me interesa abordar
aspectos de la organización física de la base de datos que soportan la eficiencia de las operaciones
en las mismas.
Acceso físico a la base de datos
_____________________________________________________________________________ 66
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
En la figura anterior, se muestra el sistema para el acceso físico a la base de datos. Se puede ver la
interacción del usuario con el sistema de base de datos al iniciar una consulta o demanda. El
selector de estrategia (usualmente el software que transforma una consulta del usuario en una
forma efectiva para su posterior ejecución) traduce la orden del usuario a su forma más eficiente
para su ejecución. La orden traducida activa entonces al administrador de buffer, que controla el
movimiento de datos entre la memoria principal y el almacenamiento en disco. El administrador
de archivos da soporte al administrador de buffer administrando la reserva de localizaciones de
almacenamiento en disco y las estructuras de datos asociadas. Además de los datos del usuario, el
disco contiene el diccionario de datos, que define la estructura de los datos del usuario y cómo éstos
pueden usarse. Los datos del usuario se almacenan como una base de datos física o colección de
registros.
Formas de almacenamiento físico
La memoria principal es el almacenamiento intermedio usado por los datos que están disponibles
para las operaciones del usuario. Aquí es donde reside la ejecución del programa y como los datos
se necesitan por el programa para ejecutar sus funciones, se transmiten estos desde el
almacenamiento secundario a la memoria principal. Aunque la memoria principal puede ser capaz
de almacenar varios megabytes de datos, es normalmente muy pequeña para almacenar la base de
datos completa, por lo que es necesario el almacenamiento secundario. El almacenamiento
secundario para los sistemas, de base de datos está compuesto generalmente por el almacenamiento
en disco y el almacenamiento en cinta magnética. Casi siempre, la base de datos completa se
almacena en disco y porciones de ésta se transfieren desde el disco a la memoria primaria, a medida
que se necesita. El almacenamiento en disco es la forma principal de almacenamiento con acceso
directo, por lo que los registros individuales se pueden acceder directamente. Aunque el
almacenamiento en cinta magnética es menos costoso que el almacenamiento en disco, los registros
pueden ser solamente accedidos secuencialmente (y más lentamente que en disco). Su función en el
sistema de base de datos está básicamente limitada a archivar datos. La unidad física en la que está
contenido el medio de grabación del disco se llama controlador de disco (disk driver). El
controlador de disco contiene un paquete de disco o volumen, el cual está formado por un conjunto
de superficies grabables (discos) montados sobre un eje. En operación, el eje y los discos rotan a
una alta velocidad. Los datos se graban sobre las pistas, que son coronas circulares grabables
encontradas sobre cada superficie, tal como lo muestra la siguiente figura.
_____________________________________________________________________________ 67
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Una metáfora para tal descripción es que el paquete de disco es una pila de discos musicales sobre
un eje, excepto que aquí las pistas son concéntricas y no en forma de espiral interna hacia el centro.
Un conjunto de cabezas de lectura/escritura ubicadas al final de un brazo, simbólicamente como los
dientes de un peine, se mueven como un grupo, de tal forma que éstos pueden ser posicionados
sobre todas las pistas del mismo radio. El conjunto de dichas pistas se denomina cilindro. Es decir,
conceptualmente, un conjunto de pistas del mismo diámetro rotando a alta velocidad forma un
cilindro. Esta definición es muy útil, ya que cualquier posicionamiento de un conjunto de cabezas
de lectura/escritura puede ser descrito por la localización del cilindro (por ejemplo, cilindro 199).
Por lo que todas las pistas del cilindro especificado se pueden escribir o leer, sin un movimiento
adicional de las cabezas de lectura/escritura. La dirección de un registro del disco normalmente
necesita información sobre el número del cilindro, de la superficie y del bloque.
Bloques de almacenamiento físico
El registro físico o bloque es la unidad de dato más pequeña en un disco que es físicamente
direccionable, vea la figura anterior, en donde cada pista en una superficie está compuesta de un
número de bloques. Un bloque puede contener uno o más registros lógicos.
Ejemplo: Supongamos que tenemos un factor de compactación de 3, esto significa que en cada
bloque se almacenan tres registros lógicos. Supongamos que deseamos recuperar el registro Juan
Perez almacenado en la siguiente dirección, Nº cilindro = 5, Nº superficie = 2, Nº bloque =1 .
Entonces, para recuperar el registro Juan Perez, las cabezas de lectura/escritura se mueven sobre el
cilindro 5 (pista 5 en todas las superficies). Entonces se activan las cabezas de lectura/escritura para
la superficie número 2 y se leen los números de bloques a la vez que la pista gira sobre las cabezas.
Cuando se detecta el bloque 1, el bloque entero de tres registros lógicos se lee en memoria
principal, donde se selecciona el registro Juan Perez.
En nuestro ejemplo suponemos la estructura más general de un disco, donde las cabezas de
lectura/escritura están sujetas a un brazo movible. No todas las unidades de disco están
configuradas de esta forma. En algunas, las cabezas de lectura/escritura son fijas para cada cilindro.
Típicamente estas unidades son más costosas, pero más rápidas, debido a que no hay retraso en
mover las cabezas de lectura/escritura sobre un nuevo cilindro.
Generalmente, el tiempo necesario para ejecutar cálculos en un bloque es mucho menor que el
necesario para transferir los datos entre el almacenamiento secundario y el primario. Sin embargo,
una buena estrategia de diseño es identificar, donde sea posible, los registros lógicos que
probablemente se usan en las mismas operaciones y agruparlos en bloques.
Ejemplo: Supongamos que una firma almacena tres tipos de alambres, A, B y C, y que se entregan
en el mismo cargamento. Si cada bloque contiene tres registros y los registros A, B y C se
almacenan en bloques separados, se necesitarían tres operaciones de entrada/salida (E/S) para
_____________________________________________________________________________ 68
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
actualizarlos. Sin embargo, si se agrupan en el mismo bloque, entonces sólo es necesaria una
operación de E/S. Debido a que generalmente el acceso a disco es un cuello de botella en las
operaciones de acceso a una base de datos, una asignación cuidadosa de los registros en los bloques
puede mejorar significativamente el tiempo de respuesta.
Factores de Rendimiento del Disco
En general, hay cuatro factores que directamente afectan a la velocidad, con la que los datos se
transfieren a y desde el almacenamiento en disco: tiempo de posicionamiento (access motion time),
tiempo de activación de la cabeza, retraso de rotación y razón de transferencia.
-Tiempo de posicionamiento (TP), se conoce como tiempo de búsqueda (seek time), es el tiempo
necesario para mover las cabezas de lectura/escritura desde su posición actual a una nueva dirección
de cilindro. Obviamente, un movimiento hacia una posición adyacente no toma la misma cantidad
de tiempo que moverse a través de toda la superficie del disco (desde la pista más interna hasta la
más externa, y viceversa). Como una aproximación en los cálculos puede usarse el tiempo medio de
posicionamiento: aproximadamente el tiempo necesario para moverse a través de la mitad de los
cilindros, por lo que debe usarse un método más sofisticado. Un acuerdo estándar consiste en que la
probabilidad de acceso para todos los registros sea igual, brindando una distribución de
probabilidad uniforme. El promedio para una distribución uniforme se encuentra en el medio entre
los valores extremos. Para el tiempo de posicionamiento, el valor extremo pudiera ser (1)
mantenerse posicionado sobre el cilindro actual, o (2) moverse desde el cilindro más interno hacia
el más externo (o viceversa). Dado que asumimos la distribución uniforme, la media sería el tiempo
para moverse a través de la mitad de los cilindros. De doce a veinte milisegundos es el tiempo
medio típico de posicionamiento, de acuerdo con el modelo y la composición del controlador de
disco, hoy en día es factible de encontrar tiempos medios menores.
-Tiempo de activación de la cabeza es el tiempo necesario para activar electrónicamente la cabeza
que se encuentra sobre la superficie cuando ocurre la transferencia de datos. En comparación con
otros factores de rendimiento, generalmente este tiempo es despreciable. Por esta razón rara vez se
usa en los cálculos de rendimiento.
-El retraso de rotación o latencia es el tercer factor de tiempo. Representa la cantidad de tiempo
que necesita el bloque seleccionado para rotar la cabeza, de forma tal que la transferencia de datos
pueda comenzar. El tiempo de rotación depende de dos factores: a qué rapidez rota el disco y la
localización del bloque buscado en relación con el tiempo de activación de la cabeza de
lectura/escritura. Físicamente este tiempo puede variar desde cero hasta el tiempo necesario para
completar una revolución del disco (R).
Ejemplo: Supongamos que desea montar sobre el caballo negro en el carrusel (asumiendo que
existe sólo un caballo de este tipo). Si compra un ticket y corre a montarse en el carrusel, la
probabilidad de que el caballo negro puede estar justo donde se detuvo sería la misma que para
cualquiera de los otros caballos. Si fuera un fanático y lo intentara varias veces, puede que alguna
vez se detenga frente al caballo negro, pero también puede encontrarse con la situación en que lo
pierda y tenga que esperar por una vuelta completa del carrusel. Como media, espera media vuelta
para montarse en el caballo negro. La moraleja de esta historia es que los cálculos de rendimiento
generalmente asumen un retraso de rotación media de R/2.
Transferencia de datos
-Razón de transferencia de datos (D) se refiere a la cantidad de tiempo necesario para transferir
los datos desde el disco hacia (o desde) la memoria principal. Depende de la velocidad de rotación y
_____________________________________________________________________________ 69
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
de la cantidad de datos almacenados. El tiempo de transferencia de datos normalmente se expresa
en cientos de bytes por segundo.
-Tiempo de transferencia de datos
El tiempo previsto (T) para acceder a una dirección en disco y transferir un bloque de datos se
estima como T = P + R/2 + L/D, donde P es el tiempo de posicionamiento, R es el retraso de
rotación, L es el tamaño del bloque en bytes y D es la velocidad de transferencia de datos.
Ejemplo (registro accedido aleatoriamente). Supongamos que los registros de reclamación de una
compañía de seguros se almacenan en bloques de tres registros en el disco (un factor de
compactación de tres) y que cada registro de reclamación ocupa 200 bytes. La velocidad de
transferencia de datos es de 806.000 bytes por segundo. El tiempo de posicionamiento medio es de
30 milisegundos. La unidad de disco rota a una velocidad de 3.600 vueltas por minuto. Supongamos
que un usuario llama para averiguar el estado de una reclamación. ¿Cuál es el tiempo de
transferencia de datos para buscar el bloque de datos? Para responder esta pregunta se asignan
valores apropiados a las variables anteriores en la forma siguiente.
A=0,030 seg.
Revoluciones por segundos = 7.200/60=120.
R= 1/120 seg. = 0,0083 seg.
R/2 = 0, 0083 * ½(la media de espera es de media revolución) = 0, 00415
L/D = 600/806000 = 0,00074, por lo que T = 0,030 + 0, 00415 + 0, 00074 = 0, 03489 seg.
Ejemplo (registro accedido secuencialmente). Ahora analizaremos el cálculo del tiempo medio de
acceso a un registro en un archivo accedido secuencialmente. Supongamos que en vez de responder
al acceso aleatorio de un bloque de datos, como en el ejemplo anterior, estamos actualizando el
archivo de un usuario de la compañía de seguros con los pagos recibidos a principios de mes. Tiene
sentido que estos archivos estén organizados secuencialmente por el número de la póliza y que
estén localizados en bloques secuenciales por cilindros. Esto significa que primero se rellena el
cilindro N con bloques secuenciales, después el N + 1 y así sucesivamente. De esta forma se
minimiza el tiempo de movimiento de la cabeza. En particular, si las cabezas de lectura/escritura se
encuentran en el primer cilindro, entonces todos los registros en este cilindro se transfieren sin un
tiempo de posicionamiento adicional. Por lo que, en el cálculo del tiempo de acceso medio para
cada registro de un archivo procesado secuencialmente, el tiempo de posicionamiento es
despreciable y se ignora.
Pudiera existir un pequeño retraso cada vez que la función de lectura/escritura cambia desde una
pista de un cilindro hacia otra. Esto es necesario con el objetivo de disminuir pequeñas diferencias
en la alineación de las pistas sobre diferentes superficies. Para nuestros propósitos, este retraso
puede ser aproximadamente el tiempo necesario para dar una media vuelta del paquete de disco.
Una vez se haya encontrado el registro inicial de la nueva pista, se pueden transmitir el resto de los
bloques sobre la pista. Por tanto, si el archivo del usuario ocupa ocho pistas sobre el cilindro, el
número de retraso de medias vueltas sería 8.
Supongamos ahora que cada pista contiene 1.000 bloques. Tenemos un total de 8.000 bloques; si los
mismos tienen un factor de compactación de tres, tenemos 24.000 registros de pólizas. Asumamos,
como antes, que cada registro contiene 200 bytes, entonces nuestros bloques ocupan 600 bytes. Si
procesamos secuencialmente un archivo completo, el tiempo medio para acceder a un registro se
calcula como sigue: Tiempo total para leer todos los bloques = 0,00415 * (8) + 0,00074 * (8000) =
0,0664 + 5,92 = 5,9232 , T representa el tiempo de transferencia medio para un registro accedido
secuencialmente en el archivo de pólizas.
_____________________________________________________________________________ 70
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Aplicación: Suponga que tenemos almacenados registros en un dispositivo de disco que posee las
siguientes características: Tiempo de posicionamiento medio: 0,02 seg., velocidad de rotación del
disco: 3.600 revoluciones por minuto, velocidad de transferencia de datos: 312.000 bytes por
segundo. ¿Cuál es el tiempo de transferencia de datos esperado para un registro físico accedido
aleatoriamente que ocupa 500 bytes?.
Volviendo al tema de los B-trees, digamos que los B-trees son ABB balanceados diseñados para
trabajar sobre discos mágneticos u otros accesos o dispositivos de almacenemaiento secundario,
también llamados periféricos. Los B-tree son similar a los red-black trees, pero mejores que ellos,
pues minimizan los accesos al disco en operaciones de I/O. Muchos son los sistemas de BD que
usan B-trees, o variantes de ellos, para almacenar la información. Una de las grandes diferencias de
los B-tree con los red-black trees es que los nodos de los B-tree pueden tener muchos hijos, también
llamado "branching factor". Sin embargo, esto está usualmente determinado por las características
de la unidad de disco usado. ISAM (indexed sequential access method ) tiene una variante de B-tree en el
manejo y rendimiento de su información, llamado B+-tree.
Un B-tree T es un árbol (cuya raíz es root[T]) que tiene las siguientes propiedades:
1. Los datos en un nodo están ordenados
2. Un nodo contiene maximal T subárboles
3. Los datos (claves) del subárbol izquierdo son más pequeños que la raíz, así como los de la
derecha son mayores.
Existe una cota superior e inferior sobre el nº de claves que puede contener un nodo. Esta cota
puede ser expresada en términos de un entero t ≥ 2 llamado el minimum degree del B-tree:
a) Todo nodo que no sea la raíz debe tener al menos t - 1 claves. Todo nodo interno que no sea
la raíz debe tener al menos t hijos. Si el árbol es vacío, la raíz debe tener al menos una clave.
b) Todo nodo puede contener a lo más 2t - 1 claves. Por lo tanto, un nodo interno puede
tener a lo más 2t hijos. Se dice que el nodo está lleno “full” si este contiene exactamente 2t – 1
claves. El B-tree más simple ocurre para cuando t = 2. Todo nodo interno entonces tiene ya sea
2, 3, o 4 hijos, también llamado 2-3-4 tree.
El nº de accesos al disco requeridos para las operaciones sobre un B-tree es proporcional a la altura
del B-tree.
Ejemplo: Se muestra un B-tree de altura 2 que contiene sobre un billón de claves y más de un
millón de nodos. Notar que cada nodo interno y hojas que contienen 1000 claves. Existiendo 1001
nodos en la profundidad 1 y sobre un millón de nodos en la profundidad 2
Basado en un B-tree de minimum degree t, se tienen las siguientes condiciones
_____________________________________________________________________________ 71
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Teorema: Si n ≥ 1, entonces para cualquier n- claves en un B-tree T de altura h y un minimum
degree t ≥ 2, h ≤ logt(n+1/2).
A continuación se muestra un B-tree de altura h = 3 que contiene un nº posible de claves. n[x]
corresponde al interior del nodo x.
En general, n = 2th – 1. De manera que a partir de esta fórmula podemos encontrar una serie de
información, pues ella involucra varias variables, tales como altura, minimum degree y el nº de
nodos. Un B-tree de altura H se encuentra entre N min  2  (t ) h1  1 y N max  2  (t ) h  1 claves.
Digamos que los árbol B-tree son una generalización de los árbol 2-3, pues aumenta el nº de enlaces
que cada nodo puede tener. Denotemos este por M y supongamos que M = 1000. Se muestra a
continuación un B-tree con M = 5.
_____________________________________________________________________________ 72
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Búsqueda en un B-tree
Parte desde la raíz.
• Encuentra el intervalo para buscar la clave y toma su camino, (derecha) si es mayor e (izquierda)
si es menor.
• Sigue buscando hasta llegar a los nodos externo. Si lo encuentra Eureka, sino no esta.
Ejemplo:
Se muestra un ejemplo en donde se busca la clave E, considerando el mismo B-tree anterior.
Inserción en un B-tree
Buscamos el lugar apropiado para la nueva clave.
• Insertamos y listo.
• escindir (M+1)-nodos dentro del camino del árbol.
Ejemplo:
Consideremos la distribución e ingreso de las salas de clases de una Institución Educacional,
considerando que M = 5.
_____________________________________________________________________________ 73
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Otros B-tree variants son B+ tree, B*tree, B# tree, …y otros. Por ejemplo los árboles R…
Ejemplo:
Dada la figura
R2
R1
Luego, podemos representarla a través de un árbol R como :
_____________________________________________________________________________ 74
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
R1
Dr. Eric Jeltsch F.
R2
R3
R4
R5
Alsace
Auvergne
Bourgogne
R6
R7
R8
Aquitaine
Basse Normandie
Bretagne
A continuación se muestra un ejemplo de un árbol R de orden (3,4), si nos fijamos en la figura hay
tres rectángulos o cajas límites mayores que indican que el árbol debe tener tres hijos, y cada uno de
esos hijos a su vez encierran a cajas limites que son las más pequeñas, que serán las que contendrá
el nodo de cada hijo.
Además, en la figura podemos observar la principal característica de un árbol R, donde las cajas
limites definidas en el mismo nivel pueden solaparse (A, B y C).
Resumen.
El paquete java.util contiene varias clases utilitarias para ayudar al desarrollador en el diseño de
diferentes tipos de estructuras de datos, entre ellas esta la nueva clase Collection implementada
usando una forma genérica, dándole así mayor robustez a las propuestas de software. Entre otras, la
interface Enumeration, la que se utiliza para implementar una clase capaz de enumerar sus valores.
Su implementación facilita el recorrido de estructuras de datos. Tiene dos métodos:
hasMoreElements y nextElement. hasMoreElements retorna true si quedan elementos por
visitar en la estructura de datos, mientras que nextElement retorna el siguiente objeto en la
estructura que se está enumerando.
La clase Vector proporciona una manera fácil de implementar estructuras de datos dinámicas. Es
eficiente pues asigna más memoria de la necesaria cuando se agrega nuevos elementos.
capacityIncrement indica el incremento en capacidad cuando se agrega un elemento.
A continuación los principales métodos.
_____________________________________________________________________________ 75
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
JGL (Java Generics Library) mejora las funcionalidades del JDK. Los beneficios incluyen soporte
para Serialización, multi-threads seguros, rendimiento óptimo, 11 estructuras de datos, 40
algoritmos y compatible con JDK.
_____________________________________________________________________________ 76
Escuela de Ingeniería en Computación, Universidad de La Serena.
Diseño y Análisis de Algoritmos. (DAA-2009)
Dr. Eric Jeltsch F.
Bibliografía utilizada para este segmento
(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) Cormen, Leiserson, Rivest, "Introduction to algorithms", McGraw-Hill, 1990.
(4) Roberto Tamassia, http://www.cs.brown.edu/~rt/
(5) Bob Sedgewick y Kevin Wayne. http://www.cs.princeton.edu/algs4/43balanced/
_____________________________________________________________________________ 77
Escuela de Ingeniería en Computación, Universidad de La Serena.