最短路算法一

20240618
19:33 朴素版Dijkstra
47:00 Heap优化版
1:04:00 Bellman-ford

最短路算法——5种!!!
考察重点:
不会考算法证明,这里不讲了,重点是实现+抽象
1. 如何建图——如何定义点 边,抽象成一个图问题

Prim /i/, kruskal是最小生成树算法 不是prime/ai/质数
1. 是么时候用?方法
n图的node数 m边数

单源:只有一个起点,求从1个点到其他所有点/第n号点的最短距离
多源汇:源点= 起点 汇点= 终点 起点终点都不确定

朴素dijkstra(基于贪心)适合稠密图=边数多的(m~n2一个级别) 因为与m无关
稀疏图 m~n ,m和n一个级别,不能用朴素,用堆优化版

SPFA可以是bellman-ford BF的优化(基于离散数学),常用,极少BF
但是当 经过的边数<=k 只能BF,不能spfa了

多源:floyd基于DP

  1. 初始化距离:dist[1] = 0, dist[i] = +∞,只有起点1遍历到了
  2. s: 已确定最短路的点的集合
    每次更新一个点(这个点是谁呢,当前到起点1最短的点,为什么呢,基于贪心做的!!!)的最短路 不太难写
    For i: 1 - n{
    t<- 找到不在s里距离最短路的点
    s<-t 放到s里
    用t更新其他所有点的距离dist[i]
    Min(1到i距离dist[i] , 1到t距离 dist[t]+ t到i距离),
    }

O(2n2) O(n2)

  1. Dijkstra求最短路 I 为例

找不在s里, 所有没有确定变成绿色的里 距离源点1 距离dmin = 0 s={1}

更新起点1到其他所有点的dist[i]

此时待定点23距离最短的是点2, 所以 s= {1,2}

更新待定点3距离dist[3], 因为1到2 = 2, 2到3 = 1,用已经确定的点的最小距离取更新未确定点的dmin, 所以1到3= dist[3] = 3<4更新

此时不在s里dmin就是3,所以3确定 s={1,2,3} ,dmin每个点到1起点距离确定

边数多,稠密图,用邻接矩阵存,
稀疏图用邻接表——queue 单链存!!

笔试面试考的多,但是leetcode题少!!!
没有区分有向无向图,有向图的最短路 = 无向图的最短路,因为无向图= 特殊的有向图,
无向边a-b= 两条有向边互指 a->b+b->a

一些算法书上代码模板很长很难用
因为太久了, 一定有简单的短的版,算法竞赛这本书比较实用的模板
模板一定要背!!!

比较蛋疼, 存在重边 自环
重边: 点t->x 有多条边, 互相指不算,t指向x多条,保留最短就行
自环:如果权重都正,自环不会出现在最短路里,这不是绕路嘛!!!

闫总 的习惯,每次都先打main 最后打函数!!!



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

using namespace std;

const int N = 510; 

// m~n2稠密图,邻接矩阵

int n, m;
int g[N][N];
int dist[N]; //dist 当前1号到i的最短距离
bool st[N]; // 是否已经确定dist[i]

