Árboles multicaminos.
Árboles B, B+, B*

  1. Resumen
  2. Introducción
  3. Resolviendo los problemas de los accesos a disco. Generalización del concepto Árbol de búsqueda
  4. En busca del equilibrio en el árbol y un mayor aprovechamiento del espacio en disco. Árbol B
  5. Algunas modificaciones al Árbol B. Árbol B +
  6. Disminuyendo la altura del árbol y los accesos a memoria. Árbol B*
  7. Bibliografía

Resumen

El presente artículo ofrece una amplia explicación acerca de la utilización de los árboles B y sus diferentes variantes, en el manejo de grandes volúmenes de información, utilizando memoria externa. En el mismo se detalla cómo se realizan cada una de las operaciones en los diferentes tipos de árboles.

Palabras claves

Arbol B, Arbol B+, Arbol B*

Introducción

En el manejo de grandes volúmenes de información, siempre ha estado presente la necesidad de hacer eficiente el proceso de búsqueda.

A menudo se usan árboles binarios de búsqueda para ordenar listas de valores, minimizando el número de lecturas, y evitando tener que ordenar dichas listas.

Pero este tipo de árboles tienen varias desventajas:

  1. Es difícil construir un árbol binario de búsqueda perfectamente equilibrado.
  2. El número de consultas en el árbol no equilibrado es impredecible.
  3. Y además el número de consultas aumenta rápidamente con el número de registros a ordenar.

Para resolver estas desventajas, pudiera pensarse en el uso de árboles balanceados, por ejemplo AVL, pero ¿Qué ocurriría si el volumen de la información es tal que no pueda mantenerse en memoria? En este caso, a la preocupación de ganar eficiencia en la búsqueda, se le suma la necesidad de resolver los inconvenientes de reiterados accesos a disco. Utilizando un árbol AVL, el uso del disco es muy deficiente: cada acceso implica leer un bloque de memoria y de este bloque únicamente se utiliza un registro, cuyos índices de izquierda y derecha apuntan, con mucha probabilidad, a un registro de otro bloque.

Cuando el volumen de la información a procesar tiene un tamaño tal que nos conduce a la utilización de ficheros para el almacenamiento de los datos, se hace necesario pensar en otras estructuras que resuelvan los inconvenientes anteriores

Resolviendo los problemas de los accesos a disco. Generalización del concepto Árbol de búsqueda.

Un árbol de búsqueda es un tipo especial de árbol que sirve para guiarla búsqueda de un registro, dado el valor de uno de sus campos. Un árbol de búsqueda de orden p es un árbol tal que cada nodo contiene, cuando más, p-1 valores de búsqueda y p apuntadores en el orden <P1, k1, P2, k2, …, Pq-1, Kq-1, Pq> , donde q <= p, cada Pi es un apuntador a un nodo hijo (o apuntador nulo) y cada Ki es un valor de búsqueda proveniente de algún conjunto ordenado de valores. Se supone que todos los valores de búsqueda son únicos. Un árbol de búsqueda debe cumplir, en todo momento, las siguientes restricciones:

  1. Dentro de cada nodo K1 < K2 < ….< Kq-1
  2. Para todos los valores de X, del subárbol al cual apunta Pi , tenemos que Ki-1 < X < Ki , para 1 < i < q; X < Ki , para i = 1 y X > Ki-1 para i = q.

La figura 1 muestra un nodo de un árbol de búsqueda. En ella se pueden apreciar de manera más clara, las condiciones que impone la restricción 2.

Al buscar, insertar o eliminar un valor X siempre se sigue el puntero Pi apropiado, de acuerdo con las condiciones de la segunda restricción.

Podemos usar un árbol de búsqueda como mecanismo para buscar registros almacenados en un archivo de disco. Los valores del árbol pueden ser los valores de uno de los campos del registro, el llamado campo de búsqueda o llave. Cada valor del árbol está asociado a un apuntador de registro que tiene ese valor en el archivo de datos. Como alternativa, puede apuntar al bloque de disco que contiene ese registro.

El árbol de búsqueda en sí puede almacenarse en disco, asignando cada nodo del árbol a un bloque del disco. Cuando se inserta un registro nuevo, es preciso actualizar el árbol de búsqueda incluyendo en él el valor del campo de búsqueda del nuevo registro y un apuntador a este.

Para insertar valores de búsqueda en el árbol y eliminarlos, sin violar las restricciones anteriores, se utilizan algoritmos que no garantizan que el árbol de búsqueda esté equilibrado (que todas las hojas estén al mismo nivel).

Es importante mantener equilibrados los árboles de búsqueda porque esto garantiza que no habrá nodos en niveles muy profundos que requieran muchos accesos a bloques durante una búsqueda. Además, las eliminaciones de registros pueden hacer que queden nodos casi vacíos, con lo que hay un desperdicio de espacio importante que también provoca un aumento en el número de niveles y un menor aprovechamiento de los accesos a disco.

En busca del equilibrio en el árbol y un mayor aprovechamiento del espacio en disco. Árbol B

