Algoritmos e Estruturas de
Dados
Aula 07 – Vetores associativos (Dicionários);
pesquisa em memória principal – Parte 3 (Árvore Binária Balanceada) Professor Renato Vimieiro
Possíveis implementações: árvores 2-3
• O problema com árvores binárias sem balanceamento é que, no pior caso, elas se tornam uma lista
• Logo, o custo de busca e inserção torna-se O(n)
• Manter um balanceamento perfeito pode requerer diversas mudanças
• Buscar alternativas que mantenham certo equilíbrio, garantindo inserções e buscas em O(lg N) no pior caso
Árvores 2-3
• Primeiro passo para garantir buscas e inserções em O(lg N) é permitir que nós armazenem mais de uma chave/valor
• Árvores 2-3 permitem dois tipos de nós
• nós com 1 chave e 2 filhos (nó simples) • nós com 2 chaves e 3 filhos (nó duplo)
Árvores 2-3
• Definição (Sedgewick e Wayne):
• Uma árvore 2-3 é uma vazia, ou um nó simples com subárvores da esquerda com chaves menores e direita com chaves maiores, ou um nó duplo com elementos menores que a primeira chave na primeira subárvore, elementos maiores na última subárvores e intermediários na subárvore central
Árvores 2-3 (Busca)
• Árvores 2-3 perfeitamente balanceadas: todas as folhas estão no mesmo nível (mesma distância da raiz)
• A busca por uma chave é uma generalização da busca numa árvore binária
• Avaliar a raiz
• Se a chave não estiver na raiz, buscar no intervalo apropriado
• Buscar H na árvore
Árvore 2-3 (Inserção)
• Vamos analisar os casos particulares de inserção na árvore 2-3 • Caso mais simples: inserção em um nó simples
• Pesquisar o local de inserção (como em árvores binárias) • Modificar o nó para nó duplo
• Inserir chave e valor no nó
Árvores 2-3 (inserção)
• Segundo caso: inserir numa árvore contendo apenas um nó duplo
• Escolher o elemento central e criar um nó simples para ele • Esse novo nó passa a ser a raiz dessa árvore
• Criar um nó simples para o menor elemento (subárvore da esquerda) • Criar um nó simples para o maior elemento (subárvore da direita)
Árvore 2-3 (inserção)
• Terceiro caso: inserir em um nó duplo com pai simples
• Substituir pai por nó duplo
• Adicionar elemento central ao nó pai
• Criar novo nó com menor elemento (subárvore central) • Criar novo nó com maior elemento (subárvore da direita) • Subárvore da esquerda é herdada do pai
Árvore 2-3 (inserção)
• Quarto caso: inserção em um nó duplo com pai duplo
• Escolher elemento central • Inserir elemento no nó pai
• Criar nó simples com elemento menor (subárvore da esquerda) • Criar nó simples com elemento maior (subárvore da direita)
• Propagar elemento central no nó pai e repetir processo de criação de subárvores
Árvore 2-3 (inserção)
• Ao contrário de árvores binárias, inserções ocorrem de baixo para cima
• Alterações feitas nos nós preservam a ordem da árvore e mantém balanço
• Altura da árvore é preservada mesmo com inserções de chaves em ordem (pior caso da árvore binária)
• 𝑝𝑖𝑠𝑜(log) 𝑁) • 𝑝𝑖𝑠𝑜(𝑙𝑔 𝑁)
Árvores rubro-negras (Red-black trees)
• Os conceitos relativos às árvores 2-3 podem ser implementados através de uma árvore binária
• Nós passam a ter cores (vermelha ou preta)
• Nós duplos são representados por ligação (horizontal) entre nó preto e vermelho
Árvores rubro-negras
• Balanceamento preto perfeito (todo caminho desde a raiz até um nó nulo tem o mesmo número de nós pretos) • Não existe conexão entre dois nós
vermelhos
• Correspondência 1-1 com árvore 2-3 • Nós vermelhos devem vir sempre à
Árvores rubro-negras
#define RED true #define BLACK false
template <class Chave, class Valor> Dicionario<Chave,Valor>::class No { Chave chave; Valor& valor; No *esq, *dir; bool cor; Node(Chave c, Valor& v, bool co); };
template <class Chave, class Valor> bool Dicionario::isRed(No *x){
return x == nullptr? false : x->cor == RED; }
Árvores rubro-negras (inserções)
• Antes de iniciar a discussão sobre inserções, é necessário discutir algumas operações para garantir as propriedades da árvore
• Durante a inserção, podem ocorrer as seguintes situações:
• dois nós vermelhos são conectados; ou • um nó vermelho encontra-se à direita
• Nesses casos podem ser necessárias rotações para garantir a integridade da árvore
Árvore rubro-negras (inserções)
• Primeiro caso: nó vermelho encontra-se à direita • Nesse caso é necessária rotação à esquerda
template <class Chave, class Valor> No* Dicionario::girarEsq (No* h) { No* x = h->dir; h->dir = x->esq; x->esq = h; x->cor = h->cor; h->cor = RED; return x; }
Árvores rubro-negras (inserções)
• Em alguns casos, podem ser necessárias também rotações à direita
template <class Chave, class Valor> No* Dicionario::girarDir (No* h) { No x = h -> esq; h->esq = x->dir; x->dir = h; x->cor = h->cor; h->cor = RED; return x; }
Árvores rubro-negras (inserções)
• Primeiro caso: inserção em uma árvore com um nó simples • Existem três possibilidades:
• Nova chave é menor que a existente (inserção à esquerda) • Nova chave é maior que a existente (inserção à direita)
• A chave é igual (atualização do valor)
Árvores rubro-negras (inserções)
• Segundo caso: inserções em nós duplos
• Nesse caso, temos 3 subcasos possíveis (excluindo atualizações):
• Nova chave é maior que todas as outras (inserção mais à direita)
• Nova chave é menor que todas as outras (inserção mais à esquerda) • Nova chave está entre as outras (inserção central)
• Em qualquer dos três subcasos, um nó terá ligações para dois nós vermelhos que precisam ser corrigidas
Árvores rubro-negras (inserções)
• A inserção de uma chave maior que as demais é o mais simples
• Insere-se um novo nó vermelho com a nova chave na subárvore da direita do nó preto
• Inverte-se a cor dos filhos (propaga elemento central para o pai)
Árvores rubro-negras (inserções)
• Segundo subcaso (chave menor)
• Insere-se um novo nó vermelho à esquerda do menor elemento
• Dois nós vermelhos consecutivos! • Girar à direita
• Inverter cores dos filhos (propaga elemento central para o pai)
Árvores rubro-negras (inserções)
• Terceiro subcaso (inserção chave intermediária)
• Insere-se um novo nó vermelho à direita do menor elemento
• Nó vermelho à direita (girar à esquerda) • Dois nós vermelhos consecutivos (girar à
direita)
Árvores rubro-negras (inserções)
• As rotações feitas são equivalentes a ‘escolher o elemento central’ • As inversões de cores a propagar o elemento central para a raiz da
subárvore
template <class Chave, class Valor>
void Dicionario<Chave,Valor>::inverterCores(No* h){ h->cor = RED;
h->esq->cor = BLACK; h->dir->cor = BLACK; }
Árvores rubro-negras (inserções)
Árvores rubro-negras (inserções)
template<class Chave, class Valor>
No* Dicionario<Chave,Valor>::inserir(No* h, Chave c, Valor& v) {
if (h == nullptr) return new No(c, v, RED);
if (h->chave > c) h->esq = inserir(h->esq, c, v); else if (h->chave < c) h->dir = inserir(h->dir, c, v); else h->valor = v; if (isRed(h->dir) && !isRed(h->esq)) h = girarEsq(h); if (isRed(h->esq) && isRed(h->esq->esq)) h = girarDir(h); if (isRed(h->esq) && isRed(h->dir)) inverterCores(h); return h; }
Leitura
• Seção 3.3 (Sedgewick e Wayne) • Seção 5.3.2 (Ziviani)
• R. Sedgewick Left-leaning Red-Black Trees
(https://www.cs.princeton.edu/~rs/talks/LLRB/LLRB.pdf)
• E. Kohler Left-Leaning Red-Black Trees Considered Harmful