最短路——Dijkstra、Bellman-Ford、SPFA、Floyd

一、Dijkstra算法
二、Bellman-Ford算法
三、SPFA算法
四、Floyd算法

一、Dijkstra算法:
无负权图的单源最短路径问题。
求一张无负权图的某点到其他节点的最短距离。

1.算法思路
①初始化dis[1]=0,其余节点的dis为正无穷大(正无穷大的一半inf/2,保证a+b<=inf,防止相加溢出;memset(dis,0x3f,sizeof(dis));
②找出一个未被标记的、dis[x]最小的节点x,然后标记节点x。
③扫描节点x的所有出边(x,y,z)若dia[y]>dis[x]+z,则使用dis[x]+z更新dis[y];
④重复上述2–3两个步骤直到所有节点被标记。
Dijkstra算法基于贪心思想,它只适用于所有边的长度都是非负数的图。当边长z都是非负数时,全局最小值不可能再被其他节点更新,故在第一步中选出的节点x,必然满足:dis[x]已经是起点到x的最短路径。我们不断选择全局最小值进行标记和扩展,最终可以得到起点1到每个节点的最短路径长度。

2.朴素实现,vector与数组实现。
时间复杂度为O(n²)较高,一般采用优先队列优化的Dijkstra算法。

vector实现。

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int inf=0x7fffffff;
const int maxn=100005;
struct node
{
    int y;//目标顶点
    int vi;//边权
    node(){}
    node(int a,int b)
    {
        y=a;
        vi=b;
    }
};
int dis[maxn];//起点到各点的最短路径长度
bool ha[maxn]={false};//标记是否已访问
vector<node>vc[maxn];//图
int n,m;
int x,y,z;


void Dijkstra(int s)
{
    memset(ha,0,sizeof(ha));
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;//起点到自身的距离为0
    for(int i=1;i<n;i++)//循环n-1次
    {
        int x=0;
        //找到未标记节点中最小的
        for(int j=1;j<=n;j++)
            if(!ha[j]&&(x==0||dis[j]<dis[x]))
            x=j;
        ha[x]=true;//标记

        //用全局最小值点x,更新其他节点
        for(int k=0;k<vc[x].size();k++)
        {
            int y=vc[x][k].y;
            if(!ha[y]&&dis[y]>dis[x]+vc[x][y].vi)
                dis[y]=dis[x]+vc[x][y].vi;
        }
    }
    return ;
}
int main(void)
{

    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        vc[x].push_back(node(y,z));
        vc[y].push_back(node(x,z));
    }
    Dijkstra(1);

    return 0;
}

数组模拟链表实现。

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int inf=0x7fffffff;
const int maxn=100005;
int tot;
int dis[maxn];
bool ha[maxn]={false};
int ver[2*maxn],edge[2*maxn],nt[2*maxn],head[maxn];
int n,m;
int x,y,z;
void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z;
    nt[tot]=head[x],head[x]=tot;
}

void Dijkstra(int s)
{
    memset(ha,0,sizeof(ha));
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;//起点到自身的距离为0
    for(int i=1;i<n;i++)//循环n-1次
    {
        int x=0;
        //找到未标记节点中最小的
        for(int j=1;j<=n;j++)
            if(!ha[j]&&(x==0||dis[j]<dis[x]))
            x=j;
        ha[x]=true;//标记

        //用全局最小值点x,更新其他节点
        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i];
            int z=edge[i];
            if(!ha[y]&&dis[y]>dis[x]+z)
                dis[y]=dis[x]+z;
        }
    }
    return ;
}
int main(void)
{

    tot=0;
    scanf("%d%d",&n,&m);
    memset(head,0,sizeof(head));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    Dijkstra(1);
   
    return 0;
}

3.优先队列优化,将时间复杂度降至O(mlogn)。
能处理1e5 的数据。
vector实现:

