Python

Recursão e Estruturas de Dados Avançadas: pilhas, filas e árvores Já leu

8 min de leitura

Recursão e Estruturas de Dados Avançadas: pilhas, filas e árvores
Recursão e estruturas de dados avançadas são o ponto em que a programação começa a se parecer com engenharia. Pilhas,

Recursão e estruturas de dados avançadas são o ponto em que a programação começa a se parecer com engenharia. Pilhas, filas e árvores não são abstrações acadêmicas — estão presentes no histórico de navegação do seu browser, na fila de impressão do seu sistema operacional e na estrutura de pastas do seu computador. Entendê-las torna você capaz de resolver problemas que listas e dicionários simples não conseguem modelar bem.


Recursão

Uma função recursiva é aquela que chama a si mesma. Todo problema recursivo tem dois componentes obrigatórios:

  • Caso base — a condição que encerra as chamadas
  • Caso recursivo — a chamada que reduz o problema

Sem caso base, a recursão nunca termina e o Python lança RecursionError.

Exemplo clássico: fatorial

def fatorial(n):
    """
    fatorial(5) = 5 * 4 * 3 * 2 * 1 = 120
    Caso base: fatorial(0) = 1
    """
    if n == 0:          # caso base
        return 1
    return n * fatorial(n - 1)   # caso recursivo


print(fatorial(5))   # 120
print(fatorial(10))  # 3628800

Visualizando as chamadas:

fatorial(5)
  └─ 5 * fatorial(4)
           └─ 4 * fatorial(3)
                    └─ 3 * fatorial(2)
                             └─ 2 * fatorial(1)
                                      └─ 1 * fatorial(0)
                                                └─ 1  ← caso base

Sequência de Fibonacci

def fibonacci(n):
    """Retorna o n-ésimo número de Fibonacci."""
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)


for i in range(10):
    print(fibonacci(i), end=" ")
# 0 1 1 2 3 5 8 13 21 34

Esta implementação é correta, mas ineficiente — O(2ⁿ). O mesmo valor é calculado várias vezes. A solução é o memoization:

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)


print(fibonacci(50))   # 12586269025 — instantâneo
print(fibonacci(100))  # 354224848179261915075

O decorator @lru_cache armazena resultados já calculados, reduzindo a complexidade para O(n).

Limite de recursão

O Python limita a profundidade de recursão para evitar estouro de pilha:

import sys
print(sys.getrecursionlimit())  # 1000 por padrão

sys.setrecursionlimit(5000)     # aumentar se necessário, com cautela

Para problemas com profundidade muito grande, prefira a versão iterativa.


Pilhas (Stacks)

Uma pilha segue o princípio LIFO — Last In, First Out. O último elemento inserido é o primeiro a sair. Pense em uma pilha de pratos.

Inserir (push): [ ] → [A] → [A,B] → [A,B,C]
Remover (pop):  [A,B,C] → [A,B] → [A] → [ ]

Em Python, listas já implementam pilhas eficientemente:

pilha = []

# push — adiciona no topo
pilha.append("página inicial")
pilha.append("produtos")
pilha.append("detalhes do produto")

print(pilha)
# ['página inicial', 'produtos', 'detalhes do produto']

# pop — remove do topo
pagina_atual = pilha.pop()
print(pagina_atual)  # detalhes do produto
print(pilha)         # ['página inicial', 'produtos']

Implementando uma classe Pilha

class Pilha:
    def __init__(self):
        self._dados = []

    def push(self, item):
        self._dados.append(item)

    def pop(self):
        if self.vazia():
            raise IndexError("Pop em pilha vazia.")
        return self._dados.pop()

    def topo(self):
        if self.vazia():
            raise IndexError("Pilha vazia.")
        return self._dados[-1]

    def vazia(self):
        return len(self._dados) == 0

    def tamanho(self):
        return len(self._dados)

    def __repr__(self):
        return f"Pilha({self._dados})"


# Uso: verificador de parênteses balanceados
def parenteses_balanceados(expressao):
    """Verifica se os parênteses, colchetes e chaves estão balanceados."""
    pilha = Pilha()
    abre = "({["
    fecha = ")}]"
    pares = {")": "(", "}": "{", "]": "["}

    for char in expressao:
        if char in abre:
            pilha.push(char)
        elif char in fecha:
            if pilha.vazia() or pilha.pop() != pares[char]:
                return False

    return pilha.vazia()


print(parenteses_balanceados("(a + b) * [c - d]"))   # True
print(parenteses_balanceados("(a + b] * [c - d)"))   # False
print(parenteses_balanceados("{[()]}"))               # True
print(parenteses_balanceados("{[(])}"))               # False

Filas (Queues)

Uma fila segue o princípio FIFO — First In, First Out. O primeiro a entrar é o primeiro a sair. Pense em uma fila de banco.

Listas não são ideais para filas — remover do início (pop(0)) é O(n). Use collections.deque:

from collections import deque

fila = deque()

# enqueue — adiciona no final
fila.append("Cliente A")
fila.append("Cliente B")
fila.append("Cliente C")

print(fila)  # deque(['Cliente A', 'Cliente B', 'Cliente C'])

# dequeue — remove do início — O(1)
proximo = fila.popleft()
print(proximo)  # Cliente A
print(fila)     # deque(['Cliente B', 'Cliente C'])

Fila de prioridade com heapq

Quando a ordem de saída depende de uma prioridade e não da chegada:

import heapq

fila_prioridade = []

# (prioridade, tarefa) — menor número = maior prioridade
heapq.heappush(fila_prioridade, (3, "Enviar relatório"))
heapq.heappush(fila_prioridade, (1, "Corrigir bug crítico"))
heapq.heappush(fila_prioridade, (2, "Revisar pull request"))
heapq.heappush(fila_prioridade, (1, "Atender chamado urgente"))

