Está en la página 1de 10

rvores binrias

(Veja o verbete Binary tree na Wikipedia.) Uma rvore binria uma estrutura de dados mais geral que uma lista encadeada. Este captulo introduz as operaes mais simples sobre rvores binrias. O captulo seguinte trata de uma aplicao bsica.

Ns e filhos
Uma rvore binria (= binary tree) um conjunto de registros que satisfaz certas condies. (As condies no sero dadas explicitamente, mas elas ficaro implicitamente claras no contexto.) Os registros sero chamados ns(poderiam tambm ser chamados clulas). Cada n tem um endereo. Suporemos por enquanto que cada n tem trs campos: um nmero inteiro e dois ponteiros para ns. Os ns podem, ento, ser definidos assim:
struct cel { int conteudo; /* contedo */ esq dir conteudo 999

struct cel *esq; struct cel *dir; }; typedef struct cel no; /* n */

O campo conteudo a "carga til" do n; os outros dois campos servem apenas para dar estrutura rvore. O campo esq de todo n contm o endereo de outro n ou NULL. A mesma hiptese vale para o campo dir. Se o campoesq de um n X o endereo de um n Y, diremos que Y o filho esquerdo de X. Analogamente, se X.dir igual a &Y ento 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 no tem filho algum. muito conveniente confundir cada n com seu endereo. 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 endereo x".

rvores e subrvores
Suponha que x (o endereo de) um n. Um descendente de x qualquer n que possa ser alcanada pela iterao 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 binria. Dizemos que x a raiz (= root) da rvore. Se x tiver um pai, essa rvore uma subrvore de alguma rvore maior. Se x NULL, a rvore vazia. Para qualquer n x,
x->esq

a raiz da subrvore esquerda de

x->dir

a raiz da subrvore direita de x.

Endereo de uma rvore e definio recursiva


O endereo de uma rvore binria o endereo de sua raiz. conveninente confundir rvores com seus endereos: dizemos "considere a rvore r" em lugar de "considere a rvore cuja raiz tem endereo r". Essa conveno sugere a introduo do nome alternativo arvore para o tipo-de-dados ponteiro-para-n:
typedef no *arvore; /* rvore */

A conveno permite formular a definio dervore binria de maneira recursiva: um objeto r uma rvore binria se 1. 2. NULL ou r->esq e r->dir so rvores binrias.
r

Muitos algoritmos sobre rvores ficam mais simples quando escritos de forma recursiva.

Exerccio
1. rvores binrias tm uma relao muito ntima com certas sequncias bem-formadas de parnteses. Discuta essa relao. 2. rvores binrias podem ser usadas, de maneira muito natural, para representar expresses aritmticas (como ((a+b)*c-d)/(e-f)+g, por exemplo). Discuta os detalhes.

Varredura esquerda-raiz-direita
Ao contrrio de uma lista encadeada, uma rvore binria pode ser percorrida de muitas maneiras diferentes. Uma maneira particularmente importante a ordem esquerda-raiz-direita. Na varredura e-r-d (= inorder traversal), visitamos 1. a subrvore esquerda da raiz, em ordem e-r-d; 2. depois a raiz; 3. depois a subrvore direita da raiz, em ordem e-r-d. Na figura abaixo, os ns esto numeradas na ordem da varredura e-r-d.

5 / 3 / 1 0 / \ 2 \ 4 / 6 \ 7 \ 8 \ 9

is uma funo recursiva que faz a varredura e-r-d de uma rvore binria r:
// Recebe a raiz r de uma rvore binria. // Imprime os contedos dos ns em ordem e-r-d.

void erd (arvore r) { if (r != NULL) { erd (r->esq); printf ("%d\n", r->conteudo); erd (r->dir); } }

um excelente exerccio escrever uma verso iterativa desta funo. Nossa verso usa uma pilha p[0..t-1] de endereos e mais um endereo x que candidato a entrar na pilha; como se a pilha fosse
p[0], p[1],

. . . , p[t-1], x .