#include<iostream>
#include<vector>
#include<cstring>
#include<queue>
#include<map>
using namespace std;
const int inf=0x7fffffff;
const int maxn=100005;
struct node
{
    int y;
    int vi;
    node(){}
    node(int a,int b)
    {
        y=a;
        vi=b;
    }
};
int dis[maxn];
bool ha[maxn];
vector<node>vc[maxn];
int n,m;
int x,y,z;
//pair的first权值越大越优先,压入时压入负数
priority_queue<pair<int,int> >q;

void Dijkstra(int s)
{
    memset(ha,0,sizeof(ha));
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    q.push(make_pair(0,s));
    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        if(ha[x]) continue;
        ha[x]=true;
        for(int i=0;i<vc[x].size();i++)
        {
            int y=vc[x][i].y,z=vc[x][i].vi;
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                q.push(make_pair(-dis[y],y));
            }


        }
    }
    return ;
}

int main(void)
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        vc[x].push_back(node(y,z));
        vc[y].push_back(node(x,z));
    }
    Dijkstra(1);

    return 0;
}

数组模拟邻接表实现:

#include<iostream>
#include<vector>
#include<cstring>
#include<queue>
#include<map>
using namespace std;
const int inf=0x7fffffff;
const int maxn=100005;
int head[maxn],ver[2*maxn],edge[2*maxn],nt[2*maxn];
int dis[maxn];
bool ha[maxn];
int tot;
int n,m;
int x,y,z;

void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z;
    nt[tot]=head[x],head[x]=tot;
}

priority_queue<pair<int,int> >q;

void Dijkstra(int s)
{
    memset(ha,0,sizeof(ha));
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    q.push(make_pair(0,s));
    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        if(ha[x]) continue;
        ha[x]=true;
        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i],z=edge[i];
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                q.push(make_pair(-dis[y],y));
            }
        }
    }
    return ;
}

int main(void)
{
    tot=0;
    scanf("%d%d",&n,&m);
    memset(head,0,sizeof(head));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    Dijkstra(1);

    return 0;
}

由于使用vector实现图的存储和使用数组模拟邻接表实现数的存储十分相似
接下来的完整代码均有数组模拟邻接链表实现。
4.路径保存:
若已知最短路径唯一,或者在某些条件下(第二标尺最优时)最短路径唯一。
可用一个pre数组来记录最优时y的前驱节点x。
若最短路径不一定唯一,可以用一个向量把最有时y的前驱节点x全部记录下来(y的前驱节点不唯一)。

唯一:

int pre[maxn];
priority_queue<pair<int,int> >q;
void Dijkstra(int s)
{
    memset(ha,0,sizeof(ha));
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    q.push(make_pair(0,s));
    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        if(ha[x]) continue;
        ha[x]=true;
        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i],z=edge[i];
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                q.push(make_pair(-dis[y],y));
                pre[y]=x;
            }
        }
    }
    return ;
}
void print(int y,int s)
{
    if(pre[y]!=s)
    {
        print(pre[y],s);
        printf("->%d",y);
    }
    else printf("%d->%d",s,y);
    return ;
}
int main(void)
{
    tot=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        pre[i]=i;
    memset(head,0,sizeof(head));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    Dijkstra(1);

    //输出s到t的路径
    print(t,s);
    return 0;
}

不唯一的情况只需要把per写成向量,把所有的最短路保存后,进行一次DFS便可得到所有的最短路径。

vector<int> pre[maxn];
priority_queue<pair<int,int> >q;
void Dijkstra(int s)
{
    memset(ha,0,sizeof(ha));
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    q.push(make_pair(0,s));
    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        if(ha[x]) continue;
        ha[x]=true;
        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i],z=edge[i];
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                q.push(make_pair(-dis[y],y));
                pre[y].clear();
                pre[y].push_back(x);
            }
            else if(dis[y]==dis[x]+z)
            {
                //q.push(make_pair(-dis[y],y));
                pre[y].push_back(x);
            }
        }
    }
    return ;
}
vector <int>c;

void DFS(int t)
{
    if(pre[t].size()==0)
    {

        printf("%d",c[c.size()-1]);
        for(int i=c.size()-2;i>=0;i--)
            printf("->%d",c[i]);
        putchar('\n');

        return ;
    }
    for(int i=0;i<pre[t].size();i++)
    {
        c.push_back(pre[t][i]);
        DFS(pre[t][i]);
        c.pop_back();
    }
    return ;
}

