Implementando o Algoritmo de Dijkstra em Ambiente Distribuído com MapReduce

O algoritmo de Dijkstra é uma solução clássica para o problema do caminho mínimo em grafos ponderados com arestas não negativas. Sua adaptação para ambientes distribuídos — especialmente sobre frameworks como Hadoop MapReduce — exige uma reformulação conceitual, pois a versão sequenical depende fortemente de estruturas de dados centralizadas (como filas de prioridade) e atualizações iterativas síncronas. Este artigo explora os fundamentos necessários para essa transição, começando pela modelagem eficiente do grafo e da fila de prioridade em Java, seguida por uma implementação sequencial que serve como base para futuras etapas de paralelização.

Para representar o grafo, utilizamos uma estrutura baseada em Map<Vértice, Map<Vértice, Peso>>, onde cada vértice mapeia seus vizinhos e respectivos pesos das arestas. A classe Grafo oferece métodos para construção a partir de arquivos de texto no formato origem destino peso, além de operações básiccas como recuperação de adjacências e contagem de vértices:

import java.io.; import java.util.;

public class Grafo { private final Map> listaAdj;

public Grafo(Map<vertice integer="" map="">> adj) {
    this.listaAdj = new HashMap<>(adj);
}

public static Grafo carregarDeArquivo(String caminho) throws IOException {
    try (BufferedReader leitor = new BufferedReader(
            new InputStreamReader(new FileInputStream(caminho)))) {

        Map<vertice integer="" map="">> adjacencias = new HashMap<>();
        Map<string vertice=""> nomeParaVertice = new HashMap<>();

        String linha;
        while ((linha = leitor.readLine()) != null) {
            String[] partes = linha.trim().split("\\s+");
            if (partes.length != 3) continue;

            String origemNome = partes[0];
            String destinoNome = partes[1];
            int peso = Integer.parseInt(partes[2]);

            Vertice origem = nomeParaVertice.computeIfAbsent(
                origemNome, Vertice::novo);
            Vertice destino = nomeParaVertice.computeIfAbsent(
                destinoNome, Vertice::novo);

            adjacencias.computeIfAbsent(origem, k -> new HashMap<>())
                       .put(destino, peso);
        }
        return new Grafo(adjacencias);
    }
}

public Set<vertice> obterVertices() {
    return Collections.unmodifiableSet(listaAdj.keySet());
}

public Map<vertice integer=""> obterVizinhos(Vertice v) {
    return listaAdj.getOrDefault(v, Collections.emptyMap());
}

public int obterPeso(Vertice u, Vertice v) {
    return obterVizinhos(u).getOrDefault(v, Integer.MAX_VALUE);
}

public int contarVertices() {
    return listaAdj.size();
}

}


</div>A classe `Vertice` encapsula identidade e estado dinâmico (ex.: distância estimada), com sobrescrita adequada de `equals` e `hashCode` para uso em coleções genéricas:

<div>```
package br.infra.algoritmos.grafo;

public class Vertice {
    private final String identificador;
    private int distancia;

    private Vertice(String id, int dist) {
        this.identificador = id;
        this.distancia = dist;
    }

    public static Vertice novo(String id) {
        return new Vertice(id, Integer.MAX_VALUE);
    }

    public String getId() {
        return identificador;
    }

    public int getDistancia() {
        return distancia;
    }

    public void definirDistancia(int d) {
        this.distancia = d;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Vertice vertice = (Vertice) obj;
        return Objects.equals(identificador, vertice.identificador);
    }

    @Override
    public int hashCode() {
        return Objects.hash(identificador);
    }

    @Override
    public String toString() {
        return getId() + "(" + getDistancia() + ")";
    }
}

import java.util.*;

public class FilaPrioridade { private final List elementos = new ArrayList<>();

public void inserir(Vertice v) {
    // Remove instância anterior, se existir
    elementos.removeIf(x -> x.getId().equals(v.getId()));
    
    // Insere mantendo ordem crescente por distância
    int posicao = 0;
    while (posicao < elementos.size() && 
           elementos.get(posicao).getDistancia() <= v.getDistancia()) {
        posicao++;
    }
    elementos.add(posicao, v);
}

public Vertice extrairMinimo() {
    return elementos.isEmpty() ? null : elementos.remove(0);
}

public boolean estaVazia() {
    return elementos.isEmpty();
}

public void imprimir() {
    elementos.forEach(System.out::print);
    System.out.println();
}

}


</div>A implementação sequencial do algoritmo inicializa todas as distâncias com infinito (representado por `Integer.MAX_VALUE`), exceto a fonte, cuja distância é zero. Em seguida, itera enquanto houver vértices na fila: extrai o vértice de menor distância estimada e relaxa todas as suas arestas de saída, atualizando distâncias e reinserindo vértices modificados na fila:

<div>```
package br.infra.algoritmos.dijkstra;

import br.infra.algoritmos.grafo.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class DijkstraSequencial {
    public void executar(Grafo grafo, Vertice origem) {
        Map<vertice integer=""> distancias = new HashMap<>();
        FilaPrioridade fila = new FilaPrioridade();

        // Inicialização
        for (Vertice v : grafo.obterVertices()) {
            int dist = v.equals(origem) ? 0 : Integer.MAX_VALUE;
            v.definirDistancia(dist);
            distancias.put(v, dist);
            if (dist != Integer.MAX_VALUE) {
                fila.inserir(v);
            }
        }

        // Loop principal
        while (!fila.estaVazia()) {
            Vertice atual = fila.extrairMinimo();
            int distAtual = atual.getDistancia();

            for (Map.Entry<vertice integer=""> vizinho : 
                 grafo.obterVizinhos(atual).entrySet()) {

                Vertice proximo = vizinho.getKey();
                int pesoAresta = vizinho.getValue();
                int novaDistancia = distAtual + pesoAresta;

                if (novaDistancia < proximo.getDistancia()) {
                    proximo.definirDistancia(novaDistancia);
                    fila.inserir(proximo);
                }
            }
        }

        // Saída formatada
        grafo.obterVertices().forEach(v -> 
            System.out.printf("%s → %d%n", v.getId(), v.getDistancia()));
    }

    public static void main(String[] args) throws IOException {
        Grafo g = Grafo.carregarDeArquivo("dados/grafo.txt");
        DijkstraSequencial dijkstra = new DijkstraSequencial();
        dijkstra.executar(g, Vertice.novo("A"));
    }
}</vertice></vertice>

Tags: dijkstra graph-algorithms mapreduce hadoop java

Publicado em 10-25 18:01