Rubro-negra esquerdista (LLRB)
A árvore rubro-negra esquerdista (LLRB) em C, com rotações, inversão de cores, inserção, as operações auxiliares da remoção, a remoção do menor e a remoção de uma chave qualquer.
A árvore rubro-negra convencional garante altura
Esta é a árvore balanceada que vamos implementar na disciplina.
Definição
Uma árvore rubro-negra esquerdista (LLRB) é uma árvore rubro-negra que, além das cinco propriedades, satisfaz:
- se um nó tem um filho vermelho, esse filho é o esquerdo. Ou seja, nenhum nó tem filho direito vermelho: os vermelhos "se inclinam" para a esquerda.
Toda LLRB é, em particular, uma árvore rubro-negra, então ela herda o
teorema da altura:
Duas consequências diretas da propriedade 6, que vamos usar mais adiante:
Em uma LLRB, nenhum nó tem dois filhos vermelhos, e nenhum nó tem apenas o filho direito.
Demonstraçãotente antes de abrir
Se um nó tivesse dois filhos vermelhos, um deles seria o direito, o que a
propriedade 6 proíbe. Agora suponha que um nó NULL da esquerda tem um nó preto abaixo de NULL abaixo dele), o que viola a propriedade 5.
Em particular, um nó sem filho esquerdo também não tem filho direito: é uma folha. Isso vai simplificar a remoção do menor.
Uma forma de enxergar a LLRB é pensar em cada nó vermelho como "colado" ao seu pai, formando com ele um único nó com duas chaves. Na figura (a), o 25 e o 50 funcionam como um nó com as chaves 25 e 50. Lida assim, toda LLRB corresponde a uma árvore 2-3, uma árvore de busca em que cada nó tem uma ou duas chaves e todas as folhas estão no mesmo nível. Foi a partir dessa correspondência que Sedgewick projetou a LLRB. Não vamos precisar dela aqui, mas quem quiser saber mais pode consultar a seção 3.3 do livro de Sedgewick e Wayne.
O TAD
A interface é a mesma da ABB e da
AVL (pares chave e valor, com chaves únicas),
mais uma operação pública nova, removeMenorArvore. O nó troca a altura da
AVL por uma cor:
typedef enum { VERMELHO, PRETO } Cor;
typedef struct no {
Chave chave;
Valor valor;
Cor cor; /* cor do nó (NULL conta como preto) */
struct no *esq;
struct no *dir;
} No;Quase todas as decisões da árvore perguntam "esse filho é vermelho?". A
função noEhVermelho responde false para NULL, que é a propriedade 3
(folhas NULL são pretas) traduzida em código. Com ela, podemos perguntar
sobre r->esq->esq sem testar antes se r->esq existe, desde que r->esq
seja vermelho (e, portanto, não nulo) ou que saibamos por outro motivo que
ele não é NULL.
/* NULL conta como preto (as folhas NULL são pretas) */
static bool noEhVermelho(const No *r) {
return r != NULL && r->cor == VERMELHO;
}Para imprimir a árvore deitada (raiz à esquerda e SAD em cima), cada chave
aparece seguida de (V) ou (P), a sua cor.
Rotações
As rotações são as mesmas da AVL e são nomeadas da mesma forma, pela
direção: na rotação à esquerda em
/* Rotação à esquerda em A: o filho direito B (vermelho) sobe */
static No *rotacaoEsquerda(No *A) {
No *B = A->dir;
A->dir = B->esq;
B->esq = A;
B->cor = A->cor; /* B herda a cor de A */
A->cor = VERMELHO; /* A desce e fica vermelho */
return B;
}
/* Rotação à direita em A: o filho esquerdo B (vermelho) sobe */
static No *rotacaoDireita(No *A) {
No *B = A->esq;
A->esq = B->dir;
B->dir = A;
B->cor = A->cor;
A->cor = VERMELHO;
return B;
}A rotação à esquerda transforma um "vermelho à direita" em um "vermelho à esquerda", e a rotação à direita faz o contrário. É assim que a LLRB move os vermelhos de um lado para o outro.
Se o nó NULL.
Demonstraçãotente antes de abrir
A ordem é a mesma da rotação na ABB: a
em-ordem continua
A hipótese de
Inversão de cores
A terceira operação inverte a cor de um nó e a dos seus dois filhos.
static void trocaCor(No *r) {
r->cor = r->cor == VERMELHO ? PRETO : VERMELHO;
}
/* Inverte a cor do nó e a dos seus dois filhos */
static void inverteCorNo(No *r) {
trocaCor(r);
if (r->esq != NULL)
trocaCor(r->esq);
if (r->dir != NULL)
trocaCor(r->dir);
}Se
Demonstraçãotente antes de abrir
Todo caminho que passa por
Na inserção, a inversão é aplicada a um nó preto com dois filhos vermelhos,
"empurrando" o vermelho para cima. Na remoção, ela é usada no sentido
contrário, para trazer um vermelho para baixo. Em ambos os casos, ela tem
efeitos colaterais: pode criar dois vermelhos seguidos (se o pai de
Inserção
Como na rubro-negra convencional, o novo nó entra vermelho, no lugar de um
NULL, para não mudar nenhuma altura negra. Os problemas que ele pode criar
são locais, e são corrigidos na volta da recursão, em cada nó do caminho, por
três testes nesta ordem:
- vermelho à direita (e preto à esquerda): viola a propriedade 6. Rotação à esquerda;
- dois vermelhos seguidos à esquerda: viola a propriedade 4. Rotação à direita;
- os dois filhos vermelhos: inversão de cores, que empurra o vermelho para o pai.
/* Corrige r na volta da recursão. Três testes, nesta ordem. */
static No *balanceia(No *r) {
if (noEhVermelho(r->dir) && !noEhVermelho(r->esq)) /* vermelho à direita */
r = rotacaoEsquerda(r);
if (noEhVermelho(r->esq) && noEhVermelho(r->esq->esq)) /* dois vermelhos seguidos */
r = rotacaoDireita(r);
if (noEhVermelho(r->esq) && noEhVermelho(r->dir)) /* dois filhos vermelhos */
inverteCorNo(r);
return r;
}O nome balanceia é o mesmo da AVL, e o papel também: corrigir o nó na volta
da recursão e devolver a raiz (talvez nova) da subárvore. São três if
independentes, e não uma cadeia de else if, porque um caso pode produzir o
seguinte no mesmo nó: a rotação do caso 2 sempre deixa um nó com dois
filhos vermelhos, que é o caso 3. Esquecer isso é o erro do
Programa 1.
Com balanceia pronta, a inserção tem a mesma estrutura da inserção da AVL:
- 1
Desça como na ABB#
Compare a chave com a do nó atual e desça para a esquerda ou para a direita até encontrar um
NULL. Se encontrar a própria chave, só troque o valor. - 2
Crie o nó vermelho#
No lugar do
NULL, crie o novo nó, sempre vermelho. - 3
Volte corrigindo#
Na volta da recursão, cada nó do caminho passa por
balanceia, que aplica os três testes e devolve a raiz da subárvore; o pai a guarda emr->esqour->dir. - 4
Pinte a raiz de preto#
A inversão de cores pode ter pintado a raiz de vermelho. Pintá-la de preto nunca quebra a propriedade 5: todos os caminhos passam pela raiz.
static No *insereNo(No *r, Chave chave, Valor valor, bool *novo) {
if (r == NULL) {
*novo = true;
return criaNo(chave, valor);
}
if (chave < r->chave)
r->esq = insereNo(r->esq, chave, valor, novo);
else if (chave > r->chave)
r->dir = insereNo(r->dir, chave, valor, novo);
else
r->valor = valor; /* chave já existe: atualiza */
return balanceia(r); /* na volta da recursão */
}
void insereArvore(Arvore *arvore, Chave chave, Valor valor) {
bool novo = false;
arvore->raiz = insereNo(arvore->raiz, chave, valor, &novo);
arvore->raiz->cor = PRETO; /* a raiz é sempre preta */
if (novo)
arvore->tamanho++;
}A função criaNo é a da ABB, com novo->cor = VERMELHO;.
A figura mostra a inserção de 1, 3, 6, 7 e 4, nessa ordem, em uma árvore vazia. Cada quadro é a árvore inteira logo depois de uma operação, e o contorno destaca o nó onde ela foi aplicada.
A inserção do 4 é a mais interessante, porque passa pelos três casos. O 4 entra à esquerda do 6, que já era vermelho. Na volta, nada acontece no 6. No 7, o filho esquerdo 6 e o neto 4 são vermelhos (caso 2): rotação à direita e, em seguida, no mesmo nó, os dois filhos vermelhos (caso 3): inversão de cores. O 6 sobe vermelho, e, na raiz 3, ele é um vermelho à direita (caso 1): rotação à esquerda.
Insira as chaves 1, 2, 3, 4, 5, 6 e 7, nessa ordem, em uma LLRB vazia. Qual é a árvore final? Compare com a ABB comum e com a AVL.
Ver respostaEsconder resposta
A árvore final é cheia, com 4 na raiz, 2 e 6 no segundo nível e 1, 3, 5 e 7 nas folhas, e todos os nós pretos. Com chaves crescentes, cada nova chave entra à direita: ou vira um vermelho à direita (caso 1, rotação à esquerda), ou encontra um vermelho à esquerda e deixa o nó com dois filhos vermelhos (caso 3, inversão de cores), empurrando um vermelho para cima. Ao todo, são quatro rotações e quatro inversões. A ABB comum seria uma lista, com altura 6; a AVL, com a mesma ordem, também termina na árvore cheia.
Remoção
Remover um nó vermelho do fundo da árvore é inofensivo; remover um nó preto tira um preto dos caminhos que passavam por ele (a figura da página anterior). A estratégia da LLRB é evitar esse problema antes que ele aconteça: na descida, à procura do nó a ser removido, vamos "carregando" um vermelho para baixo, de modo que, ao chegar lá, o nó removido seja vermelho.
Concretamente, a descida mantém um invariante: ao chegar a um nó (abaixo
da raiz), ele ou o seu filho esquerdo é vermelho. Antes de descer, se o
filho para onde vamos não satisfaz isso, uma das duas operações auxiliares
abaixo empresta um vermelho de perto. Essas correções deixam
violações temporárias (vermelhos à direita, nós com dois filhos vermelhos),
que a mesma balanceia da inserção desfaz na volta da recursão. A remoção
corrige a árvore na ida e na volta.
Operações auxiliares
Mover um vermelho para a esquerda. Usada quando vamos descer para a
esquerda e tanto r->esq quanto r->esq->esq são pretos: não há vermelho
no caminho à frente. A função supõe que r é vermelho (ou é a raiz) e
garante que, depois dela, r->esq ou um filho de r->esq seja vermelho.
/* r é vermelho (ou a raiz), e r->esq e r->esq->esq são pretos:
torna r->esq ou um filho dele vermelho */
static No *moveVermelhoParaEsquerda(No *r) {
inverteCorNo(r);
if (noEhVermelho(r->dir->esq)) {
r->dir = rotacaoDireita(r->dir);
r = rotacaoEsquerda(r);
inverteCorNo(r);
}
return r;
}A inversão de cores em r desce o vermelho para os dois filhos. Isso basta
quando o irmão à direita não tem filho esquerdo vermelho (a). Quando tem, a
inversão deixaria dois vermelhos seguidos à direita; nesse caso, um nó do
lado direito é "emprestado": rotação à direita no irmão, rotação à esquerda
em r e uma nova inversão de cores (b).
Mover um vermelho para a direita. Usada quando vamos descer para a
direita e tanto r->dir quanto r->dir->esq são pretos. É o espelho da
anterior, com uma simplificação: como os vermelhos ficam à esquerda, o
vermelho que sobra do lado esquerdo está em r->esq->esq, e basta uma
rotação à direita em r para trazê-lo.
/* r é vermelho (ou a raiz), e r->dir e r->dir->esq são pretos:
torna r->dir ou um filho dele vermelho */
static No *moveVermelhoParaDireita(No *r) {
inverteCorNo(r);
if (noEhVermelho(r->esq->esq)) {
r = rotacaoDireita(r);
inverteCorNo(r);
}
return r;
}No caso (b), o 15 termina como filho direito vermelho do 10, uma violação da
propriedade 6 que existe só durante a descida: na volta, balanceia a
corrige.
moveVermelhoParaEsquerda lê r->dir->esq, e moveVermelhoParaDireita
lê r->esq->esq. Elas só são chamadas quando o filho do lado da descida é
preto e não nulo. Pela propriedade 5, o filho do outro lado também não pode
ser NULL: se fosse, os caminhos por ele teriam menos nós pretos que os
caminhos pelo filho preto.
Remover o nó com a menor chave
Antes da remoção geral, vale estudar o caso mais simples: remover o nó com a
menor chave, que é o nó mais à esquerda. A descida só vai para a esquerda,
então só precisamos de moveVermelhoParaEsquerda.
- 1
Chegou ao menor?#
Se
r->esqéNULL,ré o menor. Pela Proposição 1, ele também não tem filho direito, e, pelo invariante da descida, ele é vermelho: basta liberá-lo e devolverNULL. - 2
Prepare o caminho#
Se
r->esqer->esq->esqsão pretos, não há vermelho à frente: chamemoveVermelhoParaEsquerda(r). - 3
Desça#
Remova o menor da SAE:
r->esq = removeMenor(r->esq). - 4
Volte corrigindo#
Devolva
balanceia(r). No fim, a função pública pinta a raiz de preto.
static No *removeMenor(No *r) {
if (r->esq == NULL) { /* o menor: é uma folha vermelha */
free(r);
return NULL;
}
if (!noEhVermelho(r->esq) && !noEhVermelho(r->esq->esq))
r = moveVermelhoParaEsquerda(r);
r->esq = removeMenor(r->esq);
return balanceia(r);
}
void removeMenorArvore(Arvore *arvore) {
if (arvore->raiz == NULL)
return;
arvore->raiz = removeMenor(arvore->raiz);
if (arvore->raiz != NULL)
arvore->raiz->cor = PRETO;
arvore->tamanho--;
}A figura mostra a remoção do menor na árvore obtida ao inserir as chaves de 1 a 7 (a da pergunta da seção anterior, toda preta).
Na descida, as duas inversões de cores (b e c) carregam o vermelho da raiz até o 1, que é removido sem mexer em nenhuma altura negra. Na volta, as duas rotações à esquerda (e e f) corrigem os vermelhos que ficaram à direita.
Na figura, a inversão de cores em (b) é aplicada à raiz, que é preta, e
não vermelha como moveVermelhoParaEsquerda supõe. Por que isso não causa
problema?
Ver respostaEsconder resposta
Com a raiz preta e os dois filhos pretos, a inversão deixa os três
vermelhos. Todos os caminhos da árvore passam pela raiz e por um dos
filhos, e todos perdem dois pretos: continuam com o mesmo número de
pretos entre si, e a propriedade 5 continua valendo. A violação que surge
(vermelhos seguidos na raiz) é temporária: a raiz é pintada de preto no
final, e balanceia corrige o resto na volta.
Remover uma chave qualquer
A remoção de uma chave qualquer combina o que já vimos. Se a chave está à
esquerda, a descida é a mesma de removeMenor. Se está à direita, ou é a do
próprio nó, a descida usa moveVermelhoParaDireita, depois de trazer para a
direita um vermelho que esteja à esquerda.
- 1
Chave à esquerda#
Se a chave é menor que a de
r, prepare o caminho como emremoveMenor(commoveVermelhoParaEsquerda, se preciso) e continue na SAE. - 2
Traga o vermelho para a direita#
Senão, se
r->esqé vermelho, aplique uma rotação à direita emr: o vermelho passa para o lado por onde vamos descer. - 3
Achou, sem filho direito#
Se a chave é a de
rernão tem filho direito,ré uma folha vermelha (pela Proposição 1 e pelo invariante): libere-a. - 4
Prepare o caminho à direita#
Se
r->direr->dir->esqsão pretos, chamemoveVermelhoParaDireita(r). - 5
Achou, com filho direito#
Se a chave é a de
r, copie pararo par do sucessor (o menor nó da SAD) e remova o sucessor comremoveMenor(r->dir). Senão, continue a remoção na SAD. - 6
Volte corrigindo#
Em todos os casos, devolva
balanceia(r).
/* Supõe que a chave está na subárvore de r */
static No *removeNo(No *r, Chave chave) {
if (chave < r->chave) {
if (!noEhVermelho(r->esq) && !noEhVermelho(r->esq->esq))
r = moveVermelhoParaEsquerda(r);
r->esq = removeNo(r->esq, chave);
} else {
if (noEhVermelho(r->esq)) /* leva o vermelho para a direita */
r = rotacaoDireita(r);
if (chave == r->chave && r->dir == NULL) {
free(r); /* folha vermelha */
return NULL;
}
if (!noEhVermelho(r->dir) && !noEhVermelho(r->dir->esq))
r = moveVermelhoParaDireita(r);
if (chave == r->chave) {
/* dois filhos: o sucessor (mínimo da SAD) toma o lugar */
No *suc = r->dir;
while (suc->esq != NULL)
suc = suc->esq;
r->chave = suc->chave;
r->valor = suc->valor;
r->dir = removeMenor(r->dir);
} else {
r->dir = removeNo(r->dir, chave);
}
}
return balanceia(r);
}
void removeArvore(Arvore *arvore, Chave chave) {
if (buscaNo(arvore->raiz, chave) == NULL)
return; /* removeNo supõe que a chave existe */
arvore->raiz = removeNo(arvore->raiz, chave);
if (arvore->raiz != NULL)
arvore->raiz->cor = PRETO;
arvore->tamanho--;
}Na ABB e na AVL, o nó com dois
filhos recebia o par do antecessor. Aqui usamos o sucessor, porque
ele é o menor da SAD, e já temos removeMenor, que remove o menor mantendo
as propriedades. Com o antecessor, precisaríamos de uma removeMaior, que é
justamente um dos exercícios desta página.
removeNo supõe que a chave está na árvore. Se ela não estiver, a descida
pode chegar a um nó cujo filho é NULL e tentar ler, por exemplo,
r->esq->esq com r->esq nulo. Por isso removeArvore busca a chave
antes e não faz nada se ela não existe. Essa busca também é
O TAD completo
O programa abaixo usa o TAD completo, com a mesma interface Arvore.h da ABB
e da AVL mais removeMenorArvore. Ele insere 1, 3, 6, 7 e 4 (a sequência da
figura), imprimindo a árvore depois de cada inserção, remove o menor e depois
remove o 7. Na impressão deitada, a raiz fica à esquerda e a SAD em cima.
Antes de executar, desenhe a árvore depois de removeArvore(a, 7). Que
operações acontecem durante essa remoção?
Ver respostaEsconder resposta
Antes da remoção, a árvore é 6 (preto) na raiz, com 4 (preto) à esquerda,
que tem o filho esquerdo 3 (vermelho), e 7 (preto) à direita. Na raiz, o
filho esquerdo 4 é preto, então não há rotação; o filho direito 7 e o
filho dele são pretos, então moveVermelhoParaDireita(6) inverte as
cores (6, 4 e 7 ficam vermelhos) e, como 3 é vermelho, faz uma rotação à
direita em 6 e uma nova inversão em 4. A árvore fica com 4 na raiz, 3 à
esquerda e 6 à direita, com o 7 vermelho à direita do 6. A descida chega
ao 7, uma folha vermelha, que é removida. No final, a árvore tem 4 (preto)
na raiz e os filhos 3 e 6, pretos.
Altura na prática
O programa abaixo insere
Com chaves crescentes, que degeneram a ABB comum, a LLRB fica com a altura mínima, como a AVL. Com chaves aleatórias, ela fica um pouco mais alta, mas muito abaixo do limite do teorema.
Custos
| Operação | ABB | AVL | LLRB |
|---|---|---|---|
| busca | |||
| inserção | |||
| remoção | |||
| altura no pior caso | cerca de | ||
| informação extra por nó | nenhuma | altura (int) | cor (1 bit) |
A busca é a da ABB, sem nenhuma mudança. A LLRB pode fazer mais rotações
que a rubro-negra convencional (que faz no máximo duas por inserção e três por
remoção), mas em troca o seu código cabe em poucas funções, e a mesma
balanceia serve à inserção e à remoção.
Exercícios
Encontre e corrija o erro
Os programas abaixo usam uma LLRB simplificada (só com chaves) e imprimem a árvore deitada, com a cor de cada nó. Compare a saída com as propriedades da LLRB e com as figuras desta página.
Programa 1
Resposta
A saída mostra o 6 com dois filhos vermelhos, e um deles (o 7) é filho
direito: viola a propriedade 6. A função balanceia usa else if: quando o
caso 2 acontece, a rotação à direita deixa o nó com dois filhos vermelhos,
mas o teste do caso 3 nem é feito. Ao inserir o 4, a rotação à direita em 7
acontece e a inversão de cores em 6, não. Os três testes precisam ser
independentes:
if (noEhVermelho(r->dir) && !noEhVermelho(r->esq))
r = rotacaoEsquerda(r);
if (noEhVermelho(r->esq) && noEhVermelho(r->esq->esq))
r = rotacaoDireita(r);
if (noEhVermelho(r->esq) && noEhVermelho(r->dir))
inverteCorNo(r);Programa 2
Resposta
A árvore final é uma lista para a esquerda, toda preta exceto o 4, com altura
4 para 5 chaves. As rotações não movem as cores: o nó que sobe continua
vermelho e o que desce mantém a cor que tinha. Os vermelhos vão parar em
lugares errados, as alturas negras deixam de ser iguais e balanceia passa a
tomar decisões erradas. A rotação precisa transferir a cor:
B->cor = A->cor; /* B herda a cor de A */
A->cor = VERMELHO; /* A desce e fica vermelho */Programa 3
Resposta
Todos os nós são pretos, e a árvore é quase uma lista para a direita (só o 4
fica à esquerda do 6). Abaixo da raiz, o caminho até uma folha NULL do 7
passa por quatro pretos (3, 6, 7 e a folha), e o caminho até a folha NULL à
esquerda da raiz, por um só. Viola a propriedade 5. O nó novo é criado
preto, e, sem nenhum vermelho, nenhum dos três testes de balanceia é
verdadeiro: a árvore nunca é corrigida. Todo nó novo precisa ser vermelho:
novo->cor = VERMELHO;Programa 4
Resposta
Depois de remover os quatro menores, o 12 aparece vermelho como filho
direito do 10, e viola a propriedade 6. Em moveVermelhoParaEsquerda,
falta a segunda inversão de cores, depois das duas rotações. Sem ela, o nó
que subiu fica preto com os dois filhos vermelhos, e o antigo r, vermelho,
fica com o filho esquerdo também vermelho (compare com o quadro (b5) da
figura de moveVermelhoParaEsquerda). A descida continua por dois vermelhos
seguidos, e a correção na volta da recursão não consegue restaurar todas as
propriedades. O caso com rotações
termina com uma nova inversão:
if (noEhVermelho(r->dir->esq)) {
r->dir = rotacaoDireita(r->dir);
r = rotacaoEsquerda(r);
inverteCorNo(r);
}Implemente
É uma LLRB? Implemente ehLLRB, que devolve true se a árvore é uma LLRB
válida: uma árvore binária de busca (chaves estritamente crescentes na ordem
simétrica), com a raiz preta, nenhum filho direito vermelho, nenhum vermelho
com filho vermelho e o mesmo número de nós pretos em todos os caminhos da
raiz até as folhas NULL. A árvore vazia é uma LLRB. A função deve fazer uma
única passada pela árvore, em
Nos testes, cada árvore é dada em pré-ordem, com cada chave seguida da sua cor
(V ou P) e . para NULL: A 10P 5V . . . é a raiz preta 10 com o filho
esquerdo vermelho 5. Uma linha G n s gera uma LLRB com
Uma função recursiva pode devolver um único número e, mesmo assim, conferir todas as propriedades. Que número?
Ver dica 1Esconder dica 1
Faça a função devolver a altura negra da subárvore, ou
Ver dica 2Esconder dica 2
Para a ordem, não basta comparar o nó com os filhos (lembre do exercício da ABB): passe para a recursão o intervalo de chaves permitido. A raiz preta é conferida uma vez, fora da recursão.
Remover o maior. Implemente removeMaior, que remove o nó com a maior
chave de uma subárvore não vazia e devolve a nova raiz da subárvore. As demais
funções do TAD, incluindo moveVermelhoParaDireita e balanceia, estão
prontas, e removeMaiorArvore chama a sua função e pinta a raiz de preto. Os
testes conferem todas as propriedades da LLRB depois das remoções, inclusive
removendo o maior até esvaziar árvores com dezenas de milhares de chaves.
O maior é o nó mais à direita, e a descida só vai para a direita. Mas os vermelhos ficam à esquerda. O que isso muda?
Ver dica 1Esconder dica 1
A estrutura é a de removeMenor, trocando os lados, com
moveVermelhoParaDireita para preparar o caminho. A diferença está no
primeiro passo de cada nó.
Ver dica 2Esconder dica 2
Um nó sem filho direito pode ter um filho esquerdo vermelho, e então não
pode ser simplesmente liberado. Faça como em removeNo: se r->esq é
vermelho, aplique antes uma rotação à direita em r. Depois disso, um nó
sem filho direito é uma folha vermelha.
Referências
- R. Sedgewick. Left-leaning Red-Black Trees. 2008.
- R. Sedgewick e K. Wayne. Algorithms. 4ª ed. Addison-Wesley, 2011. Seção 3.3.
- A. Backes. Estrutura de Dados Descomplicada em Linguagem C. Elsevier, 2016.
- T. H. Cormen, C. E. Leiserson, R. L. Rivest e C. Stein. Algoritmos: Teoria e Prática. 3ª ed. Elsevier, 2012. Capítulo 13.
- D. Galles. Visualização interativa da árvore rubro-negra. University of San Francisco.
Árvores rubro-negras
A árvore rubro-negra convencional, suas cinco propriedades, a altura negra e a demonstração de que a altura é sempre O(log n).
Fila de prioridade
O TAD fila de prioridade, as implementações elementares e o heap binário, com inserção, remoção do máximo, alteração de prioridade e construção a partir de um vetor.