ACM集训——初识Dijkstra算法——例HDU_1874

Dijkstra算法:

        

•Dijkstra算法是解单源最短路径问题的贪心算法。


•基本思想:设置顶点集合并不断地作贪心选择来扩充这个集合。

背景及简介:
           戴克斯特拉算法英语Dijkstra's algorithm)是由荷兰计算机科学家艾兹赫尔·戴克斯特拉提出。迪科斯彻算法使用了广度优先搜索解决非负权有向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。举例来说,如果图中的顶点表示城市,而边上的权重表示著城市间开车行经的距离,该算法可以用来找到两个城市之间的最短路径。

该算法的输入:
           一个有权重的有向图 G,以及G中的一个来源顶点S
  
图中各元素表示
         V:表示G 中所有顶点的集合;
        E :表示G中所有边的集合
        两个顶点所形成的有序元素对:表示图中的
       (u, v) :表示从顶点 u 到 v 有路径相连。
         w: E → [0, ∞]  (权重函数) ,边的权重定义。
       w(u, v) :从顶点 u 到顶点 v 的非负权重(weight)。边的权重可以想像成两个顶点之间的距离。任两点间路径的权重,就是该路径上所有边的权重总和。

    在该设定下的算法功能已知有 V 中有顶点 s 及 t,Dijkstra 算法可以找到 s 到 t 的最低权重路径(例如,最短路径)。这个算法也可以在一个图中,找到从一个顶点 s 到任何其他顶点的最短路径。
         
算法地位:对于不含负权的有向图,这是目前已知的最快的单源最短路径算法。

