En informática , un árbol B es una estructura de datos de árbol autoequilibrada que mantiene los datos ordenados y permite búsquedas, acceso secuencial, inserciones y eliminaciones en tiempo logarítmico . El árbol B generaliza el árbol de búsqueda binaria , permitiendo que los nodos tengan más de dos hijos. [ 2 ]
Al permitir más hijos bajo un nodo que un árbol de búsqueda binaria autoequilibrado convencional , el árbol B reduce la altura del árbol y coloca los datos en menos bloques separados. Esto es especialmente importante para árboles almacenados en almacenamiento secundario (por ejemplo, unidades de disco), ya que estos sistemas tienen una latencia relativamente alta y trabajan con bloques de datos relativamente grandes ; de ahí el uso del árbol B en bases de datos y sistemas de archivos . Esto sigue siendo una gran ventaja cuando el árbol se almacena en memoria, ya que los sistemas informáticos modernos dependen en gran medida de las cachés de la CPU . En comparación con la lectura de la caché, la lectura de la memoria después de un fallo de caché consume un tiempo significativo. [ 3 ] [ 4 ]
Historia
Mientras trabajaban en los Laboratorios de Investigación de Boeing , Rudolf Bayer y Edward M. McCreight inventaron los árboles B para gestionar eficientemente las páginas de índice de archivos grandes de acceso aleatorio. Su premisa básica era que los índices serían tan voluminosos que solo pequeñas porciones del árbol cabrían en la memoria principal. El artículo de Bayer y McCreight, « Organización y mantenimiento de grandes índices ordenados » [ 1 ] , se difundió por primera vez en julio de 1970 y posteriormente se publicó en Acta Informatica [ 5 ] .
Bayer y McCreight nunca explicaron qué significaba la B , si es que significaba algo; se han sugerido Boeing , balanceado , entre , ancho , tupido y Bayer . [ 6 ] [ 7 ] [ 8 ] Cuando se le preguntó: "Quiero saber qué significa la B en B-Tree", McCreight respondió [ 7 ] :
¡Todo el mundo lo hace!
Así que no tienes ni idea de en qué puede convertirse una conversación a la hora del almuerzo. Así que ahí estábamos, Rudy y yo, almorzando. Teníamos que ponerle un nombre a la cosa... En ese momento trabajábamos para Boeing, pero no podíamos usar el nombre sin hablar con los abogados. Así que ahí está la B.
Tiene que ver con el equilibrio . Hay otra B.
Rudy era el autor principal. Rudy (Bayer) era varios años mayor que yo y tenía... muchas más publicaciones que yo. Así que ahí hay otra B.
Así pues, en la mesa del almuerzo, nunca llegamos a decidir si alguna de esas opciones tenía más sentido que las demás.
Lo que Rudy suele decir es que cuanto más pienses en lo que significa la B en B-Tree, ¡mejor entenderás los árboles B!
Definición
Según la definición de Knuth , un árbol B de orden m es un árbol que satisface las siguientes propiedades: [ 9 ]
- Cada nodo tiene como máximo m hijos.
- Cada nodo, excepto la raíz y las hojas, tiene al menos ⌈ m /2⌉ hijos.
- El nodo raíz tiene al menos dos hijos, a menos que sea una hoja.
- Todas las hojas aparecen al mismo nivel.
- Un nodo no hoja con k hijos contiene k − 1 claves.
Las claves de cada nodo no hoja actúan como valores de separación que dividen sus subárboles. Por ejemplo, si un nodo interno tiene 3 nodos hijos (o subárboles), entonces debe tener 2 claves: a 1 y a 2. Todos los valores en el subárbol más a la izquierda serán menores que a 1 , todos los valores en el subárbol del medio estarán entre a 1 y a 2 , y todos los valores en el subárbol más a la derecha serán mayores que a 2 .
- Nodos internos
- Los nodos internos (también conocidos como nodos internos ) son todos los nodos excepto los nodos hoja y el nodo raíz. Generalmente se representan como un conjunto ordenado de elementos y punteros a hijos. Cada nodo interno contiene un máximo de U hijos y un mínimo de L hijos. Por lo tanto, el número de elementos siempre es 1 menos que el número de punteros a hijos (el número de elementos está entre L −1 y U −1). U debe ser 2 L o 2 L −1; por lo tanto, cada nodo interno está al menos medio lleno. La relación entre U y L implica que dos nodos medio llenos se pueden unir para formar un nodo válido, y un nodo lleno se puede dividir en dos nodos válidos (si hay espacio para mover un elemento al nodo padre). Estas propiedades permiten eliminar e insertar nuevos valores en un árbol B, al tiempo que se ajusta el árbol para preservar sus propiedades.
- El nodo raíz
- El número de hijos del nodo raíz tiene el mismo límite superior que los nodos internos, pero no tiene límite inferior. Por ejemplo, cuando hay menos de L −1 elementos en todo el árbol, la raíz será el único nodo del árbol sin hijos.
- Nodos hoja
- En la terminología de Knuth, los nodos "hoja" son los objetos/fragmentos de datos propiamente dichos. Los nodos internos que se encuentran un nivel por encima de estas hojas son lo que otros autores denominarían "hojas": estos nodos solo almacenan claves (como máximo m -1, y como mínimo m /2-1 si no son la raíz) y punteros (uno por cada clave) a los nodos que contienen los objetos/fragmentos de datos.
Un árbol B de profundidad n + 1 puede almacenar aproximadamente U veces más elementos que un árbol B de profundidad n , pero el costo de las operaciones de búsqueda, inserción y eliminación aumenta con la profundidad del árbol. Como ocurre con cualquier árbol equilibrado, el costo crece mucho más lentamente que el número de elementos.
Algunos árboles equilibrados almacenan valores solo en los nodos hoja y utilizan distintos tipos de nodos para los nodos hoja y los nodos internos. Los árboles B conservan valores en todos los nodos del árbol, excepto en los nodos hoja.
Diferencias en la terminología
La literatura sobre árboles B no es uniforme en su terminología. [ 10 ]
Bayer y McCreight (1972), [ 5 ] Comer (1979), [ 2 ] y otros definen el orden de un árbol B como el número mínimo de claves en un nodo que no es la raíz. Folk y Zoellick [ 11 ] señalan que la terminología es ambigua porque el número máximo de claves no está claro. Un árbol B de orden 3 podría contener un máximo de 6 claves o un máximo de 7 claves. Knuth (1998) evita el problema definiendo el orden como el número máximo de hijos (que es uno más que el número máximo de claves). [ 9 ]
El término hoja también es inconsistente. Bayer y McCreight (1972) [ 5 ] consideraron que el nivel de hoja era el nivel más bajo de claves, pero Knuth lo consideró un nivel por debajo de las claves más bajas. [ 11 ] Existen muchas opciones de implementación posibles. En algunos diseños, las hojas pueden contener el registro de datos completo; en otros, pueden contener solo punteros al registro de datos. Estas opciones no son fundamentales para la idea de un árbol B. [ 12 ]
Para simplificar, la mayoría de los autores asumen que existe un número fijo de claves que caben en un nodo. La suposición básica es que tanto el tamaño de la clave como el del nodo son fijos. En la práctica, se pueden emplear claves de longitud variable. [ 13 ]
Descripción informal

