ACM寒假集训第三四五天:最短路(Dijkstra、Bellman-Ford、SPFA、Floyd) 基础建图模型

Floyd算法是多源最短路算法,复杂度最高(n^3),通常用在点比较少的起点不固定的问题中。能解决负边(负权)但不能解决负环。

Dijkstra算法是单源最短路算法,最常用时间复杂度(n^2)优化后可以达到(nlogn),不能解决负边问题,稀疏图(点的范围很大但是边不多,边的条数|E|远小于|V|²)需要耗费比较多的空间。

 

 

算法和 Dijkstra 算法同为解决单源最短路径的算法。对于带权有向图 G = (V, E),Dijkstra 算法要求图 G 中边的权值均为非负,而 Bellman-Ford 算法能适应一般的情况(即存在负权边的情况)。一个实现的很好的 Dijkstra 算法比 Bellman-Ford 算法的运行时间要低。

SPFA算法适合稀疏图,可以解决带有负权边,负环的问题,但是在稠密图中效率比Dijkstra要低。 

Floyd:

核心代码只有几行:

    for(int k=1;k<=n;k++)

    {

        for(int i=1;i<=n;i++)

        {

            for(int j=1;j<=n;j++)

            {

                map[i][j]=min(map[i][j],map[i][k]+map[k][j]);

            }

        }

    }

 可以找多源最短路,时间复杂度高。

下面是完整代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
const int maxn = 105;
int map[maxn][maxn];
using namespace std;
int main()
{
    int n, m, a, b, c, str, end;            //点,边,路径与权,开始坐标,结尾坐标
    cin >> n >> m;
    for (int i = 1; i <= n; i++){            //图的初始化,对角线(即本身到本身)为0,其余为无穷
        for (int j = 1; j <= n; j++){
            if (i == j) map[i][j] = 0;
            else map[i][j] = inf;
        }
    }
    for (int i = 1; i <= m; i++){             //记录路径
        cin >> a >> b >> c;
        if (map[a][b] > c){                    //重复输入保存最小值
            map[a][b] = map[b][a] = c;
        }
    }
    cin >>str >> end;                           //开始,结尾坐标
    for (int k = 1; k <= n; k++){                //k在最外层
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= n; j++){
                map[i][j] = min(map[i][j], map[i][k] + map[k][j]);
            }
        }
    }
    cout << map[str][end] << endl;
    return 0;
}

Dijkstra:

只能计算单元最短路,不能计算负权值,贪心思想。开一个dis数组用来存储起始点到其他点的最短路,初始化时存的是起始点到其它点的初始路程,通过n-1遍的遍历找最短。例如1到3的最短路就是比较dis[3]与dis[2]+map[2][3]的值。另外开辟一个book数组用来标记已经找过的最短路。

下面是完整代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
const int maxn = 105;
int map[maxn][maxn], dis[maxn], book[maxn];       //图,最短路,记录
using namespace std;
int main()
{
    memset(book, 0, sizeof(book));
    int n, m, a, b, c, u, v, Min;                  //点,边,路径和权,Min下面求最短路用
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= n; j++){
            if (i == j) map[i][j] = 0;              //图的初始化
            else map[i][j] = inf;
        }
    }
    for (int i = 1; i <= m; i++){
        cin >> a >> b >> c;
        if (map[a][b] > c){
            map[a][b] = map[b][a] = c;
        }
    }
    for (int i = 1; i <= n; i++){                    //距离初始为到i的权
        dis[i] = map[1][i];
    }
    book[1] = 1;                                      //1到1默认走过
    for (int i = 1; i < n; i++){
        Min = inf;                                    //Min初始为无穷大
        for (int j = 1; j <= n; j++){
            if (book[j] == 0 && dis[j] < Min){         //判断是否标记以及是否可走(不可走为无穷)
                Min = dis[j];
                book[j] = 1;
                u = j;
            }
        }
        for (int v = 1; v <= n; v++){                   //求最短路
            if (map[u][v] < inf){
                dis[v] = min(dis[v], dis[u] + map[u][v]);
            }
        }
    }
    for (int i = 1; i <= n; i++){                      //输出每个最短路
        cout << dis[i] << " ";
    }
    return 0;
}

Bellman-Ford:

怎样判断负权回路?

当进行n-1次循环之后,仍然可以运行核心代码部分交换值(if判断语句内的内容)则一定形成负权回路。

