最短路模板(dijkstra,bellman-ford,spfa,floyd)

dijkstra

前言:

这个算法有前提,就是没有负权边

算法思想:

        我们假设已经有一些点求的最短路,那么对于当前点 ,它的最短路要么是从起点直接走来(和起点有连边),要么就是已求得最短路得点中,距离最小得那个点走来

算法描述:

该算法有两个重要数组,一个是dist[]数组用存节点 i 到起点的最短距离,初始为无穷大。

一个是v[]数组表示节点是否以求得最短路径。(专业点的叫打上永久标记)

1、首先我们将起点 s 打上永久标记。这个好理解,起点到起点最短距离为0 嘛,这个是确定的。那么我们初始 dist[s] = 0,v[s] = true;

2、我们用最新打上永久标记的点 m 去更新未打上标记的点 i ,若从点 m 到 点 i 能使dist[i] 更小 ,那么更新 dist[i] = dist[m] + G[m][i]   (这里暂且用邻接矩阵存图),

其实可以这么写 dist[i] = min(dist[i],dist[m]+G[m][i])

3、在更新过程中 我们需要记录一下 dist 最小的那个节点,并将其打上标记。

4、重复 2 和 3  n-1 次(每次能确定一个点的最短路径)。
 

朴素版dijkstra代码:

之所以朴素是因为找最小值是暴力找的

有一些细节就是,有向图就建单边,无向图就建双边

重边只存最小的那条

若边权值极值不是很大,就可以 用 0x3f3f3f(十六进制形式)来表示无穷大,因为 0x3f3f3f3f这个正好很大,而且0x3f3f3f3f + 0x3f3f3f3f也在 int 范围内 符合无穷加无穷也为无穷的性质,比较好处理。反正注意题目边权值范围就可。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
int G[N][N];//邻接矩阵存图,空间开销很大
int dist[N],v[N];
int n,m,s;
void init()
{
    memset(dist,0x3f,sizeof dist);
    memset(v,false,sizeof v);
    memset(G,0x3f,sizeof G);

}
//朴素版dijkstra
void dijkstra(int x)
{
    dist[x] = 0;
    v[x] = true;
    for(int i = 1;i < n;i ++)
    {
        int mn = 0x3f3f3f3f,id = 1;
        for(int j = 1;j <= n;j ++)
        {
            if(v[j])  continue;
            dist[j] = min(dist[j],dist[x]+G[x][j]);
            if(dist[j]<mn)
            {
                mn = dist[j];id = j;
            }
        }
        v[id] = true;
        x = id;
    }
}
void solve()
{
    cin >> n >> m >> s;
    init();
    for(int i = 0;i < m;i ++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        G[a][b] = min(G[a][b],c);//重边只存最小的
        //G[b][a] = min(G[b][a],c);
    }
    dijkstra(s);
    for(int i = 1;i <= n;i ++) cout << dist[i] << " ";
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T = 1;//cin >> T;
    while(T--)
    {
        solve();
    }
    return 0;
}

下面这个是用前向星存图的朴素版

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4+10,M=5e5+10;
int cnt,n,m,s;
int h[N],e[M],w[M],ne[M];//链式前向星
int dist[N];
bool v[N];
void add(int a,int b,int c)
{
    ne[++cnt] = h[a];e[cnt] = b;w[cnt] = c;h[a] = cnt;
}
void init()
{
    memset(dist,0x3f,sizeof dist);
    memset(v,false,sizeof v);
}
//朴素版dijkstra
void dijkstra(int x)
{
    dist[x] = 0;
    v[x] = true;
    for(int i = 1;i <= n;i ++)
    {
        int mn = 0x3f3f3f3f,id = 1;
        for(int j = h[x];j!=0;j = ne[j])
        {
            int edg = e[j];
            if(v[edg]) continue;
            dist[edg] = min(dist[edg],dist[x]+w[j]);
            if(dist[edg]<mn)
            {
                mn = dist[edg];id = edg;
            }
        }
        v[id] = true;
        x = id;
        // for(int i = 1;i <= n;i ++) cout << dist[i] << " ";
        // cout << "\n";
    }
}
void solve()
{
    cin >> n >> m >> s;
    init();
    for(int i = 0;i < m;i ++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c);
        
    }
    dijkstra(s);
    for(int i = 1;i <= n;i ++) cout << dist[i] << " ";
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T = 1;//cin >> T;
    while(T--)
    {
        solve();
    }
    return 0;
}

dijkstra堆优化版

所谓堆优化就是利用小根堆的性质来优化查找最小值的那步的时间

这里的代码用来链式前向星存图,这是用来优化空间消耗的。不懂前向星的自行搜索

小根堆就是 一个父节点权值小于孩子节点的二叉树,那么根据这个,根节点就是最小值 所在。

一般小根堆用标准库的优先队列实现。

这里对定义所需参数解释一下:

第一个参数表示节点存储类型,

第二个参数表示存储类型的集合,这里就用vector,

第三个表示里边的排序规则。因为要模拟小根堆,因此 用的仿函数 greater<> ,尖括号里边也是数据类型,就第一个参数那个。表示从小到大排列。(大根堆就用less<>)。仿函数可以用标准库的,也可以自己根据你的数据类型写一个cmp函数。