A sequncia x, p[t-1], . . . , p[0] uma espcie de "roteiro" daquilo que ainda precisa ser feito: x representa a instruo "imprima a rvore x" e cada p[i] representa a instruo "imprima o n p[i] e em seguida a rvore p[i]->dir". Para dimensionar a pilha, suporemos que nossa rvore no tem mais que 100 ns.
// Recebe a raiz r de uma rvore binria. // Imprime os contedos dos ns em ordem e-r-d. // Supe que a rvore no tem mais que 100 ns.

void erd_i (arvore r) { no *x, *p[100]; int t = 0; x = r; while (x != NULL || t > 0) { // a pilha p[0..t-1]; o ndice do topo t-1 if (x != NULL) { p[t++] = x; x = x->esq;

} else { x = p[--t]; printf ("%d\n", x->conteudo); x = x->dir; } } }

[Note a semelhana esse cgido e o cdigo da funo que enumera subsequncias de 1..n.] 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 incio de uma iterao: esquerda esto os ns que j foram impressos; direita est a pilha x,p[t-1], . . . , p[0]. O valor NULL est indicado por N.
5 3 5 1 3 5 0 1 3 5 N 0 1 3 5 0 0 1 0 1 0 1 2 0 1 2 3 0 1 2 3 0 1 2 3 4 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 0 1 2 3 4 5 6 0 1 2 3 4 5 6 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8 9 N 1 3 5 2 3 5 N 2 3 5 N 3 5 4 5 N 4 5 N 5 8 6 8 N 6 8 7 8 N 7 8 N 8 9 N 9 N

Exerccios
3. Verifique que o cdigo abaixo equivalente ao da funo erd:
4. 5. 6. 7. 8. 9. 10. 11. 12. 13. while (1) { while (x != NULL) { p[t++] = x; x = x->esq; } if (t == 0) break; x = p[--t]; printf ("%d\n", x->conteudo); x = x->dir; }

14. Escreva uma funo que calcule o nmero de ns de uma rvore binria. 15. Escreva uma funo que imprima, em ordem e-r-d, os contedos das folhas de uma rvore binria. 16. Dada uma rvore binria, encontrar um n da rvore cujo contedo tenha um certo valor k. 17. Escreva uma funo que faa varredura r-e-d (= preorder traversal) de uma rvore binria. [A varredura r-e-d tambm conhecida como "busca em profundidade" ou depth-first search.] 18. Escreva uma funo que faa varredura e-d-r (= postorder traversal) de uma rvore binria. 19. Discuta a relao entre a varredura e-r-d e a notao infixa de expresses aritmticas. Discuta a relao entre a varredura e-d-r e a notao posfixa. (Veja exerccio acima. Veja tambm a seo sobre notao polonesa.)

Primeiro e ltimo ns
Considere o seguinte problema: encontrar o endereo do primeiro n na ordem e-r-d. claro que o problema s faz sentido se a rvore no vazia, ou seja, se r diferente de NULL. Eis uma funo que resolve o problema:
// Recebe uma rvore binria no vazia r. // Devolve o primeiro n da rvore na ordem e-r-d.

no *primeiro (arvore r) { while (r->esq != NULL) r = r->esq; return r; }

No difcil fazer uma funo anloga que encontre o ltimo n na ordem e-r-d.

Exerccios
9. Escreva uma verso recursiva da funo primeiro. 10. Escreva uma funo que encontre o ltimo n na ordem e-r-d.

Altura
A altura de um n x em uma rvore binria a distncia entre x e o seu descendente mais afastado. Mas precisamente, a altura de x o nmero de passos do mais longo caminho que leva de x at uma folha. Os caminhos a que essa definio se refere so os obtido pela iterao 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.
E / D / B / \ A C / G / \ F H \ I \ K / J

Eis como a altura de uma rvore com raiz r pode ser calculada:
// Devolve a altura da rvore binria cuja raiz r.

