Árvore Geradora Mínima (Minimum Spanning Tree - MST)
1. Conectando Cidades com Custo Mínimo
Imagine que você é um engenheiro de telecomunicações. Você precisa passar cabos de fibra óptica conectando \(N\) cidades. Você sabe o custo para cabear entre qualquer par de cidades. Objetivo: Conectar todas as cidades gastando o mínimo possível.
A solução é uma subestrutura do grafo original que: 1. Contém todos os vértices. 2. É conexa. 3. Não tem ciclos (é uma árvore). 4. A soma dos pesos das arestas é mínima.
Isso é uma MST. Se o grafo tem \(V\) vértices, a MST terá exatamente \(V-1\) arestas.
2. Algoritmo de Kruskal (Abordagem Gulosa nas Arestas)
O algoritmo de Joseph Kruskal é intuitivo: “Sempre tente pegar a aresta mais barata disponível, desde que ela não forme um ciclo”.
Passo a Passo
- Crie uma lista com todas as arestas do grafo.
- Ordene a lista por peso crescente.
- Itere pela lista ordenada:
- Seja a aresta \((u, v)\) com peso \(w\).
- Se \(u\) e \(v\) já estão conectados (estão no mesmo conjunto), discarte a aresta (formaria ciclo).
- Se não, adicione a aresta à MST e una os conjuntos de \(u\) e \(v\).
Para verificar conectividade e unir conjuntos rapidamente, usamos a estrutura Union-Find (DSU).
Implementação Java (Kruskal)
import java.util.*;
class Edge implements Comparable<Edge> {
int u, v, weight;
public Edge(int u, int v, int w) {
this.u = u; this.v = v; this.weight = w;
}
public int compareTo(Edge other) {
return this.weight - other.weight;
}
}
public class KruskalMST {
// Union-Find Simplificado (veja tópico 19 para completo)
static int[] parent;
static int find(int i) {
if (parent[i] == i) return i;
return parent[i] = find(parent[i]);
}
static void union(int i, int j) {
int rootI = find(i);
int rootJ = find(j);
if (rootI != rootJ) parent[rootI] = rootJ;
}
public static int kruskal(int V, List<Edge> edges) {
Collections.sort(edges); // O(E log E)
parent = new int[V];
for(int i=0; i<V; i++) parent[i] = i;
int mstWeight = 0;
int edgesCount = 0;
for (Edge e : edges) {
if (find(e.u) != find(e.v)) {
union(e.u, e.v);
mstWeight += e.weight;
edgesCount++;
}
}
if (edgesCount != V - 1) return -1; // Grafo desconexo
return mstWeight;
}
}Complexidade: \(O(E \log E)\) devido à ordenação.
3. Algoritmo de Prim (Abordagem Gulosa nos Vértices)
O algoritmo de Robert Prim (e Jarník) “cresce” a MST a partir de um vértice inicial, similar ao Dijkstra.
Passo a Passo
- Escolha um vértice inicial arbitrário (ex: 0).
- Adicione todas as arestas dele numa Priority Queue (Min-Heap).
- Marque o vértice como visitado.
- Enquanto a PQ não estiver vazia:
- Remova a aresta \((u, v)\) de menor peso.
- Se \(v\) já foi visitado, ignore.
- Se não, esta aresta faz parte da MST! Adicione o custo.
- Marque \(v\) como visitado e adicione todas as arestas saindo de \(v\) para a PQ.
Implementação Java (Prim)
class Pair implements Comparable<Pair> {
int v, weight;
// construtor...
public int compareTo(Pair other) { return this.weight - other.weight; }
}
public static int prim(int V, List<List<Pair>> adj) {
boolean[] visited = new boolean[V];
PriorityQueue<Pair> pq = new PriorityQueue<>();
// Começa do 0, custo 0
pq.add(new Pair(0, 0));
int mstWeight = 0;
int nodesCount = 0;
while (!pq.isEmpty()) {
Pair current = pq.poll();
int u = current.v;
if (visited[u]) continue; // Lazy deletion
visited[u] = true;
mstWeight += current.weight;
nodesCount++;
for (Pair neighbor : adj.get(u)) {
if (!visited[neighbor.v]) {
pq.add(new Pair(neighbor.v, neighbor.weight));
}
}
}
if (nodesCount != V) return -1;
return mstWeight;
}Complexidade: \(O(E \log V)\) usando Binary Heap.
4. Kruskal vs Prim: O Veredito
| Característica | Kruskal | Prim |
|---|---|---|
| Lógica | Ordena arestas. | Cresce árvore (Priority Queue). |
| Estrutura Auxiliar | Union-Find (DSU). | Heap (Priority Queue) + Visited array. |
| Melhor para | Grafos Esparsos (poucas arestas). | Grafos Densos (muitas arestas). |
| Facilidade | Muito fácil se tiver DSU pronto. | Fácil se souber Dijkstra. |
Em competições, Kruskal é geralmente o favorito porque o DSU é muito curto de implementar e a lógica de ordenar arestas é menos propensa a bugs do que gerenciar a PQ.