这里用pair存节点信息,first 存dist ,second存节点编号。因为pair默认第一个成员进行比较

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int N = 2e5+10,M=2e5+10;
int cnt,n,m,s;
int h[N],e[M],w[M],ne[M];//链式前向星
int dist[N],v[N];
void add(int a,int b,int c)
{
    ne[++cnt] = h[a];e[cnt] = b;w[cnt] = c;h[a] = cnt;
}
void init()
{
    memset(dist,0x3f,sizeof dist);
    memset(h,0,sizeof h);
    memset(v,false,sizeof v);
}
//堆优化版dijkstra
void dijkstra(int x)
{
    //priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> q;//小根堆
    priority_queue<pii,vector<pii>,greater<pii>> q;
    q.push({0,x});
    dist[x] = 0;
    int num = 0;
    while(!q.empty())
    {   
        if(num==n) break; 
        pii pa = q.top();
        q.pop();
        int id = pa.second;
        if(v[id]) continue;//有重边的话同一个点会被插入队列多次,只需要最短的那次 其余直接跳过
        v[id] = true;
        for(int j = h[id];j != 0;j = ne[j])
        {
            int eg = e[j];
            if(v[eg]) continue;
            dist[eg] = min(dist[eg],dist[id]+w[j]);
            q.push({dist[eg],eg});
        }
        num ++;
    }

}
void solve()
{
    cin >> n >> m;
    init();
    for(int i = 0;i < m;i ++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c);
    }
    s = 1;
    dijkstra(s);
    cout << dist[n];
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T = 1;//cin >> T;
    while(T--)
    {
        solve();
    }
    return 0;
}

bellman-ford

有点像对于边操作的dij,(dij是对点的),存储边集,每次遍历边集,对边两端进行松弛操作,执行n次

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10,M = 2e5+10;
int n,m,s;
struct node{
    int a,b,w;
}edge[M]; 
int dist[N];//backup[N];
//思想比较简单 就是每次对每条边看看两端点能不能进行松弛操作
void bellmanford(int x)
{
    memset(dist,0x3f,sizeof dist);
    dist[x] = 0;
    for(int i = 1;i <= n;i ++)
    {
        //memcpy(backup,dist,sizeof dist);
        for(int j = 0;j < m;j ++)
        {
            int a = edge[j].a,b = edge[j].b,c = edge[j].w;
            dist[b] = min(dist[b],dist[a]+c);//有的会backup但是目前我没想通为啥,好像不用也行的通
            // cout << a << "--" << b <<":"<< dist[b] << " ";
        }
        // cout << "\n";
    }
}
void solve()
{
    cin >> n >> m >> s;
    for(int i = 0;i < m;i ++)
    {
        int a,b,c;cin >> a >> b >> c;
        edge[i].a = a;
        edge[i].b = b;
        edge[i].w = c;
    }
    bellmanford(s);
    for(int i = 1; i <= n;i ++) cout << dist[i] << " ";

}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T = 1;//cin >> T;
    while(T--)
    {
        solve();
    }

    return 0;
}

spfa

用队列对bellman进行优化的思想。bellman本质是对于一个点,对它能到达的点进行松弛,那么这里我们就只遍历能访问的点的边,用队列存起来。

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+10,M=2e5+10;
int h[N],e[M],w[M],ne[M];
int n,m,cnt,s;
int dist[N];
bool v[N];
//容易被卡成 bellman-ford
void add(int a,int b,int c)
{
    ne[++cnt] = h[a];e[cnt] = b;w[cnt] = c;h[a] = cnt; 
}
void spfa(int x)
{
    memset(dist,0x3f,sizeof dist);
    dist[x] = 0;
    queue<int> q;
    q.push(x);
    while(!q.empty())
    {
        int  p = q.front();
        v[p] = false;
        q.pop();
        for(int i = h[p];i!=0;i = ne[i])
        {
            int j = e[i];
            if(dist[j]>dist[p]+w[i])
            {
                dist[j] = dist[p] +w[i];
                if(!v[j])
                {
                     q.push(j); 
                     v[j] = true;
                }
               
            }
        }
    }
}
void solve()
{
    cin >> n >> m >> s;
    for(int i= 0;i < m;i ++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c);
    }
    spfa(s);
    for(int i = 1; i <= n;i ++) cout << dist[i] << " ";
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T = 1;//cin >> T;
    while(T--)
    {
        solve();
    }
}

floyd

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+10;
int n,m;
int dist[N][N];
//代码简单,动态规划思想
void sovle()
{
    cin >> n >> m;
    memset(dist,0x3f,sizeof dist);
    for(int i = 1; i <= n;i ++) dist[i][i] = 0;
    for(int i = 9;i < m;i ++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        dist[a][b] = c;
    }
    //对于k放外边是因为动态规划需要利用已有的结果,而k放最里边循环会导致 i,j使用未被更新的i,k和k,j;出现错误
    for(int k = 1;k <= n;k ++)
        for(int i = 1;i <= n;i ++)
            for(int j = 1;j <= n;j ++)
                dist[i][j] = min(dist[i][j],dist[i][k]+dist[k][j]);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T = 1;//cin >> T;
    while(T--)
    {
        solve();
    }
    return 0;
}

后边写的有点草率,详细的后面再补

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值