En informática , un árbol B es una estructura de datos de árbol autoequilibrado que mantiene datos ordenados y permite búsquedas, acceso secuencial, inserciones y eliminaciones en tiempo logarítmico . El árbol B generaliza el árbol binario de búsqueda , permitiendo nodos con más de dos hijos. [2] A diferencia de otros árboles binarios de búsqueda autoequilibrados , el árbol B es adecuado para sistemas de almacenamiento que leen y escriben bloques de datos relativamente grandes , como bases de datos y sistemas de archivos .
Los árboles B fueron inventados por Rudolf Bayer y Edward M. McCreight mientras trabajaban en Boeing Research Labs , con el propósito de administrar de manera eficiente las páginas de índice para archivos grandes de acceso aleatorio. La suposición básica era que los índices serían tan voluminosos que solo pequeños fragmentos del árbol podrían caber en la memoria principal. El artículo de Bayer y McCreight Organization and maintain of large ordered indices [1] se distribuyó por primera vez en julio de 1970 y luego se publicó en Acta Informatica . [3]
Bayer y McCreight nunca explicaron qué significa la B , si es que significa algo ; se han sugerido Boeing , balanced , between , broad , bushy y Bayer . [4] [5] [6] McCreight ha dicho que "cuanto más piensas en lo que significa la B en los árboles B, mejor entiendes los árboles B". [5]
Según la definición de Knuth , un árbol B de orden m es un árbol que satisface las siguientes propiedades: [7]
Las claves de cada nodo interno actúan como valores de separación que dividen sus subárboles. Por ejemplo, si un nodo interno tiene 3 nodos secundarios (o subárboles), entonces debe tener 2 claves: un 1 y un 2. Todos los valores en el subárbol más a la izquierda serán menores que un 1 , todos los valores en el subárbol del medio estarán entre un 1 y un 2 , y todos los valores en el subárbol más a la derecha serán mayores que un 2 .
Un árbol B de profundidad n +1 puede contener 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 sucede con cualquier árbol equilibrado, el costo crece mucho más lentamente que la cantidad de elementos.
Algunos árboles equilibrados almacenan valores solo en los nodos de hoja y utilizan distintos tipos de nodos para los nodos de hoja y los nodos internos. Los árboles B mantienen los valores en todos los nodos del árbol, excepto en los nodos de hoja.
La literatura sobre árboles B no es uniforme en su terminología. [8]
Bayer y McCreight (1972), [3] 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 raíz. Folk y Zoellick [9] 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 puede 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). [7]
El término hoja también es inconsistente. Bayer y McCreight (1972) [3] consideraron que el nivel de hoja era el nivel más bajo de claves, pero Knuth consideró que el nivel de hoja era un nivel por debajo de las claves más bajas. [9] Hay muchas opciones de implementación posibles. En algunos diseños, las hojas pueden contener el registro de datos completo; en otros diseños, las hojas pueden contener solo punteros al registro de datos. Esas opciones no son fundamentales para la idea de un árbol B. [10]
Para simplificar, la mayoría de los autores suponen que hay una cantidad fija de claves que caben en un nodo. El supuesto básico es que el tamaño de la clave es fijo y el tamaño del nodo es fijo. En la práctica, se pueden utilizar claves de longitud variable. [11]
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 conocido como interior) y hoja .
Tenga en cuenta las siguientes definiciones de variables:
En los árboles B, se mantienen las siguientes propiedades para estos nodos:
Cada nodo interno de un árbol B tiene el siguiente formato:
Cada nodo de hoja en un árbol B tiene el siguiente formato:
Los límites de los nodos se resumen en la siguiente tabla:
Para mantener el rango predefinido de nodos secundarios, los nodos internos pueden unirse o dividirse.
Por lo general, se elige que la cantidad de claves varíe entre y , donde es la cantidad mínima de claves y es el grado mínimo o factor de ramificación del árbol. El factor 2 garantizará que los nodos se puedan dividir o combinar.
Si un nodo interno tiene claves, se puede agregar una clave a ese nodo dividiendo el nodo clave hipotético en dos nodos clave y moviendo la clave que habría estado en el medio al nodo padre. Cada nodo dividido tiene la cantidad mínima requerida de claves. De manera similar, si un nodo interno y su vecino tienen claves, se puede eliminar una clave del nodo interno combinándola con su vecino. Eliminar la clave haría que el nodo interno tuviera claves; unir el vecino agregaría claves más una clave más obtenida del padre del vecino. El resultado es un nodo completamente lleno de claves.
Un árbol B se mantiene equilibrado después de la inserción dividiendo un nodo que podría estar sobrecargado, de claves, en dos hermanos de clave intermedia e insertando la clave de valor medio en el padre. La profundidad solo aumenta cuando se divide la raíz, lo que mantiene el equilibrio. De manera similar, un árbol B se mantiene equilibrado después de la eliminación fusionando o redistribuyendo claves entre hermanos para mantener el mínimo de claves para nodos que no son raíz. Una fusión reduce la cantidad 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 claves y (transicionalmente) , en cuyo caso los dos hermanos y el padre se fusionan, lo que reduce 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 de las hojas estén un nodo más lejos de la raíz.
Debido a que se permite un rango de nodos secundarios, 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 tienen ventajas sustanciales sobre las implementaciones alternativas cuando el tiempo para acceder a los datos de un nodo excede en gran medida el tiempo empleado en procesar esos datos, porque entonces el costo de acceder al nodo puede amortizarse en múltiples operaciones dentro del nodo. Esto suele ocurrir cuando los datos del nodo están en un almacenamiento secundario , como unidades de disco . Al maximizar la cantidad de claves dentro de cada nodo interno , la altura del árbol disminuye y se reduce la cantidad de accesos costosos a los nodos. Además, el reequilibrio del árbol ocurre con menos frecuencia. La cantidad máxima de nodos secundarios depende de la información que se debe almacenar para cada nodo secundario y del tamaño de un bloque de disco completo o un tamaño análogo en el almacenamiento secundario. Si bien es más fácil explicar 2 o 3 árboles B, los árboles B prácticos que utilizan almacenamiento secundario necesitan una gran cantidad de nodos secundarios para mejorar el rendimiento.
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 claves en sus nodos internos, pero no necesita almacenar esas claves en los registros de las hojas. La clase general incluye variaciones como el árbol B+ , el árbol B * y el árbol B *+ .
Los algoritmos de ordenación y búsqueda se pueden caracterizar por la cantidad de operaciones de comparación que se deben realizar utilizando la notación de orden . Una búsqueda binaria de 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 se podría localizar un registro específico con un máximo de 20 comparaciones: ⌈ log 2 (1.000.000) ⌉ = 20 .
Históricamente, las bases de datos de gran tamaño se han guardado en unidades de disco. El tiempo necesario para leer un registro en una unidad de disco supera con creces el tiempo necesario para comparar las claves una vez que el registro está disponible debido al tiempo de búsqueda y a un retraso rotacional. El tiempo de búsqueda puede ser de 0 a 20 o más milisegundos, y el retraso rotacional promedia aproximadamente la mitad del período de rotación. Para una unidad de 7200 RPM, el período de rotación es de 8,33 milisegundos. Para una unidad como la 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. [17] Para simplificar, supongamos que la lectura del disco demora aproximadamente 10 milisegundos.
El tiempo para localizar un registro entre un millón en el ejemplo anterior tomaría 20 lecturas de disco multiplicadas por 10 milisegundos por lectura de disco, 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 16 kilobytes. Si cada registro tiene 160 bytes, se pueden almacenar 100 registros en cada bloque. El tiempo de lectura del disco anterior en realidad era para un bloque completo. Una vez que el cabezal del disco está en posición, se pueden leer uno o más bloques de disco con poca demora. Con 100 registros por bloque, las últimas 6 o más comparaciones no necesitan realizar ninguna lectura de disco: todas las comparaciones se realizan dentro del último bloque de disco leído.
Para acelerar aún más la búsqueda, se debe reducir el tiempo necesario para realizar las primeras 13 a 14 comparaciones (cada una de las cuales requiere acceso al disco).
Se puede utilizar un índice de árbol B para mejorar el rendimiento. Un índice de árbol B crea una estructura de árbol de varios niveles que divide una base de datos en bloques o páginas de tamaño fijo. Cada nivel de este árbol se puede utilizar para vincular esas páginas a través de una ubicación de dirección, lo que permite que una página (conocida como nodo o página interna) haga referencia a otra con páginas hoja en el nivel más bajo. Una página suele ser el punto de inicio del árbol o la "raíz". Aquí es donde comenzaría la búsqueda de una clave en particular, recorriendo una ruta que termina en una hoja. La mayoría de las páginas de esta estructura serán páginas hoja que hacen referencia a filas de tabla específicas.
Debido a que cada nodo (o página interna) puede tener más de dos hijos, un índice de árbol B generalmente tendrá una altura más corta (la distancia desde la raíz hasta la hoja más lejana) que un árbol de búsqueda binaria. En el ejemplo anterior, las lecturas iniciales del disco redujeron el rango de búsqueda por un factor de dos. Esto se puede mejorar creando un índice auxiliar que contenga el primer registro en cada bloque del disco (a veces llamado índice disperso ). Este índice auxiliar sería el 1% del tamaño de la base de datos original, pero se puede buscar rápidamente. Encontrar una entrada en el índice auxiliar nos indicaría en qué bloque buscar en la base de datos principal; después de buscar en el índice auxiliar, tendríamos que buscar solo en ese bloque de la base de datos principal, a un costo de una lectura de disco más.
En el ejemplo anterior, el índice contendría 10 000 entradas y se necesitarían como máximo 14 comparaciones para obtener un resultado. Al igual que la base de datos principal, las últimas seis comparaciones aproximadamente en el índice auxiliar se realizarían en el mismo bloque de disco. Se podría buscar en el índice en aproximadamente ocho lecturas de disco y se podría acceder al registro deseado en nueve lecturas de disco.
Se puede repetir la creación de un índice auxiliar para crear un índice auxiliar para el índice auxiliar. Esto crearía un índice auxiliar-auxiliar que necesitaría solo 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 bloques. Este bloqueo es la idea central detrás de la creación del árbol B, donde los bloques de disco completan una jerarquía de niveles para formar el índice. La lectura y búsqueda del primer (y único) bloque del índice aux-aux, que es la raíz del árbol, identifica el bloque relevante en aux-index en el nivel inferior. La lectura y búsqueda de ese bloque aux-index identifica el bloque relevante para leer, hasta que el nivel final, conocido como el nivel de 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 se realizan búsquedas frecuentes en la base de datos principal, el índice auxiliar-auxiliar y gran parte del índice auxiliar pueden residir en una caché de disco , por lo que no se produciría 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. [18]
Si la base de datos no cambia, compilar el índice es sencillo y no es necesario modificarlo nunca. 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 puede simplemente marcarse como eliminado. La base de datos permanece ordenada. Si hay una gran cantidad de eliminaciones diferidas , la búsqueda y el almacenamiento se vuelven menos eficientes. [19]
Las inserciones pueden ser muy lentas en un archivo secuencial ordenado porque se debe hacer espacio para el registro insertado. Insertar un registro antes del primero requiere desplazar todos los registros hacia abajo. Esta operación es demasiado costosa para ser práctica. Una solución es dejar algunos espacios. En lugar de comprimir densamente todos los registros en un bloque, el bloque puede tener algo de espacio libre para permitir inserciones posteriores. Esos 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 encontrar espacio libre en algún bloque cercano y ajustar los índices auxiliares. El mejor caso es que haya suficiente espacio disponible cerca para que se pueda minimizar la cantidad de reorganización de bloques. Alternativamente, se pueden utilizar algunos bloques de disco fuera de secuencia. [18]
El árbol B utiliza todas las ideas descritas anteriormente. En particular, un árbol B:
Además, un árbol B minimiza el desperdicio al garantizar que los nodos interiores estén al menos medio llenos. Un árbol B puede manejar una cantidad arbitraria de inserciones y eliminaciones. [18]
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 del mejor caso (es decir, la altura mínima) de un árbol B es:
Sea el número mínimo de hijos que debe tener un nodo interno (no raíz). Para un árbol B común,
Comer (1979) y Cormen et al. (2001) dan la altura del peor caso (la altura máxima) de un árbol B como [20]
La búsqueda es similar a la de un árbol binario de búsqueda. Comenzando por la raíz, el árbol se recorre recursivamente de arriba a abajo. En cada nivel, la búsqueda reduce su campo de visión al puntero secundario (subárbol) cuyo rango incluye el valor de búsqueda. El rango de un subárbol está definido por los valores, o claves, contenidos en su nodo principal. Estos valores límite también se conocen como valores de separación.
La búsqueda binaria se utiliza normalmente (pero no necesariamente) dentro de los nodos para encontrar los valores de separación y el árbol secundario de interés.
Todas las inserciones comienzan en un nodo de hoja. Para insertar un nuevo elemento, busque en el árbol el nodo de hoja donde se debe agregar el nuevo elemento. Inserte el nuevo elemento en ese nodo siguiendo estos pasos:
Si la división llega hasta la raíz, crea una nueva raíz con un único valor separador y dos hijos, por lo que 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 se divide un nodo, un elemento se mueve al padre, pero se agrega un elemento. Por lo tanto, debe ser posible dividir el número máximo U −1 de elementos en dos nodos legales. 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 legal, y el otro contiene un elemento más, y por lo tanto también es legal. 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 realizar un único paso por el árbol desde la raíz hasta el nodo donde se realizará la inserción, dividiendo preventivamente cualquier nodo completo que se encuentre en el camino. Esto evita la necesidad de recuperar los nodos padre en la memoria, lo que puede resultar costoso si los nodos están en un 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 legales, sin agregar un nuevo elemento. Esto requiere U = 2 L en lugar de U = 2 L −1, lo que explica por qué algunos libros de texto [¿ cuáles? ] imponen este requisito al definir árboles B.
Hay dos estrategias populares para la eliminación de un árbol B.
El algoritmo siguiente utiliza la primera estrategia.
Hay dos casos especiales a tener en cuenta al eliminar un elemento:
Los procedimientos para estos casos se detallan a continuación.
Cada elemento de un nodo interno actúa como un valor de separación para dos subárboles, por lo tanto, necesitamos encontrar un reemplazo para la separación. Tenga en cuenta que el elemento más grande en el subárbol izquierdo sigue siendo menor que el separador. Del mismo modo, el elemento más pequeño en el subárbol derecho sigue siendo mayor que el separador. Ambos elementos están en nodos de hoja y cualquiera de ellos puede ser el nuevo separador para los dos subárboles. Se describe algorítmicamente a continuación:
El reequilibrio comienza desde una hoja y avanza hacia la raíz hasta que el árbol está equilibrado. Si la eliminación de un elemento de un nodo lo ha reducido al tamaño mínimo, entonces algunos elementos deben redistribuirse para que todos los nodos alcancen el mínimo. Por lo general, la redistribución implica mover un elemento de un nodo hermano que tiene más que el número mínimo de nodos. Esa 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 un 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, hacer que la raíz sea el único nodo deficiente no es un problema. El algoritmo para reequilibrar el árbol es el siguiente:
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 problemas de E/S aleatorios y de rendimiento. [22]
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 bastante 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 medio 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 más alto.
Cuando se ordena la entrada, todas las inserciones se realizan en el borde más a la derecha del árbol y, en particular, cada vez que se divide un nodo, tenemos la garantía de que no se producirán más inserciones en la mitad izquierda. Al realizar una carga masiva, aprovechamos esto y, en lugar de dividir los nodos demasiado llenos de manera uniforme, los dividimos de la manera más desigual posible: dejamos el nodo izquierdo completamente lleno y creamos un nodo derecho con cero claves y un hijo (en violación de las reglas habituales del árbol B).
Al final de 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 menos que lleno. Debido a que esos nodos también pueden estar menos que medio llenos, para restablecer las reglas normales del árbol B, combine esos nodos con sus hermanos izquierdos (completos garantizados) y divida las claves para producir dos nodos al menos medio llenos. El único nodo que carece de un hermano izquierdo completo es la raíz, que puede estar menos que medio lleno.
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 la dirección del bloque del archivo en una dirección del bloque del disco.
Algunos sistemas operativos requieren que el usuario asigne el tamaño máximo del archivo cuando se crea el archivo. El archivo puede entonces asignarse como bloques de disco contiguos. En ese caso, para convertir la dirección del bloque de archivo en una dirección de bloque de disco, el sistema operativo simplemente suma 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 creado.
Otros sistemas operativos 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 sencilla tabla de asignación de archivos (FAT). La FAT tiene una entrada para cada bloque de disco [nota 1] y esa entrada identifica si su bloque es utilizado por un archivo y, en caso afirmativo, 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 archivo , el sistema operativo (o la utilidad de discos) 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, eso no era una gran penalización porque los discos y 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 primeros discos duros), no había más de 4080 [nota 2] entradas y la FAT normalmente residía en la memoria. A medida que los discos se hicieron más grandes, la arquitectura FAT comenzó a enfrentar penalizaciones. En un disco grande que utiliza FAT, puede ser necesario realizar lecturas de disco para conocer la ubicación del disco de un bloque de archivo que se leerá o escribirá.
TOPS-20 (y posiblemente TENEX ) usaba un árbol de nivel 0 a 2 que tiene similitudes con un árbol B [ cita requerida ] . Un bloque de disco tenía 512 palabras de 36 bits. Si el archivo cabía en un bloque de 512 (2 9 ) palabras, entonces el directorio del archivo apuntaría a ese bloque de disco físico. Si el archivo cabía en 2 18 palabras, entonces el directorio apuntaría a un índice auxiliar; las 512 palabras de ese índice serían NULL (el bloque no está asignado) o apuntarían a la dirección física del bloque. Si el archivo cabía en 2 27 palabras, entonces el directorio apuntaría a un bloque que contenía un índice auxiliar-auxiliar; cada entrada sería NULL o apuntaría a un índice auxiliar. En consecuencia, el bloque de disco físico para un archivo de 2 27 palabras podría ubicarse en dos lecturas de disco y leerse en la tercera.
Los sistemas de archivos HFS+ y APFS de Apple , NTFS de Microsoft , [23] AIX (jfs2) y algunos sistemas de archivos 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. [24]
Un árbol B crece más lentamente con una cantidad creciente de datos que 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 con una cantidad creciente de n . Un árbol T , para sistemas de bases de datos de memoria principal , es similar pero más compacto.
Lehman y Yao [25] demostraron que todos los bloqueos de lectura se podían evitar (y, por lo tanto, mejorar enormemente el acceso concurrente) vinculando los bloques del árbol en cada nivel con un puntero "next". Esto da como resultado una estructura de árbol donde tanto las operaciones de inserción como las de búsqueda descienden de la raíz a la hoja. Los bloqueos de escritura solo se requieren 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 con esta mejora es que las páginas vacías no se pueden eliminar del árbol B durante las operaciones normales. (Sin embargo, consulte [26] para conocer varias estrategias para implementar la fusión de nodos y el código fuente en [27] ) .
La patente estadounidense 5283894, otorgada en 1994, parece mostrar una forma de utilizar un "método de acceso meta" [28] para permitir el acceso y la modificación simultáneos del árbol B+ sin bloqueos. La técnica accede al árbol "hacia arriba" tanto para búsquedas como para actualizaciones por medio de índices adicionales en memoria que apuntan a los bloques en cada nivel de la caché de bloques. No se necesita ninguna reorganización para las eliminaciones y no hay punteros "siguientes" en cada bloque como en Lehman y Yao.
Dado que los árboles B son similares en estructura a los árboles rojo-negros , los algoritmos paralelos para árboles rojo-negros también se pueden aplicar a los árboles B.
Un árbol Maple es un árbol B desarrollado para su uso en el núcleo de Linux para reducir la contención de bloqueo en la gestión de memoria virtual. [29] [30] [31]
Carga masiva