zoukankan      html  css  js  c++  java
  • [转]浅谈dijkstra堆优化

    众所周知的,dijkstra是图论算法中求单源最短路的一种简单求法。可能有人会说SPFA比dijkstra要实用,而且可以用于求存在负边权的情况,但是dijkstra有着他的优点——其运行速度上优于SPFA。 (PS.需要堆进行优化。)

    我们先看一道经典(水)题:

        平面上有n个点(n<=100),每个点的坐标均在-10000~10000之间。其中的一些点之间有连线。

    若有连线,则表示可从一个点到达另一个点,即两点之间有通路,通路的距离为两点之间的直线距离。现在的任务是找出从入点到出点之间的最短路程。

    题目很简单,是最短路径的改版。由于dijkstra的算法简单来讲就是搜索和贪心的组合,所以代码可以很轻松得出:

    Var

      a:array[1..100,1..2]of longint;

      visit:array[1..100]of boolean;

      c:array[1..100]of real;

      f:array[1..100,1..100]of real;

      n,i,j,k,x,y,m,s,e:longint;

      min:real;

    Begin

      readln(n);//读入点数n

      for i:=1 to n do

        read(a[i,1],a[i,2]);//a[i,1],a[i,2]分别记录第i点的横坐标和纵坐标

      readln(m);//读入边数m

      fillchar(f,sizeof(f),$ 5f);//f用于记录

      for i:=1 to m do

        begin

          readln(x,y);

          f[x,y]:=sqrt(sqr(a[x,1]-a[y,1])+sqr(a[x,2]-a[y,2]));//使用勾股定理求出两点之间的距离

          f[y,x]:=f[x,y];//无向图,双向记边

        end;

      readln(s,e);//s,e为入点和出点

      for i:=1 to n do

        c[i]:=f[s,i];//为入点进行预处理

      for i:=1 to n-1 do

        begin

          min:=maxlongint;

          k:=0;

          for j:=1 to n do

            if (not visit[j]) and (c[j]

              then begin

                     min:=c[j];

                     k:=j;

                   end;//寻找一个离当前点最近且未被访问过的点

          if k=0

            then break;

          visit[k]:=true;//记录改点已经到达过

          for j:=1 to n do

            if c[k]+f[k,j]

              then c[j]:=c[k]+f[k,j];//记录最小值到f中

        end;

      writeln(c[e]:0:2);//输出到出点的最短路径

    End.

    当然,我们知道,这是非堆优化的dijkstra,其复杂度打到了O(n²),其与SPFA相比毫无优势可言。因此我们要对其进行堆优化,使其的复杂度降到O(n*log(n))。说白了,就是将贪心改成堆,从而使复杂度降到O(log(n))当然,为了进一步优化,我们可以用映射表来开G加速。一经典的最远路径为例题,代码如下:

    Var
      n,m,num,a,b,c,i,j:longint;
      ed,from,val,son,next,intoout,outtoin,dis,d:array[1..2000001]of longint;
      visit:array[1..2000001]of 0..1;
    Procedure swap(var a,b:longint);
      var
        temp:longint;
      begin
        temp:=a;
        a:=b;
        b:=temp;
      end;
    Procedure down(l:longint);
      begin
        while ((l<n)and (d[l]>d[l shl 1+1]))or ((l<n)and (d[l]>d[l shl 1])) do
          if d[l shl 1+1]>d[l shl 1]
            then begin
                   swap(d[l shl 1+1],d[l]);
                   swap(intoout[l],intoout[l shl 1+1]);
                   swap(outtoin[intoout[l]],outtoin[intoout[l shl 1+1]]);
                   l:=l shl 1+1;
                 end
            else begin
                   swap(d[l shl 1],d[l]);
                   swap(intoout[l],intoout[l shl 1]);
                   swap(outtoin[intoout[l]],outtoin[intoout[l shl 1]]);
                   l:=l shl 1;
                 end;
      end;//与普通堆操作相同,只是还要留心对映射表的操作
    Procedure up(l:longint);
      begin
        while(l>1) and(d[l shr 1]>d[l]) do
          begin
            swap(d[l shr 1],d[l]);
            swap(intoout[l],intoout[l shr 1]);
            swap(outtoin[intoout[l]],outtoin[intoout[l shr 1]]);
            l:=l shr 1;
          end;
      end;//与普通堆操作相同,只是还要留心对映射表的操作
    Procedure dijkstra(x:longint);
      var
        i,len,edd:longint;

      begin
        fillchar(d,sizeof(d),$7f);
        fillchar(dis,sizeof(dis),$7f);
        fillchar(visit,sizeof(visit),0);//数组的初始化,不解释
        for i:=1 to n do
          begin
            outtoin[i]:=i;//表示堆外映射堆内
            intoout[i]:=i;//表示堆内映射堆外
           end;//为了加快处理速度,我们可以开一张映射表以代替路径数组的维护
        d[x]:=0;//对入点首先进行拓展
        len:=n;//由于后面需要对点书进行操作与修改,此处用len记录n以方便操作
        up(x);//对现在的堆进行维护PS.本步不可少,即使是本程序中,我们也可以看到,0不一定在第一个位置。
        while (visit[n]=0)and(len>0) do//ps.由于可以理解为求1到n的最短路,因此当第n点被访问时,程序已经结束。当题目大意与本题不符合时,(visit[n]=0)应舍去。
          begin
            edd:=intoout[1];//以当前点为起点,记录拓展花费的最小代价的点
            dis[edd]:=d[1];//将该点记录到最小路径的数组中
            visit[intoout[1]]:=1;//表示该点已经被访问
            j:=son[edd];
            d[1]:=d[len];
            intoout[1]:=intoout[len];
            outtoin[intoout[1]]:=1;
            dec(len);//以上都为删堆的操作
            down(1);//一轮操作完了,接着对堆进行维护
            while j<>0 do
              begin
                if (visit[ed[j]]=0)and(val[j]+dis[edd]<d[outtoin[ed[j]]])
                  then begin
                         d[outtoin[ed[j]]]:=val[j]+dis[edd];
                         up(outtoin[ed[j]]);
                       end;//拓展(更新)到达堆中点的最短路径
                j:=next[j];//对下一条边进行拓展
              end;
          end;
         end;
    Begin
      readln(n,m);//读入点数与边数
      num:=0;//num用于记录边的条数
      for i:= 1 to m do
        begin
          read(a,b,c);//读入a,b,表示两点之间有通路,且路径长度为c
          inc(num);//每读入一条边,边数自然要加一
          from[num]:=a;//from数组用于记录第num条边的出点,相当于是树中的父节点。PS.在经典的最短路径求法中,这步可以不写
          ed[num]:=b;//ed数组用于记录第num条边的终点
          val[num]:=c;//val用于记录第num条边的路径长度
          next[num]:=son[a];
          son[a]:=num;//链表记边时,插入的元素要放在链表(son)首部,同时,将原来的首部放在新元素的后面(next)
          inc(num);//同样的,本题中需要双向记边
          from[num]:=b;
          ed[num]:=a;
          val[num]:=c;
          next[num]:=son[b];
          son[b]:=num;
        end;//那么到这里,读入就完成了。接下来就是dijkstra的主要部分了
      dijkstra(1);//最短路径问题,其实也可以转化为1——n之间的最短路。
      write(dis[n]);//本题也可以转化为1——n之间的最短路,因此只要输出n的最短路即可
    End.

        end;//那么到这里,读入就完成了。接下来就是dijkstra的主要部分了

      dijkstra(1);//最短路径问题,其实也可以转化为1——n之间的最短路。

      write(dis[n]);//本题也可以转化为1——n之间的最短路,因此只要输出n的最短路即可

    End.

  • 相关阅读:
    kerberos和hadoop的集成
    kerberos的原理
    Impala与Hive的异同
    为什么hdfs不适合存储小文件
    猛犸数据质量
    CIO的发展之路
    企业数据能力测评:认清现状,布局未来
    企业架构的演变
    其实,他只是个没长大的孩子【复制知乎
    看着自己之前写的便签,想笑
  • 原文地址:https://www.cnblogs.com/kirai/p/4940978.html
Copyright © 2011-2022 走看看