Conceitos
Árvores como estrutura hierárquica. Definição recursiva, terminologia, propriedades com demonstração e formas de representar uma árvore qualquer.
Até aqui, todas as estruturas foram lineares: em vetores, listas, pilhas e filas, cada elemento tem no máximo um antecessor e um sucessor. Muitos dados, porém, são hierárquicos:
- o sistema de arquivos, com pastas dentro de pastas;
- o organograma de uma empresa;
- uma árvore genealógica;
- a estrutura de uma página HTML, com elementos dentro de elementos;
- uma expressão aritmética, em que cada operador se aplica a subexpressões.
Para esses dados, a estrutura natural é a árvore. E as árvores servem também para algo menos óbvio: na tabela de símbolos, nem a lista nem o vetor ordenado conseguem buscar e inserir rápido. As árvores binárias de busca, que vêm adiante, conseguem. Esta página apresenta o vocabulário e as propriedades básicas que todas elas usam.
Definição
Uma árvore é um conjunto finito de nós tal que:
- se o conjunto é vazio, a árvore é vazia;
- senão, existe um nó especial, a raiz, e os demais nós formam
conjuntos disjuntos , cada um deles também uma árvore, chamada subárvore da raiz.
A raiz é ligada por uma aresta à raiz de cada subárvore não vazia.
A definição é recursiva: uma árvore é feita de árvores menores. Por isso, quase todo raciocínio sobre árvores (as demonstrações desta página) e quase todo algoritmo sobre árvores (as funções das próximas páginas) também é recursivo: resolve-se o problema para as subárvores e combina-se o resultado na raiz.
Terminologia
- Se a raiz de uma árvore tem a raiz de uma subárvore ligada a ela, a primeira é o pai da segunda, e a segunda é filho da primeira.
- Nós com o mesmo pai são irmãos.
- Um nó sem filhos é uma folha (ou nó terminal); um nó com pelo menos um filho é um nó interno.
- O grau de um nó é o número de filhos dele. Folhas têm grau 0.
Um caminho é uma sequência de nós
Se
A profundidade (ou nível) de um nó é o comprimento do caminho da raiz até ele. A raiz está no nível 0.
A altura de um nó é o comprimento do caminho mais longo dele até uma
folha descendente. Toda folha tem altura 0. A altura da árvore é a
altura da raiz; por convenção, a árvore vazia tem altura
A profundidade é medida de cima para baixo, a partir da raiz; a altura, de baixo para cima, a partir das folhas. Na árvore do exemplo, o nó C tem profundidade 1 e altura 2. Para a árvore inteira, as duas medidas coincidem, como mostra a Proposição 3.
Na árvore de exemplo, qual afirmação é falsa?
Propriedades
As demonstrações abaixo usam indução na estrutura da árvore: prova-se a propriedade para a árvore vazia (ou com um nó) e, supondo que ela vale para as subárvores, prova-se que vale para a árvore inteira. Como as subárvores têm menos nós que a árvore, isso é uma indução no número de nós.
Uma árvore com
Demonstraçãotente antes de abrir
Por indução no número de nós. Se
Se
Outra forma de ver o mesmo fato: todo nó, exceto a raiz, tem exatamente um
pai, e cada aresta liga um nó ao seu pai. As arestas correspondem, uma a
uma, aos
Para todo nó
Demonstraçãotente antes de abrir
Por indução no número de nós.
Existência. Se
Unicidade. Se
Por isso, os ancestrais de um nó são bem definidos: são os nós do único caminho da raiz até ele. E a profundidade também é bem definida.
Uma árvore pode ser vista como um grafo: os nós são os vértices, e as ligações entre pai e filho são as arestas, sem direção.
Vista como grafo, uma árvore não vazia é conexa (existe um caminho entre quaisquer dois nós) e acíclica (não tem ciclos).
Demonstraçãotente antes de abrir
Conexa. Sejam
Acíclica. Suponha que haja um ciclo, e seja
Em uma árvore, existe exatamente um caminho entre quaisquer dois nós.
Demonstraçãotente antes de abrir
Pelo Teorema 1, existe pelo menos um. Se existissem dois
caminhos diferentes de
Repare na ordem dos argumentos: ser conexa garante que existe pelo menos um caminho; ser acíclica é o que garante que ele é único. Vale também a recíproca: todo grafo conexo e acíclico, com qualquer vértice escolhido como raiz, é uma árvore no sentido da Definição 1. É assim que árvores são definidas em Teoria dos Grafos.
A altura de uma árvore não vazia é igual à maior profundidade de um nó.
Além disso, para todo nó
Demonstraçãotente antes de abrir
Um caminho que parte da raiz e só desce termina em algum nó
Para a fórmula: um caminho de
A fórmula recursiva da altura é exatamente o que o código das próximas
páginas calcula. Com a convenção de que a árvore vazia tem altura
Representando uma árvore qualquer
O número de filhos de cada nó (o grau) é o que mais distingue os tipos de árvore. Em uma árvore qualquer, ele não tem limite, e isso complica a representação: quantos ponteiros reservar em cada nó? Duas soluções comuns evitam a pergunta.
Vetor de pais
Se os nós são numerados de pai, em que
pai[v] é o número do pai de pai[raiz] = -1. Para a
árvore de exemplo, numerando A, B, C, ... como 0, 1, 2, ...:
| Nó | A | B | C | D | E | F | G | H | I | J | K |
|---|---|---|---|---|---|---|---|---|---|---|---|
v | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
pai[v] | -1 | 0 | 0 | 0 | 1 | 1 | 2 | 3 | 3 | 6 | 6 |
A representação é compacta (um inteiro por nó) e torna fácil subir na
árvore: a profundidade de
int profundidade(const int pai[], int v) {
int p = 0;
while (pai[v] != -1) { /* sobe até a raiz */
v = pai[v];
p++;
}
return p;
}Em compensação, descer é caro: para achar os filhos de um nó, é preciso percorrer o vetor inteiro. O primeiro exercício da página usa essa representação.
Primeiro filho, próximo irmão
A outra solução dá a cada nó exatamente dois ponteiros, qualquer que seja o grau: um para o primeiro filho e outro para o próximo irmão. Os filhos de um nó formam uma lista encadeada, que começa no primeiro filho e segue pelos irmãos.
typedef struct no {
const char *nome;
struct no *filho; /* primeiro filho */
struct no *irmao; /* próximo irmão */
} No;Para percorrer os filhos de r, basta seguir a lista:
for (No *f = r->filho; f != NULL; f = f->irmao). O programa abaixo monta
uma pequena árvore de pastas e arquivos e a imprime em pré-ordem (cada nó
antes dos seus filhos), com o recuo dado pela profundidade. A altura usa a
fórmula da Proposição 3.
Na representação primeiro filho, próximo irmão, cada nó tem exatamente dois ponteiros. Que estrutura ela lembra?
Ver respostaEsconder resposta
Uma árvore binária: cada nó tem dois ponteiros, e o desenho da
figura acima, com os ponteiros filho para baixo e irmao para a
direita, é uma árvore em que cada nó tem no máximo dois "filhos". Toda
árvore pode, então, ser guardada como uma árvore binária. Esse é um dos
motivos pelos quais as árvores binárias, assunto da próxima
página, são tão importantes.
Exercícios
Implemente
Profundidades pelo vetor de pais. Dada uma árvore no formato de vetor de
pais, calcule a profundidade de todos os nós e a altura da árvore em profundidade acima para cada nó funciona, mas
custa
O que a profundidade de
Ver dica 1Esconder dica 1
A profundidade de
Ver dica 2Esconder dica 2
Guarde as profundidades já calculadas no próprio vetor prof (com
Referências
- W. Celes, R. Cerqueira e J. L. Rangel. Introdução a Estruturas de Dados: com técnicas de programação em C. 2ª ed. Grupo GEN, 2016.
- T. H. Cormen, C. E. Leiserson, R. L. Rivest e C. Stein. Algoritmos: teoria e prática. 3ª ed. Elsevier, 2012. Apêndice B.5 (árvores).
- P. Feofiloff. Árvores binárias. Projeto de Algoritmos, IME-USP.
Tabela de símbolos
O TAD que associa valores a chaves únicas. Interface, implementações com lista encadeada e com vetor ordenado (busca binária) e a comparação de custos que motiva as árvores.
Árvores binárias
Árvores em que cada nó tem no máximo dois filhos. Tipos, limites para a altura com demonstração, representação em C, percursos e operações recursivas.