El árbol B es un árbol de búsqueda, con algunas restricciones adicionales, que resuelve hasta cierto punto los dos problemas anteriores. Estas restricciones adicionales garantizan que el árbol siempre estará equilibrado y que el espacio desperdiciado por la eliminación, si lo hay, nunca será excesivo. Los algoritmos para insertar y eliminar se hacen más complejos para poder mantener estas restricciones. No obstante, la mayor parte de las inserciones y eliminaciones son procesos simples, se complican sólo en circunstancias especiales.

De manera más formal, un Árbol B de orden p, utilizado como estructura de acceso según un campo clave para buscar registros en un archivo de datos, cumple que:

  1. Cada nodo interno del árbol B tiene la forma (Ver Fig. 2)
  2. <P1, <k1, Pr1>, P2, <k2, Pr2> , …, <kq-1, Prq-1>, Pq>

    donde q <= p. Cada Pi es un apuntador de árbol: un apuntador a otro nodo del árbol. Cada Pri es un apuntador de datos: un apuntador al registro cuyo valor del campo clave de búsqueda es igual a Ki (o un apuntador al bloque que contiene ese registro de datos)

  3. Dentro de cada nodo K1 < K2 < ….< Kq-1

  1. Para todos los valores de X, del subárbol al que apunta Pi , tenemos que Ki-1 < X < Ki , para 1 < i < q; X < Ki , para i = 1 y X > Ki-1 para i = q.
  2. Cada nodo tiene, cuando más, p apuntadores de árbol.
  3. Cada nodo, excepto la raíz y los nodos hoja, tienen por lo menos p/2 apuntadores de árbol. El nodo raíz tiene, como mínimo, dos punteros a nodos del árbol, a menos que sea el único nodo del árbol.
  4. Un nodo con q apuntadores de árbol, q <= p, tiene q-1 valores del campo clave de búsqueda y por tanto, tiene q-1 apuntadores de datos.
  5. Todos los nodos hojas están en el mismo nivel. Los nodos hoja tienen la misma estructura que los internos, excepto que todos sus apuntadores de árbol, Pi , son nulos.

Nota: Por una cuestión de simplicidad de los gráficos, en lo adelante solo se representarán los apuntadores de árboles.

A continuación se muestran dos árboles. Uno de ellos es un árbol B, en cambio, el otro no. El árbol de la figura 3.1 es un árbol B "correcto", pues cumple con todas las reglas en cuanto a su estructura y orden. En cambio, el de la figura 3.2 no es un árbol B, ya que a pesar de mantener el orden, hay una página (que no es la raíz) que tiene menos de p/2 elementos (en este caso menos de 5/2 elementos). Esta página es la que contiene al elemento 10.

El número de acceso a disco en un árbol B es proporcional a la altura del árbol, el caso peor para la altura es cuando la raíz tiene una sola llave y el resto de los nodos tiene el mínimo permitido p/2.

La siguiente figura nos muestra la cantidad de nodos por cada nivel del árbol donde t=p/2.

Generalizando, para un árbol de altura h, la cantidad de nodos que tiene el último nivel sería 2th-1.

La cantidad de nodos n, que tiene un árbol B cumple con lo siguiente:

Obtenemos que:

entonces el crecimiento del árbol es O(logt n).

Operaciones básicas en árboles B

Las operaciones básicas que se pueden realizar en un árbol B son básicamente tres:

  1. Buscar una llave
  2. Insertar una llave
  3. Eliminar una llave

Búsqueda

La búsqueda de una llave Y se realiza de manera análoga a la búsqueda en un árbol binario de búsqueda. Se comienza buscando por el nodo raíz y se compara la llave Y con las llaves Ki que se encuentran en ese nodo. Si Y es igual a algún Ki, termina la búsqueda satisfactoriamente, si no, se debe seguir el puntero adecuado hacia un nodo hijo en el que se continuará la búsqueda. El puntero seleccionado será P1, si Y < K1, Pq si Y > Kq-1 y Pi, si Ki-1 < X < Ki, para 1 < i < q. En caso que el Pi correspondiente sea nulo, la búsqueda termina insatisfactoriamente.

En la búsqueda en un árbol B el número de acceso a página es (h) = (logt n) donde h es la altura del árbol, n la cantidad de llaves, n<p y t=p/2. La búsqueda lineal de la llave en una página es O(t) por lo tanto el tiempo total es O(t*h)=O(t*logtn).

Inserción

Para realizar la inserción, lo primero que debe hacerse es un proceso de búsqueda, para localizar el nodo en el que se va a insertar. Si este proceso de búsqueda da por resultado que el elemento ya existe, no se realizará ninguna operación, pues el árbol B no permite elementos repetidos.

La búsqueda de un elemento inexistente y que, por tanto, pueda ser insertado en el árbol, terminará en el momento en que, tratando de buscar un puntero Pi que apunte al nodo donde debería estar el elemento, encontremos un puntero nulo, siendo insertado el nuevo elemento en el nodo actual. Cuando se inserta en un nodo que no está lleno, solo debemos preocuparnos por el lugar que debe ocupar la nueva llave en ese nodo y hacer un corrimiento de las restantes.

