Todos os produtos
Search
Central de documentação

MaxCompute:Caminho mais curto de fonte única

Última atualização: Jul 20, 2026

O algoritmo de caminho mais curto de fonte única (SSSP) calcula o menor caminho entre um vértice de origem especificado e todos os outros vértices alcançáveis em um grafo. O algoritmo de Dijkstra é um método clássico para resolver o problema SSSP em grafos direcionados.

Funcionamento

O algoritmo de Dijkstra utiliza vertices para atualizar os shortest distance values. Cada vertex mantém seu shortest distance value atual em relação ao source vertex. Quando esse valor muda, o vértice soma o weight de uma edge ao novo valor e envia uma mensagem para notificar seus adjacent vertices. Na iteração seguinte, os adjacent vertices atualizam seus shortest distance values com base nas mensagens recebidas. O processo iterativo termina quando os shortest distance values de todos os vertices não sofrem mais alterações.

  • Inicialização: A distância do vértice de origem s até ele mesmo é 0 (d[s]=0), enquanto a distância de qualquer outro vértice u até s é infinita (d[u]=∞).

  • Iteração: Caso exista uma aresta de u para v, a distância mínima de s até v é atualizada conforme a expressão d[v]=min(d[v], d[u]+weight(u, v)). Esse processo continua até que as distâncias de s para todos os demais vértices se estabilizem.

Nota

Em um grafo direcionado ponderado G=(V,E), podem existir múltiplos caminhos entre um vértice de origem s e um vértice de destino v. O caminho cuja soma dos pesos das arestas seja a menor representa o caminho mais curto de s até v.

Esse algoritmo se encaixa naturalmente no modelo de programação MaxCompute Graph.

Casos de uso

O MaxCompute oferece suporte tanto a grafos direcionados quanto não direcionados. Como a disponibilidade de caminhos depende dos dados de origem e da construção do grafo, os resultados do SSSP podem variar conforme o tipo de grafo utilizado. O grafo direcionado constitui o modelo de dados fundamental no qual todas as computações se baseiam.

Exemplos de código

