Skip to content

Commit 3168293

Browse files
committed
feat: add btree chapter
1 parent 13ad9f9 commit 3168293

5 files changed

Lines changed: 335 additions & 8 deletions

File tree

.github/workflows/docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ jobs:
2828
test -s docs/06-heap.md
2929
test -s docs/07-trie.md
3030
test -s docs/08-graph.md
31+
test -s docs/09-btree.md
3132
test -s examples/README.md
3233
test -s tests/README.md
3334
test -s benches/README.md
@@ -39,6 +40,7 @@ jobs:
3940
test -s benches/heap_bench.rs
4041
test -s benches/trie_bench.rs
4142
test -s benches/graph_bench.rs
43+
test -s benches/btree_bench.rs
4244
test -s diagrams/README.md
4345
test -s diagrams/01-vector.mmd
4446
test -s diagrams/02-linked-list.mmd
@@ -48,6 +50,7 @@ jobs:
4850
test -s diagrams/06-heap.mmd
4951
test -s diagrams/07-trie.mmd
5052
test -s diagrams/08-graph.mmd
53+
test -s diagrams/09-btree.mmd
5154
test -s assets/README.md
5255
5356
grep -q "Vector" docs/SUMMARY.md

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ un algoritmo sea necesario para explicar la propia estructura.
3838
| 06 | Heap | `src/heap.rs` | benchmarked |
3939
| 07 | Trie | `src/trie.rs` | benchmarked |
4040
| 08 | Graph | `src/graph.rs` | benchmarked |
41-
| 09 | B-Tree | `src/btree.rs` | planned |
41+
| 09 | B-Tree | `src/btree.rs` | benchmarked |
4242
| 10 | HashMap | `src/hashmap.rs` | planned |
4343
| 11 | Bloom Filter | `src/bloom_filter.rs` | planned |
4444
| 12 | Skip List | `src/skip_list.rs` | planned |

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ El checklist detallado vive en
2727
| 06 | Heap | benchmarked |
2828
| 07 | Trie | benchmarked |
2929
| 08 | Graph | benchmarked |
30-
| 09 | B-Tree | planned |
30+
| 09 | B-Tree | benchmarked |
3131
| 10 | HashMap | planned |
3232
| 11 | Bloom Filter | planned |
3333
| 12 | Skip List | planned |

