单源(多源)最短路算法Dijkstra、Bellman-Ford、SPFA

最短路算法
单源最短路:即一个点到任意点的最短路径
多源最短路:即任意一点到任意一点的最短路径
Dijkstra算法:
这个算法是通过去更新最短路,每次找离源点最近的一个顶点,然后以该顶点为中心进行扩展,最终找到源点到其余点的最短路径。
用一个dis数组来存源点到其余各点的最短路径,起初,dis【s】源点设为0,其余设为极大值,初始化源点到其他能达到的顶点的距离,每次从dis数组里面选择一个从未更新过的顶点进行扩展,依次循环n-1次,则能得到源点到其余各点的最短路径。
代码如下:

#include
using namespace std;
#define ll long long
#define inf 0x3f3f3f
struct node
{
    int t,w;
};
vector<node>v[2505];//存图
int dis[2505];//存源点到其余各点的最短路径
int book[2505];//标志某个点已被扩展过

int main()
{
    int n,m,s,t;
    int x,y,z;
    memset(dis,inf,sizeof dis);
    cin>>n>>m>>s>>t;
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y>>z;
        v[x].push_back({y,z});
        v[y].push_back({x,z});
    }
    for(int i=0;i<v[s].size();i++)
    {
        node to=v[s][i];
        dis[to.t]=to.w;
    }
    book[s]=1;
    int minn=inf;
    int u;
    for(int i=1;i<=n-1;i++)
    {
        minn=inf;
        for(int j=1; j<=n; j++)//选择一个离源点最近的点
        {
            if(book[j]==0&&dis[j]<minn)//如果该点没被扩展过,且离源点最近
            {
                minn=dis[j];
                u=j;
            }
        }
        book[u]=1;
        for(int j=0;j<v[u].size();j++)//从该店开始扩展
        {
            node to=v[u][j];
            if(dis[to.t]>dis[u]+to.w)
            {
                dis[to.t]=dis[u]+to.w;
            }
        }

    }
    cout<<dis[t]<<endl;

}

Bellman-Ford算法:
这个算法是通过去更新最短路,枚举每一条边,尝试对每一条边进行松弛,总共循环n-1次,因为一个最短路径上最多只有n-1条边,超过n-1条边就形成回路了,另外,在进行n-1轮松弛后,如果还能继续松弛,则表明存在负权回路。

#include
using namespace std;
#define ll long long
#define inf 0x3f3f3f
struct node
{
    int s,t,w;
}e[10505];
int dis[10505];
int p=0;
int main()
{
    int n,m,s,t;
    cin>>n>>m>>s>>t;
    int x,y,z;
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y>>z;
        e[++p]={x,y,z};
    }
    memset(dis,inf,sizeof dis);
    dis[s]=0;
    for(int i=1;i<=n-1;i++)//进行n-1轮松弛
    {
        for(int j=1;j<=m;j++)//枚举每一条边
        {
            if(dis[e[j].t]>dis[e[j].s]+e[j].w)//尝试对每一条边进行松弛
            {
                dis[e[j].t]=dis[e[j].s]+e[j].w;
            }
        }
    }
    cout<<dis[t]<<endl;
}

SPFA算法:其实就是对Bellman-Ford的队列优化;每次只把更新过的且不再队列中的点加入队列中,然后从队列中取点进行扩展。

#include
using namespace std;
#define ll long long
#define inf 0x3f3f3f
struct node
{
    int t,w;
};
vector<node>v[10505];
int dis[10505];
int book[10505];//标志点是否在队列中
int num[10505];//标志点进去队列的次数
queue<int>q;
int main()
{
    int n,m,s,t;
    cin>>n>>m>>s>>t;
    int x,y,z;
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y>>z;
        v[x].push_back({y,z});//有向图
       // v[y].push_back({x,z});
    }
    memset(dis,inf,sizeof dis);
    q.push(s);
    book[s]=1;
    num[s]++;
    dis[s]=0;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        book[x]=0;
        for(int i=0;i<v[x].size();i++)
        {
            node to=v[x][i];
            if(dis[to.t]>dis[x]+to.w)
            {
                dis[to.t]=dis[x]+to.w;
                if(book[to.t]==0)
                {
                    q.push(to.t);
                    book[to.t]=1;
                    num[to.t]++;
                    if(num[to.t]>n)//如果这个点进去队列超过n次说明存在负圈
                    {
                        cout<<"存在负圈"<<endl;
                        return 0;
                    }
                }
            }
        }
    }
    cout<<dis[t]<<endl;
}
/*
5 5 1 4
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
*/

总结:三个算法中,SPFA的时间复杂度最低。Dijkstra不能解决负权问题,而Bellman-Ford、SPFA可以解决负权问题。并并且可以判断是否存在负圈(负权回路)。
因为Dijkstra是用贪心的思想去求最短路径,每次选择离源点最近的顶点作为中心去扩展(这个点仅被选择一次),当边都是正权时,松弛后边权一定比当前最短边大,而如果存在负权边就不满足贪心的条件。
而Bellman-Ford、SPFA对点的扩展都不仅一次,Bellman-Ford是通过循环n-1轮枚举每条边对边进行松弛,SPFA是把更新过的且不在队列中的点加入队列中,只要有更新这个点就会重新被扩展,所以Dijkstra不能解决负权问题,而Bellman-Ford、SPFA可以解决负权问题。
以上三个算法都是求单源最短路径。
求多源最短路径则只需在多加一层循环即可,这里不在赘述。

你可能感兴趣的:(算法,dijkstra,数据结构,最短路径算法最短路径算法)