Árvores binárias de pesquisa
Ordem global das subárvores, procura, inserção e remoção com sucessor.
Numa árvore binária de pesquisa, ou BST, todas as chaves da subárvore esquerda são menores que a chave do nó e todas as da direita são maiores. Esta página usa chaves únicas. Outra política pode guardar um contador por chave, mas tem de ajustar inserção, remoção e tamanho.
Assume os percursos da página anterior e os intervalos semiabertos da pesquisa binária. A novidade é a ordem global mantida por rotações e limites herdados.
Invariante de ordem global
Não basta comparares um nó com os seus filhos diretos. Na árvore com raiz 8, filho esquerdo 4 e filho direito de 4 com chave 9, cada ligação pai-filho parece ordenada, mas 9 está na subárvore esquerda de 8 e viola o invariante.
Para validares, propaga limites abertos: a esquerda de x fica com limite superior x, e a direita fica com limite inferior x. Não uses x-1 e x+1 em inteiros extremos. Em cada nó, verifica a chave contra os limites herdados de todos os antepassados.
O percurso em-ordem visita chaves por ordem crescente: por hipótese indutiva, as duas subárvores produzem sequências crescentes; todas as chaves à esquerda vêm antes da raiz e todas à direita depois. O argumento depende da propriedade global.
Procurar e inserir
Compara o alvo com a raiz. Se for igual, encontraste. Se for menor, só a esquerda pode contê-lo; se maior, só a direita. Ao chegares ao vazio, a chave falta. A cada passo, o invariante é que qualquer ocorrência ainda possível está na subárvore atual.
Inserir segue o mesmo caminho até um filho vazio e cria aí uma folha. O percurso acumulou limites que a nova chave satisfaz, por isso a inserção conserva a ordem. Se encontrares uma chave igual, a política de conjunto não altera a árvore.
Inserir [8,3,12,1,6,10,14] cria uma árvore de altura 2. Procurar 6 visita 8, 3 e 6; procurar 5 visita 8, 3 e 6, depois o filho esquerdo vazio de 6. Encontrar o mínimo consiste em seguir filhos esquerdos; o máximo segue direitos.
O custo é , com h a altura. Numa árvore equilibrada é ; numa cadeia é . Inserir chaves crescentes numa BST simples produz uma cadeia. A altura esperada é logarítmica com uma ordem de inserção uniformemente aleatória de chaves distintas; essa hipótese não é uma garantia para dados arbitrários.
Ordem de inserção e altura
Para um conjunto de n chaves distintas, a maior altura possível é n-1. Inserir por ordem crescente cria apenas filhos direitos; por ordem decrescente, apenas esquerdos. São duas maneiras de construir uma cadeia, sem alterar as regras da BST.
Para minimizar a altura, ordena as chaves e insere primeiro uma mediana; depois as medianas das duas metades, repetindo até inserir todas. Em [2,5,8,11,14,17,20], uma ordem possível é [11,5,17,2,8,14,20]. A árvore final tem altura 2, o mínimo, pois altura 1 só permite três nós. A ordem crescente dos mesmos sete valores dá altura 6.
O mínimo de uma BST não precisa de ser folha: não tem filho esquerdo, mas pode ter direito. Na cadeia crescente anterior, o mínimo 2 é a raiz e tem filho 5. A definição de mínimo exclui chaves menores, sem excluir chaves maiores abaixo dele.
Remover uma chave
Primeiro procura a chave. Se faltar, a árvore mantém-se. No nó encontrado, há três casos:
- Nenhum filho: substitui a ligação que chega ao nó por vazio e liberta-o.
- Um filho: substitui essa ligação pelo único filho e liberta o nó antigo. A subárvore continua dentro dos limites herdados.
- Dois filhos: encontra o sucessor, o mínimo da subárvore direita. Copia a sua chave, e o respetivo valor se existir, para o nó a remover; depois remove o sucessor da subárvore direita. Esse sucessor não tem filho esquerdo, pelo que a segunda remoção cai num caso simples.
Na árvore construída com [8,3,12,1,6,10,14], remover 8 substitui a raiz por 10, o mínimo da direita. A antiga folha 10 sai da esquerda de 12 e a em-ordem fica [1,3,6,10,12,14].
Para ver o caso em que o sucessor tem filho, considera outra árvore: raiz 3, filho esquerdo 1 e filho direito 8; 8 tem filho esquerdo 6, e 6 tem filho direito 7. Ao remover 3, copia 6 para a raiz e substitui 8.esquerdo por 7. A em-ordem final é [1,6,7,8]. Apagar o antigo 6 sem religar 7 perderia uma subárvore. Se usares o predecessor, o máximo da esquerda, a forma final pode diferir e continuar correta.
O sucessor é maior que todas as chaves da esquerda e não é maior que qualquer outra chave da direita. Por isso a substituição conserva a ordem global. O caminho para achar o sucessor e remover o nó continua limitado pela altura: tempo , não .
Com multiplicidades, retirar uma ocorrência pode apenas reduzir o contador. Ao transplantares o sucessor, não deixes a mesma chave repartida por dois nós se o invariante usa comparações estritas. Uma remoção da raiz também tem de atualizar o apontador da raiz, não apenas um campo de um pai inexistente.
Limites, intervalos e biblioteca
O sucessor de uma chave tem dois casos: se existir subárvore direita, é o seu mínimo; caso contrário, é o primeiro antepassado em cujo ramo esquerdo está a chave. O predecessor é simétrico. A ausência de sucessor para o máximo é um resultado válido.
Para enumerares chaves num intervalo, descarta subárvores fora dos limites e visita em-ordem as restantes. Numa árvore equilibrada, uma consulta por intervalo com k resultados pode custar . Fazer um percurso completo e filtrar custa e perde essa vantagem quando k é pequeno.
std::set representa um conjunto ordenado e std::map associa um valor a cada chave. Em ambos, pesquisa, inserção e remoção por chave têm custo logarítmico. map.find(chave) consulta sem inserir, ao contrário de map[chave], que cria um valor por omissão se a chave faltar.
multiset admite valores repetidos. Na ficha 08 (2025/26), count(valor) conta ocorrências e erase(valor) apaga todas. Para apagar só uma, passa a erase um iterador devolvido por find, depois de confirmares que não é end(). Com m ocorrências numa coleção de tamanho n, contar ou apagar todas custa .
lower_bound(x) encontra o primeiro valor >=x e upper_bound(x) o primeiro >x; podem devolver end(). Percorrer o intervalo entre ambos conta os repetidos, mas os iteradores destas árvores não admitem subtração como os de um vetor. A equivalência é definida pelo comparador, não obrigatoriamente por operator==. O padrão garante os contratos e custos, sem exigir uma espécie concreta de árvore.
A comparação de chaves e a cópia dos valores contam. Com strings compridas, a análise de uma consulta pode incluir o custo de cada comparação.
Exercícios
As justificações e os programas em C++ são de autoavaliação. Compara o raciocínio com os critérios da solução.
Remover dois filhos com sucessor
Insere numa BST simples [9,4,13,2,7,11,15,10]. Remove 9 usando o sucessor. Dá a nova raiz e a em-ordem.
Pista
O sucessor é o mínimo da subárvore direita.
Mais uma pista
O caminho 13->11->10 encontra esse mínimo.
Solução
A nova raiz tem chave 10; retira a antiga folha 10 da esquerda de 11. A em-ordem é [2,4,7,10,11,13,15] e as restantes ligações não mudam. O sucessor é maior que toda a esquerda e menor que as restantes chaves da direita, por isso a ordem global conserva-se. O custo total é O(h+1).
Confere a tua resposta:
- Escolhi 10, o mínimo da direita, como nova chave da raiz.
- Retirei o nó antigo com 10, sem duplicar a chave.
- Obtive em-ordem 2,4,7,10,11,13,15 e conservei a ordem global.
Erros frequentes
Não escolhas simplesmente o filho direito 13; não deixes duas cópias de 10 se a BST usa chaves únicas.
Detetar uma ordem inválida
A raiz é 20, o filho esquerdo é 8 e o filho direito de 8 é 25. Os restantes filhos são vazios. É uma BST de chaves únicas? Justifica com limites.
Pista
O limite da raiz também se aplica a todos os descendentes esquerdos.
Mais uma pista
O nó 25 tem de estar no intervalo aberto (8,20).
Solução
Não é uma BST, porque 25 excede o limite superior 20 herdado. As comparações entre pai e filho isoladamente são insuficientes. Propagar limites abertos valida todos os nós em O(n), com espaço O(h+1) numa recursão.
Confere a tua resposta:
- Rejeitei a árvore porque 25 está na esquerda de 20.
- Propaguei ao nó 25 o intervalo aberto (8,20).
- Distingui comparações locais da condição sobre toda a subárvore.
Erros frequentes
Não concluas validade só porque 8<20 e 25>8; evitar x+1 e x-1 também evita overflow nos limites extremos.
Religar o filho do sucessor
Insere [3,1,8,6,7] numa BST vazia, com chaves únicas. Remove 3 usando o
sucessor. Desenha a árvore antes e depois, mostra a ligação que evita perder
7 e dá a em-ordem final.
Pista
Solução
Antes, 3 tem filhos 1 e 8; o filho esquerdo de 8 é 6, e 6 tem filho
direito 7. O mínimo da direita de 3 é 6. Copia 6 para a raiz e substitui
8.esquerdo por 7 antes de libertar o antigo nó 6. Depois, a raiz 6 tem
filhos 1 e 8, com 7 à esquerda de 8. A em-ordem é [1,6,7,8]. Perder 7
violaria a pós-condição de remover apenas 3, mesmo que a ordem entre os
nós sobreviventes continuasse válida.
Confere a tua resposta:
- Copiei o sucessor para o nó removido.
- Liguei o filho direito do sucessor ao seu pai.
- Verifiquei a em-ordem final.
Erros frequentes
Apagar o sucessor sem transplantar o filho direito perde uma subárvore.
Construir a menor e a maior altura
Insere as chaves distintas {2,5,8,11,14,17,20} numa BST simples,
inicialmente vazia. Dá uma ordem de inserção que maximize a altura e
outra que a minimize, desenhando as ligações. Justifica os dois extremos.
Na primeira árvore, o mínimo tem de estar numa folha?
Solução
Inserir [2,5,8,11,14,17,20] cria a cadeia direita com seis arestas,
altura 6, o máximo para sete nós. O mínimo 2 é a raiz e tem filho direito,
logo não é folha. Inserir [11,5,17,2,8,14,20] cria raiz 11, filhos 5 e 17,
com folhas 2 e 8 sob 5, 14 e 20 sob 17. A altura é 2. Altura 1 só permite
três nós; sete exigem pelo menos altura 2. Escolher medianas recursivamente
e inserir cada raiz antes dos seus descendentes atinge esse limite.
Confere a tua resposta:
- Construí uma cadeia de altura 6 e uma árvore de altura 2.
- Justifiquei o mínimo pela capacidade de cada altura e insiro a raiz antes dos descendentes.
- Mostrei que o mínimo pode ter filho direito e não ser folha.