Uma árvore AVL é uma árvore binária de pesquisa em que, para cada nó interno, a altura (height) de ambos os filhos difere, no máximo, em 1.
Análise
Seja $N(h)$ o número mínimo de nós numa árvore AVL com altura $h$. É fácil de verificar que $N(1) = 1$ e que $N(2) = 2$. Por definição, temos que $N(h) \geq N(h-1)$ e, considerando que a altura dos dois filhos de um nó difere no máximo em 1, obtemos $$ N(h) \geq N(h − 1) + N(h − 2) + 1 \geq 2 N(h − 2) + 1 > 2 N(h − 2)$$ Por indução, temos então que $N(h) > 2^i N(h - 2i)$ e, em particular, para $i = h/2 - 1$, obtemos $N(h) > 2^{h/2}$. Logo, aplicando o logaritmo, obtemos $h < 2\log N(h)$ e, portanto, a altura de uma árvore AVL é $O(\log N)$.
Inserção em árvores AVL
Antes demais é preciso notar que a inserção de novos elementos pode desequilibrar a árvore. Por exemplo, no caso do exemplo acima, ao inserirmos a chave $1$, obtemos a seguinte árvore desequilibrada:
Portanto pode ser necessário corrigir após a inserção de um novo elemento. A inserção em árvores AVL funciona da seguinte forma:
- inserir o novo elemento, numa folha, como numa BST normal;
- percorrer o caminho desde a nova folha até à raiz e
- para cada nó encontrado, verificar se as alturas dos dois filhos (esquerdo e direito) não diferem em mais do que 1
- e, se diferirem, equilibrar a sub-árvore com raiz nesse nó, efectuando uma rotação simples ou uma rotação dupla;
- após equilibrar a sub-árvore com raiz num determinado nó $x$, não será necessário equilibrar as sub-árvores com raiz nos antecessores de $x$;
- terminar após uma operação de equilíbrio, ou quando atingirmos a raiz.
No exemplo anterior basta fazer uma rotação à direita no nó com a chave 12, ou seja,
para obter a árvore equilibrada
Rotações em árvores AVL
Pode ser necessário fazer uma rotação simples, à esquerda
ou à direita
Ou pode ser necessário fazer uma rotação dupla, direita-esquerda
ou esquerda-direita
Casos de aplicação (na inserção):
- Se o novo nó é inserido na sub-árvore da esquerda, da sub-árvore da esquerda do elemento desequilibrado, basta-nos aplicar uma rotação simples para a direita aplicada a esse elemento.
- Se o novo nó é inserido na sub-árvore da direita, da sub-árvore da direita do elemento desequilibrado, basta-nos aplicar uma rotação simples para a esquerda aplicada a esse elemento.
- Se o novo nó é inserido na sub-árvore da direita, da sub-árvore da esquerda do elemento desequilibrado, fazemos uma rotação dupla esquerda-direita.
- Se o novo nó é inserido na sub-árvore da esquerda, da sub-árvore da direita do elemento desequilibrado, fazemos uma rotação dupla direita-esquerda.
Remoção em árvores AVL
Tal como na inserção, a remoção de um elemento pode desequilibrar a árvore. Por exemplo, se removermos a chave $45$ na árvore
obtemos a seguinte árvore desequilibrada
Por forma a garantir as propriedades de uma árvore AVL, a remoção funciona da seguinte forma:
- remover um nó como numa BST normal;
- percorrer o caminho desde o nó removido até à raiz e
- para cada nó encontrado, verificar se as alturas dos dois filhos (esquerdo e direito) não diferem em mais do que 1
- e, se diferirem, equilibrar a sub-árvore com raiz nesse nó, efectuando uma rotação simples ou uma rotação dupla.
- após equilibrar a sub-árvore com raiz num determinado nó $x$, poderá ser necessário equilibrar as sub-árvores com raízes nos antecessores de $x$;
- terminar apenas quando atingirmos a raiz.
Complexidade das operações sobre árvores AVL
Antes demais é importante notar que cada operação para equilibrar, quer seja através de rotação simples ou dupla, decorre em tempo $O(1)$. Notar também que a pesquisa é realizada como numa árvore BST normal e que, em particular, não altera a árvore, decorrendo em tempo $O(\log N)$ dado que como vimos acima a altura de uma árvore AVL é também $O(\log N)$.
A inserção em árvores AVL decorre em $O(\log N)$ tempo dado que procurar a posição para inserir é $O(\log N)$ e manter a árvore equilibrada é $O(\log N)$ (subir na árvore e equilibrar uma única vez).
A remoção em árvores AVL decorre em $O(\log N)$ tempo dado que procurar o elemento a remover é $O(\log N)$ e manter a árvore equilibrada é $O(\log N)$ (subir na árvore e equilibrar no máximo $O(\log N)$ vezes).
Exercício
Construa uma árvore AVL inserindo os seguintes elementos (por esta ordem):
63, 9, 19, 27, 18, 108, 99 e 81
Desenhe a árvore equilibrada resultante, e escreva o resultado de uma travessia pré-ordem sobre essa árvore.
Implementação
Ficheiro ST.h
:
#ifndef _ST_ #define _ST_ #include <stdlib.h> #include <stdio.h> #include "Item.h" typedef struct STnode* link; void STinit(link*); int STcount(link); Item STsearch(link, Key); void STinsert(link*, Item); void STdelete(link*, Key); void STsort(link, void (*visit)(Item)); void STfree(link*); #endif
Ficheiro ST.c
com apenas as funções modificadas ou novas em relação à implementação das BST:
#include "ST.h” struct STnode { Item item; link l, r; int height; /* altura do no */ }; link NEW(Item item, link l, link r) { link x = (link) malloc(sizeof(struct STnode)); x->item = item; x->l = l; x->r = r; x->height=1; return x; } int height(link h){ if (h == NULL) return 0; return h->height; } link rotL(link h) { int height_left, height_right; link x = h->r; h->r = x->l; x->l = h; height_left = height(h->l); height_right = height(h->r); h->height = height_left > height_right ? height_left + 1 : height_right + 1; height_left = height(h->l); height_right = height(x->r); x->height = height_left > height_right ? height_left + 1 : height_right + 1; return x; } link rotR(link h) { int height_left, height_right; link x = h->l; h->l = x->r; x->r = h; height_left = height(h->l); height_right = height(h->r); h->height = height_left > height_right ? height_left + 1 : height_right + 1; height_left = height(x->l); height_right = height(h->r); x->height = height_left > height_right ? height_left + 1 : height_right + 1; return x; } link rotLR(link h) /*rotação dupla esquerda direita*/ { if (h==NULL) return h; h->l=rotL(h->l); return rotR(h); } link rotRL(link h) /*rotação dupla direita esquerda*/ { if (h==NULL) return h; h->r=rotR(h->r); return rotL(h); } /* balance factor, i.e., diferenca entre as alturas */ int balance(link h) { if (h == NULL) return 0; return height(h->l)-height(h->r); } link AVLbalance(link h) { int balanceFactor, height_left, height_right; if (h==NULL) return h; balanceFactor = balance(h); if (balanceFactor > 1) { if (balance(h->l) > 0) h = rotR(h); else h = rotLR(h); } else if (balanceFactor < -1) { if (balance(h->r) < 0) h = rotL(h); else h = rotRL(h); } else { height_left = height(h->l); height_right = height(h->r); h->height = height_left > height_right ? height_left + 1 : height_right + 1; } return h; } link insertR(link h, Item item) { if (h == NULL) return NEW(item, NULL, NULL); if (less(key(item), key(h->item))) h->l = insertR(h->l, item); else h->r = insertR(h->r, item); h = AVLbalance(h); return h; } link deleteR(link h, Key k) { Item x; if (h==NULL) return h; else if (less(k, key(h->item))) h->l=deleteR(h->l,k); else if (less(key(h->item), k)) h->r=deleteR(h->r,k) ; else{ if (h->l != NULL && h->r != NULL){ link aux = max(h->l); x = h->item; h->item = aux->item; aux->item=x; h->l = deleteR(h->l, key(aux->item)); } else { link aux=h; if (h->l == NULL && h->r == NULL) h = NULL; else if (h->l == NULL) h = h->r; else h = h->l; deleteItem(aux->item); free(aux); } } h = AVLbalance(h); return h; }