En problema surge cuando ya se han insertado varias llaves en un mismo nodo hasta que este queda completo. Una vez lleno un nodo con p-1 valores de la clave de búsqueda, si intentamos insertar una entrada más en él, debemos seguir el siguiente procedimiento:

  1. Insertar la llave como si realmente tuviese sitio libre.
  2. Extraer el nodo que ocupa la posición del medio e insertarlo en la página padre. Si no hubiese página padre se crearía una nueva página con ese nodo.
  3. Dividir equitativamente la página original en dos nuevas páginas Estas páginas serán los hijos derechos e izquierdos del nodo que promocionó a la página superior.
  4. Si la página a la que promociona el nodo mediano también se encuentra completa, se repetiría la misma operación de división y promoción.

La división puede propagarse hasta llegar al nodo raíz. En caso que el nodo raíz deba dividirse también, el elemento que es promocionado para ser insertado en el padre, será insertado en un nuevo nodo que se debe crear, que pasará a ser la nueva raíz del árbol, creando un nuevo nivel cada vez que se divida la raíz..

Es importante hacer notar la curiosa manera de crecer en altura que tienen los árboles B, que lo hacen hacia la raíz, a diferencia de los comúnmente utilizados, cuyas inserciones se realizan en forma de hojas y por tanto crecen en sentido inverso al árbol B. Esto es muy importante, porque garantiza que en el árbol B, todas las hojas estén en el mismo nivel y por tanto, esté balanceado.

Veamos algunos ejemplos:

Ej. 1 Supongamos que queremos insertar la clave 52 en el árbol siguiente.

No hay espacio para almacenar la clave en nodo, por lo tanto, este nodo debe dividirse, promocionando la llave que queda en el medio, en este caso, el 65:

Ahora tenemos que promocionar la clave intermedia, insertándola en el nodo padre. Como la llave promocionada, pudo insertarse sin dificultad en el nodo padre, el proceso termina.

Ej. 2 Veamos otro ejemplo que implicará que aumente la altura del árbol. Supongamos que tenemos un árbol con esta estructura, y queremos insertar la clave 68:

En primer lugar, dividimos nodo en dos, repartimos las claves y promocionamos el valor intermedio, el 69:

Ahora estamos de nuevo en la misma situación, cuando tratamos de insertar el 69 en el nodo padre, pues este no cabe en dicho nodo, luego, el anterior nodo pasa a ser el nodo padre, y el padre de éste es null. Ahora procedemos igual, dividimos nodo en dos, separamos las claves, y promocionamos el valor intermedio:

Y otra vez repetimos el proceso, al tratar de insertar el 65 en el nodo padre del nodo que se dividió, vemos que este no existe, porque el nodo que se acaba de dividir era la raíz. En este caso, se crea un nuevo nodo en el que se inserta la llave promovida y se actualizan los punteros. Note la variación en la altura del árbol y su crecimiento hacia la raíz.

En la inserción en un árbol B el número de acceso a página es donde h es la altura del árbol, n la cantidad de llaves, n<p y t=p/2. La búsqueda lineal de la llave en una página es O(t) por lo tanto el tiempo total es O(t*h)=O(t*logtn).

Eliminación

El proceso de eliminación puede llegar a ser algo más complejo que la inserción. La eliminación siempre debe realizarse en una hoja. Si después de realizarla búsqueda, el nodo a borrar no estuviese en una hoja, de la misma manera que se procede en un árbol binario de búsqueda, el nodo a borrar se sustituiría por su antecesor o sucesor, que sí debe estar en una hoja.

Al hacer la sustitución, caemos en el caso de la eliminación de una llave de un nodo hoja, la que ya fue replicada.

El caso más sencillo se produce cuando al eliminar un nodo de una página hoja, bien porque sea el nodo a eliminar, bien porque sea un elemento que sustituyó al nodo a eliminar, el tamaño de la página sigue siendo al menos p/2. De esta forma, la estructura del árbol se sigue cumpliendo y no hay que realizar ninguna reestructuración.

El problema surge cuando al eliminar un nodo de la hoja, la página se queda con menos del mínimo número de nodos permitidos. Si la eliminación de un valor hace que un nodo quede ocupado hasta menos de la mitad, este le "pedirá prestado un elemento" a uno de sus hermanos, o el izquierdo o el derecho. Si el hermano está en condiciones de ceder un elemento, o sea, si al perder dicho elemento no hace que quede ocupado a menos de la mitad, cederá el elemento más a la izquierda o más a la derecha, en dependencia de su ubicación respecto al hermano.

Esta "donación", debe realizarse con participación del nodo padre. El elemento donado, debe intercambiarse con el valor Ki correspondiente en el nodo padre y es este valor Ki quien iría a suplir la falta en el hermano afectado. En este caso, pudiera adoptarse la variante de chequear primero si el hermano derecho tiene la posibilidad de donar una llave y en caso negativo, pasar a chequear si el hermano izquierdo puede, pero esto provocaría un nuevo acceso a disco y por tanto, es preferible chequear solamente a uno de los dos hermanos.

Aún resta un caso más. Este caso es idéntico al anterior, pero ahora la página vecina tiene el número mínimo de nodos y por tanto, no existe la posibilidad de suplir la falta de llave tomando una llave del hermano En esta situación se produce el efecto inverso a la división, las dos páginas se agrupan en una sola, formando una nueva página en la que todavía existe capacidad para otra llave. A esta página se le añade la llave Ki que estaba situada en la página padre, entre ambos nodos. Si al fundirse los dos nodos y tomar la llave Ki de su padre, este se queda con una cantidad de llaves menor que p/2, el proceso se repite, propagándose hasta la raíz.

