Você está na página 1de 7

Estruturas de Dados

Árvores

Definição e terminologia básicas


Árvores (em inglês: trees) são estruturas de dados fundamentais na Ciência da Computação.
Permitem representar uma grande variedade de situações e viabilizam a construção de algoritmos
eficazes para a resolução de uma série de importantes problemas computacionais. A figura 1
apresenta uma árvore simples que será utilizada para ilustrar os principais conceitos e a
terminologia básica envolvidos na criação e manipulação dessas estruturas de dados.
8

5 16

2 6 12 20

9 15 18 22

12 14
Fig. 1: Uma árvore binária ordenada.

• Cada elemento de uma árvore é chamado de nó (em inglês: node). A árvore da figura 1
possui um total de 13 nós.
• A ligação entre dois nós da árvore é chamada de ramo (em inglês: branch). Como toda
árvore é um grafo conexo sem ciclos, sabemos de antemão que a quantidade de ramos de
uma árvore é igual à sua quantidade de nós menos 1. Verifique que a árvore da figura 1
possui exatamente 12 ramos.
• Há uma hierarquia entre os elementos da árvore, de forma que para atingir um
determinado nó, é necessário passar antes por uma quantidade ≥ 0 de outros nós. Os nós
que precisamos visitar para chegar a um nó x são denominados ancestrais (em inglês:
ancestors) de x. Em contrapartida, dizemos que o nó x é um descendente (em inglês:
descendant) de cada um desses ancestrais. Dentre os ancestrais de um nó x dado, aquele
nó y que é mais próximo de x é chamado de pai de x. Na árvore da figura 1 o nó contendo
um 16 é o pai dos nós contendo os números 12 e 20. Os descendentes diretos de um nó
são seus filhos. Na árvore da figura 1 o nó contendo um 16 tem como filhos os nós cujos
valores são 12 e 20. Frequentemente chamamos os nós que possuem o mesmo pai de
irmãos.
• O grau de uma árvore é definido pela quantidade máxima de descendentes diretos que
um nó pode ter. Na árvore da figura 1 um nó pode ter no máximo 2 descendentes diretos,
então dizemos que ela possui grau 2 ou, então, que é uma árvore binária (em inglês:
binary tree).
• Dizemos que o nó que possui grau 0, ou seja, que não possui descendentes, é uma folha
(em inglês: leaf) da árvore. Na árvore do exemplo temos 6 folhas (os nós com os valores
2, 6, 12, 14, 18 e 22). Um nó que possui pelo menos um descendente costuma ser
chamado de nó interno (em inglês: internal node) da árvore, e a árvore da figura 1 possui
7 nós internos (os nós com os valores 8, 5, 16, 12, 20, 9 e 15).

Prof. Antonio Cesar de Barros Munari 1


Estruturas de Dados
Árvores
• O conjunto de descendentes de um nó, juntamente com suas ligações ou ramos, é uma
subárvore (em inglês: subtree). Uma árvore binária possui duas subárvores possíveis
para cada nó: a subárvore da esquerda e a subárvore da direita. A título de exemplo, na
árvore da figura 1, em relação ao nó com o valor 8, temos em sua subárvore da esquerda
os nós com os valores 5, 2 e 6, e na sua subárvore da direita, os nós com os valores 16,
12, 20, 9, 15, 18, 22, 12 e 14.
• Um nó que não possui ancestrais é a raiz (em inglês: root) da árvore ou subárvore. Na
árvore do exemplo, a raiz da árvore é o nó com o valor 8. O nó com o valor 5 é a raiz da
subárvore composta pelos nós contendo os valores 5, 2 e 6.
• A quantidade de ramos entre a raiz da árvore e a folha mais distante é a altura (em
inglês: height) da árvore. A árvore da figura 1 possui altura = 4. O nível (em inglês: level)
de um nó é a quantidade de ramos que existem entre ele e a raiz. Assim, a raiz possui
nível 0, seus filhos possuem nível 1, e assim sucessivamente. A definição utilizada aqui
para o conceito de nível é a mais comum, mas existem variações importantes na definição
desse conceito.
• Quando uma árvore possui suas subárvores com aproximadamente a mesma altura,
dizemos que ela é balanceada. Uma árvore binária balanceada com n nós possui uma
altura aproximada de log2n. Um critério muito comum utilizado para determinar se o
balanceamento ocorre ou não é o critério AVL (sigla devida às iniciais de seus criadores,
chamados Adelson, Velsky e Landis) onde a árvore é considerada balanceada se para um
nó x qualquer a altura de suas subárvores difere em no máximo 1.
Finalmente, com base nos conceitos e termos apresentados, podemos definir árvores de uma
maneira mais formal: uma árvore é um conjunto finito de n elementos denominados nós de
forma que:
• existe um elemento que é a raiz da árvore;
• os n - 1 elementos restantes formam, por sua vez, n – 1 subárvores, das quais cada um
deles é a raiz.
A definição de uma árvore é, então, recursiva, e é comum utilizarmos algoritmos recursivos para
processar tais estruturas, pois uma lógica recursiva sobre uma estrutura de dados também
recursiva produz soluções mais elegantes e fáceis de manter.