#include <iostream>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
using namespace std; 
const int MAX=105;
int u[MAX],v[MAX],w[MAX],dis[MAX];
int main()
{
    int n,m,check,flag;                      //顶点个数,边数 ,检验是否可以退出循环,负权回路判断 
    cin>>n>>m;
    for(int i=1;i<=m;i++)         //读入边 
    {
        cin>>u[i]>>v[i]>>w[i];
    }
    for(int i=1;i<=n;i++)         //1号点到各个点的初始位置设为无穷 
    {
        dis[i]=inf;
    }
    dis[1]=0;
    for(int k=1;k<=n-1;k++)       //Bellman-Ford算法核心 
    {
        check=0; 
        for(int i=1;i<=m;i++)
        {
            if(dis[v[i]]>dis[u[i]]+w[i])
            {
            	dis[v[i]]=dis[u[i]]+w[i];
            	check=1;
            }
        }			
        if(check==0)                 //若不再更新则可以退出 
        {
            break;
        }
    }
    flag=0;
    for(int i=1;i<=m;i++)            //判断负权回路 
    {
        if(dis[v[i]]>dis[u[i]]+w[i])
        {
            flag=1;
        }
    }
    if(flag==1)
    {
        cout<<"存在负权回路"<<endl;
    }
    else
    {
        cout<<"不存在负权回路"<<endl;
    }
    for(int i=1;i<=n;i++)         //输出1到各点距离 
    {
        cout<<dis[i]<<" ";
    }
    return 0;
}
    /*5 5
    2 3 2
    1 2 -3
    1 5 5
    4 5 2
    3 4 3*/ 

Spfa(Bellman-Ford的队列优化):

#include <iostream>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int MAX=1e3+5;
int u[MAX],v[MAX],w[MAX],first[MAX],next[MAX],dis[MAX],book[MAX];   //起始,终止,权,此值的最后一条边的存储位置,此值的上一条边的存储位置,距离,记录 
int main()
{
    int n,m;          //点,边 
    cin>>n>>m;
    memset(dis,0x3f,sizeof(dis));     //距离初始无穷 
    dis[1]=0;                         //1-1初始为0 
    memset(book,0,sizeof(book));      //清空记录数组 
    for(int i=1;i<=n;i++)             //初始-1 
    {
        first[i]=-1;
    }
    for(int i=1;i<=m;i++)             //读入图 
    {
        cin>>u[i]>>v[i]>>w[i];
        next[i]=first[u[i]];          //链式前向星 
        first[u[i]]=i;
    }
    int que[MAX]={0},head=1,tail=1,k; //队列,头,尾 
    que[tail]=1,tail++;               //1号顶点入队 
    book[1]=1;                        //入队标记 
    while(head<tail)                  //队列不为空的时候循环 
    {
        k=first[que[head]];           //当前需要处理的首顶点 
        while(k!=-1)                  //扫描当前所有的边 
        {
            if(dis[v[k]]>dis[u[k]]+w[k])   //判断是否松弛 
            {
                dis[v[k]]=dis[u[k]]+w[k];  //更新值 
                if(book[v[k]]==0)          //用数组记录顶点v[k]是否在队列中,不然每次都要从head-tail扫一遍 
                {
                    que[tail]=v[k];        //入队 
                    tail++;
                    book[v[k]]=1;
                }
            }
            k=next[k];                    
        }
        book[que[head]]=0;                 //出队 
        head++;
    }
    for(int i=1;i<=n;i++)                  //输出所有1-i的最短距离 
    {
        cout<<dis[i]<<" ";
    }
    return 0;
}
  /*  5 7
    1 2 2
    1 5 10
    2 3 3
    2 5 7
    3 4 4
    4 5 5
    5 3 6
    
    0 2 5 9 9 */ 

queue实现:

#include <iostream>
#include <cstring>
#include <queue> 
#include <algorithm>
#define inf 0x3f3f3f3f
using namespace std;
const int MAX=1e3+5;
struct point 
{
    int to,next,d;
};
point a[MAX];
int head[MAX];
int main()
{
    int n,m,x,y,d;
    cin>>n>>m;
    int cnt=0;
    memset(head,0,sizeof(head));
    for(int i=1;i<=m;i++)        //存图 
    {
        cin>>x>>y>>d;
        a[++cnt].next=head[x];
        a[cnt].to=y;
        a[cnt].d=d;
        head[x]=cnt;
        a[++cnt].next=head[y];
        a[cnt].to=x;
        a[cnt].d=d;
        head[y]=cnt;
    }
    int v0=1;                      //这里是从1,不是head[1] 
    int book[MAX],dis[MAX];
    memset(book,0,sizeof(book));
    memset(dis,0x3f,sizeof(dis));
    book[v0]=1;
    queue<int>q;
    q.push(v0);
    dis[v0]=0;
    while(!q.empty())              //非空循环 
    {
        int u=q.front();           //当前需要处理的元素 
        q.pop();                   //出队 
        book[u]=0;                 
        for(int i=head[u];i;i=a[i].next)
        {
            int v=a[i].to;
            if(dis[v]>dis[u]+a[i].d)  //松弛 
            {
                dis[v]=dis[u]+a[i].d;
                if(book[v]==0)
                {
                    q.push(v);        //进队 
                    book[v]=1;
                }
            }
        }
    }
    for(int i=1;i<=n;i++)                  //输出所有1-i的最短距离 
    {
        cout<<dis[i]<<" ";
    }
    return 0;
}
  /*  5 7
    1 2 2
    1 5 10
    2 3 3
    2 5 7
    3 4 4
    4 5 5
    5 3 6
    
    0 2 5 9 9 */ 

 

    原文作者:Bellman - ford算法
    原文地址: https://blog.csdn.net/qq_43678290/article/details/86532040
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