Можем ли мы изменить алгоритм Дейкстры для работы с отрицательными весами?

псевдокод, взятый из Википедии:

function Dijkstra(Graph, source):
 2      for each vertex v in Graph:                                // Initializations
 3          dist[v] := infinity ;                                  // Unknown distance function from source to v
 4          previous[v] := undefined ;                             // Previous node in optimal path from source
 5      end for ;
 6      dist[source] := 0 ;                                        // Distance from source to source
 7      Q := the set of all nodes in Graph ;                       // All nodes in the graph are unoptimized - thus are in Q
 8      while Q is not empty:                                      // The main loop
 9          u := vertex in Q with smallest distance in dist[] ;    // Start node in first case
10          if dist[u] = infinity:
11              break ;                                            // all remaining vertices are inaccessible from source
12          end if ;
13          remove u from Q ;
14          for each neighbor v of u:                              // where v has not yet been removed from Q.
15              alt := dist[u] + dist_between(u, v) ;
16              if alt < dist[v]:                                  // Relax (u,v,a)
17                  dist[v] := alt ;
18                  previous[v] := u ;
19                  decrease-key v in Q;                           // Reorder v in the Queue
20              end if ;
21          end for ;
22      end while ;
23      return dist[] ;
24  end Dijkstra.

теперь, в строке 14 мы видим, что релаксация применяется только к соседям u, которые еще не были удалены от Q. Но если взять и соседей u, которые были изъяты из Q, мне кажется, что алгоритм тут работа с отрицательными весами. Я не нашел ни одного случая, который противоречил бы этому утверждению.

Итак, почему алгоритм Дейкстры не изменен способ?

6 ответов


Dijkstra может позволить себе посетить каждый узел один и один раз, потому что, когда он выбирает новый узел для посещения, он выбирает не посещаемый узел, который имеет самый короткий путь от корня. Как следствие, он может с уверенностью предположить, что нет более короткого пути к этому узлу через другой не посещаемый узел (потому что, если лучший способ, который вы знаете от A до B, стоит 2, а лучший способ от A до C стоит 3, нет шансов найти лучший способ от A до B, например A>C>B).

, Если вы добавите отрицательный Весы, вы внезапно нарушаете это предположение.

вы, конечно, можете использовать предлагаемую модификацию, но тогда вы потеряете преимущество посещения каждого узла только один раз ; и, таким образом, он потеряет свою производительность по сравнению с другими алгоритмами, такими как Ford-Bellman


вы, безусловно, можете сделать алгоритм Дейкстры работа с отрицательными значениями, просто убедившись, что вы не пересекаете какой-либо узел или край дважды. Вот, к работа, Я имею в виду прекратить. Однако результат может быть не оптимальным.

алгоритм Дейкстры имеет жадный смысл. Представьте себе следующий график:

A --- 1 --- B
|           |
3          -4
|           |
C -- -1 --- D

Если мы хотим перейти от A к B, лучшим путем будет A-C-D-B, но алгоритм Дейкстры находит A-B. Вы не можете сделать Dijkstra алгоритм предсказывать будущее потому что это жадный алгоритм. By предсказание будущего Я имею в виду, зная, что позже, стоимость пути может быть уменьшена. Обратите внимание, что это означает, что ваша модификация будет работать неправильно, если она применяется к версии алгоритма Dijkstra, которая завершается, как только назначение видно. В опубликованной версии ваша модификация работает, за исключением более эффективных способов обработки отрицательных краев (см. Примечание.)

в качестве примечания, кратчайший путь в неориентированных графах с отрицательными значениями или направленных графах с отрицательными петлями даже не имеет смысла!


У вас есть в основном два варианта.

  1. вы можете изменить алгоритм, как вы предлагаете. Если у вас есть ориентированный граф без цикла, то это правильный алгоритм, но он может быть очень медленным (потому что вы будете посетить каждый узел много раз). Я думаю, что есть случай, когда этот алгоритм имеет экспоненциальную временную сложность.

  2. вы можете изменить затраты краям, используя потенциал. Каждая вершина имеет некоторый потенциал h (v) и вес для ребра u - >v будет w(u,v) + h(u) - h (v). Обратите внимание, что это не влияет на то, какой путь между заданными двумя вершинами (s, t) является самым коротким, только его стоимость изменяется на h(s) - h(t). Но вам нужно просчитать потенциал. Хороший способ сделать это предлагается здесь:http://en.wikipedia.org/wiki/Johnson s_algorithm


нет, не возможно, как говорится. Алгоритм не делает смысле с отрицательными весами, если вы не сильно ограничиваете тип графика.

предположим, что граф с узлами A, B, C и ребрами с весами AB=-1, BA=0, BC=1.

теперь нет кратчайшего пути между А и С, и вы всегда можете сделать более короткий путь, еще раз пройдя взад и вперед между А и В.

алгоритм все равно будет работать, конечно, но это даст неправильные ответы.


Да, ваша модификация будет работать при 2 предположениях, которые вы не упомянули, но я думаю, были подразумеваемы:

  1. кратчайший путь должен быть фактически определен в вашем входном графике. Если вы отбросите ограничение о неотрицательных весах, вы должны по крайней мере потребовать, чтобы не было циклов с отрицательным весом.
  2. операция "уменьшение ключа v в Q" в строке 19 на самом деле не имеет смысла на узлах v, которые не находятся в Q. но, конечно, вы можете повторно добавить их в Q с новым значением.

однако вы потеряете важную особенность алгоритма Дейкстры: его хорошую асимптотическую производительность в худшем случае. Готовая Dijkstra гарантирует хорошую производительность, потому что она посещает каждый узел и каждый край не более одного раза. С включенным изменением узлы, которые уже были удалены, могут быть повторно добавлены в очередь приоритетов, и, возможно, большие части графика должны быть посещены снова и снова. В худшем случае вам придется выполнить как можно больше релаксации, как, например, алгоритм Беллмана-Форда, но у вас есть дополнительные накладные расходы очереди приоритетов. Это делает вашу худшую производительность хуже, чем Bellman-Ford, что поэтому предпочтительно для графиков с отрицательными ребрами.

Это не означает, что ваша модифицированная Dijkstra не полезна. Он может работать намного лучше, чем Bellman-Ford, если у вас очень мало отрицательных ребер и/или если эти отрицательные ребра отделены от остальной части графика дорогими путями. Но будьте готовы к наихудшему результату.


ну, просто расслабить уже посещенные узлы в модифицированной версии Dijkstra недостаточно (и это даст вам неправильный ответ на графике, содержащем отрицательные ребра). Кроме того, вам нужно поместить любое расслабленное ребро в контейнер(например, очередь приоритетов или просто очередь). Таким образом, вы можете пересмотреть код из строки 19 как:

if v is in priority queue
    then decrease-key(v)
else
    add v into priority queue

в этом случае, ваш алгоритм может сработать. Кроме того, эффективность модифицированной версии Dijkstra не будет снижаться для положительный взвешенный график. Это можно легко доказать, потому что это, естественно, жадный алгоритм.

однако для графов, содержащих отрицательные ребра, новый алгоритм может стать медленным с точки зрения теоретического анализа, но он будет хорошо работать на практике.

на самом деле, вы можете взглянуть на алгоритм под названием SPFA(алгоритм кратчайшего пути быстрее), опубликованный Dingfan Duan в Китае(1994). Многие OIer (Olympic of Info) знают, что этот алгоритм иногда может бить Дейкстра.