Os exemplos a seguir demonstram implementações do SSSP para grafos direcionados e não direcionados.

  • Grafo direcionado

    • Defina a classe BaseLoadingVertexResolver. Essa classe é referenciada na classe principal SSSP.

      import com.aliyun.odps.graph.Edge;
      import com.aliyun.odps.graph.LoadingVertexResolver;
      import com.aliyun.odps.graph.Vertex;
      import com.aliyun.odps.graph.VertexChanges;
      import com.aliyun.odps.io.Writable;
      import com.aliyun.odps.io.WritableComparable;
      
      import java.io.IOException;
      import java.util.HashSet;
      import java.util.Iterator;
      import java.util.List;
      import java.util.Set;
      
      @SuppressWarnings("rawtypes")
      public class BaseLoadingVertexResolver<I extends WritableComparable, V extends Writable, E extends Writable, M extends Writable>
              extends LoadingVertexResolver<I, V, E, M> {
        @Override
        public Vertex<I, V, E, M> resolve(I vertexId, VertexChanges<I, V, E, M> vertexChanges) throws IOException {
      
          Vertex<I, V, E, M> vertex = addVertexIfDesired(vertexId, vertexChanges);
      
          if (vertex != null) {
            addEdges(vertex, vertexChanges);
          } else {
            System.err.println("Ignore all addEdgeRequests for vertex#" + vertexId);
          }
          return vertex;
        }
      
        protected Vertex<I, V, E, M> addVertexIfDesired(
                I vertexId,
                VertexChanges<I, V, E, M> vertexChanges) {
          Vertex<I, V, E, M> vertex = null;
          if (hasVertexAdditions(vertexChanges)) {
            vertex = vertexChanges.getAddedVertexList().get(0);
          }
      
          return vertex;
        }
      
        protected void addEdges(Vertex<I, V, E, M> vertex,
                                VertexChanges<I, V, E, M> vertexChanges) throws IOException {
          Set<I> destVertexId = new HashSet<I>();
          if (vertex.hasEdges()) {
            List<Edge<I, E>> edgeList = vertex.getEdges();
            for (Iterator<Edge<I, E>> edges = edgeList.iterator(); edges.hasNext(); ) {
              Edge<I, E> edge = edges.next();
              if (destVertexId.contains(edge.getDestVertexId())) {
                edges.remove();
              } else {
                destVertexId.add(edge.getDestVertexId());
              }
            }
          }
      
          for (Vertex<I, V, E, M> vertex1 : vertexChanges.getAddedVertexList()) {
            if (vertex1.hasEdges()) {
              List<Edge<I, E>> edgeList = vertex1.getEdges();
              for (Edge<I, E> edge : edgeList) {
                if (destVertexId.contains(edge.getDestVertexId())) continue;
                destVertexId.add(edge.getDestVertexId());
                vertex.addEdge(edge.getDestVertexId(), edge.getValue());
              }
            }
          }
        }
      
        protected boolean hasVertexAdditions(VertexChanges<I, V, E, M> changes) {
          return changes != null && changes.getAddedVertexList() != null
                  && !changes.getAddedVertexList().isEmpty();
        }
      }

      Explicação do código:

      • Linha 15: Define BaseLoadingVertexResolver. Esta classe gerencia conflitos durante o carregamento de dados para um grafo direcionado.

      • Linha 18: O método resolve contém a lógica para tratar conflitos. Por exemplo, se um vértice for adicionado duas vezes por meio de duas operações addVertexRequest, ocorre um conflito de carregamento. Resolva esse conflito antes que a computação prossiga.

    • Defina a classe SSSP.

      import java.io.IOException;
      
      import com.aliyun.odps.graph.Combiner;
      import com.aliyun.odps.graph.ComputeContext;
      import com.aliyun.odps.graph.Edge;
      import com.aliyun.odps.graph.GraphJob;
      import com.aliyun.odps.graph.GraphLoader;
      import com.aliyun.odps.graph.MutationContext;
      import com.aliyun.odps.graph.Vertex;
      import com.aliyun.odps.graph.WorkerContext;
      import com.aliyun.odps.io.WritableRecord;
      import com.aliyun.odps.io.LongWritable;
      import com.aliyun.odps.data.TableInfo;
      
      public class SSSP {
        public static final String START_VERTEX = "sssp.start.vertex.id";
      
        public static class SSSPVertex extends
                Vertex<LongWritable, LongWritable, LongWritable, LongWritable> {
          private static long startVertexId = -1;
      
          public SSSPVertex() {
            this.setValue(new LongWritable(Long.MAX_VALUE));
          }
      
          public boolean isStartVertex(
                  ComputeContext<LongWritable, LongWritable, LongWritable, LongWritable> context) {
            if (startVertexId == -1) {
              String s = context.getConfiguration().get(START_VERTEX);
              startVertexId = Long.parseLong(s);
            }
            return getId().get() == startVertexId;
          }
      
          @Override
          public void compute(
                  ComputeContext<LongWritable, LongWritable, LongWritable, LongWritable> context,
                  Iterable<LongWritable> messages) throws IOException {
            long minDist = isStartVertex(context) ? 0 : Long.MAX_VALUE;
            for (LongWritable msg : messages) {
              if (msg.get() < minDist) {
                minDist = msg.get();
              }
            }
            if (minDist < this.getValue().get()) {
              this.setValue(new LongWritable(minDist));
              if (hasEdges()) {
                for (Edge<LongWritable, LongWritable> e : this.getEdges()) {
                  context.sendMessage(e.getDestVertexId(), new LongWritable(minDist + e.getValue().get()));
                }
              }
            } else {
              voteToHalt();
            }
          }
      
          @Override
          public void cleanup(
                  WorkerContext<LongWritable, LongWritable, LongWritable, LongWritable> context)
                  throws IOException {
            context.write(getId(), getValue());
          }
      
          @Override
          public String toString() {
            return "Vertex(id=" + this.getId() + ",value=" + this.getValue() + ",#edges=" + this.getEdges() + ")";
          }
        }
      
        public static class SSSPGraphLoader extends
                GraphLoader<LongWritable, LongWritable, LongWritable, LongWritable> {
          @Override
          public void load(
                  LongWritable recordNum,
                  WritableRecord record,
                  MutationContext<LongWritable, LongWritable, LongWritable, LongWritable> context)
                  throws IOException {
            SSSPVertex vertex = new SSSPVertex();
            vertex.setId((LongWritable) record.get(0));
            String[] edges = record.get(1).toString().split(",");
            for (String edge : edges) {
              String[] ss = edge.split(":");
              vertex.addEdge(new LongWritable(Long.parseLong(ss[0])), new LongWritable(Long.parseLong(ss[1])));
            }
            context.addVertexRequest(vertex);
          }
        }
      
        public static class MinLongCombiner extends
                Combiner<LongWritable, LongWritable> {
          @Override
          public void combine(LongWritable vertexId, LongWritable combinedMessage,
                              LongWritable messageToCombine) throws IOException {
            if (combinedMessage.get() > messageToCombine.get()) {
              combinedMessage.set(messageToCombine.get());
            }
          }
        }
      
        public static void main(String[] args) throws IOException {
          if (args.length < 3) {
            System.out.println("Usage: <startnode> <input> <output>");
            System.exit(-1);
          }
          GraphJob job = new GraphJob();
          job.setGraphLoaderClass(SSSPGraphLoader.class);
          job.setVertexClass(SSSPVertex.class);
          job.setCombinerClass(MinLongCombiner.class);
          job.setLoadingVertexResolver(BaseLoadingVertexResolver.class);
          job.set(START_VERTEX, args[0]);
          job.addInput(TableInfo.builder().tableName(args[1]).build());
          job.addOutput(TableInfo.builder().tableName(args[2]).build());
          long startTime = System.currentTimeMillis();
          job.run();
          System.out.println("Job Finished in "
                  + (System.currentTimeMillis() - startTime) / 1000.0 + " seconds");
        }
      }
      
                                  

      Explicação do código:

      • Linha 19: Define SSSPVertex. Nesta classe:

        • O valor do vértice representa a distância mínima deste vértice até o vértice de origem startVertexId.

        • O método compute() aplica a fórmula iterativa d[v]=min(d[v], d[u]+weight(u, v)) para calcular a distância mínima e atualizar o valor do vértice atual.

        • O método cleanup() grava a distância mínima do vértice atual até o vértice de origem na tabela de saída.

      • Linha 54: Se o Value do vértice atual (o caminho mais curto do vértice até a origem) não mudar, chame o método voteToHalt() por meio do framework para colocar o vértice no estado halt. A computação termina quando todos os vértices entram no estado halt.

      • Linha 71: Define um GraphLoader para carregar os dados do grafo como um grafo direcionado. Ele analisa os registros da tabela, transformando-os em vértices e arestas do grafo, e os carrega no framework. Neste exemplo, o método addVertexRequest carrega as informações dos vértices no contexto de computação do grafo.

      • Linha 90: Define MinLongCombiner. Este componente combina mensagens enviadas para o mesmo vértice, otimizando o desempenho e reduzindo o consumo de memória.

      • Linha 101: A função main define o GraphJob. Ela configura as implementações de Vertex, GraphLoader, BaseLoadingVertexResolver e Combiner, além de definir as tabelas de entrada e saída.

      • Linha 110: Configura a classe BaseLoadingVertexResolver para gerenciar conflitos.

  • Grafo não direcionado

    import com.aliyun.odps.data.TableInfo;
    import com.aliyun.odps.graph.*;
    import com.aliyun.odps.io.DoubleWritable;
    import com.aliyun.odps.io.LongWritable;
    import com.aliyun.odps.io.WritableRecord;
    
    import java.io.IOException;
    import java.util.HashSet;
    import java.util.Set;
    
    public class SSSPBenchmark4 {
        public static final String START_VERTEX = "sssp.start.vertex.id";
    
        public static class SSSPVertex extends
                Vertex<LongWritable, DoubleWritable, DoubleWritable, DoubleWritable> {
            private static long startVertexId = -1;
            public SSSPVertex() {
                this.setValue(new DoubleWritable(Double.MAX_VALUE));
            }
            public boolean isStartVertex(
                    ComputeContext<LongWritable, DoubleWritable, DoubleWritable, DoubleWritable> context) {
                if (startVertexId == -1) {
                    String s = context.getConfiguration().get(START_VERTEX);
                    startVertexId = Long.parseLong(s);
                }
                return getId().get() == startVertexId;
            }
    
            @Override
            public void compute(
                    ComputeContext<LongWritable, DoubleWritable, DoubleWritable, DoubleWritable> context,
                    Iterable<DoubleWritable> messages) throws IOException {
                double minDist = isStartVertex(context) ? 0 : Double.MAX_VALUE;
                for (DoubleWritable msg : messages) {
                    if (msg.get() < minDist) {
                        minDist = msg.get();
                    }
                }
                if (minDist < this.getValue().get()) {
                    this.setValue(new DoubleWritable(minDist));
                    if (hasEdges()) {
                        for (Edge<LongWritable, DoubleWritable> e : this.getEdges()) {
                            context.sendMessage(e.getDestVertexId(), new DoubleWritable(minDist
                                    + e.getValue().get()));
                        }
                    }
                } else {
                    voteToHalt();
                }
            }
    
            @Override
            public void cleanup(
                    WorkerContext<LongWritable, DoubleWritable, DoubleWritable, DoubleWritable> context)
                    throws IOException {
                context.write(getId(), getValue());
            }
        }
    
        public static class MinLongCombiner extends
                Combiner<LongWritable, DoubleWritable> {
            @Override
            public void combine(LongWritable vertexId, DoubleWritable combinedMessage,
                                DoubleWritable messageToCombine) {
                if (combinedMessage.get() > messageToCombine.get()) {
                    combinedMessage.set(messageToCombine.get());
                }
            }
        }
    
        public static class SSSPGraphLoader extends
                GraphLoader<LongWritable, DoubleWritable, DoubleWritable, DoubleWritable> {
            @Override
            public void load(
                    LongWritable recordNum,
                    WritableRecord record,
                    MutationContext<LongWritable, DoubleWritable, DoubleWritable, DoubleWritable> context)
                    throws IOException {
                LongWritable sourceVertexID = (LongWritable) record.get(0);
                LongWritable destinationVertexID = (LongWritable) record.get(1);
                DoubleWritable edgeValue = (DoubleWritable) record.get(2);
                Edge<LongWritable, DoubleWritable> edge = new Edge<LongWritable, DoubleWritable>(destinationVertexID, edgeValue);
                context.addEdgeRequest(sourceVertexID, edge);
                Edge<LongWritable, DoubleWritable> edge2 = new
                        Edge<LongWritable, DoubleWritable>(sourceVertexID, edgeValue);
                context.addEdgeRequest(destinationVertexID, edge2);
            }
        }
    
        public static class SSSPLoadingVertexResolver extends
                LoadingVertexResolver<LongWritable, DoubleWritable, DoubleWritable, DoubleWritable> {
    
            @Override
            public Vertex<LongWritable, DoubleWritable, DoubleWritable, DoubleWritable> resolve(
                    LongWritable vertexId,
                    VertexChanges<LongWritable, DoubleWritable, DoubleWritable, DoubleWritable> vertexChanges) throws IOException {
    
                SSSPVertex computeVertex = new SSSPVertex();
                computeVertex.setId(vertexId);
                Set<LongWritable> destinationVertexIDSet = new HashSet<>();
    
                if (hasEdgeAdditions(vertexChanges)) {
                    for (Edge<LongWritable, DoubleWritable> edge : vertexChanges.getAddedEdgeList()) {
                        if (!destinationVertexIDSet.contains(edge.getDestVertexId())) {
                            destinationVertexIDSet.add(edge.getDestVertexId());
                            computeVertex.addEdge(edge.getDestVertexId(), edge.getValue());
                        }
    
                    }
                }
    
                return computeVertex;
            }
    
            protected boolean hasEdgeAdditions(VertexChanges<LongWritable, DoubleWritable, DoubleWritable, DoubleWritable> changes) {
                return changes != null && changes.getAddedEdgeList() != null
                        && !changes.getAddedEdgeList().isEmpty();
            }
        }
    
        public static void main(String[] args) throws IOException {
            if (args.length < 2) {
                System.out.println("Usage: <startnode> <input> <output>");
                System.exit(-1);
            }
            GraphJob job = new GraphJob();
            job.setGraphLoaderClass(SSSPGraphLoader.class);
            job.setLoadingVertexResolver(SSSPLoadingVertexResolver.class);
            job.setVertexClass(SSSPVertex.class);
            job.setCombinerClass(MinLongCombiner.class);
            job.set(START_VERTEX, args[0]);
            job.addInput(TableInfo.builder().tableName(args[1]).build());
            job.addOutput(TableInfo.builder().tableName(args[2]).build());
            long startTime = System.currentTimeMillis();
            job.run();
            System.out.println("Job Finished in "
                    + (System.currentTimeMillis() - startTime) / 1000.0 + " seconds");
        }
    }
                        

    Explicação do código:

    • Linha 15: Define SSSPVertex. Nesta classe:

      • O valor do vértice representa a distância mínima deste vértice até o vértice de origem startVertexId.

      • O método compute() aplica a fórmula iterativa d[v]=min(d[v], d[u]+weight(u, v)) para calcular a distância mínima e atualizar o valor do vértice atual.

      • O método cleanup() grava a distância mínima do vértice atual até o vértice de origem na tabela de saída.

    • Linha 54: Se o Value do vértice atual (o caminho mais curto deste vértice até a origem) não mudar, o vértice entra no estado halt ao chamar voteToHalt() por meio do framework. A computação termina quando todos os vértices entram no estado halt.

    • Linha 61: Define MinLongCombiner. Este componente combina mensagens enviadas para o mesmo vértice, otimizando o desempenho e reduzindo o consumo de memória.

    • Linha 72: Define um GraphLoader para carregar os dados do grafo como um grafo não direcionado. Ele utiliza addEdgeRequest para carregar a aresta entre dois vértices como uma aresta bidirecional, garantindo que os dados da tabela sejam interpretados como um grafo não direcionado.

      • Linha 80: A primeira coluna representa o ID do vértice de origem.

      • Linha 81: A segunda coluna representa o ID do vértice de destino.

      • Linha 82: A terceira coluna representa o peso da aresta.

      • Linha 83: Crie uma aresta composta pelo ID do vértice de destino e pelo peso da aresta.

      • Linha 84: Faz uma solicitação para adicionar a aresta ao vértice de origem.

      • Linhas 85-87: Cada Record representa uma aresta bidirecional. A lógica das linhas 83 e 84 se repete para a direção inversa.

    • Define SSSPLoadingVertexResolver. Esta classe gerencia conflitos durante o carregamento de dados para um grafo não direcionado. Por exemplo, se a mesma aresta for adicionada duas vezes por meio de duas operações addEdgeRequest, ocorre um conflito de carregamento. Trate arestas duplicadas para garantir a correção da computação.

    • Linha 101: A função main define o GraphJob. Ela configura as implementações de Vertex, GraphLoader, SSSPLoadingVertexResolver e Combiner, além de definir as tabelas de entrada e saída.

Resultados da execução

A saída abaixo resulta da execução do exemplo de código para grafo direcionado. Para mais informações, consulte Develop Graph programs.

vertex    value
1        0
2        2
3        1
4        3
5        2
  • vertex: O vértice atual.

  • value: A distância mínima do vértice atual até o vértice de origem (1).

Nota

Para criar dados para um grafo não direcionado, utilize o ID do vértice de origem, o ID do vértice de destino e o peso da aresta, conforme demonstrado no exemplo de código anterior.

Tutorial

Para obter mais detalhes sobre como implementar os exemplos de código anteriores, consulte Develop Graph programs.