模板 --单源最短路

模板 --单源最短路

求最短路一般有两种方法,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;
}

posted @ 2020-09-18 19:16  故犹  阅读(157)  评论(0编辑  收藏  举报