Estructura del nodo
Al igual que otros árboles, los árboles B se pueden representar como una colección de tres tipos de nodos: raíz , interno (también llamado interior) y hoja .
Tenga en cuenta las siguientes definiciones de variables:
- K - Número máximo de claves de búsqueda potenciales para cada nodo en un árbol B. (Este valor es constante en todo el árbol).
- pt i - El puntero a un nodo hijo que inicia un subárbol.
- pr i - El puntero a un registro que almacena los datos.
- k i - La clave de búsqueda en el índice de nodo basado en cero i .
En los árboles B, se mantienen las siguientes propiedades para estos nodos:
- Si k i existe en cualquier nodo de un árbol B, entonces k i -1 existe en ese nodo donde.
- Todos los nodos hoja tienen el mismo número de ancestros (es decir, todos están a la misma profundidad).
Cada nodo interno en un árbol B tiene el siguiente formato:
Cada nodo hoja en un árbol B tiene el siguiente formato:
Los límites de los nodos se resumen en la tabla siguiente:
Inserción y eliminación
Para mantener el rango predefinido de nodos secundarios, los nodos internos pueden unirse o dividirse.
Por lo general, el número de claves se elige para que varíe entre d y, donde d es el número mínimo de claves, yes el grado mínimo o factor de ramificación del árbol. Un factor de 2 garantiza que los nodos se puedan dividir o combinar.
Si un nodo interno tieneclaves, luego agregar una clave a ese nodo se puede lograr dividiendo la hipotéticaun nodo clave en dos d nodos clave y moviendo la clave que habría estado en el medio al nodo padre. Cada nodo dividido tiene el número mínimo requerido de claves. De manera similar, si un nodo interno y su vecino tienen cada uno d claves, entonces se puede eliminar una clave del nodo interno combinándola con su vecino. Eliminar la clave haría que el nodo interno tuvieraclaves; unirse al vecino agregaría d claves más una clave adicional tomada del padre del vecino. El resultado es un nodo completamente lleno dellaves.
Un árbol B se mantiene equilibrado después de la inserción dividiendo un nodo que podría estar sobrecargado, declaves, en dos hermanos de d claves e insertando la clave de valor medio en el padre. La profundidad solo aumenta cuando la raíz se divide, manteniendo el equilibrio. De manera similar, un árbol B se mantiene equilibrado después de la eliminación mediante la fusión o redistribución de claves entre hermanos para mantener el mínimo de d claves para nodos que no son la raíz. Una fusión reduce el número de claves en el padre, lo que potencialmente lo obliga a fusionar o redistribuir claves con sus hermanos, y así sucesivamente. El único cambio en la profundidad ocurre cuando la raíz tiene dos hijos, de d y (transicionalmente)claves, en cuyo caso los dos hermanos y el padre se fusionan, reduciendo la profundidad en uno.
Esta profundidad aumentará lentamente a medida que se agreguen elementos al árbol, pero un aumento en la profundidad general es poco frecuente y da como resultado que todos los nodos hoja estén un nodo más lejos de la raíz.
Comparación con otros árboles
Debido a que se permite un rango de nodos hijos, los árboles B no necesitan reequilibrarse con tanta frecuencia como otros árboles de búsqueda autoequilibrados, pero pueden desperdiciar algo de espacio ya que los nodos no están completamente llenos.
Los árboles B ofrecen ventajas sustanciales sobre otras implementaciones cuando el tiempo de acceso a los datos de un nodo supera ampliamente el tiempo de procesamiento, ya que el costo de acceso puede amortizarse entre múltiples operaciones dentro del nodo. Esto suele ocurrir cuando los datos del nodo se almacenan en almacenamiento secundario , como discos duros . Al maximizar el número de claves dentro de cada nodo interno , la altura del árbol disminuye y se reduce el número de accesos costosos a los nodos. Además, el reequilibrio del árbol se produce con menos frecuencia. El número máximo de nodos hijos depende de la información que debe almacenarse para cada uno y del tamaño de un bloque de disco completo o un tamaño análogo en el almacenamiento secundario. Si bien los árboles B de 2 a 3 nodos son más fáciles de explicar, los árboles B prácticos que utilizan almacenamiento secundario requieren un gran número de nodos hijos para mejorar el rendimiento.
Variantes
El término árbol B puede referirse a un diseño específico o a una clase general de diseños. En sentido estricto, un árbol B almacena las claves en sus nodos internos, pero no necesariamente en los registros de las hojas. La clase general incluye variantes como el árbol B+ , el árbol B * y el árbol B *+ .
- En el árbol B+ , los nodos internos no almacenan punteros a registros; por lo tanto, todos los punteros a registros se almacenan en los nodos hoja. Además, un nodo hoja puede incluir un puntero al siguiente nodo hoja para acelerar el acceso secuencial. [ 2 ] Debido a que los nodos internos del árbol B+ tienen menos punteros, cada nodo puede contener más claves, lo que hace que el árbol sea menos profundo y, por lo tanto, más rápido de buscar.
- El árbol B * equilibra más nodos internos vecinos para mantener los nodos internos más densamente empaquetados. [ 2 ] Esta variante asegura que los nodos que no son raíz estén al menos 2/3 llenos en lugar de 1/2. [ 15 ] Como la parte más costosa de la operación de insertar el nodo en un árbol B es dividir el nodo, los árboles B * se crean para posponer la operación de división tanto como sea posible. [ 16 ] Para mantener esto, en lugar de dividir inmediatamente un nodo cuando se llena, sus claves se comparten con un nodo adyacente. Esta operación de desbordamiento es menos costosa que la división porque solo requiere mover las claves entre nodos existentes, no asignar memoria para uno nuevo. [ 16 ] Para la inserción, primero se verifica si el nodo tiene algo de espacio libre y, de ser así, la nueva clave simplemente se inserta en el nodo. Sin embargo, si el nodo está lleno (tiene m − 1 claves, donde m es el orden del árbol como el número máximo de punteros a subárboles desde un nodo), es necesario comprobar si existe el hermano derecho y si tiene espacio libre. Si el hermano derecho tiene j < m − 1 claves, entonces las claves se redistribuyen entre los dos nodos hermanos de la manera más equitativa posible. Para ello, m − 1 claves del nodo actual, la nueva clave insertada, una clave del nodo padre y j claves del nodo hermano se consideran como un arreglo ordenado de m + j + 1 claves. El arreglo se divide por la mitad de manera que ⌊ ( m + j + 1)/2 ⌋ las claves más bajas permanecen en el nodo actual, la siguiente clave (la del medio) se inserta en el padre y el resto va al hermano derecho. [ 16 ] (La clave recién insertada podría terminar en cualquiera de los tres lugares.) La situación cuando el hermano derecho está lleno y el izquierdo no lo está es análoga. [ 16 ] Cuando ambos nodos hermanos están llenos, entonces los dos nodos (nodo actual y un hermano) se dividen en tres, y una clave más se desplaza hacia arriba en el árbol hasta el nodo padre. [ 16 ] Si el padre está lleno, entonces la operación de desbordamiento/división se propaga hacia el nodo raíz. [ 16 ] Sin embargo, eliminar nodos es algo más complejo que insertar.
- El árbol B *+ combina las características principales del árbol B+ y del árbol B * . [ 17 ]
- Los árboles B se pueden convertir en árboles de estadísticas de orden para permitir búsquedas rápidas del enésimo registro en el orden de la clave, o contar el número de registros entre dos registros cualesquiera, y varias otras operaciones relacionadas. [ 18 ]
Uso de árboles B en bases de datos
Tiempo de búsqueda de archivos ordenados
Los algoritmos de ordenación y búsqueda se pueden caracterizar por el número de operaciones de comparación que deben realizarse utilizando la notación de orden . Una búsqueda binaria en una tabla ordenada con N registros, por ejemplo, se puede realizar en aproximadamente ⌈ log 2 N ⌉ comparaciones. Si la tabla tuviera 1.000.000 de registros, entonces un registro específico podría localizarse con como máximo 20 comparaciones: ⌈ log 2 (1.000.000) ⌉ = 20 .
Históricamente, las bases de datos grandes se han almacenado en discos duros. El tiempo necesario para leer un registro en un disco duro supera con creces el tiempo necesario para comparar las claves una vez que el registro está disponible, debido al tiempo de búsqueda y al retardo de rotación. El tiempo de búsqueda puede ser de 0 a 20 milisegundos o más, y el retardo de rotación es, en promedio, aproximadamente la mitad del período de rotación. Para un disco de 7200 RPM, el período de rotación es de 8,33 milisegundos. Para un disco como el Seagate ST3500320NS, el tiempo de búsqueda de pista a pista es de 0,8 milisegundos y el tiempo de búsqueda de lectura promedio es de 8,5 milisegundos. [ 19 ] Para simplificar, supongamos que la lectura del disco tarda unos 10 milisegundos.
En el ejemplo anterior, el tiempo necesario para localizar un registro entre un millón sería de 20 lecturas de disco, cada una de las cuales tardaría 10 milisegundos, lo que equivale a 0,2 segundos.
El tiempo de búsqueda se reduce porque los registros individuales se agrupan en un bloque de disco . Un bloque de disco puede tener un tamaño de 16 kilobytes. Si cada registro ocupa 160 bytes, se podrían almacenar 100 registros en cada bloque. El tiempo de lectura del disco mencionado anteriormente corresponde a un bloque completo. Una vez que el cabezal del disco está en posición, se pueden leer uno o más bloques con poca demora. Con 100 registros por bloque, las últimas 6 comparaciones aproximadamente no requieren ninguna lectura del disco, ya que todas se encuentran dentro del último bloque leído.
Para acelerar aún más la búsqueda, debe reducirse el tiempo necesario para realizar las primeras 13 o 14 comparaciones (cada una de las cuales requería acceso al disco).
Rendimiento del índice
Un índice B-tree puede utilizarse para mejorar el rendimiento. Este tipo de índice crea una estructura de árbol multinivel que divide la base de datos en bloques o páginas de tamaño fijo. Cada nivel del árbol permite enlazar las páginas mediante una dirección, de modo que una página (conocida como nodo o página interna) haga referencia a otra, con las páginas hoja en el nivel más bajo. Una página suele ser el punto de partida del árbol, o la "raíz". Aquí es donde comienza la búsqueda de una clave específica, recorriendo una ruta que termina en una página hoja. La mayoría de las páginas de esta estructura son páginas hoja que hacen referencia a filas específicas de la tabla.
Debido a que cada nodo (o página interna) puede tener más de dos hijos, un índice B-tree generalmente tendrá una altura menor (la distancia desde la raíz hasta la hoja más alejada) que un árbol de búsqueda binaria. En el ejemplo anterior, las lecturas iniciales del disco redujeron el rango de búsqueda a la mitad. Esto se puede mejorar creando un índice auxiliar que contenga el primer registro de cada bloque de disco (a veces llamado índice disperso ). Este índice auxiliar tendría un tamaño del 1 % del de la base de datos original, pero se puede buscar rápidamente. Encontrar una entrada en el índice auxiliar nos indicaría qué bloque buscar en la base de datos principal; después de buscar en el índice auxiliar, solo tendríamos que buscar en ese bloque de la base de datos principal, a costa de una lectura de disco adicional.
En el ejemplo anterior, el índice contendría 10 000 entradas y requeriría como máximo 14 comparaciones para obtener un resultado. Al igual que en la base de datos principal, las últimas seis comparaciones del índice auxiliar se ubicarían en el mismo bloque de disco. La búsqueda en el índice podría realizarse en aproximadamente ocho lecturas de disco, y el acceso al registro deseado podría efectuarse en nueve lecturas.
La creación de un índice auxiliar puede repetirse para crear un índice auxiliar del índice auxiliar. Esto crearía un índice auxiliar-auxiliar que solo necesitaría 100 entradas y cabría en un bloque de disco.
En lugar de leer 14 bloques de disco para encontrar el registro deseado, solo necesitamos leer 3. Este bloqueo es la idea central detrás de la creación del árbol B, donde los bloques de disco forman una jerarquía de niveles que componen el índice. Al leer y buscar el primer (y único) bloque del índice auxiliar (la raíz del árbol), se identifica el bloque correspondiente en el índice auxiliar del nivel inferior. Al leer y buscar ese bloque del índice auxiliar, se identifica el bloque que se debe leer hasta que el último nivel, conocido como nivel hoja, identifica un registro en la base de datos principal. En lugar de 150 milisegundos, solo necesitamos 30 milisegundos para obtener el registro.
Los índices auxiliares han transformado el problema de búsqueda de una búsqueda binaria que requiere aproximadamente log 2 N lecturas de disco a una que requiere solo log b N lecturas de disco, donde b es el factor de bloqueo (el número de entradas por bloque: b = 100 entradas por bloque en nuestro ejemplo; log 100 1.000.000 = 3 lecturas).
En la práctica, si la base de datos principal se consulta con frecuencia, el índice auxiliar y gran parte del índice auxiliar pueden residir en una caché de disco , por lo que no implicarían una lectura de disco. El árbol B sigue siendo la implementación de índice estándar en casi todas las bases de datos relacionales , y muchas bases de datos no relacionales también lo utilizan. [ 20 ]
Inserciones y eliminaciones
Si la base de datos no cambia, compilar el índice es sencillo y no es necesario modificarlo. Si hay cambios, la gestión de la base de datos y su índice requiere cálculos adicionales.
Eliminar registros de una base de datos es relativamente fácil. El índice puede permanecer igual y el registro simplemente puede marcarse como eliminado. La base de datos se mantiene ordenada. Si hay un gran número de eliminaciones diferidas , la búsqueda y el almacenamiento se vuelven menos eficientes. [ 21 ]
Las inserciones pueden ser muy lentas en un archivo secuencial ordenado, ya que es necesario crear espacio para el registro que se va a insertar. Insertar un registro antes del primero requiere desplazar todos los registros una posición hacia abajo. Esta operación resulta demasiado costosa para ser práctica. Una solución consiste en dejar espacios. En lugar de agrupar densamente todos los registros en un bloque, este puede tener espacio libre para permitir inserciones posteriores. Estos espacios se marcarían como si fueran registros "eliminados".
Tanto las inserciones como las eliminaciones son rápidas siempre que haya espacio disponible en un bloque. Si una inserción no cabe en el bloque, se debe buscar espacio libre en algún bloque cercano y ajustar los índices auxiliares. Lo ideal es que haya suficiente espacio disponible cerca para minimizar la reorganización de bloques. Como alternativa, se pueden usar algunos bloques de disco fuera de secuencia. [ 20 ]
Uso en bases de datos
El árbol B utiliza todas las ideas descritas anteriormente. En particular, un árbol B:
- Mantiene las claves en orden ordenado para el recorrido secuencial.
- Utiliza un índice jerárquico para minimizar el número de lecturas de disco.
- Utiliza bloques parcialmente llenos para acelerar las inserciones y eliminaciones.
- Mantiene el índice equilibrado con un algoritmo recursivo.
Además, un árbol B minimiza el desperdicio al asegurar que los nodos internos estén al menos medio llenos. Un árbol B puede manejar un número arbitrario de inserciones y eliminaciones. [ 20 ]
alturas en el mejor y el peor de los casos
Sea h ≥ –1 la altura del árbol B clásico (véase Árbol (estructura de datos) § Terminología para la definición de altura del árbol). Sea n ≥ 0 el número de entradas en el árbol. Sea m el número máximo de hijos que puede tener un nodo. Cada nodo puede tener como máximo m −1 claves.
Se puede demostrar (por inducción, por ejemplo) que un árbol B de altura h con todos sus nodos completamente llenos tiene n = m h +1 –1 entradas. Por lo tanto, la altura óptima (es decir, la altura mínima) de un árbol B es:
Dejarsea el número mínimo de hijos que debe tener un nodo interno (no raíz). Para un árbol B ordinario,
Comer (1979) y Cormen et al. (2001) dan la altura del peor caso (la altura máxima) de un árbol B como: [ 22 ]
Algoritmos
Buscar
La búsqueda es similar a la de un árbol de búsqueda binaria. Partiendo de la raíz, el árbol se recorre recursivamente de arriba abajo. En cada nivel, la búsqueda reduce su campo de visión al puntero hijo (subárbol) cuyo rango incluye el valor buscado. El rango de un subárbol está definido por los valores, o claves, contenidos en su nodo padre. Estos valores límite también se conocen como valores de separación.
La búsqueda binaria se utiliza normalmente (aunque no necesariamente) dentro de los nodos para encontrar los valores de separación y el árbol hijo de interés.
Inserción