docs/09-btree.md

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
# B-Tree
2+
3+
> **Curso:** rust-data-structures · **Capitulo:** 09 · **Prerequisitos:** Vector, Graph, busqueda binaria y orden total
4+
> **Codigo:** [`src/btree.rs`](../src/btree.rs) · **Video:** pendiente
5+
> **Leccion en el sitio:** pendiente
6+
7+
## Introduccion
8+
9+
Un B-tree es un arbol de busqueda multiway. A diferencia de un arbol binario,
10+
cada nodo puede guardar muchas claves y muchos hijos. Esa decision no es un
11+
detalle cosmetico: esta pensada para memoria real, paginas de disco, cache y
12+
bloques contiguos.
13+
14+
En este capitulo implementamos un B-tree educativo de claves ordenadas. Se
15+
comporta como un conjunto: no guarda duplicados. La implementacion incluye
16+
busqueda, insercion, split de nodos llenos e iteracion ordenada. La eliminacion
17+
se estudia como estrategia, pero no se expone como API hasta que el capitulo
18+
pueda cubrirla con la misma calidad que insercion.
19+
20+
## Motivacion
21+
22+
Un arbol binario de busqueda puede degradarse si no se balancea. Un red-black
23+
tree mantiene balance con rotaciones y colores. Un B-tree toma otro camino:
24+
reduce altura guardando mas claves por nodo.
25+
26+
Esa forma lo vuelve canonico para indices de bases de datos y sistemas de
27+
archivos. Cuando leer un nodo cuesta traer una pagina, conviene que cada pagina
28+
contenga muchas claves utiles. Menos altura significa menos saltos.
29+
30+
## Teoria
31+
32+
### Historia
33+
34+
Los B-trees nacieron para almacenamiento secundario. En disco, el costo dominante
35+
no era comparar enteros sino leer bloques. La estructura fue disenada para
36+
mantener un arbol bajo, ancho y ordenado.
37+
38+
Aunque hoy muchos datos viven en memoria, la idea sigue vigente: localidad,
39+
paginas, caches y estructuras ordenadas siguen importando. Por eso B-tree vuelve
40+
a aparecer mas adelante en internals de bases de datos.
41+
42+
### Fundamentos
43+
44+
Un B-tree con grado minimo `t` mantiene estas reglas:
45+
46+
- Cada nodo puede guardar hasta `2t - 1` claves.
47+
- Cada nodo interno con `k` claves tiene `k + 1` hijos.
48+
- Las claves dentro de cada nodo estan ordenadas.
49+
- Los hijos separan rangos: hijo izquierdo menor, hijo derecho mayor.
50+
- La raiz puede tener menos claves que los demas nodos.
51+
- Cuando un nodo lleno recibe otra clave, se divide y sube su mediana.
52+
53+
Este capitulo usa `t = 2` por defecto para que los splits sean faciles de ver.
54+
Tambien permite `with_min_degree(t)` para observar como cambia la capacidad del
55+
nodo.
56+
57+
### Insercion
58+
59+
La insercion sigue una regla practica: no bajamos hacia un hijo lleno. Si el
60+
hijo esta lleno, primero lo dividimos. Eso garantiza que al llegar a una hoja
61+
haya espacio para insertar.
62+
63+
La operacion critica es `split_child`:
64+
65+
```text
66+
antes: [10 | 20 | 30] con t = 2
67+
sube: 20
68+
despues: [10] 20 [30]
69+
```
70+
71+
Si insertamos `40`, el camino baja al hijo derecho y queda `[30 | 40]`.
72+
73+
### Busqueda
74+
75+
Buscar una clave compara dentro del nodo. Si la encuentra, termina. Si no la
76+
encuentra y el nodo es hoja, falla. Si el nodo es interno, baja al hijo cuyo
77+
rango podria contener la clave.
78+
79+
La implementacion usa `binary_search` dentro de cada nodo. En un sistema real,
80+
el tamano de nodo se escoge por pagina o cache line; aqui se escoge por claridad
81+
educativa.
82+
83+
### Eliminacion
84+
85+
Eliminar en B-tree es mas delicado que insertar. No basta con quitar una clave:
86+
hay que conservar ocupacion minima, pedir prestado a hermanos o fusionar nodos.
87+
88+
Estrategia general:
89+
90+
1. Si la clave esta en una hoja, se puede remover directamente si el nodo queda
91+
con suficientes claves.
92+
2. Si la clave esta en un nodo interno, se reemplaza por predecesor o sucesor.
93+
3. Antes de bajar a un hijo con pocas claves, se repara: prestamo de hermano o
94+
merge.
95+
4. Si la raiz queda vacia, su unico hijo se convierte en nueva raiz.
96+
97+
No exponemos `remove` todavia porque un capitulo canonico no debe publicar una
98+
eliminacion incompleta. La estrategia queda explicada para que el estudiante vea
99+
el mapa antes de implementarla en una iteracion futura.
100+
101+
### Casos de uso
102+
103+
Usos clasicos:
104+
105+
- Indices de bases de datos.
106+
- Sistemas de archivos.
107+
- Mapas ordenados.
108+
- Indices por rango.
109+
- Almacenamiento por paginas.
110+
- Estructuras persistentes que favorecen bajo numero de lecturas.
111+
112+
### Comparacion con alternativas
113+
114+
Un arbol binario de busqueda es mas pequeno conceptualmente, pero sin balance
115+
puede degradarse. Un red-black tree mantiene altura logaritmica con rotaciones
116+
locales; suele ser buena opcion en memoria. Un skip list usa niveles
117+
probabilisticos y es simple de implementar concurrentemente en algunos disenos.
118+
Un hashmap busca rapido por clave exacta promedio, pero no mantiene orden ni
119+
rangos naturales.
120+
121+
El B-tree brilla cuando importan orden, rangos y localidad. No es "mejor que un
122+
hashmap"; resuelve otro problema.
123+
124+
## Diagramas
125+
126+
El diagrama principal vive en [`diagrams/09-btree.mmd`](../diagrams/09-btree.mmd).
127+
128+
```mermaid
129+
flowchart TB
130+
title["B-tree: claves ordenadas y split"]
131+
132+
subgraph before["Antes: raiz llena (t = 2, max = 3 claves)"]
133+
root_before["[10 | 20 | 30]"]
134+
end
135+
136+
insert["insert(40)<br/>la raiz llena se divide"]
137+
138+
subgraph after["Despues del split"]
139+
root_after["[20]"]
140+
left["[10]"]
141+
right["[30 | 40]"]
142+
root_after --> left
143+
root_after --> right
144+
end
145+
146+
before --> insert --> after
147+
148+
search["contains(30)<br/>compara en raiz y baja a un hijo"]
149+
iter["iter()<br/>hijo izquierdo, clave raiz, hijo derecho"]
150+
locality["Nodo multiway<br/>varias claves por pagina/cache line"]
151+
152+
root_after --> search
153+
root_after --> iter
154+
root_after --> locality
155+
```
156+
157+
## Analisis de complejidad
158+
159+
Sea `n` el numero de claves y `t` el grado minimo.
160+
161+
| Operacion | Mejor caso | Caso promedio | Peor caso | Espacio |
162+
|-----------|------------|---------------|-----------|---------|
163+
| `new` | O(1) | O(1) | O(1) | O(1) |
164+
| `with_min_degree` | O(1) | O(1) | O(1) | O(1) |
165+
| `len` / `is_empty` | O(1) | O(1) | O(1) | O(1) |
166+
| `contains` | O(1) | O(log n) | O(log n) | O(1) |
167+
| `insert` | O(log n) | O(log n) | O(log n) | O(t) por split |
168+
| `iter` | O(n) | O(n) | O(n) | O(n) referencias |
169+
| `height` | O(log n) | O(log n) | O(log n) | O(1) |
170+
171+
La implementacion materializa referencias en `iter()` para mantener el codigo
172+
del capitulo enfocado en representacion e invariantes. Un iterador productivo
173+
podria usar una pila explicita y no reservar `Vec<&T>`.
174+
175+
## Visualizacion interactiva (opcional)
176+
177+
No aplica todavia. El B-tree es buen candidato para una visualizacion futura:
178+
insertar claves una por una, ver nodos llenos, observar la mediana que sube y
179+
comparar alturas con diferentes grados minimos.
180+
181+
## Implementacion
182+
183+
La implementacion vive en [`src/btree.rs`](../src/btree.rs).
184+
185+
El tipo publico conserva raiz, grado minimo y longitud:
186+
187+
```rust
188+
pub struct BTree<T> {
189+
root: Node<T>,
190+
min_degree: usize,
191+
len: usize,
192+
}
193+
```
194+
195+
Cada nodo guarda claves, hijos y si es hoja:
196+
197+
```rust
198+
struct Node<T> {
199+
keys: Vec<T>,
200+
children: Vec<Node<T>>,
201+
leaf: bool,
202+
}
203+
```
204+
205+
La raiz empieza como hoja vacia. Antes de insertar, `insert` verifica duplicados
206+
con `contains`. Si la raiz esta llena, se crea una nueva raiz interna y se
207+
divide la raiz anterior como primer hijo. Despues la insercion baja por un nodo
208+
que no esta lleno.
209+
210+
El split usa `Vec::split_off` para separar las claves derechas y `pop` para
211+
extraer la mediana. No usa `unsafe`.
212+
213+
## Pruebas
214+
215+
Las pruebas viven en [`tests/btree_test.rs`](../tests/btree_test.rs) y dentro de
216+
[`src/btree.rs`](../src/btree.rs).
217+
218+
Cubren:
219+
220+
- Insercion y busqueda.
221+
- Conteo de claves.
222+
- Politica de duplicados.
223+
- Split de raiz.
224+
- Iteracion ordenada despues de multiples splits.
225+
- Validacion de grado minimo.
226+
- Relacion entre grado minimo y capacidad antes de split.
227+
228+
Los doc-comments se validan con `cargo test --doc`.
229+
230+
## Benchmarks
231+
232+
El benchmark vive en [`benches/btree_bench.rs`](../benches/btree_bench.rs) y se
233+
ejecuta con:
234+
235+
```bash
236+
cargo bench --bench btree_bench
237+
```
238+
239+
Mide:
240+
241+
- insercion ordenada en el B-tree educativo;
242+
- insercion pseudoaleatoria en el B-tree educativo;
243+
- insercion pseudoaleatoria en `std::collections::BTreeSet`;
244+
- busqueda en el B-tree educativo;
245+
- busqueda en `BTreeSet`.
246+
247+
El benchmark no pretende superar a la biblioteca estandar. Sirve para observar
248+
como la estructura mantiene busqueda e insercion ordenadas con altura baja y
249+
nodos multiway.
250+
251+
## Ejercicios
252+
253+
### Ejercicio 1: Trazar un split `[Nivel 1]`
254+
255+
Con grado minimo `2`, inserta `1`, `2`, `3` y `4`. Explica por que la raiz se
256+
divide al insertar la cuarta clave.
257+
258+
**Entrada/Salida esperada:** la altura final es `2` y la raiz tiene `1` clave.
259+
260+
<details>
261+
<summary>Pista</summary>
262+
Con `t = 2`, un nodo puede tener como maximo `3` claves.
263+
</details>
264+
265+
### Ejercicio 2: Indice ordenado `[Nivel 2]`
266+
267+
Inserta claves en desorden y usa `iter()` para producirlas ordenadas.
268+
269+
**Entrada/Salida esperada:** insertar `[30, 10, 20, 40]` produce
270+
`[10, 20, 30, 40]`.
271+
272+
<details>
273+
<summary>Pista</summary>
274+
El recorrido in-order visita hijo izquierdo, clave, hijo derecho.
275+
</details>
276+
277+
### Ejercicio 3: Politica de duplicados `[Nivel 3]`
278+
279+
Inserta dos veces la misma clave y verifica que `len` no cambie.
280+
281+
**Entrada/Salida esperada:** la primera insercion devuelve `true`; la segunda,
282+
`false`.
283+
284+
<details>
285+
<summary>Pista</summary>
286+
Este B-tree modela un conjunto de claves, no un multiset.
287+
</details>
288+
289+
### Ejercicio 4: Disenar eliminacion `[Nivel 4]`
290+
291+
Describe como implementarias `remove` sin romper las invariantes de ocupacion.
292+
Incluye prestamo de hermanos, merge y cambio de raiz.
293+
294+
**Entrada/Salida esperada:** no hay una unica solucion; se evalua que el plan
295+
mantenga ocupacion minima y orden.
296+
297+
<details>
298+
<summary>Pista</summary>
299+
Antes de bajar a un hijo con pocas claves, reparalo.
300+
</details>
301+
302+
## Soluciones
303+
304+
Soluciones ejecutables de niveles 1 a 3:
305+
306+
- [`examples/soluciones/btree_trace_split.rs`](../examples/soluciones/btree_trace_split.rs)
307+
- [`examples/soluciones/btree_ordered_index.rs`](../examples/soluciones/btree_ordered_index.rs)
308+
- [`examples/soluciones/btree_duplicate_policy.rs`](../examples/soluciones/btree_duplicate_policy.rs)
309+
310+
Discusion para el nivel 4:
311+
312+
Una eliminacion completa debe mantener el arbol balanceado y todos los nodos,
313+
salvo la raiz, con ocupacion suficiente. La forma robusta es reparar antes de
314+
bajar: si el hijo tiene pocas claves, toma prestado de un hermano con excedente
315+
o fusiona con un hermano y baja una clave del padre. Si borras una clave interna,
316+
reemplazala por predecesor o sucesor desde un subarbol con espacio suficiente.
317+
318+
## Referencias
319+
320+
- Rudolf Bayer y Edward M. McCreight, articulo original sobre B-trees.
321+
- Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest y Clifford Stein,
322+
*Introduction to Algorithms*, capitulo de B-trees.
323+
- Rust Standard Library, `std::collections::BTreeSet`.
324+
- RFC-0001 §10 y §14: ubicacion curricular y anatomia de capitulos.

