Grafos Ponderados em Python: TP3 Completo com Evidência, Big-O e Casos de Borda
Guia autodidata exercício por exercício: o que a questão pede, contrato, implementação, evidência interpretada, por que a resposta fecha o enunciado e quais bordas derrubam soluções frágeis.
▼
BASE
Como ler o TP3 sem errar a pergunta
Estabelecer o protocolo fixo de prova e o mapa didático do trabalho antes de entrar nos algoritmos.
O que este HTML passa a fazer
A página deixa de ser resumo condensado e passa a seguir o mesmo ciclo didático do arquivo de respostas: pergunta, contrato, implementação núcleo, evidência interpretada, justificativa técnica, Big-O e casos de borda.
Como ler o TP3 sem errar a pergunta
Q1 a Q3 cobram modelagem OO, travessias determinísticas e caminho mínimo com reconstrução.
Q4 separa explicitamente "menor número de arestas" de "menor soma de pesos".
Q5 a Q9 pedem não só resultado, mas leitura correta da saída: AGM, matriz, Mermaid e validação estrutural.
Q10 a Q12 exigem análise madura: custo empírico, integração de algoritmos e decisões de projeto com trade-off real.
Protocolo fixo de evidência
ENTRADA
Qual grafo, qual origem/destino, qual parâmetro foi usado.
SAÍDA
Lista, dicionário, caminho, matriz, string Mermaid, exceção ou tabela de contadores.
ONDE ESTÁ A RESPOSTA
O valor exato do output que responde à pergunta do enunciado.
PROVA MÍNIMA
assert ... ou checagem de propriedade. Printar não é provar.
Regra editorial do notebook
Em cada exercício, a resposta só está completa quando o notebook mostra: dado de entrada, resultado produzido, trecho exato do output que responde à pergunta e prova mínima por assert.
▼
EX.01
Representação de grafo ponderado (
WeightedGraph)Construir uma base OO auditável com lista de adjacência, pesos, política explícita para arestas e invariantes claros.
O que a questão está pedindo
- Classe OO para grafo ponderado.
- Lista de adjacência com pesos.
- Métodos básicos de inserção e consulta.
- Grafo de teste com pelo menos 8 vértices e pesos distintos.
- Invariantes explícitas.
Contrato / invariantes / pré-condições
add_vertex(v) cria o vértice se ele ainda não existir.add_edge(u, v, w, directed) espelha a aresta quando directed=False.neighbors(u) devolve vizinhos em ordem determinística.weight(u, v) devolve o peso exato da aresta.Aresta paralela mantém o menor peso observado.
Implementação núcleo
class WeightedGraph:
def __init__(self):
self.adj: dict[str, dict[str, float]] = {}
def add_vertex(self, v: str) -> None:
if v not in self.adj:
self.adj[v] = {}
def add_edge(self, u: str, v: str, w: float, directed: bool = False) -> None:
self.add_vertex(u)
self.add_vertex(v)
self.adj[u][v] = min(self.adj[u].get(v, float("inf")), float(w))
if not directed:
self.adj[v][u] = min(self.adj[v].get(u, float("inf")), float(w))
def neighbors(self, u: str) -> list[str]:
return sorted(self.adj[u].keys())
def weight(self, u: str, v: str) -> float:
return self.adj[u][v]
g = build_demo_graph()
assert g.neighbors("A") == ["B", "C"]
assert g.weight("A", "C") == 2.0
gd = WeightedGraph()
gd.add_edge("X", "Y", 7, directed=True)
assert gd.neighbors("X") == ["Y"]
assert gd.neighbors("Y") == []
Evidência interpretada
ENTRADA: consulta em
A e microcaso dirigido X -> Y.SAÍDA: vizinhos de
A, peso de A-C e diferença entre vizinhança de X e Y.ONDE ESTÁ A RESPOSTA: nos retornos de
neighbors("A"), weight("A","C") e nos asserts do caso dirigido.PROVA MÍNIMA: igualdade exata dos asserts.
Por que isso responde à pergunta
A questão não quer "qualquer código de grafo". Ela quer uma representação consultável, auditável e capaz de provar que dirigido e não direcionado foram realmente implementados.
Big-O
add_edge: O(1) esperado com dict.weight(u, v): O(1) esperado.neighbors(u): O(grau(u) log grau(u)) por causa do sorted.Casos de borda
Inserir aresta com vértice novo.
Repetir aresta com peso maior e verificar que o menor foi preservado.
Exigir assimetria controlada no caso dirigido.
▼
EX.02
BFS e DFS determinísticos
Mostrar que a travessia não apenas visita vértices, mas o faz em ordem reproduzível e explicável.
O que a questão está pedindo
- BFS com fila.
- DFS com pilha ou recursão.
- Ordem determinística de visita.
- Registro explícito da estrutura usada.
- Análise de tempo e memória.
Contrato / invariantes / pré-condições
BFS usa
deque como fila.DFS usa
list como pilha.Vizinhos são sempre processados em ordem determinística.
Vértice inexistente deve levantar
KeyError.Implementação núcleo
g = build_demo_graph()
bfs_order = g.bfs("A")
dfs_order = g.dfs("A")
assert bfs_order == ['A','B','C','D','E','F','G','H']
assert dfs_order == ['A','B','C','D','E','F','H','G']
Evidência interpretada
ENTRADA:
start = "A".SAÍDA: listas
bfs_order e dfs_order.ONDE ESTÁ A RESPOSTA: nas duas listas de visita.
PROVA MÍNIMA: igualdade exata com a ordem esperada.
Por que isso responde à pergunta
O enunciado não pede só que a busca alcance os vértices. Ele exige ordem determinística e identificação da estrutura usada. Sem isso, a visita vira resultado casual e difícil de defender.
Big-O
Tempo: O(|V| + |E|).
Memória auxiliar: O(|V|).
Casos de borda
Vértice inexistente.
Componente desconexo: a travessia visita apenas o componente do
start.
▼
EX.03
Dijkstra com distâncias e reconstrução de caminho
Fechar a resposta completa para caminho mínimo ponderado: distância, predecessores, reconstrução e prova de custo.
O que a questão está pedindo
- Executar Dijkstra no grafo ponderado.
- Calcular distâncias da origem para todos os vértices.
- Reconstruir pelo menos um caminho mínimo.
- Demonstrar com mais de uma origem.
Contrato / invariantes / pré-condições
Pesos precisam ser não negativos.
dist[target] e path respondem perguntas diferentes e complementares.O custo somado do caminho reconstruído precisa ser igual à menor distância registrada.
Implementação núcleo
def path_cost(g: WeightedGraph, path: list[str]) -> float:
return sum(g.weight(path[i], path[i + 1]) for i in range(len(path) - 1))
g = build_demo_graph()
for source, target, expected_dist, expected_path in [
("A", "H", 23.0, ['A','C','B','D','F','H']),
("D", "H", 15.0, ['D','F','H']),
("F", "H", 9.0, ['F','H']),
]:
dist, prev = g.dijkstra(source)
path = g.reconstruct_path(prev, source, target)
assert dist[target] == expected_dist
assert path == expected_path
assert path_cost(g, path) == dist[target]
gneg = WeightedGraph()
gneg.add_edge("A", "B", -1, directed=False)
try:
gneg.dijkstra("A")
assert False
except ValueError:
pass
Evidência interpretada
ENTRADA: pares
(source, target) e um microcaso com peso negativo.SAÍDA:
dist, prev e path.ONDE ESTÁ A RESPOSTA:
dist[target] é a menor distância; path é o percurso mínimo.PROVA MÍNIMA: custo somado do caminho = distância registrada; peso negativo dispara exceção.
Por que isso responde à pergunta
Quem mostra só
dist não provou o caminho. Quem mostra só path não provou minimalidade. A resposta correta precisa fechar o triângulo: distância, reconstrução e verificação do custo.Big-O
Com heap: O((|V| + |E|) log |V|).
Memória: O(|V| + |E|).
Casos de borda
Origem inexistente.
Vértice inalcançável:
dist = inf e path = None.Múltiplos caminhos com mesmo custo: usar tie-break para reprodutibilidade.
▼
EX.04
Menor caminho em grafo não ponderado via BFS
Separar explicitamente "menor número de arestas" de "menor soma de pesos".
O que a questão está pedindo
Retornar o menor caminho entre dois vértices em número de arestas, usando BFS com predecessores e caminho explícito como lista.
Contrato / invariantes / pré-condições
Esta questão ignora peso e trabalha com número de saltos.
O caminho retornado precisa ser válido no grafo.
Se não houver conectividade, o resultado esperado é
None.Implementação núcleo
g = build_demo_graph()
path = g.shortest_path_unweighted("A", "H")
assert path == ["A", "B", "D", "F", "H"]
assert all(path[i + 1] in g.neighbors(path[i]) for i in range(len(path) - 1))
Evidência interpretada
ENTRADA:
src = "A" e dst = "H".SAÍDA: lista
path.ONDE ESTÁ A RESPOSTA: na própria lista retornada.
PROVA MÍNIMA: cada par consecutivo é uma aresta real do grafo.
Por que isso responde à pergunta
Em grafo não ponderado, a BFS resolve "menor número de arestas". Dijkstra resolveria outra pergunta: menor soma de pesos. Essa distinção precisa ser escrita, não assumida.
Big-O
Tempo: O(|V| + |E|).
Memória: O(|V|).
Casos de borda
Origem igual ao destino.
Destino fora do componente da origem.
Grafo com pesos altos: os pesos continuam irrelevantes para esta pergunta.
▼
EX.05
Prim para árvore geradora mínima
Provar que a AGM não é só uma lista de arestas bonita, mas uma estrutura coerente em custo, cardinalidade e existência.
O que a questão está pedindo
- Implementar Prim em grafo ponderado.
- Devolver as arestas da AGM.
- Calcular o custo total.
- Explicar o que acontece em grafo desconexo.
Contrato / invariantes / pré-condições
mst precisa ter |V|-1 arestas no componente alcançável.Toda aresta da solução precisa existir no grafo original.
total precisa ser igual à soma dos pesos em mst.Implementação núcleo
g = build_demo_graph()
mst, total = g.prim("A")
assert mst == [
('A','C',2.0), ('C','B',1.0), ('B','D',5.0),
('D','E',3.0), ('D','F',6.0), ('E','G',7.0),
('F','H',9.0)
]
assert total == 33.0
assert total == sum(w for _, _, w in mst)
for u, v, w in mst:
assert (v in g.adj[u] and g.adj[u][v] == w) or (u in g.adj[v] and g.adj[v][u] == w)
assert len(mst) == len(g.vertices()) - 1
Evidência interpretada
ENTRADA:
start = "A".SAÍDA:
mst e total.ONDE ESTÁ A RESPOSTA: na lista de arestas e no custo acumulado.
PROVA MÍNIMA: cardinalidade
|V|-1, soma dos pesos e existência de cada aresta.Por que isso responde à pergunta
A pergunta quer a árvore e quer o custo. Sem provar cardinalidade, soma dos pesos e existência das arestas, o aluno só mostrou uma coleção qualquer de conexões.
Big-O
Com heap: O((|V| + |E|) log |V|).
Memória: O(|V| + |E|).
Casos de borda
Grafo desconexo: a implementação retorna a AGM do componente alcançável a partir de
start.Arestas com mesmo peso: o tie-break ajuda na reprodutibilidade.
▼
EX.06
Floyd-Warshall com leitura correta de matriz
Ensinar o aluno a localizar e interpretar a célula certa, e não apenas imprimir uma matriz sem saber lê-la.
O que a questão está pedindo
- Calcular menor caminho entre todos os pares.
- Devolver matriz final.
- Explicar o significado de
verts,idxedist. - Demonstrar consistência com Dijkstra.
Contrato / invariantes / pré-condições
verts é a lista ordenada de vértices.idx[v] mapeia cada vértice para o índice correto da matriz.A célula certa é
dist[idx[src]][idx[dst]], e não dist["A"]["H"].A diagonal precisa ficar em zero.
Implementação núcleo
g = build_demo_graph()
verts, idx, dist = g.all_shortest_paths_matrix()
for v in verts:
i = idx[v]
assert dist[i][i] == 0.0
assert dist[idx["A"]][idx["H"]] == 23.0
dA, _ = g.dijkstra("A")
for v in verts:
assert dist[idx["A"]][idx[v]] == dA[v]
Evidência interpretada
ENTRADA: grafo de teste completo.
SAÍDA:
verts, idx e dist.ONDE ESTÁ A RESPOSTA: na célula
dist[idx["A"]][idx["H"]] e na linha cruzada com Dijkstra.PROVA MÍNIMA: diagonal zero e igualdade entre a linha de
A na matriz e dijkstra("A").Por que isso responde à pergunta
A questão não quer só "uma matriz impressa". Ela quer leitura correta da estrutura. Explicar
verts e idx é parte da resposta, não um detalhe cosmético.Big-O
Tempo: O(|V|^3).
Memória: O(|V|^2).
Casos de borda
Grafo desconexo: células inalcançáveis permanecem
inf.Grafo não direcionado: a matriz tende a ser simétrica.
▼
EX.07
Comparação entre Dijkstra e Floyd-Warshall
Transformar comparação solta em decisão técnica objetiva, com tempo, memória e cenário recomendado.
O que a questão está pedindo
Comparar complexidade, memória e caso de uso. Aqui a resposta boa não é parágrafo genérico; é decisão técnica auditável.
Contrato / invariantes / pré-condições
Separar custo por origem de custo para todos os pares.
Separar memória auxiliar de cenário recomendado.
Justificar a escolha pelo problema, não só pela fórmula.
Implementação núcleo / resposta auditável
| Critério | Dijkstra (heap) | Floyd-Warshall |
|---|---|---|
| Problema que resolve melhor | Uma origem para todos os demais | Todos os pares |
| Tempo | O((|V| + |E|) log |V|) por origem | O(|V|^3) |
| Memória | O(|V| + |E|) além do grafo | O(|V|^2) |
| Melhor cenário | Poucas origens, grafo esparso | Grafo pequeno ou médio com análise global |
| Ponto fraco | Repetir para muitas origens pode custar caro | Tempo e memória explodem quando |V| cresce |
Evidência interpretada
ENTRADA: os dois algoritmos e seus cenários de uso.
SAÍDA: tabela comparativa.
ONDE ESTÁ A RESPOSTA: nas colunas de tempo, memória e melhor cenário.
PROVA MÍNIMA: a tabela força a separar custo, memória e contexto, impedindo explicação vaga.
Por que isso responde à pergunta
A comparação correta não é "qual é melhor?". É "qual é melhor para este cenário?". A tabela fecha exatamente essa decisão.
Big-O
Dijkstra: O((|V| + |E|) log |V|) por origem.
Floyd-Warshall: O(|V|^3).
Casos de borda
Se precisar de todos os pares, repetir Dijkstra pode se tornar desvantajoso.
Se o grafo crescer muito, Floyd-Warshall pode deixar de ser viável por memória.
▼
EX.08
Exportação em Mermaid com regra anti-duplicação
Transformar validação visual em evidência auditável por contagem de arestas e checagem de pesos-chave.
O que a questão está pedindo
Gerar uma visualização reproduzível do grafo ponderado e provar que ela representa corretamente o grafo sem duplicar arestas não direcionadas.
Contrato / invariantes / pré-condições
Toda aresta não direcionada precisa ser emitida uma única vez.
O peso precisa sair junto da aresta.
A validação não pode ser subjetiva.
Implementação núcleo
g = build_demo_graph()
m = g.to_mermaid(directed=False)
print(m)
edge_lines = [ln for ln in m.splitlines() if "---|" in ln]
assert len(edge_lines) == 12
assert "A ---|2.0| C" in m or "C ---|2.0| A" in m
assert "B ---|5.0| D" in m or "D ---|5.0| B" in m
assert "G ---|12.0| H" in m or "H ---|12.0| G" in m
Evidência interpretada
ENTRADA: grafo de teste completo.
SAÍDA: string Mermaid.
ONDE ESTÁ A RESPOSTA: nas linhas
u ---|w| v.PROVA MÍNIMA: contagem de arestas e presença de arestas-chave com peso correto.
Por que isso responde à pergunta
"Validar visualmente" não pode significar "olhei e gostei". O mínimo técnico é provar que a quantidade de arestas bate com o grafo e que a regra anti-duplicação foi respeitada.
Big-O
O(|V| + |E|) para percorrer a estrutura e emitir as linhas.
Casos de borda
Duplicação acidental de
u-v e v-u.Aresta importante omitida ou com peso incorreto.
▼
EX.09
Validação estrutural com casos válidos e inválidos
Mostrar engenharia defensiva: o método aceita o grafo correto e reprova exatamente os cenários quebrados.
O que a questão está pedindo
- Detectar pesos inválidos.
- Detectar vértices inexistentes.
- Detectar arestas inconsistentes em simetria exigida.
Contrato / invariantes / pré-condições
Grafo válido passa sem exceção.
Grafo inválido precisa falhar com
ValueError.Quando a exigência é não direcionada, a simetria precisa existir nos dois sentidos.
Implementação núcleo
g = build_demo_graph()
g.validate_structure(require_undirected_symmetry=True)
g_bad = build_demo_graph()
g_bad.adj["A"]["Z"] = 1.0
try:
g_bad.validate_structure()
assert False
except ValueError:
pass
g_bad2 = WeightedGraph()
g_bad2.add_edge("A", "B", 1, directed=True)
try:
g_bad2.validate_structure(require_undirected_symmetry=True)
assert False
except ValueError:
pass
Evidência interpretada
ENTRADA: um grafo válido e dois grafos deliberadamente inválidos.
SAÍDA: execução normal ou exceção.
ONDE ESTÁ A RESPOSTA: na exceção disparada e no cenário que a causou.
PROVA MÍNIMA: o
try/except confirma que falhou onde deveria falhar.Por que isso responde à pergunta
A questão pede robustez estrutural. Não basta dizer que o grafo está correto; é preciso provar que o método detecta estrutura quebrada e reage do modo esperado.
Big-O
O(|V| + |E|), porque a validação percorre vértices e arestas relevantes.
Casos de borda
Aresta apontando para vértice inexistente.
Assimetria em grafo exigido como não direcionado.
Peso não numérico ou não finito.
▼
EX.10
Instrumentação e custo empírico
Ligar teoria assintótica a observação concreta usando contadores reais de execução.
O que a questão está pedindo
Instrumentar um algoritmo e comparar o crescimento observado com a teoria, sem fingir que o experimento prova a fórmula exata.
Contrato / invariantes / pré-condições
Registrar contadores reais:
heap_pop, heap_push, relax_attempts e relax_success.Comparar cenários de tamanhos crescentes.
Interpretar tendência, não exigir duplicação perfeita.
Implementação núcleo
sizes = [20, 40, 80]
rows = []
for n in sizes:
gg = gen_sparse_graph(n, extra_edges_per_vertex=3, seed=123)
_, _, c = gg.dijkstra_instrumented("0")
rows.append((n, c["heap_pop"], c["heap_push"], c["relax_attempts"], c["relax_success"]))
print(rows[-1])
assert rows[0][3] < rows[1][3] < rows[2][3]
Evidência interpretada
ENTRADA: grafos de tamanhos 20, 40 e 80.
SAÍDA: tabela
rows.ONDE ESTÁ A RESPOSTA: nas colunas de contagem, especialmente
relax_attempts.PROVA MÍNIMA:
relax_attempts cresce monotonicamente.Por que isso responde à pergunta
Comparar empiricamente com Big-O não significa provar a fórmula exata. Significa mostrar que, quando o tamanho cresce, as contagens relevantes crescem de modo compatível com a teoria.
Big-O
A análise teórica segue O((|V| + |E|) log |V|), mas o experimento observa contagens e tendência de crescimento, não o limite formal.
Casos de borda
Python tem overhead e constantes influenciam muito em tamanhos pequenos.
Grafos aleatórios diferentes podem alterar a contagem absoluta.
▼
EX.11
Integração BFS → componente → Dijkstra
Mostrar fluxo de decisão real: usar informação estrutural da BFS para decidir se vale a pena rodar Dijkstra.
O que a questão está pedindo
Usar BFS para validar conectividade e só então executar Dijkstra em componentes conectados.
Contrato / invariantes / pré-condições
Se
target não estiver no componente de source, a função devolve None.Dijkstra roda apenas quando a conectividade foi comprovada.
Implementação núcleo
def safe_shortest_path_if_connected(g: WeightedGraph, source: str, target: str) -> list[str] | None:
comp = g.connected_component(source)
if target not in comp:
return None
dist, prev = g.dijkstra(source)
return g.reconstruct_path(prev, source, target)
g2 = WeightedGraph()
g2.add_edge("A", "B", 1, False)
g2.add_edge("B", "C", 2, False)
g2.add_edge("X", "Y", 1, False)
assert g2.connected_component("A") == {"A", "B", "C"}
assert safe_shortest_path_if_connected(g2, "A", "Y") is None
g = build_demo_graph()
assert safe_shortest_path_if_connected(g, "A", "H") == ['A','C','B','D','F','H']
Evidência interpretada
ENTRADA: um grafo desconexo e um grafo conectado.
SAÍDA: componente conectado e caminho final, ou
None.ONDE ESTÁ A RESPOSTA: no conjunto retornado pela BFS e na decisão de retornar
None ou caminho.PROVA MÍNIMA: se
target não está no componente, não há por que rodar Dijkstra.Por que isso responde à pergunta
Isto não é só encadear chamadas. A BFS fornece a informação estrutural que evita rodar Dijkstra sem sentido em alvos inalcançáveis. Isso é integração real de algoritmos.
Big-O
BFS: O(|V| + |E|).
Se o alvo estiver no componente, soma-se o custo do Dijkstra.
Casos de borda
Origem ou destino inexistentes.
Grafo totalmente desconexo.
Alvo no mesmo componente, mas com vários caminhos de custo equivalente.
▼
EX.12
Decisões de projeto e complexidade
Fechar o TP3 com maturidade de engenharia: custo, memória, justificativa estrutural e trade-off real.
O que a questão está pedindo
Uma seção final madura, com tabela de custos, justificativa das escolhas estruturais e relação entre decisão e trade-off concreto.
Contrato / invariantes / pré-condições
A tabela precisa separar tempo, memória auxiliar e observação prática.
As decisões precisam mostrar ganho e custo pago.
Implementação núcleo / tabela pronta
| Algoritmo / operação | Tempo | Memória auxiliar | Observação |
|---|---|---|---|
neighbors(u) com ordenação |
O(grau(u) log grau(u)) | O(grau(u)) | Custo pago para garantir determinismo. |
| BFS / DFS | O(|V| + |E|) | O(|V|) | Base para conectividade e inspeção. |
| Dijkstra (heap) | O((|V| + |E|) log |V|) | O(|V| + |E|) | Melhor para poucas origens em grafos esparsos. |
| BFS shortest path | O(|V| + |E|) | O(|V|) | Correto apenas para não ponderado. |
| Prim (heap) | O((|V| + |E|) log |V|) | O(|V| + |E|) | AGM do componente de start. |
| Floyd-Warshall | O(|V|^3) | O(|V|^2) | Melhor para todos os pares em grafos menores. |
to_mermaid() |
O(|V| + |E|) | O(|E|) | Com regra anti-duplicação. |
adjcomodict[str, dict[str, float]]: ganha consulta O(1) e integração simples com Dijkstra, Prim e validação; paga memória maior que uma estrutura mais compacta.- Determinismo por
sorted(neighbors)+ tie-break no heap: ganha auditoria e reprodutibilidade; paga ordenação e um contador extra.
Evidência interpretada
ENTRADA: todas as operações usadas no TP3.
SAÍDA: tabela de custos e duas decisões justificadas.
ONDE ESTÁ A RESPOSTA: nas colunas de custo e nos pares ganho/custo das decisões.
PROVA MÍNIMA: cada decisão conecta benefício técnico e preço pago.
Por que isso responde à pergunta
A seção final do TP não quer só recitar Big-O. Ela quer mostrar maturidade de engenharia: estrutura de dados, algoritmo e cenário de uso precisam aparecer conectados.
Big-O
Já está consolidado na tabela para facilitar transcrição direta no notebook.
Casos de borda
Pagar determinismo onde reprodutibilidade importa e abrir mão dele onde custo seria injustificável.
Evitar estrutura "bonita" mas ruim para consulta de peso e integração com os algoritmos do TP.
▼
CHECK
Checklist final de prova auditável
Fechar a página como material de respostas-autodidata completo, garantindo que cada exercício mostre o que provar e onde a resposta aparece.
Checklist de prova
- WeightedGraph: consulta de vizinhos e pesos funciona; caso dirigido demonstrado.
- BFS/DFS: ordem determinística demonstrada e estrutura de dados declarada.
- Dijkstra: distância e caminho reconstruído provados; peso negativo bloqueado.
- BFS shortest path: caminho válido em não ponderado e distinção explícita para Dijkstra.
- Prim: arestas, custo, cardinalidade e existência das arestas provados.
- Floyd: diagonal zero, leitura correta via
idxe consistência com Dijkstra. - Comparação: tabela objetiva preenchida com tempo, memória e cenário recomendado.
- Mermaid: contagem sem duplicação e arestas-chave presentes.
- Validação estrutural: caso válido e casos inválidos com exceções esperadas.
- Instrumentação: contadores reais e interpretação do crescimento empírico.
- Integração: Dijkstra não roda quando
targetestá fora do componente. - Cada seção contém: o que a questão pede, contrato, implementação, evidência interpretada, por que isso responde, Big-O e casos de borda.
Fecho didático
Se o aluno consegue explicar cada exercício neste formato, ele não está apenas mostrando que o código rodou. Está mostrando que entendeu a estrutura do problema, a lógica do algoritmo e o significado técnico da saída.