Aplicações
Árvores são adequadas para representar hierarquias, como por exemplo, a ordem de execução
das operações requeridas por uma expressão aritmética, ou a cadeia de subordinação de
conceitos (um organograma ou auto-relacionamento), ou ainda para representar eficientemente
valores que precisam ser pesquisados com freqüência. A figura 2 mostra dois exemplos clássicos
do uso de árvores: a representação de expressões aritméticas e uma hierarquia. Para ambos é
mostrado o conceito original e a árvore correspondente.

Prof. Antonio Cesar de Barros Munari 2


Estruturas de Dados
Árvores

Expressão aritmética: Hierarquia:


(a + b) / c
X
/
A F
Y
+ c B

P
a b
E S
C

A F

B C Y P

E S

Fig. 2: Uso de árvores para modelar situações concretas.

Representações de árvores por encadeamento


A implementação de árvores por encadeamento é muito flexível e simples. Definimos um tipo de
dado para o nó contendo a informação útil a ser armazenada e membros para os ponteiros para os
seus descendentes diretos. Se a árvore for de grau 2, serão dois ponteiros, se for de grau 3, 3
ponteiros, de grau n, n ponteiros. Vamos construir uma árvore binária ordenada em que cada nó
conterá um valor inteiro positivo e a estrutura de dados do nó poderia ser conforme mostrado na
figura 3. Nosso programa não se preocupará em manter a árvore balanceada, limitando-se a
acrescentar os novos elementos nas extremidades da árvore, como folhas, seguindo o critério de
alocação em que na subárvore da esquerda de um nó estarão os elementos cujo valor é menor ou
igual ao seu e na subárvore da direita estarão alocados os elementos cujo valor é maior que o
valor contido nesse nó raiz.
struct regNo
{ struct regNo *esq;
int valor;
struct regNo *dir;
};
typedef struct regNo TNo;

Fig. 3: Definição do tipo de dados dos elementos da lista.


O programa precisará manter uma variável descritora para indicar o elemento raiz da árvore, que
inicialmente terá um endereço vazio (NULL em C) e que, após a criação do primeiro elemento,
passará a apontar para ele. A figura 4 ilustra a situação da árvore quando tiverem sido
informados pelo usuário os valores 8, 6, 16 e 2 e uma visão geral do código fonte é apresentada
na listagem a seguir.

Prof. Antonio Cesar de Barros Munari 3


Estruturas de Dados
Árvores
raiz
• esq valor dir
• 8 •

esq valor dir esq valor dir


• 5 • / 16 /

esq valor dir esq valor dir


/ 2 / / 6 /

Fig. 4: Situação parcial da construção da árvore.

#include <stdio.h>

struct regNo
{ struct regNo *esq;
int valor;
struct regNo *dir;
};
typedef struct regNo TNo;

TNo *AchaPai( TNo *r, int n );


void ImprimeArvore(TNo *r, int n);
int ContaNos(TNo *r);
int SomaNos(TNo *r);
int ContaPares(TNo *r);

int main()
{ TNo *raiz = NULL, *aux, *pai;
int numero;

while(1)
{ printf("\nInforme o valor:\n"); scanf("%d", &numero);

if( numero < 0 ) break;

aux = (TNo *) malloc( sizeof(TNo) );

aux->valor = numero;
aux->dir = NULL;
aux->esq = NULL;

/* Fazendo o encadeamento do novo noh */


pai = AchaPai( raiz, numero );
if( pai == NULL )
raiz = aux;
else
if( numero <= pai->valor )
pai->esq = aux;
else
pai->dir = aux;
}

printf("\n\nA arvore possui %d elementos:\n", ContaNos(raiz));


ImprimeArvore(raiz, 0);

Prof. Antonio Cesar de Barros Munari 4


Estruturas de Dados
Árvores
return 0;
}
Para determinar o ancestral do novo nó o programa utiliza a rotina AchaPai, que recebe como
parâmetros o endereço de um nó da árvore e o valor contido no novo nó e retorna o endereço do
ancestral. Caso o endereço correspondente ao primeiro parâmetro seja nulo, a rotina retorna nulo.
A implementação recursiva da rotina AchaPai é mostrada na figura 5, e uma versão não
recursiva equivalente é apresentada na figura 6.
TNo *AchaPai( TNo *r, int n )
{ if( r == NULL )
return NULL;
else
if( n <= r->valor )
/* n é descendente do lado esquerdo de r */
if( r->esq == NULL )
return r;
else
return AchaPai( r->esq, n );
else
/* n é descendente do lado direito de r */
if( r->dir == NULL )
return r;
else
return AchaPai( r->dir, n );
}

