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
- 初始化距离:dist[1] = 0, dist[i] = +∞,只有起点1遍历到了
- 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)
- 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
- 堆实现
a. 手写
可以保证时刻只有n个数
b. Stl: priority_queue 每次修改查一个新数,不支持直接修改, 会有m个数!
20240619
最短路用邻接表存,不用处理重边,为什么??????
一个图论题,至少5-6边,类似肌肉记忆游泳
bellman-ford
20240618
-
存边的方式牛逼
a. Struct 不用邻接表 -
两重循环 很简单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上题太少了
- 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不行
备份保证每次最短路只向前扩展 一步,每步更新所有边?????这里没懂