Caminhos Mais Curtos
Problema
O professor Patrick quer descobrir o caminho mais curto de Phoenix a Indianapolis. Dado um mapa dos Estados Unidos (onde a distância entre cada par de interseções adjacentes está marcada), como pode ele determinar o caminho mais curto?
O livro que acompanha a cadeira introduz assim o tema dos caminhos mais curtos em grafos. Como podemos, então, ajudar o professor Patrick (e de forma eficiente)?
Num problema de caminhos mais curtos, é-nos dado um grafo dirigido pesado, , com uma função de pesos que mapeia cada aresta de a um peso numérico. O peso de um dado caminho corresponderá, então, à soma dos pesos de todas as arestas que compõem o caminho, tal que:
O peso do caminho mais curto, dado por , é definido tal que:
O caminho mais curto de para é, portanto, qualquer caminho tal que .
Podemos, claro, notar que a BFS corresponde a um algoritmo deste género, que funciona para grafos não-pesados/com pesos uniformes.
Arcos com peso negativo
Podemos, ao resolver problemas de caminho mais curto, acabar por nos deparar com arcos com peso negativo. Caso um grafo não contenha ciclos de peso negativo atingíveis a partir da fonte, temos que o peso do caminho mais curto entre a fonte e qualquer outro vértice atingível está bem definido (é sempre o mesmo, portanto), pelo que a existência de arcos de peso negativo não influencia o determinismo da solução. Caso contenha pelo menos um ciclo de peso negativo, não o podemos afirmar - tratando-se de um ciclo, podemos percorrê-lo infinitamente. Sendo o seu peso negativo, o "caminho mais curto" será tão mais curto (isto é, menos pesado) quantas mais vezes o percorrermos, chegando, no limite, a . Abaixo podemos encontrar um exemplo do mesmo, onde o caminho de , fonte, ao vértice passa por um ciclo negativo:
O algoritmo de Dijkstra, abordado mais à frente, assume que o grafo argumento não tem ciclos negativos. Pelo contrário, o algoritmo de Bellman-Ford permite que tal ocorra: retorna uma resposta caso o grafo não contenha ciclos de peso negativo, reportando a existência dos mesmos, sem devolver uma resposta, caso contrário.
Caminhos mais Curtos com Fonte Única
Esta secção irá abordar a identificação do caminho mais curto partindo de um vértice-fonte a qualquer vértice . O algoritmo em questão permitirá resolver outros problemas, tais como:
-
Caminhos mais Curtos com Destino Único (corresponde a inverter a direção de cada aresta e aplicar o algoritmo normal de caminhos mais curtos de fonte única).
-
Caminhos mais Curtos entre Par Único (corresponde a identificar o caminho mais curto entre dois vértices).
-
Caminhos mais Curtos entre todos os Pares (corresponde a uma generalização do último, em que encontramos o caminho mais curtos entre cada par de vértices do Grafo).
Serão abordados três algoritmos principais, com use-cases diferentes consoante o grafo em mãos:
-
Algoritmo de Dijkstra, que não permite arcos com peso negativo.
-
Algoritmo de Bellman-Ford, que permite arcos com peso negativo e identifica ciclos negativos.
-
Caminhos mais Curtos em DAGs, que só permite grafos acíclicos.
Representação e Propriedades de Caminhos mais Curtos
Tenhamos , um grafo dirigido e pesado, com função de pesos . Assumimos que o mesmo não contém ciclos com peso negativo atingíveis a partir da fonte. Assim sendo, podemos afirmar que a sua árvore de caminhos mais curtos é um sub-grafo de dirigido, , tal que:
-
corresponde ao conjunto de vértices atingíveis a partir de em .
-
forma uma árvore com raiz em (a fonte).
-
, o único caminho de até em é o caminho mais curto de até em .
Podemos, claro, notar que os caminhos mais curtos não são necessariamente únicos, e, por consequência, nem as árvores de caminhos mais curtos o são. Abaixo temos um exemplo de duas árvores de caminhos mais curtos diferentes:
Temos ainda que todo o sub-caminho de um caminho mais curto é também um caminho mais curto. Tenhamos, por exemplo, o caminho . Seja, ainda, um sub-caminho de , de até . Podemos afirmar que é um caminho mais curto de até em , dado que, caso tal não acontecesse, poderíamos construir um caminho mais curto de a , contradizendo a premissa de ser um caminho mais curto.
Podemos, decompondo um caminho, voltar a definir uma recorrência para o seu peso. Tendo e um caminho tal que pode ser decomposto em e , então :
-
é um caminho mais curto de a .
-
.
Podemos, por fim, afirmar que . Escrevemos em vez de porque pode haver um sub-caminho mais barato de a que não inclua necessariamente o arco , como no exemplo abaixo:
Relaxação
Os algoritmos abordados mais abaixo irão recorrer à operação de relaxação.
É mantido um vector, , com elementos, onde para cada vértice
de , denota a estimativa atual do caminho mais curto de até .
Corresponderá a um majorante do peso do mesmo - isto é, se já sabemos que a melhor
estimativa que temos é aquela, nunca iremos atualizar a mesma caso se trate de um custo mais caro.
O algoritmo começa por inicializar todas as estimativas a , bem como todos os pais de todos os vértices a Nil
:
InitializeSingleSource(G, s) // ocorre em Theta(|V|) tempo
for each v in V[G] do
d[v] := +infinity
pi[v] := Nil
d[s] := 0 // temos, claro, que o custo de s a s é 0
Relaxar uma aresta consiste em verificar se podemos imediatamente melhorar a nossa estimativa atual do custo de a passando pela aresta - caso possamos, atualizamos tanto a nossa estimativa como o pai de :
Relax(G, u, v, w) // ocorre em Theta(1) tempo
if d[v] > d[u] + w(u, v) then
d[v] := d[u] + w(u, v)
pi[v] := u
Abaixo encontramos exemplos de relaxação de arestas:
No caso , tínhamos que tinha estimativa atual e que tinha estimativa atual . Assim, como a aresta que estávamos a ver tinha peso 2, atualizamos a estimativa de para e o pai de para , já que deste modo temos uma estimativa mais barata em . Por outro lado, em , tinha estimativa atual e tinha estimativa atual . Com uma aresta de custo 2, não faz sentido atualizar agora a estimativa de para , pelo que nada acontece.
Propriedades da Relaxação
-
- a estimativa é atualizada ao longo do algoritmo, não podendo, contudo, ser menor que o custo final, seria uma contradição. Mais ainda, quando , nunca se voltará a alterar.
-
Caso não haja um caminho de a , nunca será atualizado, mantendo-se portanto sempre a .
-
Seja um caminho mais curto de a , passando por . Se tivermos num qualquer momento anterior à relaxação de , então .
-
De modo semelhante à propriedade anterior, tendo um caminho mais curto de a , se relaxarmos as arestas de pela ordem , ..., , então . A propriedade mantém-se com quaisquer outras relaxações que se possam fazer posteriormente, dado que estas nunca poderão ter influência - já temos o custo mais baixo, nunca poderá ser mais baixo que isso.
Algoritmo de Dijkstra
O algoritmo de Dijkstra resolve o problema do caminho mais curto de fonte única para um grafo, , pesado (função de pesos ) e dirigido, sem arcos negativos. Mantém um conjunto, , de vértices cujo respetivo já foi calculado.
Trata-se de um algoritmo greedy, cuja escolha greedy corresponde a selecionar sempre o vértice em com menor estimativa de peso mais curto. é adicionado a e todas as arestas que originam de são relaxadas.
O algoritmo tem complexidade temporal no pior caso melhor que a do algoritmo de Bellman-Ford, abordado mais abaixo.
É utilizada uma min priority queue, , para ordenar os vértices de acordo com a sua estimativa de peso mais curto.
Dijkstra(G, w, s)
InitializeSingleSource(G, s) // ocorre em Theta(V) tempo
let S := new array
let Q := new queue(V)
while Q is not empty do // ocorre Theta(V) vezes
let u := extractMin(Q) // V vezes * O(logV) durante o algoritmo
S.add(u)
for each v in Adj[u] do // Theta(E) vezes durante o algoritmo
Relax(G, u, v, w(u, v))
A complexidade temporal total do algoritmo é, portanto, . Temos , não apenas , já que por cada operação de relaxação, com a estimativa a ser atualizada, também temos de atualizar o vértice na queue - até por vértice. Esta operação está implícita no pseudocódigo.
Exemplo - Aplicação do Algoritmo
Tenhamos o grafo abaixo:
Temos, claro, que todos os vértices começam com estimativa , exceto , com estimativa .
O primeiro passo consiste em extrair de , adicionando-o a , e passar por todas as suas adjacências, procurando relaxá-las. Neste caso, como tanto como têm estimativa atual , são ambos necessariamente atualizados, para e , respetivamente. Como a estimativa de ambos é atualizada, também o respetivo pai é - fica pai de ambos. Fica ainda subentendido que, com a atualização das estimativas de cada vértice, a prioridade em de cada um deles é também alterada.
De seguida, é extraído de (sendo uma min priority queue e tendo o custo mínimo dentro dos vértices de , a escolha é natural) e adicionado a . Tem três arestas que dele originam. e são atualizados normalmente, já que a sua estimativa é atualmente (ficando, respetivamente, com pesos e ). é também atualizado, desta feita porque o seu peso atual, , é maior que a estimativa agora proposta, . O pai é também atualizado (agora ).
Com estimativa menor atual associada, é o escolhido para ser extraído de . Tem apenas um arco que dele origina, para , e a estimativa é menor que o seu custo atual, pelo que é atualizado.
É, agora, extraído , que tal como tem apenas um arco que dele sai, para . A sua estimativa é de , pelo que a estimativa de é atualizada.
Por fim, é extraído, não havendo qualquer atualização feita. O grafo resultante fica então assim (as estimativas de cada vértice estão indicadas):
Podemos, no fim, afirmar que o caminho mais curto que se pode fazer entre todos os vértices (com fonte em ) tem este aspeto:
Prova do Invariante do Algoritmo de Dijkstra
Temos, como invariante do algoritmo de Dijkstra, que quando é adicionado a , e que esta igualdade é sempre verdade até ao fim do algoritmo. Procuremos prová-lo (recorrendo ao absurdo):
Vamos então assumir que existe um qualquer vértice tal que não é verdade - isto é, que aquando da inserção de em , a sua estimativa atual de custo não é a menor possível. Temos, claro, que esse vértice nunca poderá ser (pois é o vértice inicial, e o seu custo é invariavelmente zero, dado que estamos a trabalhar apenas com arestas não negativas). Além disso, no momento em que é adicionado a , não está vazio, visto que se e é o primeiro elemento a ser adicionado a , então este não se pode encontrar vazio aquando da inserção de . Por fim, claro, existe necessariamente um qualquer caminho mais curto de a , caso contrário teríamos que .
Vamos, então, supor que é o primeiro vértice tal que aquando da sua inserção em . Além disso, tenhamos que é o caminho mais curto de a . Para , tem que existir um vértice de que ainda não tenha sido inserido em , caso contrário teríamos obrigatoriamente , já que o caminho mais curto teria sido completamente explorado, não havendo margem para dúvida.
Consideremos, então, o arco - um arco que liga dois vértices de , com . Como admitimos anteriormente que é o primeiro vértice em que , temos que . Além disso, temos que , já que foi relaxado assim que foi adicionado a . Temos ainda, necessariamente, que , visto que precede em .
Vamos agora adicionar a . Como , vamos inevitavelmente ter que - ora, mas como , como referido acima, temos que . Além disso, como aparece em antes de , temos que , o que claramente contradiz o pressuposto de que !
Fica, então, provado o invariante do algoritmo - para todo o vértice , aquando da sua inserção em , !
Finalmente, depois de apresentado o algoritmo, podemos mostrar porque é que, aqui, só podemos ter grafos sem arestas negativas:
Aqui, claro, o invariante não se verifica, já que .
Algoritmo de Bellman-Ford
O algoritmo de Bellman-Ford resolve o caso geral do problema dos caminhos mais curtos de fonte única, onde os arcos podem ter peso negativo. Indica, no fim, a existência (ou não) de ciclos negativos, sendo que, caso não existam, pode produzir ainda o caminho mais curto e os custos associados a cada vértice.
O algoritmo em si tem um aspeto bastante mais simples que o de Dijkstra: trata-se de operações de relaxação sucessivas, passando por todas as arestas do grafo vezes, com vista a atualizar gradualmente a estimativa de custo associado a cada vértice. Findas as relaxações, todas as arestas são percorridas, de modo a verificar se há algum ciclo negativo. O pseudocódigo é tal que:
BellmanFord(G, w, s)
InitializeSingleSource(G, s)
for each i in 1 .. |V| - 1 do
for each (u, v) in E do
Relax(G, u, v, w(u, v))
for each (u, v) in E do
if d[v] > d[u] + w(u, v) then
return false
return true
A complexidade temporal do algoritmo é
(de realçar que o está associado a InitializeSingleSource
).
Como podemos notar, o algoritmo de Bellman-Ford é menos eficiente (do ponto de
vista temporal) que o de Dijkstra, pelo que devemos recorrer a Dijkstra caso
tenhamos a certeza que não há arestas negativas, a Bellman-Ford caso contrário.
Lema
Seja um grafo pesado dirigido, com fonte e função de pesos . Assumindo que não há ciclos negativos, após as iterações do primeiro loop do algoritmo de Bellman-Ford, temos que tal que é atingível a partir de .
A prova baseia-se, claro, nas propriedades da relaxação. Comecemos por considerar qualquer vértice atingível a partir de , com (com ) o caminho mais curto de a em . Temos que é simples - tem, no máximo, arcos, já que um caminho mais curto não passará por um vértice mais que uma vez, caso contrário estaríamos necessariamente na presença de um ciclo negativo. Cada arco é relaxado vezes. Temos, então, de procurar provar que , após a iteração do primeiro loop de Bellman-Ford sobre os arcos de , e que nunca se altera.
Provando por indução:
-
, obrigatoriamente, já que = . Temos, claro, que não se altera.
-
assumindo que após a iteração , temos que o arco será necessariamente relaxado na iteração seguinte (já que sabemos que agora o arco "anterior" em já está relaxado), pelo que (e também não se alterará).
Esta prova foi retirada do livro que acompanha a cadeira, encontrando-se na página 673 do mesmo.
Como corolário do lema acima, podemos afirmar que só existe um caminho de a um qualquer vértice caso, finda a execução do algoritmo de Bellman-Ford .
Exemplo da aplicação do algoritmo
Abaixo podemos observar um exemplo da execução completa do algoritmo de Bellman-Ford (passos de a ). A ordem de relaxação das arestas é, em cada iteração do loop, :
Na primeira iteração, temos, claro, que as únicas arestas cuja relaxação provoca diferença nas estimativas são as que saem de , já que são as únicas em que o vértice-fonte não tem estimativa . Na segunda, podemos notar que já há mais alterações. Relaxar provoca alterações em , passando a sua estimativa para (que será já de seguida novamente alterada). Por outro lado, relaxar não provoca alterações ao custo de , já que a sua estimativa é . Relaxar provoca alterações (), tal como (). Nenhuma das outras relaxações provoca diferenças. O resto das iterações do algoritmo não serão aqui detalhadas, visto que os passos a seguir são os mesmos (a diferença é quais as arestas cujo relaxamento nessa vez provoca diferenças).
Podemos, por fim, notar que o algoritmo devolve true
, já que não ocorrem ciclos negativos!
Segundo Lema
Se não contém ciclos negativos, o algoritmo retorna true
, e false
caso contrário. A primeira parte da afirmação é fácil de provar - caso não existam
ciclos negativos, então é impossível que, no fim do algoritmo, seja
menor que , visto que caso isso acontecesse o caminho não seria o mais curto.
A segunda parte, contudo, pode parecer mais difícil à primeira vista.
Procuremos uma prova por contradição, ou seja, admitir que o algoritmo retorna true
mesmo havendo ciclos negativos. Ora, temos que Bellman-Ford só retorna true
caso,
. Dado que todas estas desigualdades
são verificadas no último loop do algoritmo, podemos afirmar que as desigualdades
ao longo do ciclo negativo (não confundir com durante todo o grafo) são dadas por:
Ora, estando perante um ciclo, teremos que (cada vértice ocorre apenas uma vez em cada um dos somatórios). Este último ponto pode ser difícil de passar por escrito, por isso abaixo encontra-se uma imagem que pode ajudar a compreender esta afirmação:
Sendo eles iguais, podemos afirmar que:
Ora, isto claramente contradiz a existência de um ciclo negativo - ocorreria um
ciclo negativo caso o resultado da soma fosse negativo. Conclui-se, então, que o
algoritmo só retorna true
caso não existam ciclos negativos!
Caminhos mais curtos em DAGs
O algoritmo para descobrir o caminho mais curto num DAG é ainda mais simples (e mais eficiente) que os de Dijkstra e Bellman-Ford. Sendo acíclico por definição, não nos teremos de preocupar com arestas negativas - caso estas existam, poderão provocar caminhos mais curtos, mas nunca ciclos negativos (visto que estamos a falar de DAGs).
A pedra basilar do algoritmo é a ordenação topológica dos vértices do grafo - com os vértices assim ordenados, o caminho mais curto será necessariamente relaxado de trás para a frente (seguindo a ordem topológica), pelo que nunca haverá o problema de termos de procurar relaxar arcos mais que uma vez (a prova desta afirmação encontra-se mais abaixo). O pseudocódigo do algoritmo é o seguinte:
DAG_ShortestPath(G, s)
TopologicalSort(G)
InitializeSingleSource(G, s)
for each u in V, taken in topologically sorted order
for each (u, v) in Adj[u]
Relax(G, u, v, w(u, v))
Temos que a complexidade de TopologicalSort
é e que a de InitializeSingleSource
é .
Adicionalmente, o for
loop exterior percorre todos os vértices (), e
cada aresta é percorridas apenas uma vez () ao longo do loop exterior;
tendo em conta que Relax
leva tempo, então a complexidade total é .
Exemplo da Aplicação do Algoritmo
Consideremos o grafo cuja ordenação topológica é a seguinte:
Temos que na figura acima foi já aplicado InitializeSingleSource
. Consideremos
que a ordem de passagem pelos vértices é . As arestas serão, então, relaxadas tal que:
Podemos, no exemplo acima, reparar que o caminho mais curto vai sendo gradualmente relaxado, vértice a vértice. Estando ordenados topologicamente, será impossível que o custo de um dado vértice possa, depois de explorado, vir a ser menor no futuro - os arcos estão de trás para a frente, é impossível voltar atrás. Contudo, nem todas as iterações têm de relaxar necessariamente uma aresta que faça parte do caminho mais curto - se tivéssemos, por exemplo, um vértice que viesse antes de na ordenação topológica, mas continuássemos a considerar como a fonte, a iteração que explorava as arestas que saíam de não contribuiria para o caminho mais curto, já que este vértice não é atingível a partir da fonte. Para provar que no final do algoritmo, observemos o seguinte:
Prova da correção do Algoritmo
Como consideração inicial, podemos notar que se um vértice não é atingível a partir de , então (tal como nos algoritmos de Dijkstra e Bellman-Ford). Suponhamos então que é atingível a partir de , com um caminho mais curto de até . Visto que os vértices serão exploradas pela sua ordem topológica, podemos garantir que os arcos que compõem são relaxados por ordem (). Pela última propriedade abordada na relaxação, podemos garantir que, para todas as iterações do loop do algoritmo, .
Curiosidade
A título de curiosidade, podemos notar que o caminho mais longo entre dois vértices num grafo (assumindo que podemos atingir o vértice-destino a partir da fonte) pode ser obtido ao negar os pesos de todas as arestas, passar os iniciais para e, na operação de relaxação, trocar os por .
O caminho mais longo pode ser particularmente útil para, entre outros, calcular um minorante para a quantidade de tempo/operações que uma dada tarefa vai levar - daí o nome pelo qual também é conhecido, caminho crítico.
Caminhos mais Curtos entre Todos os Pares
Nota
Esta secção tem a coautoria do João Rocha.
Os algoritmos de Dijkstra e Bellman-Ford, abordados acima, permitem-nos encontrar caminhos mais curtos de fonte única. Conhecendo-os, a nossa primeira intuição para descobrir os caminhos mais curtos entre todos os pares de vértices de um grafo poderá apenas passar por aplicar o algoritmo de Dijkstra vezes, com vértice-fonte a alterar para cada aplicação. A ideia não está errada, claro: funciona! Temos, contudo, um problema em mãos - continuamos a ter a limitação estudada acima (Dijkstra requer a ausência de arcos negativos). Será, então, interessante procurar uma solução alternativa que a remova, e é aqui que entra o algoritmo de Johnson, que curiosamente combina os algoritmos de Dijkstra e Bellman-Ford para chegar a este fim.
Algoritmo de Johnson
O algoritmo de Johnson permite-nos justamente remover a limitação acima mencionada. Para tal, usa uma estratégia: a repesagem de Johnson, baseada em Bellman-Ford, e acaba na aplicação de Dijkstra, usando todos os vértices como fonte. De realçar que a repesagem só é requerida caso haja pelo menos um arco negativo.
Durante o decorrer do algoritmo, são calculadas duas matrizes bidimensionais (onde cada dimensão tem tamanho ):
-
, onde corresponde ao peso do caminho mais curto de a ;
-
, onde corresponde ao predecessor de no caminho mais curto de a .
Vamos por partes:
Repesagem de Johnson
Tendo um grafo , a repesagem de Johnson devolve um grafo , onde corresponde a arcos "equivalentes" aos de , porém sem arcos negativos. Queremos, claro, que os caminhos mais curtos em sejam os mesmos que os de .
A intuição poderá levar-nos a pensar que uma maneira possível de chegar a é encontrar a aresta com peso mais negativo, pegar no seu módulo e somá-lo ao peso de todas os arcos de . A estratégia, contudo, não funciona para todos os casos - temos um contraexemplo abaixo:
Podemos observar, então, que esta abordagem acaba por penalizar caminhos mais curtos com mais arcos.
A abordagem correta passa, então, por adicionar um novo vértice, , ao grafo, e conectá-lo com arcos de peso 0 a todo o vértice de . De seguida, aplicar o algoritmo de Bellman-Ford ao grafo para descobrir as alturas de Johnson, , de cada vértice - correspondem ao peso do caminho mais curto de a cada vértice.
Obtidas as alturas de Johnson, temos que o peso de cada arco em , , é dado por*:
No fim, ao remover o vértice e respetivos arcos, ficamos com ! A complexidade temporal da repesagem é, claro, , tal como no usual Bellman-Ford.
* A razão pela qual a repesagem funciona não é trivial à primeira vista. A prova da sua correção encontra-se mais abaixo, pelo que podem preferir ler a mesma antes de ver o exemplo seguinte.
Exemplo da Repesagem de Johnson
Consideremos o grafo abaixo:
Após adicionar o vértice (e respetivas arestas), o grafo fica assim:
Tenhamos, ainda, uma ordem arbitrária de relaxação de arcos, por exemplo .
A primeira iteração de Bellman-Ford dará:
A próxima iteração resulta num grafo igual, pelo que o algoritmo de Bellman-Ford termina aqui. Temos, então, que as alturas de Johnson dos vértices do grafo são:
- ;
- ;
- ;
- ;
Assim sendo, os novos pesos dos arcos de são:
- ;
- ;
- ;
- ;
- ;
Assim, temos que é tal que:
De realçar que nenhum arco tem agora peso negativo!
Por fim, aplicamos Dijkstra a cada um dos vértices do grafo (não mostrado aqui, é igual à aplicação normal de Dijkstra), e obteríamos os caminhos mais curtos no grafo para todos os pares! Cada linha da matriz corresponde, claro, à aplicação de Dijkstra a com como fonte.
A repesagem de Johnson assenta em dois pilares:
-
contém um ciclo negativo se e só se contém um ciclo negativo;
-
Se é um caminho mais curto de a em , então também o é em .
Prova do primeiro ponto
Admita-se que tem um ciclo (em que ). Temos que:
Ou seja, o peso de qualquer ciclo em é igual ao peso de qualquer ciclo em . Desta forma, a existência de um ciclo negativo em qualquer um dos grafos equivale à existência de um ciclo negativo no outro grafo.
Prova do segundo ponto
Resta agora provar o segundo ponto acima proposto. Comecemos por notar que, com , um caminho mais curto em , temos que:
A passagem de para é feita através de somas telescópicas - teríamos .
Suponhamos agora, por contradição, que é um caminho mais curto de a em mas não em . Assim, teríamos um caminho, , que teria peso menor que em a ligar a . Teríamos, então:
Ora, chegámos então a . Tínhamos, contudo, começado por afirmar que é um caminho mais curto de a em . Partindo dessa premissa, não pode haver nenhum caminho que ligue a em , pelo que estamos perante uma contradição, e o segundo ponto fica então provado.
Depois da repesagem de Johnson, o grafo já não apresenta arcos negativos. Podemos, então, proceder à aplicação do algoritmo de Dijkstra em cada vértice para determinar os caminhos mais curtos entre todos os pares de vértices.
Resta, então, notar que a complexidade temporal do algoritmo é , predominando, portanto, a complexidade de Dijkstra pelos vértices.