int main(void)
{
    tot=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        pre[i].clear();

    memset(head,0,sizeof(head));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    Dijkstra(1);

    //输出s到t的路径
    c.push_back(t);
    DFS(t);
    return 0;
}

5.s–t两点间最短距离:
求s–t两点间的最短距离,其中有x–y之间的距离可为0,或者可减半。
对于无向图,只需要从s跑一次Dijkstra,求出dis1;从t跑一次Dijkstra,求出dis2.
对于有向图,从s跑一次Dijkstra,求出dis1;反向建图,从t跑一次Dijkstra,求出dis2.

_min=min(dis1[t],dis1[x]+dis2[y])

其中对于x,y是否有向;x,y权值减半均可类似求出(可以跑一次Dijkstra求出x,y间最小距离)。
6.在所有最短路中求第二标尺最优的路:
①加边权,求最短路边权最优;
②加点权,求最短路点权最优;
③直接问有多少条最短路;
此类题目有一般有两种思路
一、直接在最短路求节过程中更新状态。
二、先求出所有的最短路,再在所有的最短路中求出最优解(DFS或BFS)
以PTA A1003 Emergeney为例:
1003 Emergency (25 分)
As an emergency rescue team leader of a city, you are given a special map of your country. The map shows several scattered cities connected by some roads. Amount of rescue teams in each city and the length of each road between any pair of cities are marked on the map. When there is an emergency call to you from some other city, your job is to lead your men to the place as quickly as possible, and at the mean time, call up as many hands on the way as possible.

Input Specification:
Each input file contains one test case. For each test case, the first line contains 4 positive integers: N (≤500) - the number of cities (and the cities are numbered from 0 to N−1), M - the number of roads, C​1​​ and C​2​​ - the cities that you are currently in and that you must save, respectively. The next line contains N integers, where the i-th integer is the number of rescue teams in the i-th city. Then M lines follow, each describes a road with three integers c​1​​ , c​2​​ and L, which are the pair of cities connected by a road and the length of that road, respectively. It is guaranteed that there exists at least one path from C1 to C​2​​ .

Output Specification:
For each test case, print in one line two numbers: the number of different shortest paths between C1 and C​2​​ , and the maximum amount of rescue teams you can possibly gather. All the numbers in a line must be separated by exactly one space, and there is no extra space allowed at the end of a line.

Sample Input:
5 6 0 2
1 2 1 5 3
0 1 1
0 2 2
0 3 1
1 2 1
2 4 1
3 4 1
Sample Output:
2 4

作者: CHEN, Yue
单位: 浙江大学
时间限制: 400 ms
内存限制: 64 MB
代码长度限制: 16 KB

给出N个城市M条无向边,每个城市中都有一定数量的救援小组,所有边的边权已知。现在给出起点和终点,求从起点到终点的最短路径条数以及最短路径上的救援小组数目之和。如果有多条最短路径,输出数目之和最大的。

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<map>
using namespace std;
const int maxn=505;
const int _max=250008;
int head[maxn],ver[_max],edge[_max],nt[_max];
int dis[maxn],sum[maxn];
int w[maxn],weight[maxn];
bool ha[maxn];
int n,m;
int tot=0;
void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z;
    nt[tot]=head[x],head[x]=tot;
}
priority_queue<pair<int,int> >q;
void Dijkstra(int s)
{
    memset(ha,0,sizeof(ha));
    memset(sum,0,sizeof(sum));
    memset(dis,0x3f,sizeof(dis));
    memset(weight,0,sizeof(weight));
    q.push(make_pair(0,s));
    dis[s]=0;
    sum[s]=1;
    weight[s]=w[s];

    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        if(ha[x]) continue;
        ha[x]=true;
        for(int i=head[x];i!=-1;i=nt[i])
        {
            int y=ver[i],z=edge[i];
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                weight[y]=weight[x]+w[y];
                sum[y]=sum[x];
                q.push(make_pair(-dis[y],y));
            }
            else if(dis[y]==dis[x]+z)
            {
                if(weight[x]+w[y]>weight[y])
                    weight[y]=weight[x]+w[y];
                sum[y]+=sum[x];
            }

        }
    }
    return ;
}

