最短路算法二

spfa

99.9% 题 没有负环
2024.6.20

笔试题 都能SPFA一般,不是笔试题水,是笔试数据比较水!!!!,不是一锤子买卖 不担心超时TLE

BF的优化! 有点傻,遍历所有边更新,但是不一定要都更新啊!!! 优化!

只有当dist[a]变小,才更新

spfa巨快

很多正权图也能用SPFA过掉!!!
用BFS优化,用队列(用其他也行,推荐队列) 长得很像dijkstra算法II!!!
时间复杂度 O(m) <O(nm)
起点进queue
While 队列不空
1. t取队头; 弹出q.pop()队头
2.更新t的所有出边(从t指出来的边) 用已经更新过的 点的距离更新其他从这个点出来的边,我变成小了,从我出来的才会跟着一起变小!!
queue存待更新的点

// dijkstra II算法


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

using namespace std;

typedef pair<int, int> PII;

// 这里改了 1e5
const int N = 100010;

int n, m;
// 邻接表
int h[N], w[N], e[N], ne[N], idx;  // w[N]边的权重
int dist[N]; //dist 当前1号到i的最短距离
bool st[N]; // 是否已经确定dist[i]


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); //最短路 初始化成无穷大
    dist[1] = 0;
    
    queue<int> q;
    q.push(1);
    st[1] = true; //存1号点在队列了
    
    while(q.size()){
        // queue非空 取队头=t
        int t = q.front();
        q.pop();
        
        st[t] = false; // pop出来就要置为false
        
        // 看所有t的出边的点的最短路能不能更新!!!
        for(int i = h[t]; i != -1; i = ne[i]){
            int j = e[i];  // j当前这个点,t出边的另外一头点
            if(dist[j] > dist[t] + w[i]){
                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);
     
     while(m --){
         int a, b, c;
         scanf("%d%d%d", &a, &b, &c);
         add(a, b, c);
     }
     
     int t = spfa();
     
    //  上课的版本不行了,-1有bug!!!
     if (t == 0x3f3f3f3f) puts("impossible");
     else printf("%d\n", t);
    
    
    return 0;
}

用spfa可以过dijkstra1 但是II不行,超时!!!!

spfa巨快,比dijkstra块!!!法二 spfa 会超时被卡, 换堆优化dijkstra

阴险的出题人,网格形状容易卡

只要物理健康的肉体还在和法律上 社会没死亡

可以随便是错没事的

身体不行,物理没了什么都没了!!!

spfa求负环!!

思路

  1. Dist[x] 当前1到x的最短路
    cnt[x] 当前最短路的边的数量?????

  2. 每次更新
    Dist[x] = dist[t] + w[i]
    Cnt[x] = cnt[t] + 1;

从1-x的边数 = 1-t边数+1条边t-x

  1. 判负环

如果cnt[x] >=n 即边数>=n, 说明经过的点数>= n+1
但是一共n点,所以至少两点重复出现,即出现了环

又因为最短路,选择走环,说明走环让数减少,所以一定是个负环!!!

852 spfa求负环
无向图是特殊的有向图,可以用有向图的 模板

改了初始化dist不用

更新同时更新cnt
返回false true不是dist[n]
队列初始不是压入1, 因为不一定环是从1开始的,所有点都压到queue,确保每个点开始的环都被检测到!!!!

// spfa改的

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

using namespace std;

typedef pair<int, int> PII;

// 这里改了 1e5
const int N = 100010;

int n, m;
// 邻接表
int h[N], w[N], e[N], ne[N], idx;  // w[N]边的权重
int dist[N], cnt[N];
bool st[N]; // 是否已经确定dist[i]


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

bool spfa(){
    // memset(dist, 0x3f, sizeof dist); //最短路 初始化成无穷大
    // dist[1] = 0;
    // 不需要初始化,不记录距离,只判断负环

    queue<int> q;
    for(int i = 1; i <= n ; i ++){
        // 确保每个点开始的环都能被检测到!!
        q.push(i);
        st[i] = true;
    }

    while(q.size()){
        // queue非空 取队头=t
        int t = q.front();
        q.pop();

        st[t] = false; // pop出来就要置为false

        // 看所有t的出边的点的最短路能不能更新!!!
        for(int i = h[t]; i != -1; i = ne[i]){
            int j = e[i];  // j当前这个点,t出边的另外一头点
            if(dist[j] > dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                cnt[j]  =cnt[t] + 1; //这里多了
                
                if(cnt[j] >= n) return true;
            
                if(!st[j]){
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    
    return false;
}



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);
     }

    if(spfa()) puts("Yes");
    else puts("No");

    return 0;
}

floyd

图论二 01:55:10
2024.6.21

这几个算法都要牢记时间复杂度!!!!

最短路的这种图,要在5min内写完,我的妈,至少十遍就熟了吧!!!

找工作不用算法竞赛,但是模板要背,
floyd超简单

用邻接矩阵存所有边
三重循环

原理:动态规划dp
d[k, I , j] : 从i到j, 只经过1-k个中间点的最短路= d[k-1, I, k] + d[k-1, k,j] 两段拼起来的 会后面细讲

k先循环,ij顺序可以颠倒

不是所有图论都画图的,思路重要

自环删去, 重边保留最短那条

妙啊,如果出现segmentation fault 用删代码法,一块块删到不报错,看哪里出问题!!!!

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

using namespace std;

const int N = 210, INF = 1e9; //why???

int n, m, Q;
int d[N][N]; //邻接矩阵

void floyd(){
    for(int k = 1; k <=n; k ++)
        for(int i = 1; i <=n; i ++)
            for(int j = 1; j <=n; j ++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]); //dp背包
}

int main(){
    scanf("%d%d%d", &n, &m, &Q);
    
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= n ; j ++)
            if(i == j) d[i][j] = 0;
            else d[i][j] =INF;
            
    
    while(m --){
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        
        // 如果重边,最小
        d[a][b] = min(d[a][b], w);
    }
    
    floyd();
    
    while(Q --){
        int a, b;
        scanf("%d%d", &a, &b);
        // if(d[a][b] == INF) puts("impossible");
        // 因为如果没有通路,但是存在负权边,可能比无穷大小一点,所以/2
        if(d[a][b] > INF / 2) puts("impossible");
        else printf("%d\n", d[a][b]);
    }
}


调代码:
printf输出法
TLE删代码法
就是经验, 思路+代码不停敲

总结:
算法都要记时间复杂度

因为题目数据范围给了很多提示

比如n=200 m很大
多半是floyd

N m都大十万
多半堆优化dijkstra或者spfa

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值