En caso de ser la raíz el nodo al que se le quitará el Ki y esta solo tenía una llave, queda automáticamente eliminada, reduciéndose en 1 la altura del árbol. Note que, del mismo nodo que el árbol B crece hacia la raíz, este tipo de árbol se acorta también desde la raíz, garantizándose así que todas las hojas, estén al mismo nivel.

Veamos algunos ejemplos de eliminación:

Ej. 1 Eliminemos la clave 65. Es uno de los casos más sencillos, la clave está en un nodo hoja, y el número de claves del nodo después de eliminarla es mayor del mínimo.

Tan sólo tendremos que eliminar la clave:

Ej. 2 Eliminaremos ahora la clave 20.

Según el algoritmo debemos sustituir la clave por el siguiente valor, es decir, el 25.

Ahora estamos en el mismo caso que antes, sólo hay que borrar la clave 25 del nodo hoja:

Ej. 3 Pasemos a eliminar ahora el elemento 12 del árbol:

La primera parte es fácil, bastará eliminar la clave, pero el resultado es que sólo queda una clave en el nodo, y debe haber al menos dos claves en un nodo de cuatro.

Según el algoritmo, buscaremos en el hermano derecho. Si el número de claves en el nodo derecho es mayor que el mínimo, pasaremos una clave del nodo derecho al padre y de éste al nodo hoja, en este caso, el 25 pasa al nodo padre y el 20 pasa al nodo que tenía el déficit.

Ej. 4 Vamos a ver el caso en que el nodo derecho no tiene claves suficientes para transferir una al nodo hoja. Eliminaremos la clave 5:

La primera parte es igual que el caso anterior, eliminamos la clave 5. Pero ahora el nodo derecho tiene el número mínimo de claves.

Según el algoritmo, debemos fundir en el nodo hoja, las claves del nodo hoja, la clave del padre y las del nodo derecho. Y después eliminar el nodo derecho.

La cosa no termina ahí, ahora debemos comprobar el nodo padre, ya que también ha podido quedar con menos claves que el mínimo exigido. En este caso sucede eso, pero es un caso especial, ya que ese nodo es raíz y puede contener menos claves que número mínimo.

Ej. 5 Veamos cómo sería el proceso en un caso general, que implica la reducción de altura del árbol.

Borraremos la clave 65 de este árbol:

El primer paso es sustituir la clave 65 por la siguiente, la 67:

A continuación eliminamos la clave 67 del nodo hoja, y comprobamos que el nodo tiene menos claves del mínimo. Buscamos el nodo derecho

El nodo derecho tiene justo el número mínimo de claves, por lo tanto tenemos que fusionar hoja con derecho y con una clave de padre. Y eliminamos el nodo derecho.

Ahora el nodo hoja es el padre del anterior nodo hoja. De nuevo comprobamos que tiene menos claves que el mínimo, así que buscamos el nodo derecho, como no existe, buscamos el izquierdo. Y de nuevo comprobamos que el izquierdo tiene justo el número mínimo de claves.

Ahora fusionamos en el nodo izquierdo con el nodo hoja y con una clave del nodo padre, y eliminamos el nodo hoja, como además el nodo padre es la raíz y ha quedado vacía, lo eliminaremos y el nodo raíz será el izquierdo:

En la eliminación en un árbol B el número de acceso a página es donde h es la altura del árbol, n la cantidad de llaves, n<p y t=p/2. La búsqueda lineal de la llave en una página es O(t) por lo tanto el tiempo total es O(t*h)=O(t*logtn).

Algunas modificaciones al Árbol B. Árbol B +

Como se puede observar, en los árboles B todos los valores del campo de indexación aparecen alguna vez en algún nivel del árbol, junto con un puntero al fichero de datos.

En un árbol B+ los punteros a datos se almacenan sólo en los nodos hoja del árbol, por lo cual, la estructura de los nodos hoja difiere de la de los nodos internos.

Los nodos hoja tienen una entrada por cada valor del campo de indexación, junto con un puntero al registro del fichero de datos (o al bloque que contiene el registro). Estos nodos están enlazados para ofrecer un acceso ordenado a los registros a través del campo de indexación. Los nodos hoja de un árbol B+ son similares al primer nivel (nivel base) de un índice*. Los nodos internos del árbol B+ corresponden a los demás niveles del índice. Algunos valores del campo de indexación se repiten en los nodos internos del árbol B+ con el fin de guiar la búsqueda.

* Los índices multinivel cumplen que los índices de primer nivel contienen claves con apuntadores al fichero de datos y los índices de otros niveles sirven para indexar a los anteriores.

