Por que o algoritmo de Dijkstra não funciona para arestas de peso negativo?

121

Alguém pode me dizer por que o algoritmo de Dijkstra para o caminho mais curto de fonte única assume que as arestas devem ser não negativas.

Estou falando apenas de bordas, não dos ciclos de peso negativo.

Madu
fonte
3
Uma resposta correta com um bom exemplo seria: stackoverflow.com/questions/6799172/…
Amitk

Respostas:

175

Lembre-se que no algoritmo de Dijkstra, uma vez que um vértice é marcado como "fechado" (e fora do conjunto aberto) - o algoritmo encontrou o caminho mais curto para ele e nunca terá que desenvolver este nó novamente - ele assume o caminho desenvolvido para este o caminho é o mais curto.

Mas com pesos negativos - pode não ser verdade. Por exemplo:

       A
      / \
     /   \
    /     \
   5       2
  /         \
  B--(-10)-->C

V={A,B,C} ; E = {(A,C,2), (A,B,5), (B,C,-10)}

Dijkstra de A primeiro desenvolverá C e, mais tarde, não conseguirá encontrar A->B->C


EDITAR uma explicação um pouco mais profunda:

Observe que isso é importante, porque em cada etapa de relaxamento, o algoritmo assume que o "custo" para os nós "fechados" é de fato mínimo e, portanto, o nó que será selecionado a seguir também é mínimo.

A ideia é: se tivermos um vértice aberto de modo que seu custo seja mínimo - adicionando qualquer número positivo a qualquer vértice - a minimalidade nunca mudará.
Sem a restrição de números positivos - a suposição acima não é verdadeira.

Uma vez que "sabemos" cada vértice que foi "fechado" é mínimo - podemos fazer a etapa de relaxamento com segurança - sem "olhar para trás". Se precisarmos "olhar para trás", a Bellman-Ford oferece uma solução recursiva (DP) para fazer isso.

amit
fonte
5
Desculpe, mas não estou recebendo nenhum erro. Primeiro A->Bvai 5 e A->Cvai 2. Depois B->Cvai -5. Portanto, o valor de Cserá o -5mesmo de bellman-ford. Como isso não está dando a resposta certa?
Anirban Nag 'tintinmj'
5
@tintinmj primeiro, Dijkstra irá "fechar" o nó Acom valor 0. Então, ele irá olhar para o nó de valor mínimo, Bé 5 e Cé 2. O mínimo é C, então ele fechará Ccom o valor 2 e nunca olhará para trás, quando depois Bé fechado, não pode modificar o valor de C, pois já está "fechado".
Amit
4
@amit Como o algoritmo de Dijkstra não encontra o caminho A -> B -> C? Ele atualizará primeiro Ca distância para 2 e depois Ba distância para 5. Supondo que em seu gráfico não haja bordas de saída C, não faremos nada durante a visita C(e sua distância ainda é 2). Em seguida, visitamos Dos nós adjacentes de, e o único nó adjacente é C, cuja nova distância é -5. Observe que no algoritmo de Dijkstra, também acompanhamos o pai a partir do qual alcançamos (e atualizamos) o nó e, fazendo isso C, você obterá o pai Be A, em seguida , resultando em um resultado correto. o que estou perdendo?
novembro
12
@amit O problema com o seu raciocínio (eu acho), e eu tenho visto outras pessoas fazendo isso (estranhamente), é que você acha que o algoritmo não reconsiderará nós cuja distância mais curta já foi determinada (e que já terminamos), mas isso não está correto, e é por isso que temos a etapa de "relaxamento" ... Nós iteramos através de todos os nós do gráfico e, para cada um deles, iteramos através dos nós adjacentes, mesmo que qualquer um dos nós adjacentes possa já foram removidos de nossa fila de prioridade mínima, por exemplo.
novembro
10
@amit Verifique esta resposta a uma pergunta semelhante, onde o exemplo realmente faz sentido: stackoverflow.com/a/6799344/3924118
nbro
37

Considere o gráfico mostrado abaixo com a fonte como Vértice A. Primeiro tente executar o algoritmo de Dijkstra nele.

insira a descrição da imagem aqui

Quando me refiro ao algoritmo de Dijkstra em minha explicação, estarei falando sobre o algoritmo de Dijkstra conforme implementado abaixo,

Algoritmo de Dijkstra

Portanto, começando com os valores ( a distância da fonte ao vértice ) inicialmente atribuídos a cada vértice são,

inicialização

