为防止广告,目前nocow只有登录用户能够创建新页面。如要创建页面请先登录/注册(新用户需要等待1个小时才能正常使用该功能)。

Dijkstra算法

来自NOCOW
跳转到: 导航, 搜索
 这篇文章可以证实是由NOCOW用户原创,不存在任何版权争议。
 本文作者同意以GNU FDLCC-by-saGNU LGPL(如果适用)三种版权发布此文章(不包括翻译文章中属于原始所有者的部分版权)。
 如果你修改了这篇文章并且不同意用GNU FDL以外的版权发布,可以换一个版权模板或者移除此模板。

Dijkstra算法是一种求单源最短路算法,即从一个点开始到所有其他点的最短路。其基本原理是:每次新扩展一个距离最短的点,更新与其相邻的点的距离。当所有边权都为正时,由于不会存在一个距离更短的没扩展过的点,所以这个点的距离永远不会再被改变,因而保证了算法的正确性。不过根据这个原理,用Dijkstra求最短路的图不能有负权边,因为扩展到负权边的时候会产生更短的距离,有可能就破坏了已经更新的点距离不会改变的性质。

如果用本算法求一个图中全部的最短路,则要以每个点为源调用一次Dijkstra算法。

目录

[编辑] 适用条件与限制

  • 有向图无向图都可以使用本算法,无向图中的每条边可以看成相反的两条边。
  • 用来求最短路的图中不能存在负权边。(可以利用拓扑排序检测)

[编辑] 算法流程

在以下说明中,s为源,w[u,v]为点u和v之间的边的长度,结果保存在dist[]

  1. 初始化:源的距离dist[s]设为0,其他的点距离设为无穷大,同时把所有的点的状态设为没有扩展过。
  2. 循环n-1次:
    1. 在没有扩展过的点中取一距离最小的点u,并将其状态设为已扩展。
    2. 对于每个与u相邻的点v,执行Relax(u,v),也就是说,如果dist[u]+w[u,v]<dist[v],那么把dist[v]更新成更短的距离dist[u]+w[u,v]。此时到点v的最短路径上,前一个节点即为u。
  3. 结束。此时对于任意的u,dist[u]就是s到u的距离。

[编辑] 算法实现

  function [min,path]=dijkstra(w,start,terminal)
  n=size(w,1); label(start)=0; f(start)=start;
  for i=1:n
     if i~=start
        label(i)=inf;
     end
  end
  s(1)=start; u=start;
  while length(s)<n
     for i=1:n
        ins=0;
        for j=1:length(s)
           if i==s(j)
              ins=1;
           end
        end
        if ins==0
           v=i;
           if label(v)>(label(u)+w(u,v))
              label(v)=(label(u)+w(u,v)); f(v)=u;
           end
        end
     end
     v1=0;
     k=inf;
     for i=1:n
        ins=0;
        for j=1:length(s)
           if i==s(j)
              ins=1;
           end
        end
        if ins==0
           v=i;
           if k>label(v)
              k=label(v);  v1=v;
           end
        end
     end
     s(length(s)+1)=v1;  
     u=v1;
  end
  min=label(terminal); path(1)=terminal;
  i=1; 
  while path(i)~=start
     path(i+1)=f(path(i));
     i=i+1 ;
  end
  path(i)=start;
  L=length(path);
  path=path(L:-1:1);

调用

  edge= [ 2,3,1,3,3,5,4, 4,1,7,6,6,5, 5,11, 1,8,6,9,10,8,9, 9,10;...
             3,4,2,7,5,3,5,11,7,6,7,5,6,11, 5, 8,1,9,5,11,9,8,10,9;...
             3,5,8,5,6,6,1,12,7,9,9,2,2,10,10,8,8,3,7, 2, 9,9, 2, 2];
  n=11; weight=inf*ones(n, n);
  for i=1:n
     weight(i, i)=0;
  end
  for i=1:size(edge,2)
     weight(edge(1, i), edge(2, i))=edge(3, i);
  end
  [dis, path]=dijkstra(weight, 1, 11)

[编辑] 优化

最简单的实现方法就是,在每次循环中,再用一个循环找距离最短的点,然后用任意的方法更新与其相邻的边,时间复杂度显然为O\left(n^2\right)

对于空间复杂度:如果只要求出距离,只要n的附加空间保存距离就可以了(距离小于当前距离的是已访问的节点,对于距离相等的情况可以比较编号或是特殊处理下)。如果要求出路径则需要另外V的空间保存前一个节点,总共需要2n的空间。

[编辑] 二叉堆实现

使用二叉堆(Binary Heap)来保存没有扩展过的点的距离并维护其最小值,并在访问每条边的时候更新,可以把时间复杂度变成O\left(\left|E\right|+\left|V\right|\log\left|V\right|\right)

邻接表保存边,使得扩展边的总复杂度为O(E),否则复杂度不会减小。

空间复杂度:这种算法需要一个二叉堆,及其反向指针,另外还要保存距离,所以所用空间为3V。如果保存路径则为4V。

具体思路:先将所有的点插入堆,并将值赋为极大值(maxint/maxlongint),将原点赋值为0,通过松弛技术(relax)进行更新以及设定为扩展。

[编辑] 程序

二叉堆实现_Pascal

二叉堆实现_C

二叉堆实现_C++
队列优化_pascal

[编辑] 扩展

[编辑] 第k短路

当k比较小时,可以直接在每个点保存k条最短路。更新的时候对每条能更新的路都更新一遍。此时每次更新的代价相当于把两个长度为k的表合并在一起,所以复杂度为纯Dijkstra实现的复杂度×O(k)。


曹氏短边法:每次将任意一条边赋值为MAX,重复计算数次后得到k短路径。

[编辑] 练习

Sweet Butter(USACO 3.2.6)

个人工具