La estructura de los nodos internos del árbol B+ de orden p se define como sigue(Fig 4):

  1. Todo nodo interno es de la forma <P1, k1, P2, k2, …, Pq-1, Kq-1, Pq> , donde q <= p, cada Pi es un apuntador de árbol.
  2. Dentro de cada nodo interno K1 < K2 < ….< Kq-1
  3. Para todos los valores X, del campo de búsqueda en el subárbol al cual apunta Pi , tenemos que Ki-1 <= X < Ki , para 1 < i < q; X < Ki , para i = 1 y X >= Ki-1 para i = q. En este caso pudiera encontrarse una X igual a K y debe seguirse hacia abajo, hasta llegar a un nodo hoja.
  4. Cada nodo interno tiene, cuando más, p apuntadores de árbol.
  5. Cada nodo interno, excepto la raíz y los nodos hoja, tienen por lo menos p/2 apuntadores de árbol. El nodo raíz tiene, como mínimo, dos punteros a nodos del árbol, si es un nodo interno
  6. Un nodo interno con q apuntadores de árbol, q <= p, tiene q-1 valores del campo de búsqueda

La estructura de los nodos hojas de un árbol B+ de orden p se define como sigue(Fig 5):

  1. Todo nodo hoja tiene la forma:
  2. <<k1, Pr1>, <k2, Pr2> , …, <kq-1, Prq-1>, Psiguiente,> donde q<=p, cada Pri es un apuntador de datos y Psiguiente apunta al siguiente nodo hoja del árbol B+.

  3. Dentro de cada nodo hoja K1 < K2 < ….< Kq-1, donde q<=p
  4. Cada nodo hoja tiene al menos p/2 valores
  5. Todos los nodos hoja están en el mismo nivel

Como las entradas en los nodos internos de los árboles B+ contienen valores del campo de indexación y punteros a nodos del árbol, pero no contienen punteros a los registros del fichero de datos, es posible ``empaquetar" más entradas en un nodo interno de un árbol B+ que en un nodo similar de un árbol B. Por tanto, si el tamaño de bloque (nodo) es el mismo, el orden p será mayor para el árbol B+ que para el árbol B.

Esto puede reducir el número de niveles del árbol B+, mejorándose así el tiempo de acceso. Como las estructuras de los nodos internos y los nodos hoja de los árboles B+ son diferentes, su orden p puede ser diferente.

Se ha demostrado por análisis y simulación que después de un gran número de inserciones y eliminaciones aleatorias en un árbol B, los nodos están ocupados en un 69% cuando se estabiliza el número de valores del árbol. Esto también es verdadero en el caso de los árboles B+. Si llega a suceder esto, la división y combinación de nodos ocurrirá con muy poca frecuencia, de modo que la inserción y la eliminación se volverán muy eficientes.

Operaciones básicas en árboles B+

Búsqueda

La operación de búsqueda en árboles-B+ es similar a la operación de búsqueda en árboles-B. El proceso es simple, sin embargo puede suceder que al buscar una determinada clave la misma se encuentre en un nodo raíz o interior, en dicho caso no debe detenerse el proceso, sino que debe continuarse la búsqueda con el nodo apuntado por la rama derecha de dicha clave.

Por ejemplo, al buscar la clave 55 en el árbol-B+ de la figura 6 se advierte que esta se encuentra en el nodo raíz. En este caso, debe continuarse el proceso de búsqueda en el nodo apuntado por la rama derecha de dicha clave, o sea, si se encuentra la clave Ki-1, debemos continuar la búsqueda por el apuntador Pi .

 

Inserción

El proceso de inserción en árboles-B+ es relativamente simple, similar al proceso de inserción en árboles-B. La dificultad se presenta cuando desea insertarse una clave en un nodo que se encuentra lleno. En este caso, el nodo afectado se divide en 2, distribuyéndose las claves de la siguiente forma: " las p/2 primeras claves en el nodo de la izquierda y las p/2 + 1 restantes claves en el nodo de la derecha". Una copia de la clave del medio sube al nodo padre. En la figura 7 hay dos diagramas que ilustran como funciona este caso.

Puede suceder que el nodo padre se desborde nuevamente, entonces tendrá que repetirse el proceso anterior. Es importante notar que el desbordamiento en un nodo que no es hoja no produce duplicidad de claves. El proceso de propagación puede llegar hasta la raíz, en cuyo caso la altura del árbol puede incrementarse en una unidad.

En la figura 8 se presentan dos diagramas que muestran este caso.

Supóngase que se desea insertar las siguientes claves en un árbol-B+ de orden 5 que se encuentra vacío:

claves: 10-27-29-17-25-21-15-31-13-51-20-24-48-19-60-35-66

Los resultados parciales que ilustran el crecimiento del árbol se presentan en los siguientes diagramas correspondientes a la figura 9

Fig. 9 Crecimiento del árbol B a medida que se insertan los valores señalados

Eliminación

La operación de eliminación en árboles-B+ es mas simple que en árboles-B. Esto ocurre porque las claves a eliminar siempre se encuentran en las paginas hojas. En general deben distinguirse los siguientes casos:

  1. Si al eliminar una clave, la cantidad de llaves queda mayor o igual que p/2 entonces termina la operación. Las claves de los nodos raíz o internos no se modifican por más que sean una copia de la clave eliminada en las hojas. (Se presenta un ejemplo de este caso en la figura 10).
  2. Si al eliminar una clave, la cantidad de llaves queda menor que p/2 entonces debe realizarse una redistribución de claves, tanto en el índice como en las paginas hojas. (Hay dos ejemplos que ilustran como funciona este caso en las figuras 11 y 12 ).

 