Todas las inserciones comienzan en un nodo hoja. Para insertar un nuevo elemento, busque en el árbol el nodo hoja donde se debe agregar el nuevo elemento. Inserte el nuevo elemento en ese nodo siguiendo estos pasos:
- Si el nodo contiene menos elementos de los permitidos, hay espacio para el nuevo elemento. Inserte el nuevo elemento en el nodo, manteniendo el orden de los elementos existentes.
- De lo contrario, el nodo está lleno, divídalo equitativamente en dos nodos, de la siguiente manera:
- Se elige una única mediana entre los elementos de la hoja y el nuevo elemento que se está insertando.
- Los valores inferiores a la mediana se colocan en el nuevo nodo izquierdo, y los valores superiores a la mediana se colocan en el nuevo nodo derecho, actuando la mediana como valor de separación.
- El valor de separación se inserta en el nodo padre, lo que puede provocar que se divida, y así sucesivamente. Si el nodo no tiene padre (es decir, si era la raíz), se crea una nueva raíz encima de este nodo (aumentando la altura del árbol).
Si la división llega hasta la raíz, crea una nueva raíz con un único valor separador y dos hijos, razón por la cual el límite inferior del tamaño de los nodos internos no se aplica a la raíz. El número máximo de elementos por nodo es U −1. Cuando un nodo se divide, un elemento se mueve al padre, pero se agrega otro. Por lo tanto, debe ser posible dividir el número máximo U −1 de elementos en dos nodos válidos. Si este número es impar, entonces U = 2 L y uno de los nuevos nodos contiene ( U −2)/2 = L −1 elementos, y por lo tanto es un nodo válido, y el otro contiene un elemento más, y por lo tanto también es válido. Si U −1 es par, entonces U = 2 L −1, por lo que hay 2 L −2 elementos en el nodo. La mitad de este número es L −1, que es el número mínimo de elementos permitidos por nodo.
Un algoritmo alternativo permite un único recorrido descendente del árbol desde la raíz hasta el nodo donde se realizará la inserción, dividiendo de forma preventiva cualquier nodo lleno que se encuentre en el camino. Esto evita la necesidad de recuperar los nodos padre en memoria, lo cual puede resultar costoso si los nodos se encuentran en almacenamiento secundario. Sin embargo, para utilizar este algoritmo, debemos poder enviar un elemento al padre y dividir los U −2 elementos restantes en dos nodos válidos, sin añadir un nuevo elemento. Esto requiere U = 2 L en lugar de U = 2 L −1, lo que explica por qué algunos libros de texto imponen este requisito al definir los árboles B.
Supresión
Existen dos estrategias populares para eliminar elementos de un árbol B.
- Localice y elimine el elemento, luego reestructure el árbol para conservar sus invariantes, O
- Realice un único recorrido por el árbol, pero antes de entrar (visitar) un nodo, reestructure el árbol de manera que, una vez que se encuentre la clave que se va a eliminar, se pueda eliminar sin que sea necesario realizar ninguna reestructuración adicional.
El algoritmo que se muestra a continuación utiliza la primera estrategia.
Hay dos casos especiales a tener en cuenta al eliminar un elemento:
- El elemento en un nodo interno es un separador para sus nodos hijos.
- Eliminar un elemento puede hacer que su nodo tenga un número mínimo de elementos e hijos.
Los procedimientos para estos casos se detallan a continuación.
Eliminación de un nodo hoja
- Busque el valor que desea eliminar.
- Si el valor se encuentra en un nodo hoja, simplemente elimínelo del nodo.
- Si se produce un desbordamiento negativo, reequilibre el árbol como se describe en la sección "Reequilibrio después de la eliminación" a continuación.
Eliminación desde un nodo interno
Cada elemento de un nodo interno actúa como separador entre dos subárboles; necesitamos encontrar un reemplazo para dicho separador. Cabe destacar que el elemento más grande del subárbol izquierdo sigue siendo menor que el separador. Del mismo modo, el elemento más pequeño del subárbol derecho sigue siendo mayor que el separador. Ambos elementos se encuentran en nodos hoja, y cualquiera de ellos puede ser el nuevo separador para los dos subárboles. El algoritmo se describe a continuación:
- Elija un nuevo separador (ya sea el elemento más grande del subárbol izquierdo o el elemento más pequeño del subárbol derecho), elimínelo del nodo hoja en el que se encuentra y reemplace el elemento que se va a eliminar con el nuevo separador.
- El paso anterior eliminó un elemento (el nuevo separador) de un nodo hoja. Si ese nodo hoja ahora es deficiente (tiene menos nodos de los requeridos), entonces reequilibre el árbol comenzando desde ese nodo.
Reequilibrio tras la eliminación
El reequilibrio comienza en una hoja y avanza hacia la raíz hasta que el árbol esté equilibrado. Si al eliminar un elemento de un nodo este queda por debajo del tamaño mínimo, entonces algunos elementos deben redistribuirse para que todos los nodos alcancen el mínimo. Generalmente, la redistribución implica mover un elemento de un nodo hermano que tenga más del número mínimo de nodos. Esta operación de redistribución se denomina rotación . Si ningún hermano puede prescindir de un elemento, entonces el nodo deficiente debe fusionarse con un hermano. La fusión hace que el padre pierda un elemento separador, por lo que el padre puede volverse deficiente y necesitar reequilibrio. La fusión y el reequilibrio pueden continuar hasta la raíz. Dado que el recuento mínimo de elementos no se aplica a la raíz, que la raíz sea el único nodo deficiente no representa un problema. El algoritmo para reequilibrar el árbol es el siguiente:
- Si existe un nodo hermano derecho del nodo deficiente y tiene más del número mínimo de elementos, entonces gire hacia la izquierda.
- Copia el separador del nodo padre al final del nodo deficiente (el separador se mueve hacia abajo; el nodo deficiente ahora tiene el número mínimo de elementos).
- Reemplaza el separador en el padre con el primer elemento del hermano derecho (el hermano derecho pierde un nodo pero aún conserva al menos el número mínimo de elementos).
- El árbol ahora está equilibrado.
- De lo contrario, si existe un nodo hermano izquierdo del nodo deficiente y tiene más del número mínimo de elementos, entonces gire a la derecha.
- Copia el separador del nodo padre al inicio del nodo deficiente (el separador se mueve hacia abajo; el nodo deficiente ahora tiene el número mínimo de elementos).
- Reemplaza el separador en el padre con el último elemento del hermano izquierdo (el hermano izquierdo pierde un nodo pero aún conserva al menos el número mínimo de elementos).
- El árbol ahora está equilibrado.
- De lo contrario, si ambos hermanos inmediatos tienen solo el número mínimo de elementos, entonces se fusionan con un hermano que intercala su separador tomado de su padre.
- Copia el separador al final del nodo izquierdo (el nodo izquierdo puede ser el nodo deficiente o el nodo hermano con el número mínimo de elementos).
- Mueve todos los elementos del nodo derecho al nodo izquierdo (ahora el nodo izquierdo tiene el número máximo de elementos y el nodo derecho está vacío).
- Elimine el separador del elemento padre junto con su hijo derecho vacío (el elemento padre pierde un elemento).
- Si el nodo padre es la raíz y ahora no tiene elementos, libérelo y haga que el nodo fusionado sea la nueva raíz (el árbol se vuelve menos profundo).
- De lo contrario, si el padre tiene menos elementos de los requeridos, entonces reequilibre el padre [ 23 ].
- Nota : Las operaciones de reequilibrio son diferentes para los árboles B+ (por ejemplo, la rotación es diferente porque el padre tiene una copia de la clave) y los árboles B * (por ejemplo, tres hermanos se fusionan en dos hermanos).
Acceso secuencial
Si bien las bases de datos recién cargadas tienden a tener un buen comportamiento secuencial, este comportamiento se vuelve cada vez más difícil de mantener a medida que la base de datos crece, lo que genera más E/S aleatorias y problemas de rendimiento. [ 24 ]
Construcción inicial
Un caso especial común es la adición de una gran cantidad de datos preordenados a un árbol B inicialmente vacío. Si bien es posible realizar una serie de inserciones sucesivas, la inserción de datos ordenados da como resultado un árbol compuesto casi en su totalidad por nodos parcialmente llenos. En cambio, se puede utilizar un algoritmo especial de "carga masiva" para producir un árbol más eficiente con un factor de ramificación mayor.
Cuando la entrada está ordenada, todas las inserciones se encuentran en el extremo derecho del árbol, y en particular, cada vez que se divide un nodo, se garantiza que no se realizarán más inserciones en la mitad izquierda. Al cargar datos en bloque, aprovechamos esto y, en lugar de dividir los nodos sobrecargados de manera uniforme, los dividimos de la forma más desigual posible: dejamos el nodo izquierdo completamente lleno y creamos un nodo derecho con cero claves y un hijo (en contra de las reglas habituales de los árboles B).
Al finalizar la carga masiva, el árbol está compuesto casi en su totalidad por nodos completamente llenos; solo el nodo más a la derecha de cada nivel puede estar parcialmente lleno. Dado que estos nodos también pueden estar parcialmente llenos , para restablecer las reglas normales del árbol B, se combinan con sus hermanos izquierdos (que están completamente llenos) y se dividen las claves para obtener dos nodos que estén al menos parcialmente llenos. El único nodo que carece de un hermano izquierdo completo es la raíz, que puede estar parcialmente llena.
En sistemas de archivos
Además de su uso en bases de datos, el árbol B (o variantes § ) también se utiliza en sistemas de archivos para permitir un acceso aleatorio rápido a un bloque arbitrario en un archivo en particular. El problema básico es convertir el bloque de archivo en un bloque de datos.dirección en una dirección de bloque de disco.
Algunos sistemas operativos antiguos, y algunos altamente especializados, requerían que la aplicación asignara el tamaño máximo de un archivo cuando este se creaba. El archivo se puede entonces asignar como bloques de disco contiguos. En ese caso, para convertir la dirección del bloque del archivoEn una dirección de bloque de disco, el sistema operativo simplemente agrega la dirección del bloque de archivo.a la dirección del primer bloque de disco que constituye el archivo. El esquema es simple, pero el archivo no puede exceder su tamaño de creación.
Todos los sistemas operativos modernos y convencionales permiten que un archivo crezca. Los bloques de disco resultantes pueden no ser contiguos, por lo que la asignación de bloques lógicos a bloques físicos es más compleja.
MS-DOS , por ejemplo, utilizaba una tabla de asignación de archivos (FAT) simple. La FAT tiene una entrada para cada bloque de disco [ nota 1 ] , y esa entrada identifica si su bloque está siendo utilizado por un archivo y, de ser así, qué bloque (si lo hay) es el siguiente bloque de disco del mismo archivo. Por lo tanto, la asignación de cada archivo se representa como una lista enlazada en la tabla. Para encontrar la dirección de disco del bloque de archivoEl sistema operativo (o la utilidad de disco) debe seguir secuencialmente la lista enlazada del archivo en la FAT. Peor aún, para encontrar un bloque de disco libre, debe escanear secuencialmente la FAT. Para MS-DOS, esto no suponía una gran desventaja porque los discos y los archivos eran pequeños y la FAT tenía pocas entradas y cadenas de archivos relativamente cortas. En el sistema de archivos FAT12 (utilizado en disquetes y discos duros antiguos), no había más de 4080 [ nota 2 ] entradas, y la FAT solía residir en la memoria. A medida que los discos se hicieron más grandes, la arquitectura FAT comenzó a enfrentar desventajas. En un disco grande que utiliza FAT, puede ser necesario realizar lecturas de disco para conocer la ubicación en disco de un bloque de archivo que se va a leer o escribir.
TOPS-20 utilizaba un árbol de nivel 0 a 2 con similitudes a un árbol B. Un bloque de disco constaba de 512 palabras de 36 bits. Si el archivo cabía en un bloque de 512 (2⁹ ) palabras, el directorio del archivo apuntaba a ese bloque físico del disco. Si el archivo cabía en 2¹⁸ palabras , el directorio apuntaba a un índice auxiliar; las 512 palabras de ese índice eran NULL (el bloque no estaba asignado) o apuntaban a la dirección física del bloque. Si el archivo cabía en 2²⁷ palabras , el directorio apuntaba a un bloque que contenía un índice auxiliar-auxiliar; cada entrada era NULL o apuntaba a un índice auxiliar. En consecuencia, el bloque físico del disco para un archivo de 2²⁷ palabras podía localizarse en dos lecturas de disco y leerse en la tercera.
Los sistemas de archivos HFS+ y APFS de Apple , NTFS de Microsoft , [ 25 ] AIX (jfs2) y algunos sistemas de archivos de Linux , como Bcachefs , Btrfs y ext4 , utilizan árboles B.
Los árboles B * se utilizan en los sistemas de archivos HFS y Reiser4 .
El sistema de archivos HAMMER de DragonFly BSD utiliza un árbol B+ modificado. [ 26 ]
Actuación
Un árbol B crece más lentamente a medida que aumenta la cantidad de datos, en comparación con la linealidad de una lista enlazada. En comparación con una lista de saltos , ambas estructuras tienen el mismo rendimiento, pero el árbol B escala mejor a medida que aumenta n . Un árbol T , para sistemas de bases de datos en memoria principal , es similar pero más compacto.
Variaciones
Acceso concurrente
Lehman y Yao [ 27 ] demostraron que se podían evitar todos los bloqueos de lectura (y, por lo tanto, mejorar considerablemente el acceso concurrente) al vincular los bloques del árbol en cada nivel con un puntero "siguiente". Esto da como resultado una estructura de árbol donde tanto las operaciones de inserción como las de búsqueda descienden desde la raíz hasta la hoja. Los bloqueos de escritura solo son necesarios cuando se modifica un bloque del árbol. Esto maximiza la concurrencia de acceso por parte de múltiples usuarios, una consideración importante para las bases de datos y/u otros métodos de almacenamiento ISAM basados en árboles B. El costo asociado a esta mejora es que las páginas vacías no se pueden eliminar del árbol B durante las operaciones normales. (Existen estrategias para implementar la fusión de nodos. [ 28 ] [ 29 ] )
La patente estadounidense 5283894, concedida en 1994, parece mostrar una forma de utilizar un «método de metaacceso» [ 30 ] para permitir el acceso y la modificación concurrentes de árboles B+ sin bloqueos. La técnica accede al árbol «hacia arriba» tanto para búsquedas como para actualizaciones mediante índices adicionales en memoria que apuntan a los bloques de cada nivel en la caché de bloques. No se requiere reorganización para las eliminaciones y no hay punteros «siguiente» en cada bloque como en Lehman y Yao.
Algoritmos paralelos
Dado que los árboles B tienen una estructura similar a la de los árboles rojo-negro , los algoritmos paralelos para árboles rojo-negro también se pueden aplicar a los árboles B.
Arce
Un árbol Maple es un árbol B desarrollado para su uso en el kernel de Linux para reducir la contención de bloqueos en la gestión de memoria virtual. [ 31 ] [ 32 ] [ 33 ]
Árbol (a,b)
Los árboles (a,b) son generalizaciones de los árboles B. Los árboles B requieren que cada nodo interno tenga un mínimo deniños y un máximo deniños, para algún valor preestablecido deEn cambio, un árbol (a,b) permite que el número mínimo de hijos para un nodo interno se establezca arbitrariamente bajo. En un árbol (a,b), cada nodo interno tiene entre a y b hijos, para algunos valores preestablecidos de a y b .
Véase también
Notas
- ↑ En FAT, lo que aquí se denomina "bloque de disco" es lo que la documentación de FAT llama "clúster", que es un grupo de tamaño fijo de uno o más sectores de disco físico completos y contiguos . Para los fines de esta explicación, un clúster no presenta diferencias significativas con respecto a un sector físico.
- ↑ Dos de ellos estaban reservados para fines especiales, por lo que solo 4078 podían representar bloques de disco (clústeres).
Referencias
- 1 2 Bayer, R.; McCreight, E. (julio de 1970). "Organización y mantenimiento de grandes índices ordenados" (PDF) . Actas del Taller ACM SIGFIDET de 1970 (ahora SIGMOD) sobre Descripción, Acceso y Control de Datos - SIGFIDET '70 . Laboratorios de Investigación Científica de Boeing. pág. 107. doi : 10.1145/1734663.1734671 . S2CID 26930249 .
- 1 2 3 4 Comer 1979 .
- ↑ "BTreeMap en std::collections - Rust" . doc.rust-lang.org .
- ↑ «rappel / Contenedores Rappel» . rappel.io .
- 1 2 3 Bayer y McCreight 1972 .
- ↑ Comer 1979 , pág. 123, nota al pie 1.
- 1 2 Weiner, Peter G. (30 de agosto de 2013). "4- Edward M McCreight" – vía Vimeo.
- ↑ "Centro de Desarrollo Profesional de Stanford" . scpd.stanford.edu . Archivado del original el 4 de junio de 2014. Consultado el 16 de enero de 2011 .
- 1 2 Knuth 1998 , pág. 483.
- ↑ Folk y Zoellick 1992 , pág. 362.
- 1 2 Folk y Zoellick 1992 , pág. 363.
- ↑ Bayer y McCreight (1972) evitaron el problema al afirmar que un elemento de índice es un par (físicamente adyacente) de ( x , a ), donde x es la clave y a es alguna información asociada. Esta información asociada podría ser un puntero a uno o varios registros en un acceso aleatorio, pero su naturaleza no era relevante. Bayer y McCreight (1972) declaran: «Para este trabajo, la información asociada carece de mayor interés».
- ↑ Folk y Zoellick 1992 , pág. 379.
- ↑ Knuth 1998 , pág. 488.
- ^ Tomašević , Milo ( 2008 ) . Algoritmos y estructuras de datos . Belgrado, Serbia: Akademska misao. págs. 274-275 . ISBN 978-86-7466-328-8.
- ↑ Rigin AM, Shershakov SA (10 de septiembre de 2019). "Extensión de SQLite RDBMS para indexación de datos mediante modificaciones de árboles B" . Actas del Instituto de Programación de Sistemas de la RAS . 31 (3). Instituto de Programación de Sistemas de la RAS (ISP RAS): 203–216 . doi : 10.15514/ispras-2019-31(3)-16 . S2CID 203144646. Consultado el 29 de agosto de 2021 .
- ↑ "Árboles B contados" . www.chiark.greenend.org.uk . Consultado el 27 de diciembre de 2024 .
- ↑ Manual del producto: Barracuda ES.2 Serial ATA, Rev. F, publicación 100468393 (PDF) . Seagate Technology LLC. 2008. pág. 6.
- 1 2 3 Kleppmann, Martin (2017). Diseño de aplicaciones intensivas en datos . Sebastopol, California : O'Reilly Media . pág. 80. ISBN 978-1-449-37332-0.
- ↑ Jan Jannink. "Implementación de la eliminación en árboles B+". Sección "4 Eliminación perezosa" .
- ↑ Comer 1979 , pág. 127 ; Cormen et al. 2001 , págs. 439–440
- ↑ "Eliminación en un árbol B" (PDF) . cs.rhodes.edu . Archivado (PDF) del original el 9 de octubre de 2022. Consultado el 24 de mayo de 2022 .
- ↑ "Cache Oblivious B-trees" . Universidad Estatal de Nueva York (SUNY) en Stony Brook . Consultado el 17 de enero de 2011 .
- ↑ Mark Russinovich (30 de junio de 2006). "Dentro de Win2K NTFS, Parte 1" . Microsoft Developer Network . Archivado del original el 13 de abril de 2008. Recuperado el 18 de abril de 2008 .
- ↑ Matthew Dillon (21/06/2008). "El sistema de archivos HAMMER" (PDF) . Archivado (PDF) del original el 09/10/2022.
- ↑ Lehman, Philip L.; Yao, s. Bing (1981). "Bloqueo eficiente para operaciones concurrentes en árboles B" . ACM Transactions on Database Systems . 6 (4): 650– 670. doi : 10.1145/319628.319663 . S2CID 10756181 .
- ↑ Wang, Paul (1 de febrero de 1991). "Análisis en profundidad de algoritmos concurrentes de árboles B" (PDF) . dtic.mil . Archivado del original (PDF) el 4 de junio de 2011. Consultado el 21 de octubre de 2022 .
- ↑ "Descargas - high-concurrency-btree - Código B-Tree de alta concurrencia en C - Alojamiento de proyectos en GitHub" . GitHub . Consultado el 27 de enero de 2014 .
- ↑ "Método de metadatos de índice B-tree concurrente sin bloqueo para nodos en caché" .
- ↑ "Presentando los arces [ LWN.net ] " . lwn.net .
- ↑ "Maple Tree — La documentación del kernel de Linux" . docs.kernel.org .
- ↑ "Presentando el árbol de arce [ LWN.net ] " . lwn.net .
Este artículo incorpora material de dominio público de Paul E. Black. "(a,b)-tree" . Diccionario de algoritmos y estructuras de datos . NIST .
Fuentes
- Bayer, R.; McCreight , E. (1972). "Organización y mantenimiento de grandes índices ordenados" (PDF) . Acta Informatica . 1 (3): 173– 189. doi : 10.1007/bf00288683 . S2CID 29859053 . .
- Comer, Douglas (junio de 1979). "El árbol B ubicuo" . Computing Surveys . 11 (2): 123– 137. doi : 10.1145/356770.356776 . ISSN 0360-0300 . S2CID 101673 . .
- Cormen, Tomás ; Leiserson, Charles ; Rivest, Ronald ; Stein, Clifford (2001). Introducción a los algoritmos (Segunda ed.). MIT Press y McGraw-Hill. págs. 434–454 . ISBN 0-262-03293-7.Capítulo 18: Árboles B.
- Folk, Michael J.; Zoellick, Bill (1992). Estructuras de archivos (2.ª ed.). Addison-Wesley. ISBN 0-201-55713-4..
- Knuth, Donald (1998). Ordenación y búsqueda . El arte de la programación informática . Vol. 3 (Segunda edición). Addison-Wesley. ISBN 0-201-89685-0.Sección 6.2.4: Árboles multidireccionales, págs. 481–491. Asimismo, las págs. 476–477 de la sección 6.2.3 (Árboles equilibrados) tratan sobre árboles de 2 a 3 ramas.
Documentos originales
- Bayer, Rudolf ; McCreight, E. (julio de 1970), Organización y mantenimiento de grandes índices ordenados , vol. Informe de Ciencias Matemáticas e Informáticas n.º 20, Laboratorios de Investigación Científica de Boeing.
- Bayer, Rudolf (1971). "Árboles B binarios para memoria virtual". Actas del taller ACM-SIGFIDET de 1971 sobre descripción, acceso y control de datos . San Diego, California..
Enlaces externos
- Conferencia sobre árboles B impartida por David Scot Taylor, SJSU.
- Visualización del árbol B (haga clic en "inicializar")
- Visualización animada de un árbol B
- Árbol B y árbol UB en Scholarpedia. Curador: Dr. Rudolf Bayer.
- Árboles B: Estructuras de datos de árboles equilibrados. Archivado el 5 de marzo de 2010 en Wayback Machine.
- Diccionario de algoritmos y estructuras de datos del NIST: Árbol B
- Tutorial sobre el árbol B
- Implementación del árbol B de InfinityDB
- Árboles B(+) que ignoran la caché
- Entrada del Diccionario de Algoritmos y Estructuras de Datos para el árbol B*
- Estructuras de datos abiertas - Sección 14.2 - Árboles B , Pat Morin
- Árboles B contados
- B-Tree .Net, una implementación moderna y virtualizada de RAM y disco. Archivado el 4 de marzo de 2016 en Wayback Machine.
Carga a granel
- Shetty, Soumya B. (2010). Una implementación configurable por el usuario de árboles B (Tesis). Universidad Estatal de Iowa.
- Kaldırım, Semih (28 de abril de 2015). "Organización de archivos, ISAM, árbol B+ y carga masiva" (PDF) . Ankara, Turquía: Universidad de Bilkent . págs. 4–6 . Archivado (PDF) del original el 9 de octubre de 2022.
- "ECS 165B: Implementación de sistemas de bases de datos: Lección 6" (PDF) . Universidad de California, Davis . 9 de abril de 2010. pág. 23. Archivado (PDF) del original el 9 de octubre de 2022.
- "INSERCIÓN MASIVA (Transact-SQL) en SQL Server 2017" . Documentación de Microsoft. 6 de septiembre de 2018.
- Árbol B
- Introducciones relacionadas con la informática en 1971
- Técnicas de indexación de bases de datos