Bell-man ford算法 和 它的进阶算法:SPFA算法(队列实现,类似BFS的实现)(从一点到其余点的最短路径,解决了负权值边的问题)

 Bellman—Ford算法的时间复杂度比较高,原因在于Bellman—Ford算法要递推n-1次,每次递推,扫描所有的边,在递推n次的过程中很多判断是多余的,SPFA算法是Bellman—Ford算法的一种队列实现,减少了不必要的冗余判断。

大致流程:用一个队列来进行维护。初始时将源点加入队列。每次从队列中取出一个顶点,并与所有与它相邻的顶点进行松弛,若某个相邻的顶点松弛成功,则将其入队。重复这样的过程直到队列为空时算法结束。

实现过程:(1)取出队列头顶点v,扫描从顶点v发出的每条边,设每条边的终点为u,边<v,u>的权值为w,如果dist[v]+w<dist[u],则dist[u]=dist[v]+w,修改path[u]为v,若顶点u不在队列当中,还要将u入队列;如果dist[v]+w<dist[u],则对顶点u不做任何处理。(代码中u为起点,v为终点,这点不同)。

struct node
{
    int y,z;
    node(int a,int b):y(a),z(b){};
};

while(!q.empty())
    {
        int st=q.front();
        q.pop();
        //v[st]=0;
        for(int i=0;i<e[st].size();i++)//有多少个有向边出去,邻接表!!!!
        {
            if( d[st]+e[st][i].z < d[e[st][i].y] )//e[1][0]={2,-3},e[1][1]={5,5}
            {
                d[e[st][i].y]=d[st]+e[st][i].z;
                if(v[ e[st][i].y ]==0)
                {
                    q.push(e[st][i].y);
                    enqueue_num[e[st][i].y]++;

                    if (enqueue_num[e[st][i].y] >= n)//判断是否有负环,一个节点入队最多可能是n-1次
                    {
                        cout << "Sorry,it have negative circle!\n";
                        return;
                    }

                    v[e[st][i].y]=1;
                }
            }
        }
    }

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
#define N 100000+10
using namespace std;
int n,m;
int x,y,z;
bool v[100];
int d[100];
int enqueue_num[100];  //记录入队次数
int vertex_num;        //顶点数
struct node
{
    int y,z;
    node(int a,int b):y(a),z(b){};
};
vector<node> e[100];//邻接表

void spfa(int x)
{
    memset(v,0,sizeof(v));
    memset(enqueue_num, 0, sizeof(enqueue_num));

    d[x]=0;
    queue<int>q;
    q.push(x);
    v[x]=1;//是否在队里
    enqueue_num[x]++;//入队次数

    while(!q.empty())
    {
        int st=q.front();
        q.pop();
        //v[st]=0;
        for(int i=0;i<e[st].size();i++)//有多少个有向边
        {
            if( d[st]+e[st][i].z < d[e[st][i].y] )//e[1][0]={2,-3},e[1][1]={5,5}
            {
                d[e[st][i].y]=d[st]+e[st][i].z;
                if(v[ e[st][i].y ]==0)
                {
                    q.push(e[st][i].y);
                    enqueue_num[e[st][i].y]++;

                    if (enqueue_num[e[st][i].y] >= n)//判断是否有负环,一个节点入队最多可能是n-1次
                    {
                        cout << "Sorry,it have negative circle!\n";
                        return;
                    }

                    v[e[st][i].y]=1;
                }
            }
        }
    }
}
/*
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
0 -3 -1 2 4

5 7
1 2 3
1 3 2
2 4 1
4 1 4
4 3 6
4 5 4
5 3 4
0 3 2 4 8
*/
int main()
{
    scanf("%d%d",&n,&m);
    memset(d,inf,sizeof(d));

    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
       e[x].push_back( (node){y,z} );
       //e[y].push_back((node){x,z});
    //存入无向图
    }
    spfa(1);
    for(int i=1;i<=n;i++)
    {
        printf("%d ",d[i]);
    }
}

 