int dijkstra(){
    // 初始化
    memset(dist, 0x3f, sizeof dist); //正无穷, 按字节初始化int 4字节,所以实际是0x3f3f3f3f
    dist[1] = 0;
    
    // 更新最短路
    for(int i = 0;i <n; i ++){
        int  t= -1; //一开始一个没有确定???
        
        for(int j = 1;  j <= n; j ++){
            // 找到当前待定点里st=false里最短的点
            if(!st[j] && (t==-1 || dist[t] > dist[j]))
                t = j;
        }
        if (t == n) break; //优化可加
        
        st[t] = true;
        
        // t更新其他点到1距离!!
        for(int j = 1;  j <= n; j ++)
            dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}


int main(){
    scanf("%d%d", &n , &m);
    
    // 比较蛋疼, 存在重边 自环
    // 初始化 memset或者for
    // memset(g, 0x3f, sizeof g); 将会把数组 g 中每个元素的所有字节都设置为 0x3f
    // 通常是一个很大的数,如 0x3f)是为了表示这些位置还没有被使用或者是无穷大等特殊状态
    memset(g, 0x3f, sizeof g);
    /* for(int i = 1; i <= n ; i ++)
         for(int j = 1; j <= m; j ++)
             if(i == j) g[i][j] = 0;  //指自己
             else g[i][j] = INF;
     */
     
     while(m --){
         int a, b, c;
         scanf("%d%d%d", &a, &b, &c);
         g[a][b] = min (g[a][b], c);
     }
     
     int t = dijkstra();
     
     printf("%d\n", t);
    
    
    return 0;
}


算法:追求最短时间内实现,是让自己看懂
工程:追求日后好维护好修改,让别人也看懂

人适合记形象的 规律的

堆优化版
如果n是105 O(n2)会爆掉,优化

s<-t O(1)n次, 更新其他点距离,就是遍历所有边所以m
最费时 n2 在一堆点里找dmin点

用堆 在一堆点里找点, n2->n
但是在堆里修改数logn 所以m->mlogn
三个最小是mlogn

  1. 堆实现
    a. 手写
    可以保证时刻只有n个数
    b. Stl: priority_queue 每次修改查一个新数,不支持直接修改, 会有m个数!

20240619
最短路用邻接表存,不用处理重边,为什么??????
一个图论题,至少5-6边,类似肌肉记忆游泳

bellman-ford

20240618

  1. 存边的方式牛逼
    a. Struct 不用邻接表

  2. 两重循环 很简单O(nm)

更新过满足程——松弛操作
更新得到的结果——三角不等式

bellmanford处理负权边的,不一定有最短路,比如上面右上角例子
除非下面这种,负环不在1到n的最短路上,那就不影响

可以在2-3-4这个环走无穷多圈, 负无穷然后转出去!!!

再比如 下面在负环里走无数圈

所以求的出最短路的一般不存在负权路
3. 找负环
可以用BF做,但是一般用SPFA, SPFA(要求图一定没有负环)各个方面都好于BF(除了当有边数限制)
迭代k次
得到的最短距离= 从起点1 走不超过k条边,走到每个点的最短距离

如果第n次还更新, 说明存在一条从起点1 走n条边的路径,有n+1个点,但是一共n个点,抽屉原理鸽巢原理,一定两个点一样,存在负环

853

边权可能为负数——不能dijkstra
当限制了边的条数,那有负环也没事,因为不可以在负环里无限转求不出最短路

实际含义:旅客坐飞机中转一次,心情变差一点,所以限制中转次数,用BF

图论题,笔试很多,面试不多,一般是很难的了,但是leetcode上题太少了

  1. BF核心函数里 备份dist why 见笔记
    如果不备份,可能发生串联
    串联:由于这个算法的特性决定,每次更新得到的必然是在多考虑 1 条边之后能得到的全局的最短路。而串联指的是一次更新之后考虑了不止一条边:由于使用了松弛,某节点的当前最短路依赖于其所有入度的节点的最短路;假如在代码中使用dist[e.b]=min(dist[e.b],dist[e.a] + e.c);,我们无法保证dist[e.a]是否也在本次循环中被更新,如果被更新了,并且dist[e.b] > dist[e.a] + e.c,那么会造成当前节点在事实上“即考虑了一条从某个节点指向a的边,也考虑了a->b”,共两条边。而使用dist[e.b]=min(dist[e.b],last[e.a] + e.c);,可以保证a在dist更新后不影响对b的判定,因为后者使用last数组,保存着上一次循环中的dist的值。

比如用d[2]的最短路去更新d[3] = d[2] + w[2-3] = 1+1 其实是不对的这个串联,不符合边数限制
举个例子 1到3最短路 k限制1, 那么1->2->3就不可以,因为k = 2>1不行
备份保证每次最短路只向前扩展 一步,每步更新所有边?????这里没懂

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值