Nota: Al eliminar la clave 27 de la página A, la cantidad de llaves queda menor que p/2 por lo que debe realizarse una redistribución de las claves. Se toma la clave que se encuentra más a la derecha en la rama izquierda de 25 (21 de la página B). Se coloca dicha clave en la página A y una copia de la misma, como índice, en la página C. (Se le llama página a un nodo)

Nota: Al eliminar la clave 21 de la página A, la cantidad de llaves queda menor que p/2 por lo que debe realizarse una redistribución de claves. Como no se puede tomar una clave de la página B puesto que m quedaría menor a d, entonces se realiza una fusión de las páginas A y B.

Puede suceder que al eliminar una clave y al realizar una redistribución de las mismas, la altura del árbol disminuya en una unidad. (En la figura 13 se presentan dos diagramas que muestran este caso).

Nota: Al eliminar la clave 37 de la página A, la cantidad de llaves queda menor que p/2 por lo que debe realizarse una redistribución de claves. Como no puede tomarse una clave de la página B puesto que m quedaría menor a d, entonces se realiza una fusión de las páginas A y B. Sin embargo, luego de está fusión la cantidad de llaves queda menor que p/2 en la página C, por lo que debe bajarse la clave 29 de la página E y realizarse una nueva fusión, ahora de las páginas C y E. La altura del árbol disminuye en una unidad.

Disminuyendo la altura del árbol y los accesos a memoria. Árbol B*

El Árbol B* es un caso particular de Árbol B, al que se le adiciona la restricción de que todos los nodos se mantienen 2/3 llenos, o sea, la cantidad de llaves en cada nodo no puede ser menor que (2/3)p. Esta condición favorece que se aprovechen los accesos a disco, pues cada nodo está obligado a permanecer más lleno que los del árbol B.

Otra particularidad del Árbol B*, que lo distingue del Árbol B, es que el nodo raíz puede tener hasta (4/3)p llaves. Esta salvedad surge producto de que si se restringe a este nodo a tener a lo sumo p-1 llaves, al insertar una nueva llave, deberíamos proceder a dividir el nodo en dos nodos, que no cumplirían con la condición de tener al menos (2/3)p llaves.

Esto se comprenderá mejor en lo adelante, cuando se expliquen las operaciones básicas en este tipo de árboles.

De manera más formal, un Árbol B*, de orden p, cumple que:

  1. Cada nodo interno del árbol B tiene la forma (Ver Fig. 2)
  2. <P1, <k1, Pr1>, P2, <k2, Pr2> , …, <kq-1, Prq-1>, Pq>

    donde q <= p. Cada Pi es un apuntador de árbol: un apuntador a otro nodo del árbol. Cada Pri es un apuntador de datos: un apuntador al registro cuyo valor del campo clave de búsqueda es igual a Ki (o un apuntador al bloque que contiene ese registro de datos)

  3. Dentro de cada nodo K1 < K2 < ….< Kq-1

  1. Para todos los valores de X, del subárbol al que apunta Pi , tenemos que Ki-1 < X < Ki , para 1 < i < q; X < Ki , para i = 1 y X > Ki-1 para i = q.
  2. Cada nodo, excepto la raíz, tiene, cuando más, p apuntadores de árbol. La raíz puede contener hasta (4/3)p + 1 apuntadores.
  3. Cada nodo, excepto la raíz y los nodos hoja, tienen por lo menos (2/3)p +1 apuntadores de árbol. El nodo raíz tiene, como mínimo, dos punteros a nodos del árbol, a menos que sea el único nodo del árbol.
  4. Un nodo con q apuntadores de árbol, q <= p, tiene q-1 valores del campo clave de búsqueda y por tanto, tiene q-1 apuntadores de datos.
  5. Todos los nodos hojas están en el mismo nivel. Los nodos hoja tienen la misma estructura que los internos, excepto que todos sus apuntadores de árbol, Pi , son nulos.

Operaciones básicas en árboles B*

Búsqueda

La búsqueda en el Árbol B* se realiza de misma manera que en el B.

Inserción

La inserción en un árbol B* se realiza del mismo modo que en el árbol B, siempre y cuando exista capacidad en el nodo para colocar la nueva llave. La diferencia está en el momento en que se va a insertar un elemento en un nodo que ya está lleno.

La manera de proceder en caso de insertar un elemento en un nodo lleno, varía si dicho nodo es la raíz o si es un nodo hoja. Note que cuando se habla de una inserción en la raíz es o porque la raíz es el único nodo del árbol y el nuevo elemento debe ser insertado en ella o porque la inserción en una hoja, provocó la promoción de una llave para ser insertada en el padre y esta promoción se propagó hasta la raíz.

Cuando se va a insertar en la raíz del árbol y está llena, caso en que ocurre un crecimiento en la altura del árbol, se procede de la manera siguiente:

  1. Insertar la llave como si realmente tuviese sitio libre.
  2. Crear un nuevo nodo, que pasará a ser la raíz del árbol. Extraer el nodo que ocupa la posición del medio en el nodo en que se insertó e insertarlo en la nueva raíz.
  3. Dividir equitativamente la página original en dos nuevas páginas Estas páginas serán los hijos derecho e izquierdo del nodo que promocionó a la página superior.

