美文网首页
最短路径 | 深入浅出Dijkstra算法(二)

最短路径 | 深入浅出Dijkstra算法(二)

作者: 0与1的邂逅 | 来源:发表于2019-05-25 21:56 被阅读0次

    写在前面:

    前面我们说到Dijkstra算法,每次找到离 1 号顶点最近的顶点的时间复杂度是 O(N),可以用优先队列(堆)来优化,使得这一部分的时间复杂度降低到O(logN)

    代码实现:

    【vector+优先队列 优化Dijkstra】
    #include<iostream>
    #include<cstdio>
    #include<vector>// vector
    #include<queue>// priority_queue 
    using namespace std;
    
    const int Maxsize=1e4+5;// 顶点数量的最大值 
    const int INF=0x3f3f3f3f;// 正无穷大 
    
    // 边的结构体 
    struct edge
    {
        int d,v;// d:距离;v:节点(弧头)
        edge(){}
        edge(int a,int b)// 初始化 d 和 v 
        {
            d=a,v=b;
        }
        // 重载"<"运算符,以便更改优先队列的排序规则 
        // 根据"最短距离"d来进行排序 
        bool operator < (const edge&x)const
        {
            if(d==x.d)return v<x.v;
            else return d>x.d;
        }
    };
    
    vector<edge>G[Maxsize];// 图 G 
    int dis[Maxsize];// 距离表 
    int n,m;// n:顶点个数 m:边数 
    int v1,v2,w;// v1->v2,weight==w 
    
    // Dijkstra算法,源点为 s 
    void dijkstra(int s)
    {
        // 初始化dis数组 
        for(int i=0;i<=n;i++)dis[i]=INF;
        dis[s]=0;
        
        priority_queue<edge>que;// 优先队列
        que.push(edge(dis[s],s));// 将起点压入队列 
        
        // 队列非空 
        while(!que.empty())
        {
            // get the min_index 
            edge temp=que.top();
            que.pop();
            
            int v=temp.v;// 顶点 
            if(dis[v]<temp.d)continue;//  
            
            // 遍历顶点v相连的所有边 
            for(int i=0;i<G[v].size();i++)
            {
                edge e=G[v][i];
                if(dis[e.v]>dis[v]+e.d)
                {
                    dis[e.v]=dis[v]+e.d;
                    que.push(edge(dis[e.v],e.v));
                }
            }
        }
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<=n;i++)
        {
            G[i].clear();
        }
        while(m--)
        {
            scanf("%d%d%d",&v1,&v2,&w);
            G[v1].push_back(edge(w,v2));
            G[v2].push_back(edge(w,v1));
        }
        dijkstra(1);
        for(int i=1;i<=n;i++)
        {
            printf("%d ",dis[i]);
        }
        puts("");
    }
    

    【vector实现邻接表+优先队列 (假设边一开始是字符型的,这么假设是为了加点难度)】

    因为顶点是字符型,所以需要用到映射map来解决。

    #include <iostream>
    #include <cstdio>
    #include <queue>
    #include <map>
    #include <vector>
    #include <algorithm>
    #include <cstring>
    using namespace std;
     
    const int N=205;
    const int INF=0x3f3f3f3f;
     
    typedef pair<int,int>pii;// 相当于struct pii
     
    int n,m;// 点,边
    int dis[N];// 距离表 
    string src,ed;// src:起点;ed:终点 
    
    // 用结构体存放边和权值 
    struct edge
    {
        int u;// 起点(弧尾) 
        int v;// 终点(弧头) 
        int w;// 边权 
        edge(){}// 构造函数
        edge(int uu,int vv,int ww):u(uu),v(vv),w(ww){}
    };
     
    map<string,int> M;// 给的点是字符串,map转换成数字 
    vector<edge>G[N];// 图 G 
     
    void init()
    {
        M.clear();// 对于每个case,每次循环对M清空 
        int cnt=0;// 为了给每个字符串key,一个value,value值就是cnt++
        cin>>n>>m;// 输入点数和边数 
        string u,v;// 输入字符型的节点,用于一会输入
        int w;// 每条边的权值 
        
        // 输入每条边的信息 
        for(int i=1;i<=m;i++)
        {
            cin>>u>>v>>w;// 输入边和权值
            // 如果M(map定义的)里面没有这个key,就将他插入将他对应为++cnt值,
            // 如果有,不变,意味着,cnt不变,这时的u仍为原来的value值
            if(!M.count(u))
            {
                // 用make_pair()函数,将key和value(给每个的编号)值插入M(map)
                M.insert(make_pair(u,++cnt));
            }
            //同理,这些都是为了将字符串用数字编号表示
            if(!M.count(v))
            {    
                M.insert(make_pair(v,++cnt));
            }
            // 用struct构造两个方向的边,且用构造函数赋值,两点的编号,和边的权值
            // 从而合成,无向的图-->这样得到两条边的信息
            edge E1(M[u],M[v],w);
            edge E2(M[v],M[u],w);
     
            G[M[u]].push_back(E1);// 建立邻接表,用vector表示,将这一条边E1压入邻接表
            G[M[v]].push_back(E2);// 另一条边也压入邻接表
        }
        
        cin>>src>>ed;// 输入最终要求的,起点和终点,编号就是M[src],M[ed]
        // 初始化dis,dis里存的是,起点src到各点的最短距离,dis[src]为零,其他的初始化为inf
        for(int i=1;i<=m;i++)
        {
            dis[i]=(i==M[src]?0:INF);
        }
    }
     
    void Dijkstra()
    {
        // 优先队列,用pair,相当于一个新的结构体,有两个属性
        priority_queue<pii,vector<pii>,greater<pii> > que;
        
        // 用make_pair()将起点压入优先队列(即通过pair的两个属性)。距离和编号
        que.push(make_pair(0,M[src]));
        
        // 优先队列不为空 
        while(!que.empty())
        {
            // 声明一个新的pii tmp。让tmp存放优先队列队前的元素,因为是greater,即que.top就是目前队列里最短距离
            pii tmp=que.top();
            // tmp拥有了优先队列里最小值的两个属性分别赋给了tmp.first(距离)和tmp.second(编号)
            que.pop();// 把这个点清掉(开始时,清理的是起点) 
            
            int x=tmp.second;// x存放编号 
            if(tmp.first!=dis[x])// 如果距离和他带的点的距离不一样了,说明这个点处理过了,continue跳过这个点
                continue;
     
            for(int i=0;i<G[x].size();i++)
            {
                int y=G[x][i].v;// y存放邻接表的的v值 
                int w=G[x][i].w;
                if(dis[y]>dis[x]+w)// 如果距离 
                {
                    dis[y]=dis[x]+w;
                    que.push(make_pair(dis[y],y));// 压入队列 
                }
            }
        }
    }
     
    int main()
    {
        int ttt;
        cin>>ttt;
        while(ttt--)
        {
            init();// 初始化,和输入
            Dijkstra();// dijkstra函数
            if(dis[M[ed]]==INF) cout<<"-1"<<endl;// 如果不联通,输出-1
            else cout<<dis[M[ed]]<<endl;//可 以写成,cout<<(dis[M[ed]]==INF?-1:dis[M[ed]]])<<endl;
        }
        return 0;
    }
    

    【数组实现邻接表+优先队列】

    直接放了代码,因为时间关系,就没有自己码一边,有错还望指出。

    
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<queue>
    #define inf 0x7fffffff
    using namespace std;
    const int MAXN = 205;
    int dis[MAXN];
    int u[MAXN],v[MAXN],w[MAXN];
    int first[MAXN],next[MAXN];
    int n,m;
    int src,ed;
    typedef pair<int,int> pii;
    void init()
    {
        scanf("%d%d",&n,&m);
        memset(first,-1,sizeof(first));
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&u[i],&v[i],&w[i]);
            next[i]=first[u[i]];
            first[u[i] ]=i;
            u[i+m]=v[i],v[i+m]=u[i],w[i+m]=w[i];  //无向边,所以加一条反向边
            next[i+m]=first[u[i+m] ];
            first[u[i+m] ]=i+m;
        }
        cin>>src>>ed;
        for(int i=1;i<=n;i++) dis[i]= (i==src? 0:inf);//不要把dis[i]初始化成源点到各点的直接距离,否则会有点没入队。
    }
    void dijkstra()
    {
        priority_queue<pii,vector<pii>,greater<pii> >que;
        que.push(pii(0,src));
        while(!que.empty()){
            pii tmp=que.top();
            que.pop();
            int x = tmp.second;
            if(tmp.first!=dis[x]) continue;  //如果出队的这个元素,他带的dis,和当前的dis不相同,证明这个结点是被处理过的已确定了最短路,
            for(int e=first[x];e!=-1;e=next[e]){   //这种数组式邻接表的遍历
                if(dis[v[e]]>dis[x]+w[e]){
                     dis[v[e] ]=dis[x]+w[e];
                     que.push(pii(dis[v[e] ],v[e]));
                }
            }
        }
     
    }
    int main()
    {
     //   freopen("1.in","r",stdin);
        int _;
        scanf("%d",&_);
        while(_--){
            init();
            dijkstra();
            if(dis[ed]==inf) cout<<"-1"<<endl;
            else cout<<dis[ed]<<endl;
            for(int i=1;i<=n;i++) printf("%d ",dis[i]);//输出dist数组各个值
            putchar('\n');
        }
    }
    

    写在最后:

    参考资料:

    优先队列,在最近的学习中,用的频率挺多的,后面有时间再来仔细研究其实现原理。

    图论是真的难呀,建图可以建一年……慢慢来吧!!!

    相关文章

      网友评论

          本文标题:最短路径 | 深入浅出Dijkstra算法(二)

          本文链接:https://www.haomeiwen.com/subject/hbrrjqtx.html