最短路模板及思想(Floyd,Dijkstra及其堆优化,SPFA)

1 篇文章 0 订阅
1 篇文章 0 订阅


定义

松弛:更新本节点与其他节点的距离(更小或更大)

前言

我没有写Bell算法,我认为你只要会SPFA就行了
代码只有关键的


Floyd算法

思想

暴力枚举两个节点 i , j i,j i,j的中间节点 k k k,( i i i通过 k k k后可以到达 j j j
时间复杂度 O ( n 3 ) O(n^3) O(n3)

特点
  • 时间复杂度高
  • 可以处理多源点的问题(多个起点)
Code
memset(dis, 0x3f, sizeof(dis));
for(int k = 1; k <= n; k ++){//枚举中继节点
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= n; j ++){
            if( i != j && j != k && i != k ){
                if( dis[i][j] > dis[i][k] + dis[k][j] )//松弛
                    dis[i][j] = dis[i][k] + dis[k][j];
            }
        }
    }
}//dis[i][j]表示i到j的最短距离


Dijkstra算法

思想
  1. 固定一个源点,找到与这个点连接的值最小的且未被标记的边以及对应的点
  2. 对这个对应的点打上标记
  3. 用这条边对其他没有标记过的点进行松弛操作
特点
  • 一次只能解决单源点路径(你也可以多次用啊)
  • 负环会爆炸
  • 复杂度 ( n 2 ) (n^2) (n2)
Code
//area[i][j]表示i到j的距离(邻接矩阵)
inline void Dijkstra(int x){
    memset(v, 0, sizeof(v));
    dis[x] = 0;
    v[x] = 1;
    for(int i = 1; i <= n; i ++){
        int m_i_n = INF;
        for(int j = 1; j <= n; j ++){
            if( dis[j] < m_i_n && !vis[j] ){//找最小边
                m_i_n = dis[j];
                x = j;
            }
        }
        vis[x] = 1;//标记
        for(int j = 1; j <= n; j ++){
            if( !vis[j] && dis[j] > dis[x]+area[x][j] )//松弛
                dis[j] = dis[x]+area[x][j];
        }
    }
}
玄学优化

不要看字面意思“堆优化”,其实不难
我们可以发现,我们一直都在寻找最小边,如果遍历一波时间就会大大上涨,有没有办法将找边的时间复杂度降为 O ( 1 ) O(1) O(1)呢?
当然有,优先队列小根堆嘛,它自动排序,而因为要存点,直接存pair类型不就行了?
优化之后时间直降 ( n l o g n ) (nlogn) nlogn,怎么样,是不是连SPFA都不想学了

优化Code
#define Pair pair<int,int>

struct node{
    int to, weight;
};

inline Dijkstra_2(int x){
    priority_queue< Pair, vector<Pair>, greater<Pair> > q;//优先队列小根堆
    memset(v, 0, sizeof(v));
    memset(dis, 0x3f, sizeof(dis));
    dis[x] = 0;
    q.push(make_pair(0, x));//前一个是距离,后一个是点
    while( !q.empty() ){
        Pair t = q.top();
        q.pop();
        if( v[t.seond] )
            continue;
        v[t.seond] = 1;
        for(int i = 0; i < G[t.second].size; i ++){
            int son = G[t.second][i].to;
            if( dis[son] > dis[t.seond]+G[t.second][i].weight ){//松弛
                dis[son] = dis[t.seond]+G[t.second][i].weight;
                if( !v[son] )
                    q.push(make_pair(dis[son], son));
            }
        }
    }
}

SPFA

思想

我们用数组 d i s dis dis记录每个结点的最短路径估计值,用邻接表来存储图 G G G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点 u u u,并且用 u u u点当前的最短路径估计值对离开 u u u点所指向的结点 v v v进行松弛操作,如果 v v v点的最短路径估计值有所调整,且 v v v点不在当前的队列中,就将 v v v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止

操作

建立一个队列,初始时队列里只有起始点,再建立一个数组记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点作为起始点去刷新到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。

特点
  • 无法处理带负环的图
  • 期望的时间复杂度 O ( k e ) O(ke) O(ke), 其中 k k k为所有顶点进队的平均次数,可以证明k一般小于等于 2 2 2
Code
#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define M 100005

struct node{
    int to, no;

    node(int V, int W) : to(V), no(W) {};
};

vector<node>G[M];

int n, m, s;
int u, v, w;
int dis[M];
bool vis[M];

inline void SPFA(){
    queue<int>q;
    dis[s] = 0;
    vis[s] = 1;
    q.push(s);
    while( !q.empty() ){
        int t = q.front();
        q.pop();
        vis[t] = 0;
        int siz = G[t].size();
        for(int i = 0; i < siz; i ++){
            int son = G[t][i].to;
            if( dis[son] > dis[t] + G[t][i].no ){
                dis[son] = dis[t] + G[t][i].no;
                if( !vis[son] ){
                    vis[son] = 1;
                    q.push(son);
                }
            }
        }
    }
}

int main(){
    scanf("%d%d%d", &n, &m, &s);
    for(int i = 1; i <= m; i ++){
        scanf("%d%d%d", &u, &v, &w);
        G[u].push_back(node(v, w));
    }
    for(int i = 0; i <= n; i ++)
        dis[i] = 2147483647;
    SPFA();
    for(int i = 1; i <= n; i ++)
        printf("%d ", dis[i]);
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值