while fila_prioridade:
    prioridade, tarefa = heapq.heappop(fila_prioridade)
    print(f"[P{prioridade}] {tarefa}")

# [P1] Atender chamado urgente
# [P1] Corrigir bug crítico
# [P2] Revisar pull request
# [P3] Enviar relatório

Árvores

Uma árvore é uma estrutura hierárquica composta por nós. Cada nó tem um valor e pode ter nós filhos. O nó sem pai é chamado de raiz.

        10          ← raiz
       /  \
      5    15       ← filhos da raiz
     / \     \
    3   7     20    ← folhas

Árvore Binária de Busca (BST)

Na BST, para cada nó: todos os valores à esquerda são menores, todos à direita são maiores.

class No:
    def __init__(self, valor):
        self.valor = valor
        self.esquerda = None
        self.direita = None


class ArvoreBinariaBusca:
    def __init__(self):
        self.raiz = None

    def inserir(self, valor):
        self.raiz = self._inserir(self.raiz, valor)

    def _inserir(self, no, valor):
        if no is None:
            return No(valor)
        if valor < no.valor:
            no.esquerda = self._inserir(no.esquerda, valor)
        elif valor > no.valor:
            no.direita = self._inserir(no.direita, valor)
        return no

    def buscar(self, valor):
        return self._buscar(self.raiz, valor)

    def _buscar(self, no, valor):
        if no is None:
            return False
        if valor == no.valor:
            return True
        if valor < no.valor:
            return self._buscar(no.esquerda, valor)
        return self._buscar(no.direita, valor)

    def em_ordem(self):
        """Percurso em ordem — retorna valores em ordem crescente."""
        resultado = []
        self._em_ordem(self.raiz, resultado)
        return resultado

    def _em_ordem(self, no, resultado):
        if no:
            self._em_ordem(no.esquerda, resultado)
            resultado.append(no.valor)
            self._em_ordem(no.direita, resultado)


arvore = ArvoreBinariaBusca()
for valor in [10, 5, 15, 3, 7, 20]:
    arvore.inserir(valor)

print(arvore.em_ordem())    # [3, 5, 7, 10, 15, 20]
print(arvore.buscar(7))     # True
print(arvore.buscar(99))    # False

Exemplo Completo: Avaliador de Expressões

Usando pilha para avaliar expressões matemáticas em notação pós-fixada (RPN — Reverse Polish Notation):

def avaliar_rpn(expressao):
    """
    Avalia expressão em notação pós-fixada.
    Exemplo: "3 4 + 2 *" = (3 + 4) * 2 = 14
    """
    pilha = []
    operadores = {
        "+": lambda a, b: a + b,
        "-": lambda a, b: a - b,
        "*": lambda a, b: a * b,
        "/": lambda a, b: a / b,
    }

    for token in expressao.split():
        if token in operadores:
            b = pilha.pop()
            a = pilha.pop()
            resultado = operadores[token](a, b)
            pilha.append(resultado)
        else:
            pilha.append(float(token))

    return pilha[0]


print(avaliar_rpn("3 4 +"))         # 7.0  → 3 + 4
print(avaliar_rpn("3 4 + 2 *"))     # 14.0 → (3 + 4) * 2
print(avaliar_rpn("5 1 2 + 4 * + 3 -"))  # 14.0 → 5 + ((1+2)*4) - 3

Resumo

  • Recursão resolve problemas dividindo-os em subproblemas menores — sempre exige caso base
  • @lru_cache adiciona memoization automaticamente, tornando recursões repetitivas eficientes
  • Pilhas seguem LIFO — list.append() e list.pop() são suficientes em Python
  • Filas seguem FIFO — use collections.deque com append() e popleft() para eficiência O(1)
  • Filas de prioridade são implementadas com heapq — menor valor sai primeiro
  • Árvores representam hierarquias — BST garante busca em O(log n) para árvores balanceadas
  • Pilhas e recursão são naturalmente complementares — toda recursão usa a pilha de chamadas internamente

Referências e Leituras Complementares


Prof. Ricardo Matos — Dominando o Python em 1 Ano · Artigo 12 de 52


✅ Módulo 2 Concluído — Estruturas de Dados e Algoritmos


Plano Completo da Série — Estado Atual

Módulo Tema Artigos Status
1 Fundamentos da Linguagem 01–06 ✅ Concluído
2 Estruturas de Dados e Algoritmos 07–12 ✅ Concluído
3 Orientação a Objetos 13–18 ⬜ Pendente
4 Arquivos, I/O e Banco de Dados 19–24 ⬜ Pendente
5 Python para Web (Flask/FastAPI) 25–30 ⬜ Pendente
6 Automação e Scripts 31–34 ⬜ Pendente
7 Data Science e Machine Learning 35–42 ⬜ Pendente
8 Testes, Qualidade e Boas Práticas 43–47 ⬜ Pendente
9 Projetos Reais e Carreira 48–52 ⬜ Pendente
Comentários

Mais em Python

Classes e Objetos: os fundamentos da Orientação a Objetos
Classes e Objetos: os fundamentos da Orientação a Objetos

At&eacute; aqui trabalhamos com fun&ccedil;&otilde;es e estruturas de dados s...

Tuplas e Sets: imutabilidade e unicidade
Tuplas e Sets: imutabilidade e unicidade

Python oferece mais de uma forma de agrupar dados. Enquanto listas s&atilde;o...

Listas: criação, manipulação e métodos
Listas: criação, manipulação e métodos

Listas s&atilde;o a estrutura de dados mais utilizada em Python. Elas permite...