// Bellman-Ford算法,计算单点到全部点的最短距离。
    for (j = 1; j <= n-1; ++j)  //最多循环n-1轮(图退化为链表),这是最大需要松弛的次数
    {    // n-1个节点,n-1轮

        check = 0;  // 用来标记在本轮松弛中数组dis是否发生更新,判断是否还需要循环

        for (i = 1; i <= m; ++i)//m条边的松弛算法,每次根据指定的点和边进行松弛算法
        {
            if (/*dis[u[i]] != INF && */ dis[u[i]] + w[i] < dis[v[i]])// relax
            {//需要dis[u[i]] != INF,用来判断前一个点是否已经被访问,如果不被访问就不可能需要去寻找下一个节点的距离
             //也可以不需要,dis[u[i]] == INF时是无穷大,无穷大加无穷大还是无穷大,无穷大大于所有值,dis[u[i]] + w[i] < dis[v[i]]不会成立

                dis[v[i]] = dis[u[i]] + w[i];
                bak[v[i]] = u[i];
                check = 1;
            }
        }
        for (i = 1; i <= n; ++i)
        {
            cout<<i-1<<" "<<dis[i]<<endl;
        }
        system("pause");
        if (check == 0)
        {
            break;
        }
    }

    // 检测负权回路,若存在,则在对边进行一次遍历后必定会有relax的操作
    int flag = 0;
    for (i = 1; i <= m; ++i)
    {
        if (dis[u[i]] + w[i] < dis[v[i]])
        {
            flag = 1;
        }
    }

7 10
1 2 6
1 3 5
1 4 5
2 5 -1
3 2 -2
3 5 1
4 3 -2
4 6 -1
5 7 3
6 7 3

     这是距离值,单边多次重复进行判断求最小值,每一次都会进行一次松弛算法的实现,使距离值每一次都减小,从无穷大到最小值的实现!!

 

 

#include <bits/stdc++.h>
#define INF 1e9
using namespace std;

void DFSPrint(int bak[], int k)
{
    if (bak[k] == k)
    {
        printf("%d ", k);
        return;
    }
    DFSPrint(bak, bak[k]);
    printf("%d ", k);
    return;
}
/*
7 10
1 2 6
1 3 5
1 4 5
2 5 -1
3 2 -2
3 5 1
4 3 -2
4 6 -1
5 7 3
6 7 3
*/
int main()
{
    int i, j, n, m;
    int dis[10], bak[10], u[10], v[10], w[10];
    int check;

    // 读入n和m, n表示顶点个数,m表示边的条数
    scanf("%d %d", &n, &m);

    // 读入边
    for (i = 1; i <= m; ++i)
    {
        scanf("%d %d %d", &u[i], &v[i], &w[i]);
    }

    // 初始化bak[]数组,前驱结点均为自己
    // 初始化dis[]数组,源点为1号顶点
    for (i = 1; i <= n; ++i)
    {
        bak[i] = i;
        dis[i] = INF;
    }
    dis[1] = 0;

    // Bellman-Ford算法,计算单点到全部点的最短距离。

    for (j = 1; j <= n-1; ++j)  //最多循环n-1轮(图退化为链表),这是最大需要松弛的次数
    {    // n-1个节点,n-1轮

        check = 0;  // 用来标记在本轮松弛中数组dis是否发生更新,判断是否还需要循环

        for (i = 1; i <= m; ++i)//m条边的松弛算法,每次根据指定的点和边进行松弛算法
        {
            if (/*dis[u[i]] != INF && */ dis[u[i]] + w[i] < dis[v[i]])// relax
            {//需要dis[u[i]] != INF,用来判断前一个点是否已经被访问,如果不被访问就不可能需要去寻找下一个节点的距离
             //也可以不需要,dis[u[i]] == INF时是无穷大,无穷大加无穷大还是无穷大,无穷大大于所有值,dis[u[i]] + w[i] < dis[v[i]]不会成立
                dis[v[i]] = dis[u[i]] + w[i];
                bak[v[i]] = u[i];
                check = 1;
            }
        }
        for (i = 1; i <= n; ++i)
        {

            cout<<i-1<<" "<<dis[i]<<endl;
        }
        system("pause");
        if (check == 0)
        {
            break;
        }
    }

    // 检测负权回路,若存在,则在对边进行一次遍历后必定会有relax的操作
    int flag = 0;
    for (i = 1; i <= m; ++i)
    {
        if (dis[u[i]] + w[i] < dis[v[i]])
        {
            flag = 1;
        }
    }

    if (flag)
    {
        printf("该图有负权回路");
    }
    else
    {
        // 输出最终结果
        printf("最终结果为:\n");
        for (i = 1; i <= n; ++i)
        {
            printf("1号顶点到%d号顶点的最短距离为:%d\n", i, dis[i]);
        }
        printf("\n打印1号顶点到5号顶点的最短路径:\n");

        DFSPrint(bak, 5);
    }

    return 0;
}

 

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值