【问题标题】:Why does Dijkstra's algorithm work?为什么 Dijkstra 的算法有效?
【发布时间】:2010-05-18 11:17:27
【问题描述】:

我明白Dijkstra's algorithm 是什么,但我不明白它为什么起作用。

在选择下一个要检查的顶点时,为什么 Dijkstra 算法会选择权重最小的那个?为什么不只是任意选择一个顶点,因为算法无论如何都会访问所有顶点?

【问题讨论】:

  • @APC - 感谢您的链接。根据该 wiki 条目,选择最小的顶点会使 Dijkstra 变得“贪婪”。我想我现在需要看看贪心算法的好处。
  • @BeeBand,“贪婪”一词仅仅意味着它在当前时刻做出了最佳选择。贪婪往往相当直观且易于实现,但并不总是最佳的(但在这种情况下,它是)。
  • 试试 Cormen 的书以获得更好的解释 why 贪心算法是合适的......
  • 不一定会访问所有顶点。在您链接的维基百科页面中,请注意顶部的动画。最右边的顶点永远不会被访问(即使它被认为是访问顶点的邻居)
  • 我认为它不会选择权重最小的边,而是寻找可以提高当前节点与顶点之间距离的边。

标签: algorithm


【解决方案1】:

您可以将 Djikstra 的算法视为注水算法(即修剪后的广度优先搜索)。在每个阶段,目标是用尽可能低的成本路径覆盖整个图的更多部分。假设您在已填充区域的边缘有顶点,并按照距离列出它们:

v0 <= v1 <= v2 <= v3 ...

是否有更便宜的方法可以到达顶点v1?如果是这样,路径必须通过v0,因为没有未测试的顶点可以更接近。因此,您检查顶点 v0 以查看您可以到达的位置,检查通过 v0 的任何路径是否更便宜(到任何其他顶点一步之遥)。

如果您以这种方式解决问题,则可以保证您的距离都是最小值,因为您始终会准确检查可能导致最短路径的那个顶点。要么找到最短路径,要么排除它,然后移动到下一个顶点。因此,您可以保证每一步消耗一个顶点。

而您无需做任何多余的工作就停止了,因为当您的目标顶点占据“我是最小的”v0 槽时,您就停止了。

让我们看一个简短的例子。假设我们试图通过乘法从112,并且节点之间的成本是您必须乘以的数字。 (我们将顶点限制为从112 的数字。)

我们从1 开始,乘以该值可以到达任何其他节点。所以节点2 的成本为23 的成本为3,...12 的成本为12,如果你一步一步来。

现在,如果有从212 的免费链接,则通过2 的路径可以(不了解结构)最快地到达12。没有,但如果有,那将是最快的。所以我们检查2。我们发现我们可以到4 获得成本2,到6 获得3,等等。因此,我们有一个这样的成本表:

3  4  5  6  7  8  9 10 11 12 // Vertex
3  4  5  5  7  6  9  7 11  8 // Best cost to get there so far.

好的,现在也许我们可以免费从3 访问12!更好的检查。我们发现3*2==6 所以6 的成本是3 加上2 的成本,9 是加上312 是加上4

4  5  6  7  8  9 10 11 12
4  5  5  7  6  6  7 11  7

很公平。现在我们测试4,我们看到我们可以访问8 以获得额外的2,并访问12 获得额外的3。同样,到达12 的成本不超过4+3 = 7

5  6  7  8  9 10 11 12
5  5  7  6  8  7 11  7

现在我们尝试56——到目前为止没有任何改进。这给我们留下了

7  8  9 10 11 12
7  6  8  7 11  7

现在,我们第一次看到到达 8 的成本低于到达 7 的成本,所以我们最好检查一下从812 的一些免费方式。没有——用整数根本无法到达那里——所以我们把它扔掉了。

7  9 10 11 12
7  8  7 11  7

现在我们看到12 与剩下的任何路径一样便宜,因此到达12 的成本必须是7。如果我们一直跟踪迄今为止最便宜的路径(仅在绝对更好时才替换路径),我们会发现3*4 是最便宜的访问12 的方式。

