• 模板 --单源最短路


    模板 --单源最短路

    求最短路一般有两种方法,dij,SPFA;

    大多数情况下最常用并且最稳妥的就是dij,SPFA一般用于判断负权值和负环,并且如果边较多,SPFA容易被卡死。所以一般情况下都是使用dij。

    首先先介绍dij

    dij算法的主要思想是先寻找一个点A1,将这个点并入一个集合,然后找出与这个点相连的点中路径最小的点A2,然后将A2并入集合,将与A2相连点A3的路径加上A1到A2点的路径与A1到A3的路径比较,如果前者小于后者,就将A1到A3路径更新。

    以此类推,直到将所有点加入集合中。我们用一个数组储存点A到所有点的最短路径。

    图的存储也有多种方式,例如二维数组储存(邻接矩阵),C++中STL里的vector储存,还有链式前向星(邻接表)。其中用二维数组储存只能使用与数据范围较小的题,如果数据范围超过1e5,基本都是使用vector和链式前向星储存。

    下面先放一个二维数组储存的代码模板;

    void Dij(int s) // s为起点
    {
        memset(vis, 0, sizeof(vis));
        dis[s]=0;
        for(int i = 1; i <= n; i++)dis[i] = INF;//INF为一个很大的数
        int v,mn;
        for(int i = 1; i <= n; i++)
        {
            mn = INF;
            for(int j = 1; j <= n; j++)//找最小值的点
            {
                if(!vis[j] && dis[j] < mn)
                {
                    mn = dis[j];
                    v = j;
                }
            }
            vis[v] = 1;
            for(int j = 1; j <= n; j++)
            {
                if(!vis[j] && (mn + mp[v][j]) < dis[j])dis[j] = mn + mp[v][j];
            }
        }
    }
    
    

    一般情况下不推荐使用邻接矩阵存图,很容易TLE;

    下面是使用C++STL中的vector存图;

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<string>
    #include<queue>
    #include<stack>
    #include<algorithm>
    #include<vector>
    #include<map>
    #include<cmath>
    #define long long ll;
    using namespace std;
    const int MAXN= 0x3f3f3f3f;
    const int INF =2147483647;
    struct node{
        int v;
        int w;
    };//创建一个结构体,到达点和距离。
    node make_out(int x,int y)
    {
        node t;
        t.v = x;
        t.w = y;
        return t;
    //将到达点和距离存入vector。
    }
    vector<node> q[500001];
    int n, m, s;
    int d[500001];
    int vis[500001];
    void dij(int x)
    {
            memset(vis, 0, sizeof(vis));
             for (int i = 1; i <= n;i++)
           { d[i] = INF;}//初始化赋值为最大值
        for (int i=0; i < q[x].size();i++)
        {
            int h = q[x][i].v;
            d[h] = q[x][i].w;
        }//更新与X到与X相邻点的距离
        d[x] = 0;//到本身的距离为0
        int pos;
        for (int i = 1; i < n; i++)//寻找路程最小的点
        {
            int mm = INF;
            for (int j = 1; j <= n; j++)
            {
                if (!vis[j] && mm > d[j])
                {
                    mm = d[j];
                    pos = j;
                }
            }
            vis[pos] = 1;
            for (int k = 0; k < q[pos].size(); k++)//松弛操作
            {
                int h = q[pos][k].v;
                if (d[h]>q[pos][k].w +d[pos])
                {
                    d[h] = q[pos][k].w +d[pos];
                }
            }
        }
    }
    int main()
    {
        cin >> n >> m >> s;
            for (int i = 1; i <= m; i++)
            {
                int x, y, z;
                cin >> x >> y >> z;
                q[x].push_back(make_out(y, z));
            }
            dij(s);
            for (int i = 1; i <= n;i++)
            {
        
                cout << d[i]<<' ';
            }
            return 0;
    }
    
    
    
    
    
    

    然后就是使用链式前向星,跟Vector一样都是邻接表储存方式。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<string>
    #include<queue>
    #include<stack>
    #include<algorithm>
    #include<vector>
    #include<map>
    #include<cmath>
    using namespace std;
    const int INF= 0x3f3f3f3f;
    const int MAXN = pow(2, 31) - 1;
    int head[500001];//head是储存最新起始点的位置
    int ans = 0;
    queue<int> q;
    struct edge
    {
        int u, v, w, next;//next是储存上一条边的位置
    }e[500001];
    int n, m;
    void add(int u,int v,int w)//链式前向星存图
    {
        e[++ans].next = head[u];
        e[ans].v = v;
        e[ans].w = w;
        head[u] = ans;
    }
    int d[500001];
    int vis[500001];
    void DIJ(int x)
    {
        memset(vis, 0, sizeof(vis));//跟vector类似
        for (int i = 1; i <= n;i++)
        {
            d[i] = MAXN;
        }
            d[x] = 0;
    
            int pos = x;
            while (!vis[pos])//这里的意思是是否访问所有点的
                {for (int i = head[pos]; i != 0;i=e[i].next)
                {
                    int h = e[i].v;//松弛操作
                    if(d[h]>d[pos]+e[i].w)
                    {
                        d[h] = d[pos] + e[i].w;
                    }
                }
                vis[pos] = 1;
                int mn = MAXN;
                for (int j = 1; j <= n; j++)//寻找最大值的点
                {
                    if (!vis[j] && mn > d[j])
                    {
                        mn = d[j];
                        pos = j;
                    }
    
                    }
                    
                }
    }
    int main()
    {
        int s;
        cin >> n >> m>>s;
        for (int i = 1; i <= m;i++)
        {
            int x, y, z;
            cin >> x >> y >> z;
            add(x, y, z);
        }
       DIJ(s);
        for (int i = 1; i <= n;i++)
        {
            cout << d[i] << " ";
        }
            return 0;
    }
    
    
    
    

    以上三种方式都是使用普通的dij,但是有时候会遇到一些数据很大的,dij还是可以优化的,最常用的就是dij的堆优化。

    所谓的堆优化就是使用C++STL中的优先队列来维护dij,dij中有一步是寻找距离最小的点。使用堆优化就可以减少时间复杂度。

    下面放堆优化模板

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<string>
    #include<queue>
    #include<stack>
    #include<algorithm>
    #include<vector>
    #include<map>
    using namespace std;
    const int MAXN= 0x3f3f3f3f;
    struct node{
        int u;
        int s2;
        bool operator <(const node& r)const//重载优先队列,最小堆
        {
            return r.s2 < s2;
        }
    };
    struct MS
    {
        int u, v, w, next;
    }e[200001];
    int head[200001];
    int ans = 0;
    void add(int u,int v,int w)//链式前向星
    {
        e[++ans].next = head[u];
        e[ans].v = v;
        e[ans].w = w;
        head[u] = ans;
    }
    priority_queue<node> q;//优先队列
    int d[200001];
    int n, m,s;
    void dij()
    {
        for (int i = 1; i <= n;i++)
           { d[i] = 2147483647;}
            d[s] = 0;
        q.push((node){s, 0});//将距离和点放入优先队列中
    while(!q.empty())
    {
        node t = q.top();//取堆顶元素,距离一定是最小的
        q.pop();
        int u = t.u;
        int s1 = t.s2;
         if(s1!=d[u])//这步是堆优化的基本操作,防止没更新的进入循环,减少时间复杂度
                continue;
        for (int i = head[u]; i != 0;i=e[i].next)
        {int v1 = e[i].v;
            if (d[v1]>d[u]+e[i].w)//链式前向星基本遍历操作,松弛操作
            {
                d[v1] = d[u] + e[i].w;
                q.push((node){v1, d[v1]});//入队
            }
        }
    }
    }
    int main()
    {
        cin >> n >> m >> s;
        for (int i = 1; i <= m;i++)
        {
            int x, y, z;
            cin >> x >> y >> z;
            add(x, y, z);
        }
        dij();
        for (int i = 1; i <= n;i++)
           { cout << d[i]<<' ';}
            return 0;
    }
    
    
    
    
    
    

    dij算法差不多结束了,接下来就是SPFA.

    SPFA也是一种求单源最短路的算法。

    其实它的思路跟dij差不多,但是不同的是dij的入集合的点是不会再变动,而SPFA中集合中的点一直有变动,并且SPFA是使用队列来维护,已经入集合的点也是有可能出来。

    下面放代码

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<string>
    #include<queue>
    #include<stack>
    #include<algorithm>
    #include<vector>
    #include<map>
    #include<cmath>
    using namespace std;
    const int INF= 0x3f3f3f3f;
    const int MAXN = pow(2, 31) - 1;
    struct node{
        int u, v, w, next;
    }e[500001];
    int head[500001];
    int ans = 0;
    void add(int u,int v,int w)//链式前向星
    {
        e[++ans].next = head[u];
        e[ans].v = v;
        e[ans].w = w;
        head[u] = ans;
    }
    int n, m, s;
    queue<int> q;
    int vis[500001];
    int d[500001];
    void SPFA(int x)
    {
        for (int i = 1; i <= n;i++)
            d[i] = MAXN;
        d[x] = 0;
        q.push(x);
        vis[x] = 1;//入队标记为1
        while(!q.empty())
        {
            int h = q.front();
            q.pop();
            vis[h] = 0;//出队标记为0
            for (int i = head[h]; i!=0;i=e[i].next)
            {
                int t = e[i].v;
               if(d[t]>d[h]+e[i].w)//松弛操作
                   { d[t] = d[h] + e[i].w;
                    if(!vis[t])//如果队里不存在相同的点,则入队。
                    {
                        q.push(t);
                    }}
            }
        }
    }
    int main()
    {
        cin >> n >> m >> s;
        memset(vis, 0, sizeof(vis));
        memset(d, 0, sizeof(d));
        for (int i = 1; i <= m;i++)
        {
            int x, y, z;
            cin >> x >> y >> z;
            add(x, y, z);
        }
        SPFA(s);
        for (int i = 1; i <= n;i++)
        {
            cout << d[i] << ' ';
        }
            return 0;
    }
    
    
  • 相关阅读:
    整数N分解,搭积木,离散数学中的母函数,ZOJ(1163)
    背包问题模板,POJ(1014)
    DP之背包问题详解及案例
    Java基础部分全套教程.
    一位资深程序员大牛给予Java初学者的学习路线建议
    给Java新手的一些建议——Java知识点归纳(Java基础部分)
    一位10年Java工作经验的架构师聊Java和工作经验
    假如时光倒流,我会这么学习Java
    一位资深程序员大牛给予Java初学者的学习路线建议
    2年Java开发工作经验面试总结
  • 原文地址:https://www.cnblogs.com/a821403286/p/13693036.html
Copyright © 2020-2023  润新知