求单源最短路径算法

在这里插入图片描述
dijkstra算法可以用来实现无负权值的最短路求法,bellman_fort算法可用于实现有限制访问次数的最短路问题,spfa算法是最常用来求单源最短路的算法。
Dijkstra堆排序实现:

#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

typedef pair<int, int> PII;//定义序列对,用于存储距离和结点下标

const int N = 1e6 + 10;

int n, m;
int h[N], w[N], e[N], ne[N], idx;//定义带权值的邻接表需要的数组和索引号
int dist[N];
bool st[N];//用于标记离结点最短距离的相邻接点

void add(int a, int b, int c)//实现邻接表
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> heap;//堆排序实现查找队列中的最短距离的结点
    heap.push({0, 1});//入堆第1个结点距离为0

    while (heap.size())
    {
        auto t = heap.top();//堆头用top
        heap.pop();

        int ver = t.second, distance = t.first;//取堆中距离最短的第一个元素的下标和距离

        if (st[ver]) continue;//如果已经标记过了,重复标记没有意义只会重复占用计算资源,所以已经标记了的元素不应该进行
        st[ver] = true;

        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});
            }
        }
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    scanf("%d%d", &n, &m);

    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    cout << dijkstra() << endl;

    return 0;
}

作者:yxc



Bellman_fort代码实现:

//这里填你的代码^^
//注意代码要放在两组三个点之间,才可以正确显示代码高亮哦~
#include<iostream>
#include<algorithm>
#include <cstring>//memset函数的基础库

using namespace std;

const int N = 510,M = 10010;
int n,m,k;
int dist[N];
int backup[N];//存储原始结点的距离,防止串联导致结果出错

struct Edge
{
    int x,y,w;
}e[M];//结构体

void bell_fort()//进行边数k限制次循环
{
   memset(dist,0x3f,sizeof dist);//初始化
   dist[1] = 0;

   for(int i=0;i<k;i++)
   {
       memcpy(backup,dist,sizeof dist);//备份该节点的原始距离
       for(int j=0;j<m;j++)//进行所有边的距离更新
       {
           int a=e[j].x,b = e[j].y,c=e[j].w;
           dist[b]=min(dist[b],backup[a]+c);
       }
   }

}

int main()
{
    scanf("%d%d%d",&n,&m,&k);

    for(int i = 0;i < m;i++)
    {
       int x,y,w;
       scanf("%d%d%d",&x,&y,&w);
       e[i] = {x,y,w};
    }

    bell_fort();
    if(dist[n] > 0x3f3f3f3f / 2) puts("impossible");
    else printf("%d\n",dist[n]);

    return 0;

}

作者:ovOr


spfa代码实现:

//使用已经更新过的结点,来更新已经更新过节点的相邻结点
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 100010;
int n,m;
int e[N],ne[N],h[N],w[N],idx;
int dist[N];
bool st[N];//存储的是已经更新过的结点

void add(int a,int b,int c)
{
    e[idx] = b,w[idx] = c,ne[idx] = h[a], h[a] = idx++;
}

int spfa()
{
    memset(dist,0x3f,sizeof dist);
    queue<int> q;
    dist[1] = 0;
    q.push(1);
    st[1] = true;

    while(q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = false;

        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])//有点bellman_fort算法,dist[b] =min(dist[b], backup[a] + w)
            {
                dist[j] = dist[t] + w[i];
                if(!st[j])//若已经标记过了
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return dist[n];
}

int main()
{
    scanf("%d%d",&n,&m);

    memset(h,-1,sizeof h);
    for(int i = 0;i < m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }

    int t = spfa();
    if(t == 0x3f3f3f3f) puts("impossible");
    else printf("%d\n",t);

}

stl实现:

#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;
const int N=100010;

vector<vector<pair<int,int> > >edge(N);
int dist[N];
bool vis[N];
int n,m;

int spfa()
{
    memset(dist,0x3f,sizeof(dist));
    dist[1]=0;
    
    queue<int>q;
    q.push(1);
    vis[1]=true;
    
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        vis[t]=false;
        for(int i=0;i<edge[t].size();i++)
        {
            int p=edge[t][i].first;
            int len=edge[t][i].second;
            if(dist[p]>dist[t]+len)
            {
                dist[p]=dist[t]+len;
                if(!vis[p])
                {
                    q.push(p);
                    vis[p]=true;
                }
            }
        }
    }
    if(dist[n]==0x3f3f3f3f)return -1;
    return dist[n];
}
int main()
{
    cin>>n>>m;
    while(m--)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        edge[a].push_back({b,c});
    }
    
    int ans=spfa();
    
    if(ans==-1)puts("impossible");
    else printf("%d\n",ans);
    return 0;
}

三者的异同:

对负权值:

dijkstra算法不能实现负权值问题的原因是因为,该算法是基于假设图中的边不含负权值边进行求解的,好像一个只会加法而不会减法的算法。其余的两种算法都是可以计算负权值边的,他们依据放缩的办法,能够备份节点的初始值,从而能对任何权值属性的边进行更新。

st[N]数组存储节点的要求:

dijkstra算法st数组中存储的是距离结点最近的节点的堆,spfa算法存储的已经更新了的节点,且可逆,意思就是可以true后false的进行改变,从而实现节点的进队和出队。

有关一些实现的细节问题:

在这里插入图片描述
为什么要/2原因是,到达一个节点的距离为无穷大后,若后续有负权值的结点就会导致无穷大变小,所以你对无穷大/2/3都是可以实现的,只要比无穷大小就行。

负权回路的问题

bellman_fort算法由于存在有限条边数的访问设置,所以可以在存在复权回路的图中进行实现,但是spfa一旦存在负权回路就会一直在负权回路中打转,不断添加更新节点直到最后超时结束。但是可以利用上述两项技术检测图中是否存在负权值回路,n个结点最多n-1条边,每次访问一条边就将计数器+1,检测最后的cnt是否>=n,若是最图中一定存在负权回路,原因是存在复权回路就会打转,结果就是负权回路中的结点可能会被访问很多次,单只计数器的结果大于n-1,。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值