int main(void)
{
    memset(head,-1,sizeof(head));
    int s,t;
    int x,y,z;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=0;i<n;i++)
        scanf("%d",&w[i]);
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    Dijkstra(s);

    printf("%d %d\n",sum[t],weight[t]);

    return 0;
}

二、Bellman-Ford算法:

可处理有负边的单源最短路径。
若无从源点可达负环,则经过此算法一定可以求出源点到各点的最短路径距离。
执行完n-1次操作后,再执行一次操作,若无从源点可达负环,则源点到其余各点的距离已最短。若仍有某条边x->y,若仍有d[x]+lenth[x->y]<d[y],则说明有源点可达负环。
时间复杂度为O(nm)较高。
1.朴素模板

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<map>
using namespace std;
const int maxn=100005;
const int _max=200008;
int head[maxn],ver[_max],edge[_max],nt[_max];
int dis[maxn];
int n,m;
int tot=0;
void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z;
    nt[tot]=head[x],head[x]=tot;
}
bool Bellman(int s)
{
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    for(int i=1;i<n;i++)
    {
        for(int x=1;x<=n;x++)
        {
            for(int j=head[x];j;j=nt[j])
            {
                int y=ver[j],z=edge[j];
                if(dis[y]>dis[x]+z)
                    dis[y]=dis[x]+z;
            }
        }
    }
    for(int x=1;x<=n;x++)
    {
        for(int j=head[x];j;j=nt[j])
        {
            int y=ver[j],z=edge[j];
            if(dis[y]>dis[x]+z)
                return false;
        }
    }
    return true;
}
int main(void)
{
    memset(head,0,sizeof(head));

    int x,y,z;
    scanf("%d%d%d%d",&n,&m);

    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }

    Bellman(1);

}

2.第二标尺最优:
和Dij思路差不多,只是在求最短路径数量时有些许不同,同样以pta1003 为例:

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<map>
#include<set>
using namespace std;
const int maxn=505;
const int _max=250008;
int head[maxn],ver[_max],edge[_max],nt[_max];
int dis[maxn],sum[maxn];
int w[maxn],weight[maxn];
set<int>pre[maxn];
int n,m;
int tot=0;
void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z;
    nt[tot]=head[x],head[x]=tot;
}
//会多次访问已经访问过的节点
//数目不能直接累加
bool Bellman(int s)
{
    memset(dis,0x3f,sizeof(dis));
    memset(weight,0,sizeof(weight));
    memset(sum,0,sizeof(sum));
    sum[s]=1;
    dis[s]=0;
    weight[s]=w[s];
    for(int i=1;i<n;i++)
    {
        for(int x=0;x<n;x++)
        {
            for(int j=head[x];j!=-1;j=nt[j])
            {
                int y=ver[j],z=edge[j];
                if(dis[y]>dis[x]+z)
                {
                    dis[y]=dis[x]+z;
                    weight[y]=weight[x]+w[y];
                    sum[y]=sum[x];
                    pre[y].clear();
                    pre[y].insert(x);
                }
                else if(dis[y]==dis[x]+z)
                {
                    if(weight[y]<weight[x]+w[y])
                        weight[y]=weight[x]+w[y];
                        
                    pre[y].insert(x);
                    sum[y]=0;
                    set<int>::iterator it;
                    for(it=pre[y].begin();it!=pre[y].end();it++)
                    {
                        sum[y]+=sum[*it];
                    }
                }

            }
        }
    }
    for(int x=0;x<n;x++)
    {
        for(int j=head[x];j!=-1;j=nt[j])
        {
            int y=ver[j],z=edge[j];
            if(dis[y]>dis[x]+z)
                return false;
        }
    }
    return true;
}

