如果这个更简单,更快的算法可行,为什么我们需要Dijkstra算法?

问题描述

我有一个算法,可以通过BFS迭代整个图形,它将更新分数以找到所有节点的最小值。而且我相信它的运行时复杂度是O(V + E),我认为它比Dijkstra好。现在显然我还不够天真,无法真正认为该算法是正确的。但是,我很好奇在这种情况下,这找不到最佳的最小路径。这是我的代码

from queue import Queue

ad_list = {
    'A': {'B': 1,'D': 3},'B': {'A': 1,'D': 1,'C': 5},'D': {'A': 3,'B': 1,'C': 3},'C': {'B': 5,'D': 3}
}

min_weights = {
    'A': 0,'B': float('inf'),'C': float('inf'),'D': float('inf'),}


def fake_dijkstra(ad_list):
    queue = Queue()
    queue.put('A')

    visited = {}

    global min_weights

    while not queue.empty():
        key = queue.get()

        # update score
        children = ad_list[key]

        for child_key,value in children.items():
            if min_weights[child_key] > min_weights[key] + value:
                min_weights[child_key] = min_weights[key] + value

        if key in visited:
            continue

        visited[key] = True

        children = ad_list[key]

        for child_key,value in children.items():
            queue.put(child_key)


fake_dijkstra(ad_list)
print(min_weights)

# for this case,it correctly finds the min weight to all nodes
{'A': 0,'C': 5,'D': 2}

任何反馈将不胜感激。我喜欢算法:)。

解决方法

您的算法不是 O(| V | + | E |)

考虑一个完全连接的图 G(V,E),其中每个节点都与其他每个节点相连。

在您的算法中,您要将此类图的每个节点添加到队列 | V-1 | 次,将startnode添加到队列 | V | 次。因此,队列中曾经有过的元素总数为 | V |。 x | V-1 | + 1

对于队列中的每个元素,您要遍历其所有 | V-1 | 子级以检查最小权重。

因此,步骤总数为 | V |。 x | V-1 | x | V-1 | 当然是 O(| V |³)

根据队列使用的数据结构,Dijkstra的算法范围从 O(| V |²+ | ​​E |) O(| V | x log | V | + | E |)。对于完全连接的图 | E | = | V |²,因此Dijkstras算法为 O(| V |²)

所以看来您的算法可以找到给定图的最短路径(我不确定100%,但是找不到反例)。但这不是典型的BFS,因为您要多次重新评估节点的子代。这也是Dijkstra算法的区别。因为在那时,对节点X的子代进行了评估(即,您从X-> Y开始),因此可以保证从A到X的路径不短。因此,它位于较低的 O中-复杂度

,

在未加权的图中,BFS确实是获取距离的正确工具。但是,在边长不同的加权图中,它会失败。构造一个自己的图,其中有一条从节点s到节点t的路径:一条路径的节点很少,且边缘较重,另一条路径(较短)的路径则具有许多节点且边缘较轻。看看您的算法能做什么。

,

考虑此图:

 A --- 5 --> B --- 1 --> C --- 1 ---> D
 |           ^
 |           |
  
 1           1

 |           |
 v           |
 E --- 1 --> F

从A到D的最短路径是通过A-> E-> F-> B-> C-> D总距离为5。但是,您的算法不能保证找到它。想象发生以下情况:

首先,我们处理节点A。这将到B和E的距离分别标记为5和1,并将B排入队列,然后将E排队。队列现在为[B,E]。

接下来,我们处理节点B。这将到C的距离标记为6(B在距离5,再加上从边缘到1的距离),然后将C放入队列。队列现在是[E,C]。

接下来,我们处理节点E。这将到F的距离标记为2(E在距离1,再加上从边缘到1的距离),然后使F排队。队列现在为[C,F]。

接下来,我们处理节点C。这将到D的距离标记为7(C在距离6,再加上从边缘到1的距离),然后将D排队。队列现在是[F,D]。

接下来,我们处理节点F。这将到B的距离标记为3(F在距离2,再加上从边缘到1的距离),然后使B排队。队列现在为[D,B]。

接下来,我们处理节点D。这没有效果。队列现在为[B]。

接下来,我们处理节点B。这会将节点C的距离更改为4(B的距离为3,再加上距边缘的1)。但是,由于B已被处理,因此队列未更新。

至此,我们完成了。但是请注意,到D的距离是错误的-我们将D设置为距离7,但实际距离为5。为什么是这样?这是因为当我们沿着距离3的A-> E-> F-> B的路径重新访问B时,我们已经假设B的距离为5时已经处理了B,现在我们已经意识到到B的距离更低不要再有机会跟随B的所有路径去看看会发生什么。我们确实更新了与它紧邻的C,但没有更新与D无关的D。

Dijkstra的算法通过在完成看完从A-> E-> F-> B的路径之前一直不扩展C并意识到对C的原始猜测是不正确的,从而避免了这种情况。这就是为什么按估计的成本顺序而不是按常规的广度优先顺序处理节点很重要的原因。