Primeiro extraímos o vértice em Q = [A, B, C] que possui o menor valor, ou seja, A, após o qual Q = [B, C] . Nota A tem uma aresta direcionada para B e C, também ambos estão em Q, portanto, atualizamos ambos os valores,

primeira iteração

Agora extraímos C como (2 <5), agora Q = [B] . Observe que C não está conectado a nada, então o line16loop não funciona.

segunda iteração

Finalmente extraímos B, após o qual Q é Phi. Nota B tem um bordo dirigido para C mas não C está presente em Q, por conseguinte, mais uma vez não entram no loop for em line16,

3º?

Então acabamos com as distâncias como

sem mudança pessoal

Observe como isso está errado, pois a distância mais curta de A a C é 5 + -10 = -5, quando você vai a para b para c.

Portanto, para este gráfico, o Algoritmo de Dijkstra calcula erroneamente a distância de A a C.

Isso acontece porque o algoritmo de Dijkstra não tentar encontrar um caminho mais curto para vértices que são já extraídos Q .

O que o line16loop está fazendo é pegar o vértice u e dizer "ei, parece que podemos ir para v da fonte via u , essa distância (alt ou alternativa) é melhor do que a dist [v] atual que obtivemos? Se sim, vamos atualizar dist [v] "

Note-se que no line16que verificar todos vizinhos v (isto é, um bordo dirigido existe a partir de u para v ), de L que estão ainda em Q . Em line14eles removem notas visitada de Q. Então, se x é um vizinho visitada de u , o caminho fonte para u para xé nem mesmo considerado como um possível caminho mais curto da origem para o v .

Em nosso exemplo acima, C era um vizinho visitado de B, portanto, o caminho A para B para Cnão foi considerado, deixando o caminho mais curto atual A a Cinalterado.

Isso é realmente útil se os pesos das arestas forem todos números positivos , porque então não perderíamos nosso tempo considerando caminhos que não podem ser mais curtos.

Portanto, digo que ao executar esse algoritmo se x for extraído de Q antes de y , não será possível encontrar um caminho - não é possivelque é mais curto. Deixe-me explicar isso com um exemplo,

Como y acabou de ser extraído e x foi extraído antes de si mesmo, então dist [y]> dist [x], caso contrário, y teria sido extraído antes de x . ( line 13distância mínima primeiro)

E como já assumimos que os pesos das arestas são positivos, ou seja, comprimento (x, y)> 0 . Portanto, a distância alternativa (alt) por meio de y é sempre maior, ou seja, dist [y] + comprimento (x, y)> dist [x] . Portanto, o valor de dist [x] não teria sido atualizado mesmo se y fosse considerado um caminho para x , portanto, concluímos que faz sentido considerar apenas os vizinhos de y que ainda estão em Q (observe o comentário em line16)

Mas isso depende de nossa suposição de comprimento positivo da aresta, se comprimento (u, v) <0, então, dependendo de quão negativa é essa aresta, podemos substituir dist [x] após a comparação em line18.

Portanto, qualquer cálculo dist [x] que fizermos será incorreto se x for removido antes de todos os vértices v - de modo que x é um vizinho de v com aresta negativa conectando-os - sejam removidos.

Porque cada um desses v vértices é o segundo último vértice em um caminho "melhor" potencial da origem até x , que é descartado pelo algoritmo de Dijkstra.

Portanto, no exemplo que dei acima, o erro foi porque C foi removido antes de B ser removido. Enquanto aquele C era vizinho de B com uma borda negativa!

Só para esclarecer, B e C são vizinhos de A. B tem um único vizinho C e C não tem vizinhos. comprimento (a, b) é o comprimento da aresta entre os vértices a e b.

Aditya P
fonte
2
Como você disse, a melhor maneira de resolver isso é usar o método heapq.heappush após cada comparação. Recuamos a distância atualizada na fila. Sob esta condição, o Dijkstra pode trabalhar com pesos negativos. Tentei e o resultado saiu como 0,5, -5
nosense
1
"a origem do caminho de x para u nem mesmo é considerada"; você quis dizer fonte para ux?
slmatrix de
1
@slmatrix obrigado por perceber isso, sim, eu quis dizer que o caminho da fonte para u até x, porque x é um vizinho de u.
Aditya P
23

O algoritmo de Dijkstra assume que os caminhos só podem se tornar "mais pesados", de modo que se você tiver um caminho de A para B com peso 3 e um caminho de A a C com peso 3, não há como adicionar uma aresta e vá de A para B por meio de C com peso menor que 3.

Essa suposição torna o algoritmo mais rápido do que os algoritmos que precisam levar em consideração pesos negativos.

zmbq
fonte
8