int main(void)
{
    memset(head,-1,sizeof(head));
    int s,t;
    int x,y,z;
    scanf("%d%d%d%d",&n,&m,&s,&t);
    for(int i=0;i<n;i++)
        scanf("%d",&w[i]);
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    Bellman(s);

    printf("%d %d\n",sum[t],weight[t]);

    return 0;
}

若把每条边的松弛条件由d[x]+z<d[y]—>d[y]=d[x]+z改为d[x]+z>d[y]–>d[y]=d[x]+z
可求源点到其余各点的最大距离。
若无源点可达正环,则进行n-1次松弛后,源点到其余各点距离已达最大。
再进行一次松弛,若仍有d[y]<d[x]+z说明有源点可达正环

三、SPFA算法:
期望时间复杂度O(km),k是较小的常数。
但是在某些情况下,其可能退化为O(nm)–有从源点可达负环。所以必须谨慎使用。
但是其经常性的优于堆优化的Dijkstra算法。
如果事先知道图中不会有负环,则记录松弛次数的sum数组可以省略。
同样的,使用SPFA算法可以判断图中是否存在从源点可达负环。
其第二标尺算法与bellman-ford算法类似,不再写板子。
此算法可以判断图中是否有负环(不管从源点可达与否),如果负环从源点不可达,则需要添加一个辅助顶点C,并添加一条从源点到达C的有向边,以及n-1条从C到达除源点外各顶点的有向边才能判断负环。
1.BFS版本,朴素queue:
求最短路

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=100010,M=1000010;
const int inf=0x7fffffff;
int head[N],ver[M],nt[M],edge[M];
bool ha[N];
int n,m,tot;
int x,y,z;
int s,t;
int sum[N],dis[N];



void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z;
    nt[tot]=head[x],head[x]=tot;
}

bool SPFA(int s)
{
    memset(ha,0,sizeof(ha));
    memset(sum,0,sizeof(sum));
    memset(dis,0x3f,sizeof(dis));
    queue<int>q;
    q.push(s);
    dis[s]=0;
    ha[s]=true;
    sum[s]++;
    while(q.size())
    {
        int x=q.front();
        q.pop();
        ha[x]=false;
        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i],z=edge[i];
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                if(!ha[y])
                {
                    q.push(y);
                    ha[y]=true;
                    sum[y]++;
                    if(sum[y]>=n) return false;//有源点可达负环
                }
            }
        }
    }
    return true;//无源点可达负环
}





int main(void)
{
    tot=0;
    memset(head,0,sizeof(head));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }

    SPFA(s);
    return 0;
}

2.DFS版本,stack:
判断负环

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=100010,M=1000010;
const int inf=0x7fffffff;
int head[N],ver[M],nt[M],edge[M];
bool ha[N];
int n,m,tot;
int x,y,z;
int s,t;
int dis[N];



void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z;
    nt[tot]=head[x],head[x]=tot;
}
//也可以没有返回值,设置全局变量flag;
//找到负环标记flag退出。
bool SPFA(int x)
{
   ha[x]=true;
   for(int i=head[x];i;i=nt[i])
   {
       int y=ver[i],z=edge[i];
       if(dis[y]>dis[x]+z)
       {
           dis[y]=dis[x]+z;
           if(ha[y]) return false;
           if(!SPFA(y)) return false;
       }
   }
   ha[x]=false;
   return true;

}
int main(void)
{
    tot=0;
    memset(head,0,sizeof(head));
    memset(ha,0,sizeof(ha));
    memset(dis,0x3f,sizeof(dis));
    //只关心是否有负环,不关心最短路。
    memset(dis,0,sizeof(dis));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    //判断一张图是否有负环
    bool flag=false;
    for(int i=1;i<=n;i++)
    {
        if(!SPFA(i))
        {
            flag=true;
            break;
        }
    }
    if(flag) printf("有负环\n");
    else printf("没有负环\n");
    return 0;
}