Fig. 5: Implementação recursiva da rotina AchaPai.

TNo *AchaPai( TNo *r, int n )


{ TNo *c;

if( r == NULL )
return NULL;
else
{ c = r;
while(1)
{ if( n <= c->valor )
/* n é descendente do lado esquerdo de c */
if( c->esq == NULL )
return c;
else
c = c->esq;
else
/* n é descendente do lado direito de c */
if( c->dir == NULL )
return c;
else
c = c->dir;
}
}
}
Fig. 6: Implementação não-recursiva da rotina AchaPai.
Após a inclusão dos elementos na árvore, o programa imprime quantos nós existem na árvore e,
em seguida, os valores nela contidos. Para determinar quantos elementos existem na árvore é
utilizada a função ContaNos, que recebe como parâmetro o endereço de um nó da árvore e
retorna um inteiro com o valor da contagem, como mostra o código apresentado na figura 7. A
abordagem adotada nessa rotina é, recursivamente, somar 1 referente ao nó raiz da árvore mais a

Prof. Antonio Cesar de Barros Munari 5


Estruturas de Dados
Árvores
quantidade de nós para a subárvore da sua esquerda mais a quantidade de nós para a subárvore
da sua direita.
int ContaNos(TNo *r)
{ if(r == NULL)
return 0;
else
return 1 + ContaNos(r->esq) + ContaNos(r->dir);
}

Fig. 7: Implementação recursiva da rotina ContaNos.


Para imprimir o conteúdo da árvore o programa é empregada a rotina ImprimeArvore, que
recebe como parâmetros o endereço de um nó da árvore e um inteiro correspondente ao seu nível
e faz a impressão. A rotina alinha a raiz geral da árvore à esquerda da tela e, à medida que o
nível dos nós vai aumentando, os seus valores vão sendo exibidos progressivamente mais à
direita na tela, como ilustrado na figura 8. A figura 9 mostra uma implementação recursiva dessa
rotina e na figura 10 é apresentada uma versão não-recursiva, a título de exemplo. Nessa versão
não-recursiva é necessária a utilização de uma pilha, para manter as referências ao ancestral de
um nó. As duas rotinas de impressão primeiro processam o elemento da raiz e, em seguida,
tratam as suas subárvores, primeiro a esquerda, depois a direita. Essa abordagem raiz-subárvore-
subárvore é denominada Pré-Ordem (em inglês: pre-order). Caso primeiro fizéssemos a
impressão de uma subárvore, depois da raiz e então da subárvore restante, teríamos a abordagem
Em-Ordem (em inglês: in-order). A terceira possibilidade, que processa os dados na sequência
subárvore-subárvore-raiz, é chamada de Pós-Ordem (em inglês: pos-order). Para rotinas como a
de impressão, adotar um desses critérios tem grande influência no resultado da rotina, mas em
outras, como a de contagem dos nós, o critério adotado é irrelevante.
8
5
2
6
16
12
9
12
15
14
20
18
22
Fig. 8: Resultado impresso pela rotina ImprimeArvore para a árvore da figura 1.

void ImprimeArvore(TNo *r, int n)


{ int c;

if( r != NULL )
{ for( c=0; c<n; c++) printf("\t");
printf("%d\n", r->valor);

ImprimeArvore(r->esq, n+1);
ImprimeArvore(r->dir, n+1);
}
}
Fig. 9: Implementação recursiva da rotina ImprimeArvore.

Prof. Antonio Cesar de Barros Munari 6


Estruturas de Dados
Árvores

void ImprimeArvore(TNo*r, int n)


{ typedef struct{ TNo *ender; int nivel;} TPilha;

TPilha pilha[1000];
TNo *noh;
int d, topo = 0;

noh = r;
while(noh != NULL || topo > 0)
{ if(noh != NULL)
{ for( d=0; d<n; d++) printf(" ");
printf("%d\n", noh->valor);

pilha[topo].ender = noh;
pilha[topo].nivel = n;
topo++;
noh = noh->esq;
n++;
}
else
{ topo--;
noh = pilha[topo].ender;
n = pilha[topo].nivel;
noh = noh->dir;
n++;
}
}
}

Fig. 10: Implementação não-recursiva da rotina ImprimeArvore.

Prof. Antonio Cesar de Barros Munari 7

Você também pode gostar