Es importante hacer notar que esto sucede solo en el momento en que la raíz, producto de la inserción, excede las (4/3)p llaves, lo cual garantiza que al dividirse en dos el nodo original, en cada nodo queden las (2/3)p llaves que necesita un nodo del árbol B*.

Cuando va a insertarse en un nodo hoja y está lleno se procede de la siguiente manera:

  1. Insertar la llave como si hubiese capacidad.
  2. Chequear si en el hermano izquierdo o derecho existe capacidad y en caso positivo se le cede al hermano seleccionado, la llave que está más a la izquierda o más a la derecha, en caso de ser cedido al hermano izquierdo o derecho, respectivamente.
  3. Esta llave cedida, debe intercambiarse con la llave Ki que se encuentra en el nodo padre, entre los dos nodos que intercambiarán llaves y será dicha llave Ki la que se insertará en el hermano.
  4. Si al tratar de cederle una llave al hermano, este también se encuentra lleno, deben combinarse ambos nodos para formar tres. Esta combinación debe realizarse incluyendo la llave Ki que se encontraba en el padre, entre ambos nodos. Se dividirán equitativamente todas las llaves entre 3, sin contar las dos que deben pasar al nodo padre. Al insertar una nueva llave en el nodo padre (una de las dos ya se encontraba allí) con su respectivo apuntador a árbol, puede ocurrir un desbordamiento en ese nodo, por lo que debe hacerse nuevamente el procedimiento de inserción.

Veamos unos ejemplos de inserción en un árbol de orden 7:

Ejemplo 1 Insertemos un 45 en el siguiente árbol:

El nodo de un árbol B* de orden 7 tiene a lo sumo 6 llaves, pero en este caso, que se trata de una inserción en la raíz del árbol, la inserción se realiza en el propio nodo, porque la raíz admite hasta 8 llaves. Luego, el árbol que se obtiene es:

Si al árbol anterior le insertamos un 70, estaríamos nuevamente en un caso fácil y obtendríamos el árbol:

Ahora insertemos un 35. Ya el nodo se encuentra lleno, por lo que tenemos que dividirlo en dos e insertar la llave del medio en un nuevo nodo raíz que debe ser creado, obteniendo al árbol:

Ejemplo 2 Insertemos el 25 en el siguiente árbol:

Hay capacidad en el nodo, por tanto, solo hay que buscar el lugar que le corresponde e insertar. Se obtiene el árbol:

Ahora insertemos un 15 en el árbol anterior

No hay capacidad en el nodo en que se inserta. Existe capacidad en el hermano derecho, por lo que puede cedérsele la llave más a la derecha, el 40 en este caso; esta debe intercambiarse con la llave que está en el padre, el 45 y este debe ser insertado en el hermano. El árbol resultante se muestra en la figura siguiente:

Ejemplo 3 Veamos qué ocurre al insertar una llave en un nodo sin capacidad, que tiene un hermano en el que no hay capacidad. Insertemos el 32 en el árbol:

En este caso, debemos combinar el nodo en que se inserta, con el hermano, para obtener tres nodos. En esta combinación también se tiene en cuenta la llave que se encuentra en el padre, entre ambos nodos, o sea, el 40.

La figura anterior muestra la combinación. Están representados todas las llaves a combinar, que son las llaves de ambos nodos, más la llave a insertar y la que estaba en el padre. Estas dos últimas se encuentran encerradas en un círculo. Se muestra además las llaves que quedarán en el padre y la división equitativa en tres nodos. El árbol resultante es:

Eliminación

La eliminación se realiza del mismo modo que en el árbol B, siempre y cuando no provoque que un nodo quede con menos llaves que las que debe tener, porque en este caso, el procedimiento a seguir en el árbol B* difiere de la vista anteriormente para el árbol B.

Cuando va a eliminarse una llave de un nodo con la cantidad mínima de llaves, deben seguirse los pasos siguientes:

  1. Chequear si el hermano tiene una llave para ceder, o sea, si no tiene también la cantidad mínima de llaves, en este caso, se procede a hacer una rotación de manera análoga a la inserción: la llave cedida se intercambia con la llave Ki del padre, que se encuentra entre ambos nodos y esta se inserta en el nodo del cual se eliminó.
  2. Si los hermanos inmediatos del nodo del cual se elimina, no tuvieran la posibilidad de ceder una llave, debe analizarse el nodo que está un lugar más allá que el hermano analizado, o sea, el hermano del hermano, para valorar la posibilidad de que sea este nodo quien ceda la llave. Para ganar en claridad, llamaremos H0 al nodo del cual se eliminó, H1 al hermano que no podía ceder y H2 al otro hermano En caso de ser posible, deben hacerse dos rotaciones para hacer llegar la llave a H0: una primera rotación de H1 a H0 (ahora H1 es quien tiene la falta) y otra rotación desde H2 a H1. Recuerde que estas rotaciones involucran a las llave Ki del nodo padre, que se encuentran entre los nodos H0 y H1, H1 y H2, respectivamente.
  3. De no poder realizar estas rotaciones y persistir el faltante de una llave en el nodo del que eliminó, podemos estar en dos situaciones: o existen dos nodos hermanos del nodo del que se eliminó que no tenían posibilidad de ceder una llave o el nodo del que eliminó solo tenía un hermano. En el primer caso, podemos resolver la situación realizando una combinación de tres nodos para obtener dos, con participación de las llaves Ki del padre que se encuentran entre los tres nodos. La idea es, dividir las llaves de los tres nodos en dos conjuntos que pasarán a estar en los dos nodos a formar, extrayendo la llave del medio, que quedará en el nodo padre, entre los nodos resultantes. Note que como parte de este proceso, el nodo padre ha perdido una llave. Lo cual puede provocar una falta de llave en este, con lo que debe seguirse analizando el árbol hacia arriba.
  4. Si estamos en la situación en la que el nodo del cual se eliminó solo tiene un hermano (árbol con dos niveles, en uno la raíz y el otro las dos hojas) y este hermano no puede ceder la llave, debemos fundir las llaves que quedan en un solo nodo, que quedaría como raíz del árbol.