3.priority_queue优化:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map>
using namespace std;
const int N=100010,M=1000010;
const int inf=0x7fffffff;
int head[N],ver[M],nt[M],edge[M];
bool ha[N];
int n,m,tot;
int x,y,z;
int s,t;
int sum[N],dis[N];



void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z;
    nt[tot]=head[x],head[x]=tot;
}

bool SPFA(int s)
{
    memset(ha,0,sizeof(ha));
    memset(sum,0,sizeof(sum));
    memset(dis,0x3f,sizeof(dis));
    priority_queue<pair<int,int> >q;
    q.push(make_pair(0,s));
    dis[s]=0;
    ha[s]=true;
    sum[s]++;
    while(q.size())
    {
        int x=q.top().second;
        q.pop();
        ha[x]=false;
        for(int i=head[x];i;i=nt[i])
        {
            int y=ver[i],z=edge[i];
            if(dis[y]>dis[x]+z)
            {
                dis[y]=dis[x]+z;
                if(!ha[y])
                {
                    q.push(make_pair(-dis[y],y));
                    ha[y]=true;
                    sum[y]++;
                    if(sum[y]>=n) return false;//有源点可达负环
                }
            }
        }
    }
    return true;//无源点可达负环
}





int main(void)
{
    tot=0;
    memset(head,0,sizeof(head));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }

    SPFA(s);

    return 0;
}

4.LLL优化和SLF优化:
SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。
SLF 带容错:每次将入队结点距离和队首比较,如果比队首大超过一定值则插入至队尾

LLL:Large Label Last 策略,设队首元素为i,每次弹出时进行判断,队列中所有dis值的平均值为x,若dis(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作
(LLL 优化:每次将入队结点距离和队内距离平均值比较,如果更大则插入至队尾。)

5.swap优化:
每当队列改变时,(已经压入此次节点)如果队首节点 i 的 dis[i] 大于队尾节点 j 的 dis[j],则交换首尾节点。

SPFA在随机数据中表现还是较好的,但是时间复杂度很容易就增至O(nm),题目数据往往随手就把SPFA卡掉了。谨慎使用。

四、Floyd算法:
此算法求全源最短路,时间复杂度为O(nnn),一般n限制在200以内,用邻接矩阵实现即可。(还不如每个点都跑一边Dij O(nmlogn)。
1.朴素模板:
貌似也没得优化。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map>
using namespace std;
int dis[310][310],path[310][310];
int n,m;
int x,y,z;
int main(void)
{
    memset(dis,0x3f,sizeof(dis));
    for(int i=1;i<=n;i++) dis[i][i]=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        path[i][j]=j;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        dis[x][y]=dis[y][x]=z;
    }
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(dis[i][j]>dis[i][k]+dis[k][j])
            {
                dis[i][j]=dis[i][k]+dis[k][j];
                path[i][j]=path[i][k];
            }
        }

   for(int i=1;i<=n;i++)
   {
        for(int j=1;j<=n;j++)
        {
            printf("%d\n",dis[i][j]);
            printf("%d",i);
            int k=i;
            while(k!=j)
            {
                printf("   %d",path[k][j]);
                k=path[k][j];

            }
        }
   }
   return 0;

}

2.传递闭包:
在交际网络中,给定若干个元素和若干对二元关系,且关系具有传递性。“通过传递性推导出尽量多的元素之间的关系”的问题被称为传递闭包。
建立邻接矩阵d,其中d(i,j)=1表示i,j有关系,d(i,j)=0表示i,j没关系。
特别的d(i,i)始终为1;使用floyd算法可以解决传递闭包问题。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map>
using namespace std;
int dis[310][310];
int n,m;
int x,y;
int main(void)
{
    memset(dis,0,sizeof(dis));
    for(int i=1;i<=n;i++) dis[i][i]=1;

    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        dis[x][y]=dis[y][x]=1;
    }
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            dis[i][j]|=dis[i][k]&dis[k][j];
        }


   return 0;

}

最短路径一般算法至此已经介绍完毕。裸的最短路径并不是特别的难。

猜你喜欢

转载自blog.csdn.net/zhaoxinxin1234/article/details/89441772