•算法描述
      

       这个算法是通过为每个顶点 v 保留目前为止所找到的从s到v的最短路径来工作的。

       初始时,原点 s 的路径长度值被赋为 0 (d[s] = 0),若存在能直接到达的边(s,m),则把d[m]设为w(s,m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大,即表示我们不知道任何通向这些顶点的路径(对于V 中所有顶点 vs 和上述 md[v] = ∞)。当算法退出时,d[v] 中存储的便是从sv 的最短路径,或者如果路径不存在的话是无穷大。


•基础操作

       边的拓展:如果存在一条从 uv 的边,那么从 sv 的最短路径可以通过将边(u,v)添加到尾部来拓展一条从 s 到 v 的路径。这条路径的长度是 d[u] + w(u, v)。如果这个值比目前已知的 d[v] 的值要小,我们可以用新值来替代当前d[v] 中的值。拓展边的操作一直运行到所有的 d[v] 都代表从 s 到 v 最短路径的花费。这个算法经过组织因而当 d[u] 达到它最终的值的时候每条边(u,v)都只被拓展一次。

        算法维护两个顶点集 S 和 Q。集合 S 保留了我们已知的所有 d[v] 的值已经是最短路径的值顶点,而集合 Q 则保留其他所有顶点。集合S初始状态为空,而后每一步都有一个顶点从 Q 移动到 S。这个被选择的顶点是 Q 中拥有最小的 d[u] 值的顶点。当一个顶点 u 从 Q 中转移到了 S 中,算法对每条外接边 (u, v) 进行拓展。

       

伪代码:
 
           1.

       S     :源;

p[ ]   :记录点属于哪个集合:true表示属于Va;false表示属于Vb;

map[ ][ ]:记录图信息,map[u][v]为点到边的长度;

dist[ ]:保存结果;

pre[ ]: 记录最短路径终点的前驱。

(1)  初始化:源的距离dist[s]设为0,其他点的距离设为map[s][i],p[s]=true,其他各点的p[i] = false;

(2)  循环n – 1次:

I.在Vb中的点中取一s到其距离最小的点k,p[k] =true.如果所有k都不可达,退出循环,算法结束。

II.对于每个与k相邻的在Vb中的点j,更新s到j的最短路径。如果dist[k] +map[k][j] < dist[j],那么dist[j] = dis t[k] + map[k][j]。此时到点j的最短路径上,终点j的前一个节点即为k,即pre[j] = k.

           2.  在下面的算法中,u := Extract_Min(Q) 在顶点集合 Q 中搜索有最小的 d[u] 值的顶点 u。这个顶点被从集合 Q 中删除并返回给用户。
 
        
function Dijkstra(G, w, s)
      for each vertex v in V[G]                        // 初始化
           d[v] := infinity                            // 將各點的已知最短距離先設成無窮大
     previous[v] := undefined                         // 各点的已知最短路径上的前趋都未知
     d[s] := 0       // 因为出发点到出发点间不需移动任何距离,所以可以直接将s到s的最小距离设为0
     S := empty set
     Q := set of all vertices
     while Q is not an empty set                      // Dijkstra演算法主體
           u := Extract_Min(Q)
           S.append(u)
          for each edge outgoing from u as (u,v)
              if d[v] > d[u] + w(u,v)             // 拓展边(u,v)。w(u,v)为从u到v的路径长度。
                        d[v] := d[u] + w(u,v)               // 更新路径长度到更小的那个和值。
                      previous[v] := u                    // 紀錄前趨頂點

       

如果我们只对在 st 之间查找一条最短路径的话,我们可以在第9行添加条件如果满足 u = t 的话终止程序。

通过推导可知,为了记录最佳路径的轨迹,我们只需记录该路径上每个点的前趋,即可通过迭代来回溯出 st 的最短路径(当然,使用后继节点来存储亦可。但那需要修改代码):

  s := empty sequence 
  u := t
  while defined u                                        
      insert u to the beginning of S
       u := previous[u]      //previous数组即为上文中的p


现在串行 S 就是从 st 的最短路径的顶点集。


实现方法:
        1.直接实现:
          用一个链表或者数组来存储所有顶点的集合Q,所以搜索Q 中最小元素的运算(Extract-Min(Q))只需要线性搜索Q 中的所有元素。
Codes:
//Dijkstra
const int maxn = 10001;   //点个数
void Dijkstra(int n,int dist[maxn],int map[maxn][maxn],int pre[maxn],int s)
//n个点,dist[i]表示点i到源点s的最短距离,map记录图信息,pre记录前驱、源点、终点
{
    int i,j,k;
    int min;
    bool p[maxn];          //记录该点是否属于Va,不属于Va的点属于Vb
    for(i = 1;i <= n; i++)//初始化
    {
        p[i] = false;
        if(i != s)
        {
            dist[i] = map[s][i];
            pre[i] = s;
        }
    }
    dist[s] = 0;
    p[s] = true;
    for(i = 1;i <= n - 1;i++)//循环n - 1次,求s到其他n - 1个点的最短路径
    {
        min = INT_MAX;
        k = 0;
        for(j = 1;j <= n;j++)//在Vb中的点中取一s到其距离最小的点k
        {
            if(! p[j] && dist[j] < min)
            {
                min = dist[j];
                k = j;
            }
        }
        if(k == 0) return;//如果没有点可以扩展,即剩余的点不可达,返回
        p[k] = true;      //将k从Vb中除去,加入到Va中
        for(j = 1; j <= n;j++)
        {
            //对于每个与k相邻的在Vb中的点j,更新s到j的最短路径
            if(! p[j] && map[k][j] != INT_MAX && dist[j] > dist[k] + map[k][j])
            {
                dist[j] = dist[k] + map[k][j];
                pre[j] = k;
            }
        }        
    }
}


        2.堆优化(堆+优先队列):

         •每次只找离源点距离最小的点u,从u去松弛到其他点的最短距离
         •用堆可以优化掉dijkstra过程中查找离源点距离最小的点的过程
void dijkstra(int src){
    priority_queue <pii,vector<pii>,greater<pii> > q;
    memset(d,-1,sizeof(d));
    d[src] = 0;
    q.push(make_pair(0,src));
    while(!q.empty()){
        while(!q.empty() && q.top().first > d[q.top().second])    q.pop();
        if(q.empty())   break;
        int u = q.top().second;
        q.pop();
        for(int i = first[u];i != -1;i = next[i]){
            if(d[v[i]] == -1 || d[v[i]] > d[u]+w[i]){
                d[v[i]] = d[u]+w[i];
                q.push(make_pair(d[v[i]],v[i]));
            }
        }
    }
}



HDU_1874

畅通工程续

Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 27161    Accepted Submission(s): 9803


Problem Description
某省自从实行了很多年的畅通工程计划后,终于修建了很多路。不过路多了也不好,每次要从一个城镇到另一个城镇时,都有许多种道路方案可以选择,而某些方案要比另一些方案行走的距离要短很多。这让行人很困扰。

现在,已知起点和终点,请你计算出要从起点到终点,最短需要行走多少距离。
 

Input
本题目包含多组数据,请处理到文件结束。
每组数据第一行包含两个正整数N和M(0<N<200,0<M<1000),分别代表现有城镇的数目和已修建的道路的数目。城镇分别以0~N-1编号。
接下来是M行道路信息。每一行有三个整数A,B,X(0<=A,B<N,A!=B,0<X<10000),表示城镇A和城镇B之间有一条长度为X的双向道路。
再接下一行有两个整数S,T(0<=S,T<N),分别代表起点和终点。
 

Output
对于每组数据,请在一行里输出最短需要行走的距离。如果不存在从S到T的路线,就输出-1.
 

Sample Input
   
   
3 3 0 1 1 0 2 3 1 2 1 0 2 3 1 0 1 1 1 2
 

Sample Output
   
   
2 -1
最裸最短路,用 Dijkstra解决妥妥的

谢谢陶昱正学长,谢谢维基百科

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值