Dijkstra的算法:为什么需要在队列中找到最小距离元素

时间:2013-01-04 14:53:25

标签: c algorithm graph shortest-path dijkstra

我编写了Dijksta算法的这个实现,它在循环while Q is not empty的每次迭代中,而不是找到队列的最小元素,而是占据队列的头部。

这是我写的代码

#include <stdio.h>
#include <limits.h>

#define INF INT_MAX
int N;
int Dist[500];
int Q[500];
int Visited[500];
int Graph[500][500];

void Dijkstra(int b){
     int H = 0;
     int T = -1;
     int j,k;

Dist[b] = 0;

Q[T+1] = b;
T = T+1;

while(T>=H){
    j = Q[H];
    Visited[j] = 1;
    for (k = 0;k < N; k++){
        if(!Visited[k] && Dist[k] > Graph[j][k] + Dist[j] && Graph[j][k] != -1){
            Dist[k] = Dist[j]+Graph[j][k];
            Q[T+1] = k;
            T = T+1;
        }
    }

    H = H+1;
}
}  

int main(){

int src,target,m;
int a,w,b,i,j;

scanf("%d%d%d%d",&N,&m,&src,&target);

for(i = 0;i < N;i ++){
    for(j = 0;j < N;j++){
        Graph[i][j] = -1;
    }
}

for(i = 0; i< N; i++){
    Dist[i] = INF;
    Visited[i] = 0;
}


for(i = 0;i < m; i++){
    scanf("%d%d%d",&a,&b,&w);
    a--;
    b--;
    Graph[a][b] = w;
    Graph[b][a] = w;
}

Dijkstra(src-1);


if(Dist[target-1] == INF){
    printf("NO");
}else {
    printf("YES\n%d",Dist[target-1]);
}

return 0;
}

我为我找到的所有测试案例运行了这个,并给出了正确的答案 我的问题是为什么我们需要找到最小值?谁能解释一下用简单的英语?此外,我需要一个测试用例,证明我的代码错误。

4 个答案:

答案 0 :(得分:3)

看一下这个样本:

1-(6)-> 2 -(7)->3
  \          /
   (7)     (2)
     \    /
       4

即。边缘长度为6从1到2,边缘长度7从2到3,边缘长度7从1到4,边缘从4到3.我相信你的算法会认为从1到3的最短路径长度为13通过2,实际上最好的解决方案是长度为9到4。

希望这说清楚。

编辑:抱歉,这个例子没有制作代码。看看这个:

8 9 1 3
1 5 6
5 3 2
1 2 7
2 3 2
1 4 7
4 3 1
1 7 3
7 8 2
8 3 2

您的输出为Yes 8。虽然路径1->7->8->3仅占用7.此处是ideone

上的链接

答案 1 :(得分:2)

我认为您的代码具有错误的时间复杂性。您的代码(几乎)比较所有节点对,它们具有二次时间复杂度。

尝试添加10000个具有10000个边的节点,并查看代码是否可以在1秒内执行。

答案 2 :(得分:0)

始终必须找出具有最小距离的未访问顶点,否则您将至少获得一个 边缘错误。例如,考虑以下情况

4 4
1 2 8
2 4 5
1 3 2
3 2 1

             (8)   (5)
           1-----2----4
            \   /  
          (2)\ / (1) 
              3

我们从vertex 1

开始
distance[1]=0

您访问过vertex 1后,就放松了vertex 2vertex 3 所以现在

distance[2]=8distance[3]=2

在此之后,如果我们不选择最小值,而是选择vertex 2,则会得到

distance[4]=13

,然后选择vertex 3,将显示

distance[2]=3

因此我们以distance[4]=13结尾

distance[4]=8

因此,我们应该在Dijkstra的每个阶段中从未访问的项目中选择最小值,可以使用priority_queue有效地做到这一点。

答案 3 :(得分:0)

如果为以下图形运行算法,则取决于子级的顺序。假设我们正在寻找从1到4的最短路径。

Counter-example

如果您从队列中以1开始,

dist[1] = 0
dist[2] = 21
dist[3] = 0

seen = {1},现在我们用23推送队列时,如果我们从队列中消耗2,它将生成dist[4] = 51seen={1,2},{ {1}} 1 q = [ 2 ,,下一次从队列,3,4]中消耗3时赢了因为它已经在2中,所以不会再次添加到队列中。因此,该算法稍后会将距seen的路径的距离更新为12 + 31 = 43,但是最短路径为32并且位于1->3-5->4上。

让我用代码示例讨论其他一些方面。假设我们有一个1->3->2->4的连接列表,其中节点(u,v,w)的权重为u,指向v的加权和定向边。并如下准备图形和边线:

w

ALGORITHM1-如果未访问,则选择任何要添加的孩子

graph, edges = {i: set() for i in range(1, N+1)}, dict()
for u,v,w in connection_list:
    graph[u].add(v)
    edges[(u,v)] = w

上面已经讨论了这一点,对于1到4之间的最短路径,它将给我们错误的结果43,而不是32。

问题不在于将2重新添加到队列中,然后让我们摆脱q = deque([start]) seen = set() dist = {i:float('inf') for i in range(1, N+1)} dist[start] = 0 while q: top = q.pop() seen.add(top) for v in graph[top]: dist[v] = min(dist[v], dist[top] + edges[(top, v)]) if v not in seen: q.appendleft(v) 和孩子们。

ALGORITHM2-将所有子级再次添加到队列中

seen

在这种情况下可以使用,但是仅适用于此示例。此算法有两个问题,

  1. 我们将再次添加相同的节点,因此对于更大的示例,复杂度将取决于边数while q: top = q.pop() seen.add(top) for v in graph[top]: dist[v] = min(dist[v], dist[top] + edges[(top, v)]) q.appendleft(v) 而不是结点E的数量,对于密集图,我们可以假设{{1} }。
  2. 如果我们在图中添加周期,则由于没有检查要停止,它将永远运行。因此,该算法不适用于循环图。

这就是为什么如果我们通过线性搜索来选择最小的子代,我们必须花费额外的时间来选择最小的子代,结果我们将得到与上述相同的复杂性。但是,如果我们使用优先级队列,则可以将最小搜索减少为V,而不是O(E) = O(N^2)。这是代码的线性搜索更新。

ALGORITHM3-具有线性最小搜索的肮脏Dijkstra算法

O(lgN)

现在,我们知道了我们下次可以记住使用堆来获得最佳Dijkstra算法的思想过程。