Escolar Documentos
Profissional Documentos
Cultura Documentos
Árvores binárias
(Veja o verbete Binary tree na Wikipedia.)
Uma árvore binária é uma estrutura de dados mais geral que uma lista encadeada. Este capítulo
introduz as operações mais simples sobre árvores binárias. O capítulo seguinte trata de uma aplicação
básica.
Nós e filhos
Uma árvore binária (= binary tree) é um conjunto de registros que satisfaz certas condições. (As
condições não serão dadas explicitamente, mas elas ficarão implicitamente claras no contexto.) Os
registros serão chamados nós (poderiam também ser chamados células). Cada nó tem um endereço.
Suporemos por enquanto que cada nó tem três campos: um número inteiro e dois ponteiros para nós.
Os nós podem, então, ser definidos assim:
conteudo
struct cel {
999
int conteudo; /* conteúdo */
esq dir
struct cel *esq;
};
O campo conteudo é a "carga útil" do nó; os outros dois campos servem apenas para dar estrutura à
árvore. O campo esq de todo nó contém o endereço de outro nó ou NULL. A mesma hipótese vale
para o campo dir. Se o campo esq de um nó X é o endereço de um nó Y, diremos que Y é o filho
esquerdo de X. Analogamente, se X.dir é igual a &Y então Y é o filho direito de X.
Se um nó Y é filho (esquerdo ou direito) de X, diremos que X é o pai de Y. Uma folha (= leaf) é um
nó que não tem filho algum.
É muito conveniente confundir cada nó com seu endereço. Assim, se x é um ponteiro para um nó (ou
seja, se x é do tipo *no), dizemos simplesmente "considere o nó x" em lugar de "considere o nó cujo
endereço é x".
Árvores e subárvores
Suponha que x é (o endereço de) um nó. Um descendente de x é qualquer nó que possa ser
alcançada pela iteração dos comandos x = x->esq e x = x->dir em qualquer ordem. (É claro que
esses comandos só podem ser iterados enquanto x for diferente de NULL. Estamos supondo que NULL é
de fato atingido mais cedo ou mais tarde.)
Um nó x juntamente com todos os seus descendentes é uma árvore binária. Dizemos que x é a raiz
(= root) da árvore. Se x tiver um pai, essa árvore é uma subárvore de alguma árvore maior. Se x é
NULL, a árvore é vazia.
Essa convenção sugere a introdução do nome alternativo arvore para o tipo-de-dados ponteiro-para-
nó:
A convenção permite formular a definição deárvore binária de maneira recursiva: um objeto r é uma
árvore binária se
1. r é NULL ou
2. r->esq e r->dir são árvores binárias.
Muitos algoritmos sobre árvores ficam mais simples quando escritos de forma recursiva.
Exercício
1. Árvores binárias têm uma relação muito íntima com certas seqüências bem-formadas de
parênteses. Discuta essa relação.
2. Árvores binárias podem ser usadas, de maneira muito natural, para representar expressões
aritméticas (como ((a+b)*c-d)/(e-f)+g, por exemplo). Discuta os detalhes.
Varredura esquerda-raiz-direita
Ao contrário de uma lista encadeada, uma árvore binária pode ser percorrida de muitas maneiras
diferentes. Uma maneira particularmente importante é a ordem esquerda-raiz-direita. Na varredura
e-r-d (= inorder traversal), visitamos
5
/ \
3 8
/ \ / \
1 4 6 9
/ \ \
0 2 7
Eis uma função recursiva que faz a varredura e-r-d de uma árvore binária r:
if (r != NULL) {
erd (r->esq);
erd (r->dir);
}
}
É um excelente exercício escrever uma versão iterativa desta função. Nossa versão usa uma pilha
p[0..t-1] de endereços e mais um endereço x que é candidato a entrar na pilha; é como se a pilha
fosse
A seqüência x, p[t-1], . . . , p[0] é uma espécie de "roteiro" daquilo que ainda precisa ser feito: x
representa a instrução "imprima a árvore x" e cada p[i] representa a instrução "imprima o nó p[i] e
em seguida a árvore p[i]->dir". Para dimensionar a pilha, suporemos que nossa árvore não tem
mais que 100 nós.
no *x, *p[100];
int t = 0;
x = r;
if (x != NULL) {
p[t++] = x;
x = x->esq;
else {
x = p[--t];
x = x->dir;
}
}
Para a árvore sugerida na figura acima, a pilha p evolui como indicado na tabela abaixo. Cada linha
da tabela resume o estado de coisas no início de uma iteração: à esquerda estão os nós que já foram
impressos; à direita está a pilha x, p[t-1], . . . , p[0]. O valor NULL está indicado por N.
3 5
1 3 5
0 1 3 5
N 0 1 3 5
0 N 1 3 5
0 1 2 3 5
0 1 N 2 3 5
0 1 2 N 3 5
0 1 2 3 4 5
0 1 2 3 N 4 5
0 1 2 3 4 N 5
0 1 2 3 4 5 8
0 1 2 3 4 5 6 8
0 1 2 3 4 5 N 6 8
0 1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 N 7 8
0 1 2 3 4 5 6 7 N 8
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 N 9
0 1 2 3 4 5 6 7 8 9 N
Exercícios
3. Verifique que o código abaixo é equivalente ao da função erd:
4. while (1) {
5. while (x != NULL) {
6. p[t++] = x;
7. x = x->esq;
8. }
9. if (t == 0) break;
10. x = p[--t];
11. printf ("%d\n", x->conteudo);
12. x = x->dir;
13. }
14. Escreva uma função que calcule o número de nós de uma árvore binária.
15. Escreva uma função que imprima, em ordem e-r-d, os conteúdos das folhas de uma árvore
binária.
16. Dada uma árvore binária, encontrar um nó da árvore cujo conteúdo tenha um certo valor k.
17. Escreva uma função que faça varredura r-e-d (= preorder traversal) de uma árvore binária.
[A varredura r-e-d também é conhecida como "busca em profundidade" ou depth-first search.]
18. Escreva uma função que faça varredura e-d-r (= postorder traversal) de uma árvore binária.
19. Discuta a relação entre a varredura e-r-d e a notação infixa de expressões aritméticas. Discuta
a relação entre a varredura e-d-r e a notação posfixa. (Veja exercício acima. Veja também a
seção sobre notação polonesa.)
no *primeiro (arvore r) {
r = r->esq;
return r;
}
Não é difícil fazer uma função análoga que encontre o último nó na ordem e-r-d.
Exercícios
9. Escreva uma versão recursiva da função primeiro.
Altura
A altura de um nó x em uma árvore binária é a distância entre x e o seu descendente mais afastado.
Mas precisamente, a altura de x é o número de passos do mais longo caminho que leva de x até uma
folha. Os caminhos a que essa definição se refere são os obtido pela iteração dos comandos x = x-
>esq e x = x->dir, em qualquer ordem.
A altura de uma árvore é a altura da raiz da árvore. Uma árvore com um único nó tem altura 0. A
árvore da figura tem altura 3.
/ \
D I
/ / \
B G K
/ \ / \ /
A C F H J
Eis como a altura de uma árvore com raiz r pode ser calculada:
if (r == NULL)
else {
else return he + 1;
Qual a relação entre a altura, digamos h, e o número de nós, digamos n, de uma árvore binária?
Resposta:
n-1 ≥ h ≥ lg(n) ,
n lg(n)
4 2
5 2
6 2
10 3
64 6
100 6
128 7
1000 9
1024 10
1000000 19
Uma árvore binária com h = n-1 é um "tronco sem galhos": cada nó tem no máximo um filho. No
outro extremo, uma árvore com h = lg(n) é "quase completa": todos os "níveis" estão lotados exceto
talvez o último.
/ \
D K
/ \ / \
B F J L
/ \ / \ /
A C E G I
Uma árvore binária é balanceada (ou equilibrada) se, em cada um de seus nós, as subárvores
esquerda e direita tiverem aproximadamente a mesma altura. Uma árvore binária balanceada com n
nós tem altura próxima de lg(n).
Convém trabalhar com árvores balanceadas sempre que possível. Mas isso não é fácil se a árvore
aumenta e diminui ao longo da execução do seu programa.
Exercícios
11. Desenhe uma árvore binária que tenha conteúdos 1, . . . , 17 e a menor altura possível. Repita
com a maior altura possível.
12. Escreva uma função iterativa para calcular a altura de uma árvore binária.
13. Uma árvore é balanceada no sentido AVL se, para cada nó x, as alturas das subárvores que
têm raízes x->esq e x->dir diferem de no máximo uma unidade. Escreva uma função que
decida se uma dada árvore é balanceada no sentido AVL. Procure escrever sua função de
modo que ela visite cada nó no máximo uma vez.
};
É um bom exercício escrever uma função que preencha o campo pai de todos os nós de uma árvore
binária.
Exercícios
14. Escreva uma função que preencha corretamente todos os campos pai de uma árvore binária.
15. A profundidade (= depth) de um nó x em uma árvore binária com raiz r é a distância entre x e
r. Mais precisamente, a profundidade de x é o comprimento do (único) caminho que vai de r
até x. Por exemplo, a profundidade de r é 0 e a profundidade de r->esq é 1. Escreva uma
função que determine a profundidade de um nó em relação à raiz da árvore.
16. Escreva uma função que imprima os conteúdos de uma árvore binária com recuos de margem
proporcionais à profundidade do nó na árvore. Por exemplo, a árvore
555
/ \
333 888
/ \ \
111 444 999
555
333
111
444
888
999
Para resolver o problema, é necessário que os níos tenham um campo pai. Eis uma função que
resolve o problema. É claro que a função só deve ser chamada com x diferente de NULL. A função
devolve o endereço do nó seguinte a x ou devolve NULL se x é o último nó. (Note que a função não
precisa saber onde está a raiz da árvore.)
if (x->dir != NULL) {
no *y = x->dir;
return y; // *
x = x->pai; // **
return x->pai;
}
Comentários: Na linha *, y é o endereço do primeiro nó, na ordem e-r-d, da subárvore que tem raiz x-
>dir. As linhas ** fazem com que x suba na árvore enquanto for filho direito de alguém.
Exercícios
18. Escreva uma função que receba o endereço de um nó x de uma árvore binária e encontre o
endereço do nó anterior a x na ordem e-r-d.
19. Escreva uma função que faça varredura e-r-d usando as funções primeiro e seguinte.
Árvores
1. Introdução
2. Definições Básicas
3. Àrvores Binárias
4. Armazenamento de Árvores Binárias
5. Uma Aplicação de Árvores Binárias
6. Percorrendo Árvores Binárias
7. O Algoritmo de Huffman
8. Removendo Nós de Árvores Binárias
9. Árvores Binárias Balanceadas
10. Exercícios
Introdução
Definições Básicas
Árvores são estruturas de dados extremamente úteis em muitas aplicações. Uma árvore é formada por
um conjunto finito T de elementos denominados vértices ou nós de tal modo que se T = 0 a árvore é
vazia, caso contrário temos um nó especial chamado raiz da árvore (r), e cujos elementos restantes
são particionados em m>=1 conjuntos distintos não vazios, as subárvores de r, sendo cada um destes
conjuntos por sua vez uma árvore.
A forma convencional de representar uma árvore está indicado na figura aini abaixo. Esta árvore tem
nove nós sendo A o nó raiz.
Figura (aini): Uma árvore
Os conjuntos das subárvores tem de ser disjuntos tem de ser disjuntos portanto a estrutura indicada na
Figura arvn não é uma árvore.
Um caminho da árvore é composto por uma seqüência de nós consecutivos (n1, n2, ..., nk-1, nk) tal que
existe sempre a relação ni é pai de ni+1. Os k vértices formam k-1 pares e um caminho de comprimento
igual a k-1. O comprimento do caminho entre o nó A e o nó H é 3.
O nível de um nó n pode ser definido do seguinte modo: o nó raiz tem nível 0, os outros nós tem um
nível que é maior uma unidade que o nível de seu pai. Na árvore da figura anterior temos nós nos
seguintes níveis:
• nível 0 = A
• nível 1 = B, C
• nível 2 = D, E, F, G
• nível 3 = H, I
Existem diversas maneiras de representar árvores. Uma representação que reflete a idéia de árvores
como conjuntos aninhados é mostrado na figura arvconj abaixo. A figura mostra o mesmo conjunto
da figura aini.
Figura (arconj): Árvore representada como conjuntos aninhados.
Uma outra notação que encontramos a toda hora, e que está representada na figura arviden, é a forma
identada ou de diagrama de barras. Notar que esta representação lembra um sumário de livro. Os
sumários dos livros são representações da árvore do conteúdo do livro.
Uma outra forma interessante de representar uma árvore é a representação por parênteses aninhados.
Da mesma forma que a figura aini representa uma árvore no plano a representação por parênteses
representa uma árvore em uma linha. A seqüência de parênteses representa a relação entre os nós da
estrutura. O rótulo do nó é inserido à esquerda do abre parênteses correspondente. A árvore
representada planarmente pela figura aini pode ser representada em uma linha por
(A (B(D))(C(E(H)(I))(F)(G)))
Esta representação tem importância, por exemplo, no tratamento de expressões aritméticas, já que
toda expressão aritmética pode ser colocada nesta forma. Se colocarmos uma expressão nesta forma
podemos então representá-la como uma árvore, mostrando como ela seria calculada. Para colocarmos
uma expressão em forma de árvore devemos considerar cada operador como um nó da árvore e os
seus operandos como as duas subárvores. Considere a expressão C seguinte
A + (B-C)*D%(E*F)
(A + ((B-C)*(D%(E*F))))
A figura arvexp mostra como fica esta expressão representada por uma árvore.
Formalmente uma árvore binária pode ser definida como um conjunto finito de nós, que é vazio, ou
consiste de um nó raiz e dois conjuntos disjuntos de nós, a subárvore esquerda e a subárvore direita. É
importante observar que uma árvore binária não é um caso especial de árvore e sim um conceito
completamente diferente. Por exemplo, considere a figura arvbind, note que são duas árvores
idênticas, mas são duas árvores binárias diferentes. Isto porque uma delas tem a subárvore da direita
vazia e a outra a subárvore da esquerda.
Figura arcbind: Árvores binárias diferentes.
Uma árvore estritamente binária é uma árvore binária em que cada nó tem 0 ou 2 filhos. Uma árvore
binária cheia é uma árvore em que se um nó tem alguma sub-árvore vazia então ele está no último
nível. Uma árvore completa é aquela em se n é um nó com algumas de subárvores vazias, então n se
localiza no penúltimo ou no último nível. Portanto, toda árvore cheia é completa e estritamente
binária. A Figura arvbcc mostra uma árvore estritamente binária, completa e cheia.
A Figura armarv mostra um diagrama de como seria o armazenamento de uma árvore binária.
Observar que se desconsiderarmos os campos de informação para armazenar uma árvore com n nós
precisamos de 2n+1 unidades de memória.
p = cria_no();
p->info = x;
p->esq = NULL;
p->dir = NULL;
return p;
pos_esq aceita um ponteiro p para uma árvore binária sem filho esquerdo e cria um novo filho
esquerdo contendo a informação x. Um possível algoritmo para esta função pode ser:
if (p->left)
puts("Operação ilegal");
else {
q = cria_arvore();
p->left = q;
}
O algoritmo pos_dir é semelhante a este com a diferença que ele cria um nó a direita.
7 8 2 5 8 3 5 10 4
foram fornecidos pelo usuário, neste caso a árvore binária mostrada na Figura arbus seria construida.
O programa arv0300.c mostra este algoritmo. O programa insere os nós na árvore e imprime uma
mensagem caso seja fornecido um número que já foi lido antes.
/* programa arv0300.c */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void main() {
tNo *raiz, *p, *q;
char linha[80], *numero;
int num;
gets(linha);
numero = strtok(linha, " "); /* pega o primeiro numero da lista */
num = atoi(numero);
raiz = cria_arvore(num); /* insere na raiz */
numero = strtok(NULL, " ");
while (numero) {
q = raiz; p = raiz;
printf("Li numero %d\n", num); /* le novo numero */
num = atoi(numero);
while (num != p->info && q) { /* procura na arvore */
p = q;
if (num < p->info)
q = p->esq; /* passa para arvore esquerda */
else
q = p->dir; /* passa para direita */
}
if (num == p->info)
printf("O numero %d ja existe na arvore.\n", num);
else { /* vou inserir o numero na arvore */
if (num < p->info)
pos_esq(p, num);
else
pos_dir(p, num);
}
numero = strtok(NULL, " ");
} /* fim do while (numero) */
}
p = cria_no ();
if (p) {
p->info = x;
return p;
}
else {
puts("Faltou espaco para alocar no.");
exit(1);
}
}
tNo *cria_no() {
tNo *p;
if (p->esq)
puts("Operacao de insercao a esquerda ilegal.");
else {
q = cria_arvore(x);
p->esq = q;
}
}
if (p->dir)
puts("Operacao de insercao a direita ilegal.");
else {
q = cria_arvore(x);
p->dir = q;
}
}
Uma árvore é uma estrutura não seqüêncial, diferentemente de uma lista, por exemplo. Não existe
ordem natural para percorrer árvores e portanto podemos escolher diferentes maneiras de percorrê-las.
Nós iremos estudar três métodos para percorrer árvores. Todos estes três métodos podem ser
definidos recursivamente e se baseiam em três operações básicas: visitar a raiz, percorrer a subárvore
da esquerda e percorrer a subárvore da direita. A única diferença entre estes métodos é a ordem em
que estas operações são executadas.
1. Visitar a raiz;
2. Percorrer a subárvore da esquerda em pré-ordem;
3. Percorre a subárvore da direita em pré-ordem.
Para a árvore da Figura arvbinp este percurso forneceria, no caso da visita significar imprimir, os
seguintes resultados: F B A D C E H G I. Uma aplicação interessante deste tipo de percurso é aplicá-
lo à uma árvore que contenha uma expressão aritmética, a qual foi expandida e recebeu todos os
parênteses. Por exemplo, aplicando-se o percurso em pré-ordem à árvore arvexp obtém-se como
resultado a expressão em notação polonesa normal, isto é os operandos antes dos operadores. Deste
modo o resultado é +A*-BC%D*EF. Observar que esta notação é diferente da notação polonesa
reversa, em que os operadores aparecem depois dos operandos.
Um algoritmo recursivo para implementar este modo de percurso pode ser o seguinte:
Para percorrer a árvore em ordem simétrica executa-se recursivamente os três passos na seguinte
ordem:
Um algoritmo recursivo para implementar este modo de percurso pode ser o seguinte:
Este tipo de percurso é muito empregado em árvores binárias de busca. Considere a árvore mostrada
na Figura arvbus, que foi gerada como está indicado na seção Uma Aplicação de Árvores Binárias.
Caso a árvore seja percorrida em ordem simétrica o resultado seria
2 3 4 5 7 8 10
O percurso conhecido como pós-ordem é feito a partir dos três passos na seguinte ordem:
O percurso em pós-ordem pode ser aplicado no cálculo da altura de uma árvore. Para calcular a altura
de uma árvore é necessário calcular o maior caminho da raiz até uma de suas folhas. Deste modo só
podemos calcular o comprimento de um caminho a partir de um nó v após percorrermos todos os seus
descendentes. O algoritmo mostrado abaixo mostra como fica a implementação da função visita
para que ela calcule a altura do nó.
As variáveis alt1 e alt2 armazenam a altura das subárvores da esquerda e da direita e o campo
altura é um novo campo da estrutura que armazena o nó. A altura de um nó é igual ao maior valor
entre as alturas esquerda e direita incrementado de um.
Algoritmo de Huffman
Para analisarmos mais uma aplicação de árvores binárias vamos considerar o problema de codificar
uma mensagem composta de uma seqüência de símbolos de um alfabeto de n símbolos. Esta
mensagem será transformada em uma seqüência de bits, depois de a cada símbolo for atribuído um
código binário e os códigos dos símbolos da mensagem forem concatenados.
Considere um alfabeto composto de quatro símbolos A, B, C e D, sendo que a cada um dos símbolos
foi atribuído o código indicado a seguir:
Símbolo Código
A 00
B 01
C 10
D 11
Símbolo Freqüência
A 3
B 1
C 2
D 1
Desta tabela podemos verificar que se atribuirmos ao símbolo A um código binário mais curto que os
atribuídos aos símbolos B e D teríamos uma mensagem menor. Isto provém do fato que o símbolo A
aparece mais vezes do que os símbolos B e D. Suponha que os seguintes códigos sejam atribuídos aos
símbolos
Símbolo Código
A 0
B 110
C 10
D 111
Usando estge código a mensagem ABCADCA ficaria 0110100111100 que requer 13 bits. Em
mensagens longas com mais símbolos infrequentes o ganho pode ser maior. Um dos requerimentos
deste código é que nenhum código seja prefixo de outro, caso a decodificação seja feita da esquerda
para direita.
Para decodificar a mensagem vamos começar da esquerda para a direita, caso o primeiro bit seja 0 o
código corresponde ao símbolo A. No caso contrário devemos continuar a examinar os bits restantes.
Se o segundo bit for 0 o símbolo é um C, caso contrário examinamos o terceiro bit, um 0 indica um B
e D no outro caso.
Do que vomos até agora o algoritmo para encontrar o algoritmo ótimo é o seguinte. Encontre os dois
símbolos que aparecem com menor freqüência, no nosso caso B e D. Atribuímos 0 para B e 1 para D.
Combine estes dois símbolos em um BD. Este novo símbolo terá freqüência igual a soma das
freqüências de B e D, no caso 2. Temos agora os seguintes símbolos A (3), C (2) e BD (2), os
números entre parênteses são as freqüências. Novamente devemos escolher os símbolos de menor
freqüência, que são C e BD. Atribuímos o código 0 ao símbolo C e 1 ao BD. Isto siginifica adicionar
1 aos códigos de B e D, que passam a valer 10 e 11 respectivamente. Os dois símbolos são
combinados então no símbolo CBD de freqüência 4. Temos agora dois símbolos A (3) e CBD (4).
Atribuímos 0 ao símbolo A e 1 ao símbolo CBD. O símbolo ACBD é o único símbolo restante e
recebe o código NULL de comprimento 0. A Figura arvhuf1 mostra a árvore binária que pode ser
construída a partir deste exemplo. Cada nó representa um símbolo e sua freqüência.
Figura arvhuf1: Árvore de Huffman
1. nó sem filhos;
2. nó com um unico filho;
3. nó com dois filhos.
O caso de um nó sem filhos é o mais simples e significa apenas ajustar o ponteiro de seu pai. A
Figura remov0 ilustra este caso, onde o nó com o valor 8 é removido. No caso do nó ter um único
filho a mudança na árvore também é simples significa mover o nó filho daquele será removido uma
posição para cima como está ilustrado na Figura remove1, onde o nó com o valor 6 é removido. O
caso mais complexo é o do nó com dois filhos. Neste caso devemos procurar o sucessor s (ou
antecessor) do nó deverá ocupar este lugar. Este nó (sucessor) é um descendente que está na
subárvore da direita do nó e corresponde ao nó mais à esquerda desta árvore. Ele não tem filhos à
esquerda e a sua árvore à direita pode ser movida para o lugar de s. A Figura remove2 ilustra o caso
de remoção do nó com o valor 12. Observe que o nó 13 (sucessor) assumiu o lugar do nó 12.
Figura remove0: Removendo nó (8) sem filhos.
O texto abaixo mosta uma rotina que remove nós de uma árvore, que contém números inteiros. O
programa completo está em arvremov.c.
p = tree; q=NULL;
Caso a probabilidade de pesquisar uma chave em uma tabela seja a mesma para todas as chaves, uma
árvore binária balanceada terá a busca mais eficiente. Infelizmente o método de inserção em árvores
binárias apresentado anteriormente não garante que a árvore permanecerá balanceada. Como já vimos
a estrutura da árvore depende da ordem em que as chaves são inseridas na árvore.
A Figura arvbali mostra possibilidades de inserção na árvore e o que ocorreria com o seu
balanceamento. Cada inserção que mantém a árvore balanceada está indicada bor um B e as que
desbalanceiam a árvore por um D. Observe que uma árvore se torna desbalanceada quando o nó
inserido se torna descendente esquerdo de um nó que tinha anteriormente um balanceamento de 1 ou
se ele se tornar descendente direito de um nó que tinha anteriormente balanceamento de -1. Isto é
fácil de deduzir, por exemplo, um nó que tinha balanceamento 1 e recebe um descendente direito
aumenta sua altura em 1, portanto aumentando o seu desbalanceamento.
Figura arvbali: Árvore balanceada e suas possibilidades de inserção
Observemos uma subárvore que ser tornará desbalanceada quando ocorrer uma inserção. Vamos
supor também que este nó tem um balanceamento de 1. Neste caso o desbalanceamento ocorrerá se a
inserção ocorrer em um nó da direita. A Figura arvins mostra um exemplo deste caso.
Figura arvins: Inserção em árvore binária
Observar que o nó A tem balanceamento 1, isto significa que a subárvore da esquerda tem altura não
nula e maior em uma unidade que a da direita. Ao inserirmos um nó na subárvore da direita a sua
altura aumenta de um e o balanceamento passa para 2. Observar também que como o nó mais jovem a
se tornar desbalanceado é o A, o seu filho pela esquerda tem de ter balalneamento igual a 0.
Para manter a árvore balanceada é necessário que a transformação na árvore de tal modo que:
Para isto vamos definir a operação de rotação em uma árvore. Uma rotação pode ser à direita ou
esquerda. A Figura arvrot mostra uma árvore e os resultados dos dois tipos de rotação sobre esta
árvore estão mostrados na Figura arvrota.
Figura arvrot: Árvore original antes da rotação
Para verificar o que fazer em uma árvore T após a inserção de um nó q vamos considerar os vários
casos possíveis. Primeiro, se após a inclusão todos os nós se mantiveram regulados, então a árvore se
manteve AVL e nada há a fazer. Caso contrário vamos considerar o nó p mais próximo das folhas,
que se tornou desregulado. A escolha de p é única, pois qualquer subárvore de T que se tornou
desregulada deve incluir p.
|hd(p) - he(p)| = 2
neste caso q pertence a subárvore esquerda de p. Além disso p possui o filho esquerdo u <> q, senão
p não estaria desregulado. Sabe-se também que hd(u) <>he(u), pela mesma razão. Para o caso 1 temos
duas possibilidades:
caso 1.1
Exercícios
1. Escreva um programa que crie uma árvore de busca binária a partir de letras lidas do teclado.
O programa deve imprimir a árvore nos três modos de percurso.
Solução: arv0301.c
2. Faça uma função que imprima os nós de uma árvore na sequencia de seus níveis.
Solução: niveis.c
3. Escreva um programa que leia um arquivo que contem varios numeros inteiros (cada numero
em uma linha) e imprima os numeros em ordem crescente (utilize uma arvore para armazenar
os numeros na memoria.
Solução: ordem.c