docs/superpowers/plans/2026-07-14-rust-data-structures-course.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -297,12 +297,12 @@ For each chapter, complete the following checklist before moving to the next str
297297
- Create: `examples/btree_advanced.rs`
298298
- Create: `examples/btree_real_case.rs`
299299

300-
- [ ] Teach multiway search trees, node capacity, sorted keys, splitting, searching, insertion, deletion strategy, and cache/page locality.
301-
- [ ] Explain why B-tree is canonical here and reused later by database internals.
302-
- [ ] Compare against binary search tree, red-black tree, skip list, and hashmap.
303-
- [ ] Include real cases: filesystems, databases, ordered maps.
304-
- [ ] Include tests for insertion, search, split, sorted iteration, duplicate policy, and deletion if implemented.
305-
- [ ] Include benchmarks for search and insertion under ordered and random input.
300+
- [x] Teach multiway search trees, node capacity, sorted keys, splitting, searching, insertion, deletion strategy, and cache/page locality.
301+
- [x] Explain why B-tree is canonical here and reused later by database internals.
302+
- [x] Compare against binary search tree, red-black tree, skip list, and hashmap.
303+
- [x] Include real cases: filesystems, databases, ordered maps.
304+
- [x] Include tests for insertion, search, split, sorted iteration, duplicate policy, and deletion if implemented.
305+
- [x] Include benchmarks for search and insertion under ordered and random input.
306306

307307
### Task 14: HashMap
308308

0 commit comments

Comments
 (0)