#6 - Árvores e Árvores Binárias

O que são árvores como estrutura de dados hierárquica, o que diferencia uma árvore binária, seus tipos (balanceada, completa, cheia, perfeita) e as três formas de percorrê-la - pré-ordem, em ordem e pós-ordem.

#6 - Árvores e Árvores Binárias

Todas as estruturas que você viu até agora — arrays, pilhas, filas, listas encadeadas — são lineares: cada elemento vem diretamente depois do anterior, numa sequência só. A árvore é diferente: é uma estrutura hierárquica, onde um único elemento pode se ramificar em várias direções ao mesmo tempo — por isso o nome, já que a forma lembra os galhos de uma árvore.

O que é uma árvore

Uma árvore é formada por nós (nodes) conectados por arestas (edges); cada nó guarda um valor e referências pros seus nós filhos (child nodes). O nó do topo se chama raiz (root), e cada nó pode ter zero ou mais filhos.

Árvores aparecem em vários lugares:

Árvores binárias

Uma árvore binária é um tipo de árvore em que cada nó tem, no máximo, dois filhos — um filho esquerdo e um filho direito. Essa restrição de “no máximo dois” traz vantagens:

Implementando uma árvore binária

A implementação lembra bastante a de uma lista encadeada — só que, em vez de um único proximo, cada nó tem uma referência pro filho esquerdo e outra pro filho direito.

class NoArvore:
    def __init__(self, dado):
        self.dado = dado
        self.esquerda = None
        self.direita = None

raiz = NoArvore('R')
noA = NoArvore('A')
noB = NoArvore('B')
noC = NoArvore('C')
noD = NoArvore('D')
noE = NoArvore('E')
noF = NoArvore('F')
noG = NoArvore('G')

raiz.esquerda = noA
raiz.direita = noB

noA.esquerda = noC
noA.direita = noD

noB.esquerda = noE
noB.direita = noF

noF.esquerda = noG

# Teste
print("raiz.direita.esquerda.dado:", raiz.direita.esquerda.dado)

A saída é:

raiz.direita.esquerda.dado: E

Tipos de árvore binária

Percorrendo uma árvore binária

Percorrer (traversal) uma árvore significa visitar cada um dos seus nós, um de cada vez. Em arrays e listas encadeadas isso é óbvio — só existe um caminho, do primeiro elemento ao último. Numa árvore, que se ramifica em direções diferentes, existem várias formas de fazer esse percurso, divididas em duas categorias:

Pré-ordem (pre-order)

Visita a raiz primeiro, depois percorre recursivamente a subárvore esquerda, e por fim a direita. É usado, por exemplo, pra criar uma cópia da árvore.

def percorrer_pre_ordem(no):
    if no is None:
        return
    print(no.dado, end=", ")
    percorrer_pre_ordem(no.esquerda)
    percorrer_pre_ordem(no.direita)

percorrer_pre_ordem(raiz)

A saída é:

R, A, C, D, B, E, F, G,

Em ordem (in-order)

Percorre recursivamente a subárvore esquerda, visita a raiz, e por fim percorre a subárvore direita. É a mais usada em árvores de busca binária, porque retorna os valores em ordem crescente — você vai ver isso no próximo post.

def percorrer_em_ordem(no):
    if no is None:
        return
    percorrer_em_ordem(no.esquerda)
    print(no.dado, end=", ")
    percorrer_em_ordem(no.direita)

percorrer_em_ordem(raiz)

A saída é:

C, A, D, R, E, B, G, F,

Pós-ordem (post-order)

Percorre recursivamente as subárvores esquerda e direita, e só então visita a raiz. É usado, por exemplo, pra apagar uma árvore inteira nó por nó, sem perder a referência dos filhos antes de apagar o pai.

def percorrer_pos_ordem(no):
    if no is None:
        return
    percorrer_pos_ordem(no.esquerda)
    percorrer_pos_ordem(no.direita)
    print(no.dado, end=", ")

percorrer_pos_ordem(raiz)

A saída é:

C, D, A, E, G, F, B, R,

Repare no padrão: pré-ordem imprime a raiz primeiro; em ordem imprime a raiz no meio de cada subárvore; pós-ordem imprime a raiz por último — os nomes “pré”, “em” e “pós” se referem exatamente a quando a raiz é visitada em relação às suas subárvores.

Árvores vs. arrays e listas encadeadas

Isso parece bom demais pra ser verdade — e de fato tem uma pegadinha: essas vantagens só valem se a árvore estiver balanceada. No próximo post você vai ver exatamente o que acontece quando ela não está, e como uma árvore de busca binária organiza os valores pra tornar a busca rápida de verdade.

Fonte adaptada: Trees with Python, Binary Trees with Python