【讨论】:

  • 我现在明白了!好的-我现在意识到这是所有答案都试图告诉我的,但这是最清楚的解释,也是导致一分钱下跌的原因。谢谢!
  • 顺便说一句,从 3 到 12 的最佳成本不是变成 7 吗? (3*4=12,但您仍然将最佳成本列为 8...)
  • @Bee:很高兴这有帮助。但我确实说过,最好的成本是 7 到 12(一路上是 8,但当我们测试 4 时切换到 7)。
  • 我想了更多。它仍然无法解释为什么在访问顶点 1 之后,我们访问 2。我们可以简单地说,好吧,让我们访问 4,可能有比我们目前的成本 12 更便宜的方式通过 4 到达 12。我们不'此时不了解结构,是否存在成本为 1 的边缘 的可能性很高?我知道后来我们选择检查4,但是为什么不检查1之后立即选择它呢?仅仅是因为 2 的成本如此之低,我们很有可能通过这个顶点真正将成本提高到 12?
  • 好的,我明白我犯的错误了。我错过的是,该算法正在检查从一个源顶点到所有其他顶点的路径(即使最终它试图找到某个目标顶点的最低成本)。所以我们选择 2 是因为我们可以通过这个节点将当前的成本降低到 3、4、5 ……等等。例如。如果我们可以通过 2 将成本降低到 3,那么我们肯定找到了从源顶点到 3 的成本最低的路径。任何需要经过顶点 3 的路径现在都保证成本最低(无论如何到顶点 3 )。好的,我现在完成了。谢谢!
【解决方案2】:

Dijkstra 的算法选择迄今为止路径成本最低的顶点,因为通过任何其他顶点的路径至少与通过具有最小路径成本的顶点的路径一样昂贵。

因此,如果访问任何其他顶点的成本更高(这很有可能),则不仅需要访问其他顶点,而且还需要访问迄今为止路径成本最低的顶点,因此您将不得不访问更多顶点在找到最短路径之前。事实上,如果你这样做,你最终会得到Bellman-Ford algorithm

我还应该补充一点,顶点没有权重,只有边有权重。给定顶点的关键是到目前为止找到的从源顶点到该顶点的最短路径的成本。

【讨论】:

  • 我还是不明白。当您说“通过任何其他顶点的路径至少同样昂贵”时,您如何定义路径 - 两个顶点 u 和 v 之间的路径?或者 3 个顶点 之间的路径?我认为我遇到的问题是路径 可能比路径 具有更大的权重,尽管 小于 。因此,当 Dijkstra 位于顶点 u 时,它会选择访问 v(假设 是最低权重)。但是我们如何才能认识到从 u 到 w 的最短路径实际上是 而不是 ?没有图表,这很难。
  • @BeeBand - 绘制它并用手玩。你会更快地进入那个“tada”时刻。
  • @Donnie - 好主意。我现在正在工作,但我可以让它看起来像我在工作。
  • @BeeBand,“路径”不同于“边缘”。路径是一系列顶点(以及连接它们的边),而边仅在两个顶点之间。 Dijkstra 的算法使用“路径成本”,即从起始顶点到给定顶点的路径的总成本(与边的成本加上边所源自的节点的键相同)。与 Dijkstra 算法类似的是 Prim 算法,除了在 Prim 算法中,关键是“边缘成本”(简单来说就是边缘的成本,不包括任何之前的成本)。
【解决方案3】:

Dijsktra 的算法之所以如此运作,部分是因为它利用了节点 uw 之间的最短路径(包括点 v)也包含从uv 和从vw 的最短路径。如果在 u 到 v 之间存在更短的东西,那么它就不是最短路径。

要真正理解 Dijkstra 的算法为何有效,请查看 dynamic programming 的基础知识,听起来很难,但理解原理真的很容易。

【讨论】:

  • 谢谢你——我现在完全明白你在这里告诉我的内容了。通过 v 从 u 到 w 的最短路径必须确保它也包括从 u 到 v 的最短路径。我真的不知道我以前为什么会遇到这样的问题:-)
  • 没有问题 - 这是 CS 和 OR 社区之间显着重叠的领域之一。仅仅教授“什么”并不一定能提供了解“如何”和“为什么”的洞察力。
  • 这很有帮助。谢谢。
【解决方案4】:

喜欢贪婪的策略。我的英文不好。google翻译的。如果你不明白,非常抱歉。

Dijkstra算法,一个G从S到所有顶点的最短路径长度。 我们假设 V 中 G 的每个顶点都被赋予了一个标志 L(V),它要么是一个数字,要么是 ∞。假设P是G的顶点集合,P包含S,满足:

A)如果V是P,那么L(V)从V到S的最短路径的长度,并且存在这样的V从S到最短路径:路径在P中的顶点上

B) 如果 V 不属于 P,则从 S 到 V 的 L(V) 满足以下最短路径长度限制: V 是 P 不属于顶点的唯一路径。

我们可以用归纳法证明P Dijkstra算法符合上述集合的定义:

1)当P=1的元素个数时,P对应算法的第一步,P=(S),显然满足。

2)假设P为k,元素个数,P满足上面的定义,见下面第三步的算法

3) P in 和最先找出没有标记U的最小顶点,标记为L(U),可以证明从S到U的U之外的最短路径,除了P不包含元素不属于。

