Árvores B e B+
Igor Machado Coelho
04/11/2026
São requisitos para essa aula:
buscaMaiorIgual)Agradecimentos especiais à prof. Karina Mochetti (IC-UFF), cujo material sobre Árvores B inspirou esses slides.
Bancos de dados e sistemas de arquivos guardam bilhões de chaves em memória secundária (disco, SSD), onde cada acesso é milhares de vezes mais lento que na memória principal.
Em uma AVL com n=109 chaves, a altura pode chegar a 1,44lgn≈43: até 43 acessos ao disco por busca!
Ideia: o disco lê um bloco inteiro de uma vez (ex: 4 KiB). E se cada nó guardasse muitas chaves, ocupando um bloco inteiro?
Para isso, precisamos de árvores cujos nós tenham mais de dois filhos.
Uma Árvore de Busca Multivias (ABM) é uma árvore em que cada nó guarda k≥1 chaves ordenadas c0<c1<…<ck−1 e possui k+1 filhos F0,F1,…,Fk (possivelmente vazios), tais que:
[ 20 | 40 ]
/ | \
< 20 20..40 > 40
[ 5 | 10 ] [ 30 ] [ 50 | 60 | 70 ]
Ou seja, cada chave de um nó divide a busca em partes: k chaves dividem o espaço em k+1 intervalos, um para cada filho.
Observação: a ABB é exatamente uma ABM com k=1 (uma chave, dois filhos)!
Para buscar a chave
c
em um nó, precisamos achar em qual intervalo ela está.
Isso é exatamente o buscaMaiorIgual do Saco Ordenado: a
posição
p
da primeira chave
≥c!
busca(30): [ 20 | 40 ] buscaMaiorIgual(30) = 1 (chave 40)
| 40 != 30, desce para F_1
[ 30 ] buscaMaiorIgual(30) = 0 (chave 30): encontrou!
busca(45): [ 20 | 40 ] buscaMaiorIgual(45) = 2 (passou do fim)
\ desce para F_2
[ 50 | 60 | 70 ] buscaMaiorIgual(45) = 0 (chave 50)
50 != 45, desce para F_0: vazio, não encontrou!
Como as chaves do nó estão ordenadas, buscaMaiorIgual
usa busca binária:
O(logk)
comparações por nó.
Uma Árvore B de ordem m é uma Árvore de Busca Multivias em que:
Ou seja, a aridade de cada nó varia entre ⌈m/2⌉ e m (cada nó fica pelo menos “meio cheio”), e a árvore é sempre perfeitamente balanceada: não existem rotações nem fator de balanceamento!
| Ordem m | Filhos por nó | Chaves por nó | Nome |
|---|---|---|---|
| 3 | 2 a 3 | 1 a 2 | árvore 2-3 |
| 4 | 2 a 4 | 1 a 3 | árvore 2-3-4 |
| 5 | 3 a 5 | 2 a 4 | |
| 1000 | 500 a 1000 | 499 a 999 | (típica em disco) |
Proposta por Bayer e McCreight (1972). Um nó de uma Árvore B também é chamado de página.
Cuidado: os livros divergem! Aqui seguimos Knuth (ordem = máximo de filhos). O Cormen usa o grau mínimo t: entre t e 2t filhos.
Ordem 5: cada nó tem de 2 a 4 chaves (a raiz pode ter 1), e todas as folhas no mesmo nível.
É Árvore B: [ 20 | 40 ]
/ | \
[ 5 | 10 | 15 ] [ 25 | 30 ] [ 50 | 60 | 70 ]
NÃO é Árvore B: [ 20 | 40 ]
(folha [30] tem só 1 chave) / | \
[ 5 | 10 ] [ 30 ] [ 50 | 60 ]
NÃO é Árvore B: [ 20 | 40 ]
(folhas em níveis diferentes) / | \
[ 5 | 10 ] [ 25 | 30 ] [ 50 | 60 ]
|
[ 7 | 8 ]
Exercício: a terceira árvore é uma Árvore de Busca Multivias? Quais regras da Árvore B ela viola?
Seja t=⌈m/2⌉. Qual o menor número de chaves em uma Árvore B de ordem m com altura h (raiz no nível 1)?
| Nível | Nós (mínimo) | Chaves por nó |
|---|---|---|
| 1 (raiz) | 1 | 1 |
| 2 | 2 | t−1 |
| 3 | 2t | t−1 |
| i≥2 | 2ti−2 | t−1 |
Exemplo: ordem 5 (t=3), h=3:
[ 50 ]
/ \
[ 20 | 30 ] [ 70 | 80 ]
/ | \ / | \
[..] [..] [..] [..] [..] [..]
6 folhas com 2 chaves cada
1+2⋅2+6⋅2=17 chaves
n≥1+i=2∑h2ti−2(t−1)=1+2(t−1)t−1th−1−1i=2∑hti−2=2th−1−1⇒h≤1+logt2n+1
Ou seja, h=O(logmn). Com m=1000 e n=109: altura no máximo 4 (contra ≈43 na AVL)!
Cada nó guarda um vetor de chaves e um vetor de filhos. Usamos uma posição a mais em cada vetor, para que um nó possa estourar temporariamente durante a inserção:
k = 3:
chaves: | 10 | 20 | 30 | |
filhos: F0 F1 F2 F3 -
As chaves de filhos[i] ficam entre
chaves[i-1] e chaves[i].
Em uma folha, todos os filhos são vazios.
Para um Dicionário, basta um vetor
valores[M] paralelo às chaves.
A busca desce um único caminho da raiz até, no máximo, uma folha:
Custo: O(logm) comparações por nó, em O(logmn) níveis: O(logm⋅logmn)=O(logn) comparações, mas apenas O(logmn) páginas lidas do disco!
Desafio: escreva minimo() e
maximo() da Árvore B. (Dica: desça sempre pelo primeiro, ou
pelo último, filho.)
A nova chave é sempre inserida em uma folha: busque a folha onde ela deveria estar, e insira de forma ordenada. Dois casos:
Se a raiz estourar, a mediana forma uma nova raiz: a árvore cresce pela raiz, e não pelas folhas. Por isso, todas as folhas continuam no mesmo nível!
ordem 5, insere 4: [ 1 | 5 | 24 | 25 ] -> [ 1 | 4 | 5 | 24 | 25 ] (estourou!)
[ 5 ] a mediana 5 sobe
/ \
[ 1 | 4 ] [ 24 | 25 ]
Insira, nesta ordem: 25 5 1 24 4 9 42 14 16 12
25, 5, 1, 24: [ 1 | 5 | 24 | 25 ] (cabem: até 4 chaves)
4: estoura! [ 1 | 4 | 5 | 24 | 25 ] -> mediana 5 sobe, nova raiz:
[ 5 ]
/ \
[ 1 | 4 ] [ 24 | 25 ]
9, 42: [ 5 ]
/ \
[ 1 | 4 ] [ 9 | 24 | 25 | 42 ]
14: estoura! [ 9 | 14 | 24 | 25 | 42 ] -> mediana 24 sobe para o pai:
[ 5 | 24 ]
/ | \
[ 1 | 4 ] [ 9 | 14 ] [ 25 | 42 ]
16, 12: [ 5 | 24 ]
/ | \
[ 1 | 4 ] [ 9 | 12 | 14 | 16 ] [ 25 | 42 ]
Exercício: continue inserindo 13. Quem
sobe? A raiz muda?
Duas operações auxiliares: inserir uma chave (com seu filho direito) dentro de uma página, e dividir uma página que estourou:
struct Promocao { // o que sobe
int mediana;
NoB* direito; // 0: nada subiu
};
Promocao divide(NoB* no) {
int meio = M / 2; // pre: k == M
NoB* d = new NoB{};
d->k = no->k - meio - 1;
for (int i = 0; i < d->k; i++)
d->chaves[i] =
no->chaves[meio + 1 + i];
for (int i = 0; i <= d->k; i++)
d->filhos[i] =
no->filhos[meio + 1 + i];
no->k = meio; // esquerda fica
return {no->chaves[meio], d};
}A inserção desce recursivamente até a folha, e sobe devolvendo o que foi promovido em cada divisão:
Promocao insereRec(NoB* no, int c) {
int p = buscaMaiorIgual(no, c);
if (p < no->k && no->chaves[p] == c)
return {0, 0}; // já existe
if (folha(no))
insereNaPagina(no, p, c, 0);
else {
Promocao pr =
insereRec(no->filhos[p], c);
if (!pr.direito)
return {0, 0}; // nada subiu
insereNaPagina(no, p, pr.mediana,
pr.direito);
}
if (no->k < M) return {0, 0}; // coube!
return divide(no); // estourou
}Assim como na ABB, a remoção de uma chave em nó
interno é reduzida à remoção em uma
folha: troque a chave pelo seu
antecessor (o máximo de filhos[p]) ou
sucessor (o mínimo de filhos[p+1]), que
sempre estão em folhas.
Removendo da folha, ela pode ficar com menos de ⌈m/2⌉−1 chaves (underflow). Dois casos:
A fusão é o inverso da divisão: se a raiz ficar sem chaves, seu único filho vira a nova raiz, e a árvore diminui pela raiz.
ordem 5, remove 25: [ 5 | 24 ] [ 5 ]
/ | \ fusão / \
[ 1 | 4 ] [ 9 | 14 ] [ 25 | 42 ] -> [ 1 | 4 ] [ 9 | 14 | 24 | 42 ]
Desafio: implemente a remoção!
Uma Árvore B+ é uma variação da Árvore B, usada na maioria dos bancos de dados e em sistemas de arquivos (como NTFS e XFS), em que:
[ 5 | 12 | 24 ]
/ / \ \
[ 1 | 4 ] -> [ 5 | 9 ] -> [ 12 | 14 | 16 ] -> [ 24 | 25 | 42 ] -> 0
Com isso:
A inserção é igual à da Árvore B, com uma diferença na divisão de folhas:
Mesma sequência do exemplo anterior, em uma B+ de ordem 5:
25 5 1 24 4 9 42 14 16 12
4: folha estoura [ 1 | 4 | 5 | 24 | 25 ] -> 5 é COPIADO para o pai:
[ 5 ]
/ \
[ 1 | 4 ] -> [ 5 | 24 | 25 ]
... ao final: [ 5 | 12 | 24 ]
/ / \ \
[ 1 | 4 ] -> [ 5 | 9 ] -> [ 12 | 14 | 16 ] -> [ 24 | 25 | 42 ]
Exercício: refaça a sequência passo a passo. Em que momentos 24 e 12 são copiados para o pai?
Lembra do Dicionário Ordenado? Na B+, o intervalo [a,b] fica muito simples:
buscaMaiorIgual(a):
O(logmn)
páginas chaves em [9, 25]: [ 5 | 12 | 24 ]
/
[ 1 | 4 ] -> [ 5 | 9 ] -> [ 12 | 14 | 16 ] -> [ 24 | 25 | 42 ]
^ ^
início para em 42 > 25
resultado: 9 12 14 16 24 25
Na Árvore B comum, o mesmo percurso precisaria subir e
descer pela árvore, como o sucessor da ABB. Na B+,
o proximo() do iterador é só avançar na folha, ou pular
para a folha seguinte!
É exatamente a consulta
SELECT ... WHERE x BETWEEN 9 AND 25 de um banco de dados
com índice.
| Árvore B | Árvore B+ | |
|---|---|---|
| Onde ficam os valores | em todos os nós | só nas folhas |
| Nós internos | chaves e valores | só cópias de chaves (guias) |
| Chaves repetidas | não | sim (cópias nos nós internos) |
| Busca de uma chave | pode parar em nó interno | sempre vai até a folha |
| Percurso em ordem / intervalo | sobe e desce na árvore | percorre a lista de folhas |
| Divisão de folha | mediana sobe | mediana é copiada |
| Uso típico | sistemas de arquivos, memória | índices de bancos de dados |
Ambas têm altura O(logmn) e são perfeitamente balanceadas.
Na aula anterior, vimos que cada nó preto de uma rubro-negra, junto com seus filhos vermelhos, equivale a um nó com até 3 chaves. Agora sabemos o nome disso: uma Árvore B de ordem 4 (árvore 2-3-4)!
Árvore 2-3-4: [ 10 | 20 | 30 ]
Rubro-negra: 20(P)
/ \
10(V) 30(V)
Por isso, a rubro-negra é uma “Árvore B de ordem 4 disfarçada” de árvore binária: ganha o balanceamento perfeito da Árvore B, mas mantém nós binários, mais simples em memória principal.
20 40 10 30 15 35 7 26 18 22 5 42 13 46. Desenhe a árvore
após inserir 5, 9 e 14 chaves.minimo(), maximo() e
conta() (número de chaves) para a Árvore B.Simulador online: https://www.cs.usfca.edu/~galles/visualization/BTree.html
(há também a versão B+, em BPlusTree.html)
Além da bibliografia do curso, recomendamos para esse tópico:
Bibliografia Adicional:
Em especial, agradeço aos colegas que elaboraram bons materiais, como a prof. Karina Mochetti (IC-UFF), cujo material de Árvores B inspirou esta aula, o prof. Fabiano Oliveira (IME-UERJ), e o prof. Jayme Szwarcfiter cujos conceitos formam o cerne desses slides.
Estendo os agradecimentos aos demais colegas que colaboraram com a elaboração do material do curso de Pesquisa Operacional, que abriu caminho para verificação prática dessa tecnologia de slides.
Esse material de curso só é possível graças aos inúmeros projetos de código-aberto que são necessários a ele, incluindo:
Agradecimento especial a empresas que suportam projetos livres envolvidos nesse curso:
Esses slides foram escritos utilizando pandoc, segundo o tutorial ilectures:
Exceto expressamente mencionado (com as devidas ressalvas ao material cedido por colegas), a licença será Creative Commons.
Licença: CC-BY 4.0 2020
Igor Machado Coelho