La eliminación en árboles B* podrá entenderse mejor en los ejemplos que siguen, para un árbol de orden 7.

Ejemplo 1 En el árbol siguiente, eliminar la llave 35

En este caso, el nodo en que se elimina queda con una cantidad de llaves menor que la permitida. Su hermano derecho puede cederle una llave, por lo que se procede a extraer la llave más a la izquierda de este, el 45 en este caso, intercambiarla con la llave del padre, el 40, e insertar el 40 en el nodo del que se eliminó, resultando el árbol:

Ejemplo 2 Eliminemos el 40 del árbol siguiente:

En este caso, al eliminar el 40, el nodo del que se elimina queda con una cantidad de llaves por debajo del límite. Sus hermanos inmediatos no pueden cederle una llave, por lo que se analiza un hermano más allá y se comprueba que sí puede ceder una. En este caso, debe hacer dos rotaciones, para suplir la falta de llave.

La figura muestra dicha operación:

El árbol resultante sería:

Ejemplo 3 Eliminemos el 20 del árbol:

Si analizamos a su hermano (H1) y al hermano de su hermano (H2), vemos que no es posible resolver la falta de llave mediante rotaciones, por lo que debe procederse a combinar los tres nodos (H0, H1 y H2) para formar dos.

En la figura anterior se muestran las llaves de los tres nodos a combinar. Se encuentran entre círculos las llaves del nodo padre, que se encuentran entre los tres nodos, que también deben tenerse en cuenta para combinar.

Se encuentra señalado con una flecha la llave que debe quedar en el nodo padre entre los dos nodos y el árbol resultante es:

Ejemplo 4 Procedamos a eliminar el 20 en el árbol siguiente:

En este caso, la cantidad de llaves pasa por debajo del límite, el hermano no tiene para ceder una llave, solo existe un hermano para el nodo del cual se eliminó y por tanto, todos los nodos deben combinarse en uno que quedará como raíz, obteniéndose el árbol:

Bibliografía

Wirth, N (1987) Algoritmos y Estructuras de Datos

Knuth, D (1973) The Art of Computer Programming[El arte de la programación de computadoras]

Pozo, S (2001) Arboles-B. Extraído de http://articulos.conclase.net/arboles-b

Marqués, M (2001) Arboles B y árboles B+. Extraído de http://www3.uji.es/~mmarques/f47/apun/node27.html.

Luna, F (2001) Tutorial de los árboles B. Extraído de http://usuarios.lycos.es/arbolesbpro/

 

 

Datos de los autores

Lic. Joel Arencibia Ramírez

Licenciado en Ciencias de la Computación, Universidad Central de Las Villas, Cuba, 2002

joelar[arroba]uci.cu

Ing. Leansy Alfonso Pérez

Ingeniero Informático, Instituto Superior Politécnico José Antonio Echeverría, CUJAE, 2003

leansy[arroba]uci.cu

Ambos autores se desempeñan actualmente como profesores del Dpto de Técnicas de Programación de la Universidad de Ciencias Informáticas, Cuba y cursan una Maestría en Ciencias de la Computación.

Fecha de realización: Noviembre de 2005.

Comentarios


Trabajos relacionados

  • Temas generales de Prolog

    Hechos. Variables. Reglas. El ámbito de las variables. Operadores. La resolución de objetivos. El mecanismo de control d...

  • Manual de user

    Comandos avanzados para listas y cadenas. Ejercicios de Programacion. El programa dueño y una pincelada a la programacio...

  • Proceso de desarrollo de software

    Abstract. Fundamentos del Análisis de Requerimientos. Análisis de Requerimientos. Tareas del Análisis. Principios del An...

Ver mas trabajos de Programacion

   

Nota al lector: es posible que esta página no contenga todos los componentes del trabajo original (pies de página, avanzadas formulas matemáticas, esquemas o tablas complejas, etc.). Recuerde que para ver el trabajo en su versión original completa, puede descargarlo desde el menú superior.


Todos los documentos disponibles en este sitio expresan los puntos de vista de sus respectivos autores y no de Monografias.com. El objetivo de Monografias.com es poner el conocimiento a disposición de toda su comunidad. Queda bajo la responsabilidad de cada lector el eventual uso que se le de a esta información. Asimismo, es obligatoria la cita del autor del contenido y de Monografias.com como fuentes de información.