Heaps Binárias em Python OO
Guia completo para o TP1: implementação de Floyd O(n), remoção arbitrária, instrumentação empírica e transposição de paradigmas Java/C#.
▼
BASE
Introdução e Escopo do TP1
Alinhar a teoria de Heaps Máximas aos requisitos implementacionais do teste.
Neste TP, o desafio é implementar uma Heap Binária Máxima do zero. O objetivo central não é apenas "fazer funcionar", mas atuar como um engenheiro: modelar o problema, justificar a corretude através de invariantes, estimar o custo assintótico e validar o desempenho por evidência empírica.
Nota de Transposição: embora você já domine Orientação a Objetos em Java ou .NET, aqui você deve aplicar esses conceitos à sintaxe Python. Encapsular o estado (o array) em um objeto
BinaryHeap é crítico para garantir que operações externas não corrompam a estrutura.
- Invariante de Estrutura: Árvore binária quase completa representada em array.
- Invariante de Ordem: Em Max-Heap,
Pai ≥ Filhopara todo nó. - Eficiência: Operações básicas em
O(log n)e construção emO(n).
▼
EX.01
Motivação Computacional e Escolha da Heap
Justificar o uso da heap em cenários de priorização dinâmica real.
Problema: Gerenciamento de Prioridade em Triagem Hospitalar. Pacientes com gravidade "Emergência" devem ser atendidos antes de "Urgência", independentemente da ordem de chegada.
Justificativa Técnica: Diferente de uma lista ordenada (inserção O(n)) ou de uma lista não ordenada (busca do máximo O(n)), a heap oferece o equilíbrio ideal: inserção e remoção do máximo em O(log n), mantendo a latência baixa mesmo com alta taxa de novos pacientes.
| Estrutura | Inserção | Extração do Máximo | Busca Arbitrária |
|---|---|---|---|
| Lista não ordenada | O(1) | O(n) | O(n) |
| Lista ordenada | O(n) | O(1) | O(n) |
| BST / AVL | O(log n) | O(log n) | O(log n) |
| Heap máxima | O(log n) | O(log n) | O(n) |
▼
EX.02
Estruturação de Heap Binária em Python
Traduzir a estrutura lógica de árvore para a representação física de array.
A heap binária é armazenada em uma lista onde as relações pai-filho são calculadas via índices. Isso elimina o overhead de ponteiros e otimiza o cache da CPU.
class BinaryHeap:
def __init__(self):
self.heap = []
self.comparacoes = 0
self.trocas = 0
def _pai(self, i): return (i - 1) // 2
def _filho_esq(self, i): return 2 * i + 1
def _filho_dir(self, i): return 2 * i + 2
def _find_index(self, valor):
# Busca linear instrumentada para auditoria real de custo O(n)
for i, atual in enumerate(self.heap):
self.comparacoes += 1
if atual == valor: return i
return -1
▼
EX.03
Inserção com Registro de Trocas (Sift-up)
Implementar a escalada do novo nó enquanto registra evidências de auditoria.
Para o TP1, é obrigatório registrar cada troca realizada e contar as comparações. Isso permite provar que o algoritmo seguiu o caminho de custo
O(log n).
def insert(self, valor):
self.heap.append(valor)
self._sift_up(len(self.heap) - 1)
def _sift_up(self, i):
while i > 0:
p = self._pai(i)
self.comparacoes += 1
if self.heap[i] > self.heap[p]:
print(f"Swap: {self.heap[i]} <-> {self.heap[p]}")
self.heap[i], self.heap[p] = self.heap[p], self.heap[i]
self.trocas += 1
i = p
else:
break
▼
EX.04
Remoção do Elemento Raiz (extract-max)
Garantir a manutenção da propriedade de heap após a extração do máximo.
Ao remover a raiz, trocamos o topo pelo último elemento e aplicamos o sift-down para que o novo topo desça até sua posição correta.
def extract_max(self):
if not self.heap:
return None
if len(self.heap) == 1:
return self.heap.pop()
max_val = self.heap[0]
self.heap[0] = self.heap.pop()
self._sift_down(0)
return max_val
def _sift_down(self, i):
n = len(self.heap)
while True:
maior = i
esq = self._filho_esq(i)
dir = self._filho_dir(i)
if esq < n:
self.comparacoes += 1
if self.heap[esq] > self.heap[maior]:
maior = esq
if dir < n:
self.comparacoes += 1
if self.heap[dir] > self.heap[maior]:
maior = dir
if maior == i:
break
print(f"Swap: {self.heap[i]} <-> {self.heap[maior]}")
self.heap[i], self.heap[maior] = self.heap[maior], self.heap[i]
self.trocas += 1
i = maior
▼
EX.05
Busca Linear em Heap Binária (contains)
Entender por que a heap não é adequada para buscas frequentes.
Diferente de uma Árvore de Busca Binária (BST), na Heap não há ordem entre os "irmãos". O filho esquerdo pode ser maior ou menor que o direito. Por isso, para buscar um valor arbitrário, precisamos percorrer o array inteiro.
def contains(self, valor):
# Custo: O(n). Busca linear exaustiva.
for i in range(len(self.heap)):
self.comparacoes += 1
if self.heap[i] == valor: return True
return False
Assintótica: se sua aplicação precisa de buscas constantes, a Heap é a estrutura errada. Use BST ou Tabela Hash.
▼
EX.06
Remoção de Elemento Arbitrário (delete)
Dominar a operação complexa de remover qualquer nó da estrutura.
Para remover
valor, localizamos seu índice usando uma busca instrumentada (_find_index), removemos o último elemento e o colocamos na posição vaga. O reequilíbrio deve ser cirúrgico: subir se o novo valor for maior que o pai, ou descer caso contrário.
def delete(self, valor):
idx = self._find_index(valor)
if idx == -1: return False
ultimo = self.heap.pop()
# Se o removido já era o último, a tarefa termina.
if idx == len(self.heap): return True
self.heap[idx] = ultimo
# Decide a direção do reequilíbrio
if idx > 0:
self.comparacoes += 1
if self.heap[idx] > self.heap[self._pai(idx)]:
self._sift_up(idx)
else:
self._sift_down(idx)
else:
self._sift_down(idx)
return True
▼
EX.07
Validação Estrutural (is_valid_heap)
Auditar se um array arbitrário respeita as leis de uma Max-Heap.
A função deve percorrer apenas os nós que possuem filhos (os primeiros
n // 2 nós) e garantir que nenhum deles é menor que seus filhos imediatos.
def is_valid_heap(array):
n = len(array)
for i in range(n // 2):
l, r = 2*i + 1, 2*i + 2
if l < n and array[i] < array[l]: return False
if r < n and array[i] < array[r]: return False
return True
▼
EX.08
Construção O(n) por Floyd (build_heap)
Implementar o algoritmo otimizado que ignora inserções individuais.
Requisito Crítico: O TP1 proíbe o uso de inserções repetidas para o
build_heap. Você deve usar o método de Floyd (heapify), que custa O(n) e começa de baixo para cima.
def build_heap(self, lista):
self.heap = list(lista)
# Começa do último nó pai e vai até a raiz
for i in range((len(self.heap) // 2) - 1, -1, -1):
self._sift_down(i)
Por que funciona? Como a maioria dos nós está na base, eles percorrem uma altura pequena. A soma das descidas é linear.
▼
EX.09
Comparação: Floyd vs Inserção Incremental
Evidenciar a economia de operações entre os dois métodos de construção.
Ao comparar os métodos, medimos o número de trocas e comparações reais. A diferença é mais evidente em entradas desfavoráveis (como listas já ordenadas de forma crescente para uma Max-Heap).
def test_comparison(n):
# Entrada crescente: desfavorável para inserção incremental em Max-Heap
valores = list(range(n))
# 1. Incremental
h_inc = BinaryHeap()
for v in valores: h_inc.insert(v)
# 2. Floyd
h_floyd = BinaryHeap()
h_floyd.build_heap(valores)
print(f"N={n} | Incremental Swaps: {h_inc.trocas} | Floyd Swaps: {h_floyd.trocas}")
▼
EX.10
Heap Sort Parcial (K maiores)
Extrair apenas uma parcela dos dados de forma eficiente.
Extrair k vezes de uma heap construída em O(n) custa
O(n + k log n). Se k for pequeno em relação a n, é muito superior a ordenar o array inteiro.
def k_maiores(array, k):
if k < 0: raise ValueError("k negativo")
h = BinaryHeap()
h.build_heap(array)
limite = min(k, len(array))
return [h.extract_max() for _ in range(limite)]
▼
EX.11
Análise de Complexidade Empírica
Dados reais coletados via instrumentação (Entrada Crescente).
Os dados abaixo ilustram a carga de trabalho em uma entrada desfavorável (lista crescente). Note que o custo incremental cresce exponencialmente em relação à eficiência linear de Floyd.
| n | Incremental (Trocas) | Floyd (Trocas) | Diferença |
|---|---|---|---|
| 1.000 | 7.987 | 992 | ~8,1x mais trocas |
| 10.000 | 113.631 | 9.992 | ~11,4x mais trocas |
Os dados empíricos são compatíveis com a análise teórica: a construção por Floyd tende a realizar muito menos trabalho. A prova assintótica vem da soma das alturas dos nós no heapify, que resulta em O(n).
def test_comparison(n):
valores = list(range(n)) # Entrada crescente: desfavorável para Max-Heap incremental
h_inc = BinaryHeap()
for v in valores:
h_inc.insert(v)
h_floyd = BinaryHeap()
h_floyd.build_heap(valores)
print(
f"N={n} | "
f"Incremental: trocas={h_inc.trocas}, comparacoes={h_inc.comparacoes} | "
f"Floyd: trocas={h_floyd.trocas}, comparacoes={h_floyd.comparacoes}"
)
test_comparison(1000)
test_comparison(10000)
▼
EX.12
Roteiro de Documentação e Defesa
Garantir uma entrega profissional e rastreável.
- Arquitetura: Justificar a escolha do Array pela localidade de referência.
- Invariantes: Explicar como o
_sift_downrestaura a propriedade de heap. - Decisões: Discutir o custo linear da busca arbitrária.
- Evidência: Apresentar a instrumentação e a tabela comparativa.
▼
TESTES
Bateria de Testes para Evidência
Gerar saídas reais para anexar ao seu relatório de TP1.
Execute o código abaixo para validar todas as operações solicitadas no teste:
h = BinaryHeap()
# E3: Inserção
for v in [10, 4, 15, 20, 1]: h.insert(v)
print("Heap após inserções:", h.heap)
# E7: Validação
print("É heap válida?", is_valid_heap(h.heap))
# E4: Extração
while h.heap:
print("Extraído:", h.extract_max(), "Restante:", h.heap)
# E8: Build-Heap Floyd
h.build_heap([3, 9, 2, 10, 7, 1])
print("Build heap Floyd:", h.heap)
# E5 e E6: Busca e Delete
print("Contains 7?", h.contains(7))
print("Delete 9:", h.delete(9))
print("Após delete 9:", h.heap)
# E10: K Maiores
print("Top 3:", k_maiores([8, 1, 5, 20, 13, 2], 3))
▼
CHECK
Validação Final de Conformidade TP1
Garantir 100% de cobertura dos requisitos antes do envio.
- Extract-Max: trata corretamente o caso de um único elemento?
- Delete: utiliza
_find_indexinstrumentado e reequilibra corretamente? - Sift-down: implementado integralmente com busca pelo maior filho?
- Floyd: construção realizada em O(n) sem inserções individuais?
- Instrumentação:
comparacoesetrocascapturam o custo real? - Saída: a bateria de testes gera as evidências necessárias para o vídeo?
Página finalizada com rigor técnico máximo. Guia de TP1 completo.