int altura (arvore r) { if (r == NULL) return -1; // altura de rvore vazia -1 else { int he = altura (r->esq); int hd = altura (r->dir); if (he < hd) return hd + 1; else return he + 1; } }

Qual a relao entre a altura, digamos h, e o nmero de ns, digamos n, de uma rvore binria? Resposta:
n-1

lg(n)

onde

lg(n)

denota o piso de log2n .

n 4 5 6 10 64 100 128 1000 1024 1000000

lg(n) 2 2 2 3 6 6 7 9 10 19

Uma rvore binria com h = n-1 um "tronco sem galhos": cada n tem no mximo um filho. No outro extremo, uma rvore com h = lg(n) "quase completa": todos os "nveis" esto lotados exceto talvez o ltimo.
H / D / B / \ A C \ F / \ E G J / I / \ K \ L

Uma rvore binria balanceada (ou equilibrada) se, em cada um de seus ns, as subrvores esquerda e direita tiverem aproximadamente a mesma altura. Uma rvore binria balanceada com n ns tem altura prxima de lg(n). Convm trabalhar com rvores balanceadas sempre que possvel. Mas isso no fcil se a rvore aumenta e diminui ao longo da execuo do seu programa.

Exerccios
11. Desenhe uma rvore binria que tenha contedos 1, . . . , 17 e a menor altura possvel. Repita com a maior altura possvel. 12. Escreva uma funo iterativa para calcular a altura de uma rvore binria. 13. Uma rvore balanceada no sentido AVL se, para cada n x, as alturas das subrvores que tm razes x>esq e x->dir diferem de no mximo uma unidade. Escreva uma funo que decida se uma dada rvore balanceada no sentido AVL. Procure escrever sua funo de modo que ela visite cada n no mximo uma vez.

Ns com campo pai


Em algumas aplicaes (veja seo seguinte) conveniente ter acesso imediato ao pai de qualquer n. Para isso, preciso acrescentar um campo pai a cada n:

struct cel { int conteudo;

pai

999

struct cel *pai; struct cel *esq; struct cel *dir; }; typedef struct cel no; esq dir

um bom exerccio escrever uma funo que preencha o campo pai de todos os ns de uma rvore binria.

Exerccios
14. Escreva uma funo que preencha corretamente todos os campos pai de uma rvore binria. 15. A profundidade (= depth) de um n x em uma rvore binria com raiz r a distncia 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 funo que determine a profundidade de um n em relao raiz da rvore. 16. Escreva uma funo que imprima os contedos de uma rvore binria com recuos de margem proporcionais profundidade do n na rvore. Por exemplo, a rvore
/ 333 / 111 \ 444 555 \ 888 \ 999

deve se representada assim:


555 333 111 444 888 -

999 -

onde os caracteres '-' representam NULL.


17- Em que condies uma rvore binria um max-heap? Escreva uma funo que transforme uma rvore binria quase completa em heap.

N seguinte e anterior (sucessor e predecessor)


Digamos que x o endereo de um certo n de uma rvore binria. Nosso problema: calcular o endereo do n seguinte na ordem e-r-d. Para resolver o problema, necessrio que os nos tenham um campo pai. Eis uma funo que resolve o problema. claro que a funo s deve ser chamada com x diferente de NULL. A funo devolve o endereo do n seguinte a xou devolve NULL se x o ltimo n. (Note que a funo no precisa saber onde est a raiz da rvore.)
// Recebe o endereo de um n x. Devolve o endereo // do n seguinte na ordem e-r-d. // A funo supe que x != NULL.

no *seguinte (no *x) { if (x->dir != NULL) { no *y = x->dir; while (y->esq != NULL) y = y->esq; return y; } while (x->pai != NULL && x->pai->dir == x) x = x->pai; return x->pai; } // ** // ** // *

Comentrios: Na linha *, y o endereo do primeiro n, na ordem e-r-d, da subrvore que tem raiz x->dir. As linhas ** fazem com que x suba na rvore enquanto for filho direito de algum. Exerccios
18. Escreva uma funo que receba o endereo de um n x de uma rvore binria e encontre o endereo do n anterior a x na ordem e-r-d. 19. Escreva uma funo que faa varredura e-r-d usando as funes primeiro e seguinte.

También podría gustarte