因为如果除了U之外还有其他顶点,那么到S的最短路径,P1,P2...Pn,Q1,Q2...Qn,U(P1,P2...Pn就是P;Q1 ,Q2,...Qn不属于P),从性质B)最短路径长度L(Q1)+PATH(Q1,U)>L(U)。

大于S,P1,P2...Pn,U的通道长度L(U),不是最短路径。因此,从S到U的U的最短路径,除了P不包含不属于U的元素之外,从S到U的最短路径的长度(U)是给定的。

U 以 P' 的形式加到 P 上,显然 P' 符合 A) 的性质。

取V不属于P',显然不属于VP,那么从S到V除了最短路径并且在P'中遇到V以外的所有顶点的路径有两种可能,i)包含U , ii) 不包含 U。

在 i) S, P1, P2 ... Pn, U, V = L (U) + W (U, V)

ii) S, P1, P2 ... Pn, V = L (V)

显然这两个是从S中给出的最小V,以满足最小访问和除了所有顶点之外的所有顶点都是V P'的长度。

第三步算法给出的P'有k+1个元素并满足A)、B)。

由归纳命题可以允许。

这里是the source

【讨论】:

  • 感谢您的回答,很抱歉,但我不明白您所说的“Dijkstra 算法,从 S 到最短路径长度的所有顶点的 G。”是什么意思。我继续阅读,但它仍然没有填补那个空白。那里的翻译可能丢失了什么?
【解决方案5】:

它首先检查权重最低的路径,因为这最有可能(没有额外信息)减少检查的路径数量。例如:

a->b->c 成本为 20 a->b->d 成本为 10 a->b->d->e 成本为 12

如果目标是从 a 到 e,我们甚至不需要检查以下成本:

a->b->c->e

因为我们知道它至少为 20,所以我们知道它不是最优的,因为已经有另一条路径的成本为 12。您可以通过首先检查最低权重来最大化这种效果。这类似于(相同?)极小极大在国际象棋和其他游戏中如何减少博弈树的分支因子。

【讨论】:

    【解决方案6】:

    Dijsktra 算法是一种贪心算法,它遵循问题求解启发式,在每个阶段做出局部最优选择,希望找到全局最优。

    【讨论】:

      【解决方案7】:

      为了理解这个算法的基本概念,我为你写了这段代码。有一个关于它是如何工作的解释。

      graph = {}
      
      graph["start"] = {}
      graph["start"]["a"] = 6
      graph["start"]["b"] = 2
      graph["a"] = {}
      graph["a"]["finish"] = 1
      graph["b"] = {}
      graph["b"]["a"] = 3
      graph["b"]["finish"] = 5
      graph["finish"] = {}
      
      infinity = float("inf")
      costs = {}
      costs["a"] = 6
      costs["b"] = 2
      costs["finish"] = infinity
      print "The weight of each node is: ", costs
      
      parents = {}
      parents["a"] = "start"
      parents["b"] = "start"
      parents["finish"] = None
      
      processed = []
      
      def find_lowest_cost_node(costs):
          lowest_cost = float("inf")
          lowest_cost_node = None
          for node in costs:
              cost = costs[node]
              if cost < lowest_cost and node not in processed:
                  lowest_cost = cost
                  lowest_cost_node = node
          return lowest_cost_node
      
      node = find_lowest_cost_node(costs)
      print "Start: the lowest cost node is", node, "with weight",\
          graph["start"]["{}".format(node)]
      
      while node is not None:
          cost = costs[node]
          print "Continue execution ..."
          print "The weight of node {} is".format(node), cost
          neighbors = graph[node]
          if neighbors != {}:
              print "The node {} has neighbors:".format(node), neighbors
          else:
              print "It is finish, we have the answer: {}".format(cost)
          for neighbor in neighbors.keys():
              new_cost = cost + neighbors[neighbor]
              if costs[neighbor] > new_cost:
                  costs[neighbor] = new_cost
                  parents[neighbor] = node
          processed.append(node)
          print "This nodes we researched:", processed
          node = find_lowest_cost_node(costs)
          if node is not None:
              print "Look at the neighbor:", node
      
      # to draw graph
      import networkx
      G = networkx.Graph()
      G.add_nodes_from(graph)
      G.add_edge("start", "a", weight=6)
      G.add_edge("b", "a", weight=3)
      G.add_edge("start", "b", weight=2)
      G.add_edge("a", "finish", weight=1)
      G.add_edge("b", "finish", weight=5)
      
      import matplotlib.pyplot as plt
      networkx.draw(G, with_labels=True)
      plt.show()
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-08-02
        • 2015-04-24
        • 2012-06-23
        • 2013-11-21
        • 2012-09-10
        • 2020-09-12
        • 1970-01-01
        相关资源
        最近更新 更多