zoukankan      html  css  js  c++  java
  • 图论算法(四)Dijkstra算法

    最短路算法(三)Dijkstra算法

    PS:因为这两天忙着写GTMD segment_tree,所以博客可能是seg+图论混搭着来,另外segment_tree的基本知识就懒得整理了……

    Part 1:Dijkstra算法基本信息

    以下,我们用dis[n]表示1->n的最短路径长度,vis[n]表示n号节点有没有被访问过

    Dijkstra算法基于贪心的思想,每次从dis[ ]数组中取出一个dis[ ]值最小的节点x,把vis[x]标记为true,同时用这个点的所有连边去更新与x相连的点y的dis[ ]值

    其中,更新的条件是这样的:(dis[y]=min(dis[y],dis[x]+x->y)),也就是y的更新后最短路值=min(当前y的最短路值,x的最短路值+x->y的边权)

    每次松弛操作,使得1->x,1->y,x->y这三条边满足三角形不等式,扫完x点的所有边之后,此时,没有被访问过且dis值最小的点的最短路就已经被确定了

    重复取出dis[ ]值最小节点的操作,更新y的值,直到vis[ ]全部为true,也就是所有节点都被访问过。此时,dis数组中dis[i]就是1->i的最短路

    下面给出dijkstra的证明方法(拓展内容):https://www.cnblogs.com/jiangshaoyin/p/9954937.html

    证明可以仅做了解,毕竟OI不是证明能力大赛

    另外,出现负边权的时候,dijkstra算法不能正常工作

    Part 2:优化Dijkstra算法

    还记得吗?前面我们提到了在松弛之前,有一个取出dis数组中最小值的操作,对吧?

    这个操作的复杂度是(O(n))的,再加上用来更新的for循环,整体复杂度就变成了(O(n)(2)())

    显然,(O(n)(2)())的复杂度还不够快,那么考虑怎么优化?

    很容易想到数据结构对吧?这里我们可以使用一个小根堆来实时维护dis中的最小值和这个最小值所对应的编号,取得最小值所在节点编号的复杂度降低到了(O(logn))

    这样做,使得整体复杂度降低到了(O((m+n)logn))(其中m是边数,n是点数)

    问题又来了,手写小根堆优化会导致码量的暴增,而且容易出错,我们迫切的需要一种简洁,易实现的代码

    当然不,STL中queue头文件为我们提供了priority_queue这样一个大根堆,我们想想,可不可以利用这个大根堆呢?

    想要利用priority_queue,首先要解决两个问题:

    1、我们需要存下两个变量,一个是dis数组中的值,当做第一关键字入堆,还有这个dis值对应的节点编号,这需要开一个结构体

    开结构体又导致另一个问题——priority-queue不知道以谁为关键字入堆排序

    2、把优先队列的大根堆形式变成小根堆

    “做到这些,需要一些奇技淫巧” ——By 一扶苏一 2020.6.28

    解决方法一:重载‘<’运算符

    我们重载‘<’号运算符,让priority_queue一dis值为第一关键字的同时,把原来的大根堆变成小根堆

    代码如下:

    struct node{
    	int sp,num;
    	bool friend operator < (node a,node b){
    		return a.sp>b.sp;
    		priority_queue<node>q;
    	}
    }
    

    这里我们重载了‘<’运算符(PS:对于所有C++STL,需要比较大小的,只要求我们重载‘<’运算符,因为通过‘<’号的定义,可以推至所有别的符号的定义,比如,这里我把‘<’重载为返回sp大的,那么‘>’自然就是返回sp小的),一次性解决了上面的两个问题

    解决方法二:C++内置pair二元组

    我们可以使用C++内置的二元组pair来解决关键字的问题,因为pair默认以第一维为第一关键字

    (PS:pair以第二维为第二关键字,但是这里第二关键字我们并不关心,因为不会影响到dijkstra的结果)

    C++内置pair基本用法如下代码:

    #include<cstdio> 
    #include<iostream>
    using namespace std;
    int main(){
    	pair<int,int>x;//声明二元组的第一维,第二维的数据类型和二元组名字 
    	x=make_pair(1,2);//给二元组赋值,先把“1,2”用make_pair()打包,然后分别赋值给第一维和第二维 
    	int a=x.first,b=x.second;//分别返回二元组x的第一维,第二维 
    	printf("%d %d",a,b);
    }
    

    另外,把大根堆变成小根堆,我们可以在入堆的时候把dis值取反(只是在堆中是相反数,dis值保持原样),这样我们就用pair解决了这两个问题

    Part 3:Dijkstra 模板代码 仅供参考

    #include<cstring>
    #include<cstdio>
    #include<queue>
    using namespace std;
    const int N=100010,M=500010;
    int head[N],ver[M],edge[M],Next[M],d[N];
    bool v[N];
    int n,m,tot,s;
    priority_queue< pair< int,int > >q;//包含pair的优先队列 
    void add(int x,int y,int z){//链式前向星存图 
        ver[++tot]=y;
        edge[tot]=z;
        Next[tot]=head[x];
        head[x]=tot;
    }
    void dijkstra(int x){
        memset(d,0x3f,sizeof(d));//把距离初始化无限大 
        memset(v,0,sizeof(v));//初始化没有访问过 
        d[x]=0;//初始节点距离为0 
        q.push((make_pair(0,x)));//初始节点入堆 
        while(q.size()!=0){
            int x=q.top().second;//访问第二维,也就是节点编号 
            q.pop();//弹出 
            if(v[x]==1) continue;//如果走过了,直接进行下一次 
            v[x]=1;//标记访问过 
            for(int i=head[x];i;i=Next[i]){//链式前向星访问连边 
                int y=ver[i],z=edge[i];
                if(d[y]>d[x]+z){//松弛 
                    d[y]=d[x]+z;//更新最短路 
                    q.push(make_pair(-d[y],y));//把d[y]的相反数入堆,大根堆变小根堆 
                }
            }
        }
    }
    int main(){
        scanf("%d%d%d",&n,&m,&s);//n点m边,s出发点 
        for(int i=1,x,y,z;i<=m;i++){
            scanf("%d%d%d",&x,&y,&z);//读边 
            add(x,y,z);
        }
        dijkstra(s);//求单元最短路 
        for(int i=1;i<=n;i++)
            printf("%d ",d[i]);//输出到每一个节点的距离,如果到不了该节点,输出0x3f3f3f3f
        return 0;
    }
    

    板子题:

    洛谷P4779、洛谷P3371

    PS:P4779板子粘上去就过,P3371板子粘上去再加个231的特判又AC了……

    P4779传送门:https://www.luogu.com.cn/problem/P4779

    P3371传送门:https://www.luogu.com.cn/problem/P3371

    “不要光刷题意相同的题,不要光刷板子题!”——By 一扶苏一 2020.7.2

    所以,建议做完板子题之后,多找找最短路的题目,斟酌一下SPFA、Dijkstra,Floyd用哪个,具体怎么实现,快速完成代码,最好做到不要出错

    到此,图论最短路算法,终于完结。。。

  • 相关阅读:
    element-ui日期筛选:选择日期即触发查询
    js点击按钮复制内容到粘贴板
    axios配置及使用(发起请求时带上token)
    axios + vue导出excel文件
    textarea与标签组合,点击标签填入标签内容,再次点击删除内容(vue)
    vue复制textarea文本域内容到粘贴板
    ElementUI动态表格数据转换formatter
    elementUI图片墙上传
    高德地图模糊搜索地址(elementUI)
    elementUI表单验证
  • 原文地址:https://www.cnblogs.com/zaza-zt/p/13254054.html
Copyright © 2011-2022 走看看