Escolar Documentos
Profissional Documentos
Cultura Documentos
Cap13NaryTree PDF
Cap13NaryTree PDF
rvores
W. Celes e J. L. Rangel
Nos captulos anteriores examinamos as estruturas de dados que podem ser chamadas
de unidimensionais ou lineares, como vetores e listas. A importncia dessas estruturas
inegvel, mas elas no so adequadas para representarmos dados que devem ser
dispostos de maneira hierrquica. Por exemplo, os arquivos (documentos) que criamos
num computador so armazenados dentro de uma estrutura hierrquica de diretrios
(pastas). Existe um diretrio base dentro do qual podemos armazenar diversos sub-
diretrios e arquivos. Por sua vez, dentro dos sub-diretrios, podemos armazenar outros
sub-diretrios e arquivos, e assim por diante, recursivamente. A Figura 13.1 mostra uma
imagem de uma rvore de diretrio no Windows 2000.
n raiz
... sub-rvores
* 5
3 6 4 1
Numa rvore binria, cada n tem zero, um ou dois filhos. De maneira recursiva,
podemos definir uma rvore binria como sendo:
uma rvore vazia; ou
um n raiz tendo duas sub-rvores, identificadas como a sub-rvore da
direita (sad) e a sub-rvore da esquerda (sae).
A Figura 13.4 ilustra a definio de rvore binria. Essa definio recursiva ser usada
na construo de algoritmos, e na verificao (informal) da correo e do desempenho
dos mesmos.
vazia
sae sad
b c
d e f
Figura 13.5: Exemplo de rvore binria
Para descrever rvores binrias, podemos usar a seguinte notao textual: a rvore vazia
representada por <>, e rvores no vazias por <raiz sae sad>. Com essa notao,
a rvore da Figura 13.5 representada por:
<a<b<><d<><>>><c<e<><>><f<><>>>>.
Pela definio, uma sub-rvore de uma rvore binria sempre especificada como
sendo a sae ou a sad de uma rvore maior, e qualquer das duas sub-rvores pode ser
vazia. Assim, as duas rvores da Figura 13.6 so distintas.
a a
b b
Isto tambm pode ser visto pelas representaes textuais das duas rvores, que so,
respectivamente: <a <b<><>> <> > e <a <> <b<><>> >.
Exerccio: Mostrar que uma rvore binria de altura h tem, no mnimo, h+1 ns, e, no
mximo, 2h+1 1.
Representao em C
Anlogo ao que fizemos para as demais estruturas de dados, podemos definir um tipo
para representar uma rvore binria. Para simplificar a discusso, vamos considerar que
a informao que queremos armazenar nos ns da rvore so valores de caracteres
simples. Vamos inicialmente discutir como podemos representar uma estrutura de
rvore binria em C. Que estrutura podemos usar para representar um n da rvore?
Cada n deve armazenar trs informaes: a informao propriamente dita, no caso um
caractere, e dois ponteiros para as sub-rvores, esquerda e direita. Ento a estrutura
de C para representar o n da rvore pode ser dada por:
struct arv {
char info;
struct arv* esq;
struct arv* dir;
};
Da mesma forma que uma lista encadeada representada por um ponteiro para o
primeiro n, a estrutura da rvore como um todo representada por um ponteiro para o
n raiz.
Como acontece com qualquer TAD (tipo abstrato de dados), as operaes que fazem
sentido para uma rvore binria dependem essencialmente da forma de utilizao que se
pretende fazer da rvore. Nesta seo, em vez de discutirmos a interface do tipo abstrato
para depois mostrarmos sua implementao, vamos optar por discutir algumas
operaes mostrando simultaneamente suas implementaes. Ao final da seo
apresentaremos um arquivo que pode representar a interface do tipo. Nas funes que se
seguem, consideraremos que existe o tipo Arv definido por:
typedef struct arv Arv;
Vamos procurar identificar e descrever apenas operaes cuja utilidade seja a mais geral
possvel. Uma operao que provavelmente dever ser includa em todos os casos a
inicializao de uma rvore vazia. Como uma rvore representada pelo endereo do
n raiz, uma rvore vazia tem que ser representada pelo valor NULL. Assim, a funo
que inicializa uma rvore vazia pode ser simplesmente:
Para criar rvores no vazias, podemos ter uma operao que cria um n raiz dadas a
informao e suas duas sub-rvores, esquerda e direita. Essa funo tem como valor
de retorno o endereo do n raiz criado e pode ser dada por:
Arv* cria(char c, Arv* sae, Arv* sad){
Arv* p=(Arv*)malloc(sizeof(Arv));
p->info = c;
p->esq = sae;
p->dir = sad;
return p;
}
Exemplo: Usando as operaes inicializa e cria, crie uma estrutura que represente
a rvore da Figura 13.5.
Alternativamente, a rvore poderia ser criada com uma nica atribuio, seguindo a sua
estrutura, recursivamente:
Arv* a = cria('a',
cria('b',
inicializa(),
cria('d', inicializa(), inicializa())
),
cria('c',
cria('e', inicializa(), inicializa()),
cria('f', inicializa(), inicializa())
)
);
Para tratar a rvore vazia de forma diferente das outras, importante ter uma operao
que diz se uma rvore ou no vazia. Podemos ter:
Uma outra funo muito til consiste em exibir o contedo da rvore. Essa funo deve
percorrer recursivamente a rvore, visitando todos os ns e imprimindo sua informao.
A implementao dessa funo usa a definio recursiva da rvore. Vimos que uma
rvore binria ou vazia ou composta pela raiz e por duas sub-rvores. Portanto, para
imprimir a informao de todos os ns da rvore, devemos primeiro testar se a rvore
vazia. Se no for, imprimimos a informao associada a raiz e chamamos
(recursivamente) a funo para imprimir os ns das sub-rvores.
Exerccio: (a) simule a chamada da funo imprime aplicada arvore ilustrada pela
Figura 13.5 para verificar que o resultado da chamada a impresso de a b d c e f.
(b) Repita a experincia executando um programa que crie e mostre a rvore, usando o
seu compilador de C favorito.
Uma outra operao que pode ser acrescentada a operao para liberar a memria
alocada pela estrutura da rvore. Novamente, usaremos uma implementao recursiva.
Um cuidado essencial a ser tomado que as sub-rvores devem ser liberadas antes de se
liberar o n raiz, para que o acesso s sub-rvores no seja perdido antes de sua
remoo. Neste caso, vamos optar por fazer com que a funo tenha como valor de
retorno a rvore atualizada, isto , uma rvore vazia, representada por NULL.
Arv* libera (Arv* a){
if (!vazia(a)){
libera(a->esq); /* libera sae */
libera(a->dir); /* libera sad */
free(a); /* libera raiz */
}
return NULL;
}
Devemos notar que a definio de rvore, por ser recursiva, no faz distino entre
rvores e sub-rvores. Assim, cria pode ser usada para acrescentar (enxertar) uma
sub-rvore em um ramo de uma rvore, e libera pode ser usada para remover
(podar) uma sub-rvore qualquer de uma rvore dada.
importante observar que, anlogo ao que fizemos para a lista, o cdigo cliente que
chama a funo libera responsvel por atribuir o valor atualizado retornado pela
funo, no caso uma rvore vazia. No exemplo acima, se no tivssemos feito a
atribuio, o endereo armazenado em r->dir->esq seria o de uma rea de memria
no mais em uso.
Exerccio: Escreva uma funo que percorre uma rvore binria para determinar sua
altura. O prottipo da funo pode ser dado por:
int altura(Arv* a);
Uma outra funo que podemos considerar percorre a rvore buscando a ocorrncia de
um determinado caractere c em um de seus ns. Essa funo tem como retorno um
valor booleano (um ou zero) indicando a ocorrncia ou no do caractere na rvore.
int busca (Arv* a, char c){
if (vazia(a))
return 0; /* rvore vazia: no encontrou */
else
return a->info==c || busca(a->esq,c) || busca(a->dir,c);
}
Note que esta forma de programar busca, em C, usando o operador lgico || (ou)
faz com que a busca seja interrompida assim que o elemento encontrado. Isto acontece
porque se c==a->info for verdadeiro, as duas outras expresses no chegam a ser
avaliadas. Analogamente, se o caractere for encontrado na sub-rvore da esquerda, a
busca no prossegue na sub-rvore da direita.
equivalente a:
Para funo para liberar a rvore, por exemplo, tivemos que adotar a ps-ordem:
libera(a->esq); /* libera sae */
libera(a->dir); /* libera sad */
free(a); /* libera raiz */
Como veremos, as funes para manipularem uma rvore genrica tambm sero
implementadas de forma recursiva, e sero baseadas na seguinte definio: uma rvore
genrica composta por:
um n raiz; e
zero ou mais sub-rvores.
Estritamente, segundo essa definio, uma rvore no pode ser vazia, e a rvore vazia
no sequer mencionada na definio. Assim, uma folha de uma rvore no um n
com sub-rvores vazias, como no caso da rvore binria, mas um n com zero sub-
rvores. Em qualquer definio recursiva deve haver uma condio de contorno, que
permita a definio de estruturas finitas, e, no nosso caso, a definio de uma rvore se
encerra nas folhas, que so identificadas como sendo ns com zero sub-rvores.
Como as funes que implementaremos nesta seo sero baseadas nessa definio, no
ser considerado o caso de rvores vazias. Esta pequena restrio simplifica as
implementaes recursivas e, em geral, no limita a utilizao da estrutura em
aplicaes reais. Uma rvore de diretrio, por exemplo, nunca vazia, pois sempre
existe o diretrio base o diretrio raiz.
Representao em C
Dependendo da aplicao, podemos usar vrias estruturas para representar rvores,
levando em considerao o nmero de filhos que cada n pode apresentar. Se
soubermos, por exemplo, que numa aplicao o nmero mximo de filhos que um n
pode apresentar 3, podemos montar uma estrutura com 3 campos para apontadores
para os ns filhos, digamos, f1 , f2 e f3 . Os campos no utilizados podem ser
preenchidos com o valor nulo NULL, sendo sempre utilizados os campos em ordem.
Assim, se o n n tem 2 filhos, os campos f1 e f2 seriam utilizados, nessa ordem, para
apontar para eles, ficando f3 vazio. Prevendo um nmero mximo de filhos igual a 3, e
considerando a implementao de rvores para armazenar valores de caracteres simples,
a declarao do tipo que representa o n da rvore poderia ser:
struct arv3 {
char val;
struct no *f1, *f2, *f3;
};
A Figura 13.8 indica a representao da rvore da Figura 13.7 com esta organizao.
Como se pode ver no exemplo, em cada um dos ns que tem menos de trs filhos, o
espao correspondente aos filhos inexistentes desperdiado. Alm disso, se no existe
um limite superior no nmero de filhos, esta tcnica pode no ser aplicvel. O mesmo
acontece se existe um limite no nmero de ns, mas esse limite ser raramente
alcanado, pois estaramos tendo um grande desperdcio de espao de memria com os
campos no utilizados.
b f g
c e h i
d j
Uma soluo que leva a um aproveitamento melhor do espao utiliza uma lista de
filhos: um n aponta apenas para seu primeiro (prim) filho, e cada um de seus filhos,
exceto o ltimo, aponta para o prximo (prox) irmo. A declarao de um n pode ser:
struct arvgen {
char info;
struct arvgen *prim;
struct arvgen *prox;
};
A Figura 13.9 mostra o mesmo exemplo representado de acordo com esta estrutura.
Uma das vantagens dessa representao que podemos percorrer os filhos de um n de
forma sistemtica, de maneira anloga ao que fizemos para percorrer os ns de uma lista
simples.
b f g
c e h i
d j
A funo que insere uma nova sub-rvore como filha de um dado n muito simples.
Como no vamos atribuir nenhum significado especial para a posio de um n filho, a
operao de insero pode inserir a sub-rvore em qualquer posio. Neste caso, vamos
optar por inserir sempre no incio da lista que, como j vimos, a maneira mais simples
de inserir um novo elemento numa lista encadeada.
void insere (ArvGen* a, ArvGen* sa)
{
sa->prox = a->prim;
a->prim = sa;
}
Com essas duas funes, podemos construir a rvore do exemplo da Figura 13.7 com o
seguinte fragmento de cdigo:
/* cria ns como folhas */
ArvGen* a = cria('a');
ArvGen* b = cria('b');
ArvGen* c = cria('c');
ArvGen* d = cria('d');
ArvGen* e = cria('e');
ArvGen* f = cria('f');
ArvGen* g = cria('g');
ArvGen* h = cria('h');
ArvGen* i = cria('i');
ArvGen* j = cria('j');
/* monta a hierarquia */
insere(c,d);
insere(b,e);
insere(b,c);
insere(i,j);
insere(g,i);
insere(g,h);
insere(a,g);
insere(a,f);
insere(a,b);
Para imprimir as informaes associadas aos ns da rvore, temos duas opes para
percorrer a rvore: pr-ordem, primeiro a raiz e depois as sub-rvores, ou ps-ordem,
primeiro as sub-rvores e depois a raiz. Note que neste caso no faz sentido a ordem
simtrica, uma vez que o nmero de sub-rvores varivel. Para essa funo, vamos
optar por imprimir o contedo dos ns em pr-ordem:
void imprime (ArvGen* a)
{
ArvGen* p;
printf("%c\n",a->info);
for (p=a->prim; p!=NULL; p=p->prox)
imprime(p);
}
A ltima operao apresentada a que libera a memria alocada pela rvore. O nico
cuidado que precisamos tomar na programao dessa funo a de liberar as sub-
rvores antes de liberar o espao associado a um n (isto , usar ps-ordem).
void libera (ArvGen* a)
{
ArvGen* p = a->prim;
while (p!=NULL) {
ArvGen* t = p->prox;
libera(p);
p = t;
}
free(a);
}