Exatidão do algoritmo de Dijkstra:

Temos 2 conjuntos de vértices em qualquer etapa do algoritmo. O conjunto A consiste nos vértices para os quais calculamos os caminhos mais curtos. O conjunto B consiste nos vértices restantes.

Hipótese indutiva : em cada etapa, assumiremos que todas as iterações anteriores estão corretas.

Etapa indutiva : Quando adicionamos um vértice V ao conjunto A e definimos a distância como dist [V], devemos provar que essa distância é ótima. Se isso não for o ideal, deve haver algum outro caminho para o vértice V que seja de menor comprimento.

Suponha que algum outro caminho passe por algum vértice X.

Agora, como dist [V] <= dist [X], portanto, qualquer outro caminho para V terá pelo menos dist [V] comprimento, a menos que o gráfico tenha comprimentos de borda negativos.

Portanto, para que o algoritmo de dijkstra funcione, os pesos das arestas devem ser não negativos.

Nikunj Banka
fonte
6

Experimente o algoritmo de Dijkstra no gráfico a seguir, supondo que Aseja o nó de origem, para ver o que está acontecendo:

Gráfico

tb-
fonte
6
Desculpe, mas não estou recebendo nenhum erro. Primeira A->Bvontade 1e A->Cvontade 100. Então B->Dvai 2. Então C->Dvai -4900. Portanto, o valor de Dserá o -4900mesmo de bellman-ford. Como isso não está dando a resposta certa?
Anirban Nag 'tintinmj'
9
@tintinmj Se você tiver uma aresta de saída de D, ela será visitada antes que a distância de D diminua e, portanto, não será atualizada depois de ser. Isso resultará em um erro com certeza. Se você considerar D's 2 como a distância final já depois de escanear as bordas de saída, até mesmo este gráfico resulta em um erro.
Christian Schnorr de
@ tb- Desculpe por perguntar depois de tanto tempo, mas estou no caminho certo aqui? Primeiro A->Bserá 1e A->Cserá 100. Em seguida, Bé explorado e definido B->Dpara 2. Então D é explorado porque atualmente ele tem o caminho mais curto de volta à fonte? Eu estaria correto em dizer que se B->Dfosse 100, Cteria sido explorado primeiro? Eu entendo todos os outros exemplos que as pessoas dão, exceto o seu.
Pejman Poh
@PejmanPoh pelo meu entendimento, se B-> D fosse 100, uma vez que A-> C é mais alto na HeapStructure que será usada, o extrato min retornará A-> C primeiro, o que significa que o próximo caminho mais curto encontrado será o caminho para C, depois disso o caminho de C-> D que está com peso -5000 será a escolha óbvia, levando-nos a concluir que o caminho mais curto seria de A-> C-> D e tenho certeza que isso seria ser o comportamento normal. Então, às vezes, quando temos ciclos negativos, ainda podemos obter o valor correto para o caminho mais curto, mas definitivamente nem sempre, este é um exemplo onde não iremos ..
T.Dimitrov
1

Lembre-se de que no algoritmo de Dijkstra, uma vez que um vértice é marcado como "fechado" (e fora do conjunto aberto) - ele assume que qualquer nó originado dele levará a uma distância maior , então, o algoritmo encontrou o caminho mais curto para ele, e nunca terá que desenvolver este nó novamente, mas isso não é verdade no caso de pesos negativos.

vinha
fonte
0

As outras respostas até agora demonstram muito bem por que o algoritmo de Dijkstra não consegue lidar com pesos negativos em caminhos.

Mas a própria questão talvez esteja baseada em uma compreensão errada do peso dos caminhos. Se pesos negativos em caminhos fossem permitidos em algoritmos de pathfinding em geral, você obteria loops permanentes que não parariam.

Considere isto:

A  <- 5 ->  B  <- (-1) ->  C <- 5 -> D

Qual é o caminho ideal entre A e D?

Qualquer algoritmo de localização de caminho teria que fazer um loop contínuo entre B e C, pois isso reduziria o peso do caminho total. Portanto, permitir pesos negativos para uma conexão tornaria qualquer algoritmo pathfindig discutível, talvez exceto se você limitar cada conexão para ser usada apenas uma vez.

Dakkaron
fonte
0

Você pode usar o algoritmo de dijkstra com arestas negativas não incluindo ciclo negativo, mas você deve permitir que um vértice possa ser visitado várias vezes e essa versão perderá sua complexidade de tempo rápido.

Nesse caso, praticamente vi que é melhor usar o algoritmo SPFA que tem fila normal e pode lidar com bordas negativas.

CodingLab
fonte