第七期:最短路算法🔥 🔥 🔥
蓝桥杯热门考点模板总结来啦✨
你绝绝绝绝绝绝对不能错过的常考最短路算法模板 💥
❗️ ❗️ ❗️
大家好 我是寸铁✨
还没背熟模板的伙伴们背起来 💪 💪 💪
祝大家4月8号蓝桥杯上岸 ☀️
不清楚蓝桥杯考什么的点点下方👇
考点秘籍
想背纯享模版的伙伴们点点下方👇
蓝桥杯省一你一定不能错过的模板大全(第一期)
蓝桥杯省一你一定不能错过的模板大全(第二期)
想背注释模版的伙伴们点点下方👇
蓝桥杯必背第一期
蓝桥杯必背第二期
往期精彩回顾
蓝桥杯上岸每日N题 第一期(一)!!!
蓝桥杯上岸每日N题第一期(二)!!!
蓝桥杯上岸每日N题第一期(三)!!!
蓝桥杯上岸每日N题第二期(一)!!!
蓝桥杯上岸每日N题第三期(一)!!!
操作系统期末题库 第九期(完结)
LeetCode Hot100 刷题(第三期)
idea创建SpringBoot项目报错解决方案
数据库SQL语句(期末冲刺)
想看JavaB组填空题的伙伴们点点下方 👇
填空题
竞赛干货
算法竞赛字符串常用操作大全
蓝桥杯上岸必刷!!!(模拟/枚举专题)
蓝桥杯上岸必背!!! (第三期 DP)
蓝桥杯上岸必背!!!(第四期DFS)
蓝桥杯上岸必背!!!(第五期BFS)
蓝桥杯上岸必背!!!(第六期树与图的遍历)
最短路是蓝桥杯的热门考点,距离省赛仅剩4天,干就完事了 ❗️
下面让我们开始刷起来 ❗️ ❗️ ❗️
问题:请你计算通往蓝桥杯的最短路 ❓
提示:跟着寸铁背模板💪 💪 💪
答案:Accepted
SPFA求最短路
(正负权均可,存在被卡的风险,以前算竞最常背的模板,保险起见可以再背一个Dijkstra())
做法
用队列来维护搜索的顺序,一层一层往下搜,相当于BFS
。
每次用当前搜的这一轮的最短距离去更新下一轮的最短距离。
注意
初始化dist
数组为INF
,这样做的目的是便于去判断是否走到n
存在最短路。
st[]
数组存的是当前在队列中的元素,所以在每次入队时,可以将元素置为true,表示在队列中。
出队时,再将元素置为false,表示不在队列中,这样就避免了更新重复元素的最短路。
spfa模板
import java.util.*;
public class Main{
static int N=100010,n,m,idx,INF=0x3f3f3f3f;
static int []h=new int[N],e=new int[N],ne=new int[N],w=new int[N];
static int []q=new int[N];
static int []dist=new int[N];
static boolean []st=new boolean[N];
public static void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
public static void spfa(){
Queue<Integer>q=new LinkedList<>();
Arrays.fill(dist,INF);
q.offer(1);
dist[1]=0;
st[1]=true;
while(!q.isEmpty()) {
int t=q.poll();
st[t]=false;
//表示不在队列中
//避免重复元素的查找
for(int i=h[t];i!=-1;i=ne[i]) {
int j=e[i];
if(dist[j]>dist[t]+w[i]) {
dist[j]=dist[t]+w[i];
//满足这个条件的才需要入队
//写在外面就会将不满足这个条件的都入队
if(!st[j]) {
q.offer(j);
st[j]=true;
//表示在队列中
}
}
}
}
if(dist[n]==INF)System.out.println("impossible");
else System.out.println(dist[n]);
}
public static void main(String []args){
Scanner in = new Scanner(System.in);
n=in.nextInt();
m=in.nextInt();
Arrays.fill(h, -1);
while(m-->0) {
int a=in.nextInt();
int b=in.nextInt();
int c=in.nextInt();
add(a, b, c);
}
spfa();
}
}
Dijkstra朴素版
Dijkstra求最短路I
题目描述
对于边的权值不为1且边权值均为正值求最短距离的题目
采用Dijkstra算法求最短路
再看数据范围,500*10的5次方是n*m
级别,对应的是稠密图,采用Dijstra朴素做法处理。
分析
做法
由于采用的是Dijkstra朴素法,且是稠密图,所以使用邻接矩阵存储边。
循环n次,找到第1号点到n个点的距离
(1)第一次迭代,找到当前循环的最小距离的点,将其进行标记为true
。
(2)再进行第二次迭代,更新比较dist[j]
和当前最小距离的点到j的距离(dist[t]+g[t][j])
的最小值。
循环n次,可以确保找到每轮循环的最小距离的点。
注:如果最后存在有点的最小距离还是max
,也就是说没有给出从1->n
号点的边或者是从t
到第n
号点的边,使得dist[n]
的距离仍为max
,返回-1
即可。
模拟
注:像自环和重边的情况,自环自己指向自己,在求最短距离的过程中,不影响求最短路,会被自动忽略掉。
重边的情况,我们只需要在主方法中,选取g[a][b]的最短边即可。
代码
import java.util.*;
public class Main{
static int N=510,n,m,max=0x3f3f3f3f;
static int [][]g=new int[N][N];
static int []dist=new int[N];
static boolean []st=new boolean[N];
public static int dijkstra() {
Arrays.fill(dist, max);
//将dist数组里每个点的距离初始化为max
dist[1]=0;
//将1号点的距离初始化为0,不能初始化为1,初始化为1是自环,后续的最短路径的点便查找不到
for(int i=0;i<n;i++) {
//因为最短路径的寻找不是直接比较即得
//需要走各个点到点的距离即dist[t]+g[t][j]再和dist[j]比较。
//所以需要每次寻找最短距离的点,再去更新dist[t]+g[t][j]
//循环一轮后,得到一个点的最短距离,循环n轮后,得到n个点的最短距离。
//循环1次,确定一个点的最短距离。循环n次,确定n个点的最短距离。
//这样便求出每个点到起点的最短距离
int t=-1;//过渡变量
for(int j=1;j<=n;j++) {
//第一轮迭代,寻找最短路径的点,并在第2轮迭代中用这个点去更新其他点的最短路径
if(!st[j]&&(t==-1||dist[j]<dist[t]) ){
//如1号点未被标记过,就更新1号点到1号点的距离,即初始化的max
//进入迭代,更新1号点到各个点的距离,寻找当前最短路径的点。
t=j;
}
}
st[t]=true;//标记t为最短路径的点
for(int j=1;j<=n;j++) {//第二轮迭代,迭代每个点的最短距离
dist[j]=Math.min(dist[j], dist[t]+g[t][j]);
//从当前最短距离的点到j号点的距离与从最短距离点到j号点的距离进行比较,求最小值。
}
}
if(dist[n]==max)return -1;
//没有给出从1-n号点的边或者是从t到n号点的边,使得dist[n]的距离仍为max,返回-1。
else return dist[n];//返回从1号点到n号点的距离
}
public static void main(String []args) {
Scanner in = new Scanner(System.in);
n=in.nextInt();
m=in.nextInt();
for(int i=1;i<=n;i++) {
//初始化每一个点到其他点的距离均为max
Arrays.fill(g[i], max);
}
while(m-->0) {
int a=in.nextInt();
int b=in.nextInt();
int c=in.nextInt();
g[a][b]=Math.min(g[a][b], c);
//出现重边情况,取边长最短的那条边
}
System.out.println(dijkstra());
}
}
Dijkstra堆优化版
Dijkstra求最短路 II
题目分析
运用优先队列(堆)+邻接表
先让第一个点进堆,再让它出队,去找到当前最小距离,入堆,堆顶的位置为当前的最小距离。
再让堆顶的位置出堆,标记该点已出堆过,不能重复出堆,再用它去更新其它的点的最小距离。
再将每一次循环后的最小距离的点入堆,更新最短距离,出堆,再去更新最小距离的点,再让最小距离的点入堆,重复这个过程,直至每个点都被标记过。
过程分析
整个过程类似于BFS,用当前最小距离的点去更新它的邻接节点的距离,再入堆,再出堆,又用出堆的最小距离的点去更新他的邻接节点,一层一层往下搜,直至最小距离的点都被标记过。
时间复杂度分析
_O(n*n)--> O(n*logn)_
标记的作用
堆顶是整个小根堆的最小距离,更新过一次后,在陆续的循环中,堆顶是不会被更新的。
比堆顶大的最小距离的点会依次入堆,会被安放在堆下面的位置,所以,需要打上标记,防止重复出队。
再依次出队每一轮循环的最短距离去更新其它点的最小距离。
模拟
注:绿星表示的是打上标记,每次入堆的点出堆后都会打上标记。
代码
import java.io.*;
import java.util.*;
public class Main{
static int N=150010,idx,n,m;
static int dist[]=new int[N];
static int h[]=new int[N];
static int e[]=new int[N];
static int ne[]=new int[N];
static int w[]=new int[N];
static boolean st[]=new boolean[N];
static int inf=0x3f3f3f3f;
public static void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;//赋权
ne[idx]=h[a];
h[a]=idx;
idx++;
}
public static int dijkstra(){
PriorityQueue<PIIs>queue=new PriorityQueue<PIIs>();//优先队列,相当于堆
Arrays.fill(dist,inf);//初始化每个dist的距离为inf
dist[1]=0;//初始化1这个点的距离为0
queue.add(new PIIs(0,1));//PIIs类型加入堆中
while(!queue.isEmpty()){
PIIs p = queue.poll();//出队
//这里出队的话,主要是堆中的堆顶是堆中最小的,依次比它较小的元素在堆顶的下面。
//由于堆顶不会被更新,所以需要出队来依次遍历每轮循环的最小值。
int t=p.getSecond();//获得这个点
int distance=p.getFirst();//获得这个点的距离
if(st[t])continue;//如果被标记过,就从头开始循环
st[t]=true;//标记当前为最小值的点
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];//获得邻接节点
if(dist[j]>distance+w[i]){//更新邻接节点的最小值,类似于bfs一层一层往下搜
dist[j]=distance+w[i];//更新为当前点的最小值
queue.add(new PIIs(dist[j],j));//入堆
}
}
}
if(dist[n]==inf)return -1;//如果dist[n]=inf,返回-1
else return dist[n];//返回到n号点的距离
}
public static void main(String []args) throws IOException {
BufferedReader re=new BufferedReader(new InputStreamReader(System.in));
String []str1=re.readLine().split(" ");
n=Integer.parseInt(str1[0]);
m=Integer.parseInt(str1[1]);
Arrays.fill(h, -1);
while(m-->0) {
String str2[]=re.readLine().split(" ");
int a=Integer.parseInt(str2[0]);
int b=Integer.parseInt(str2[1]);
int c=Integer.parseInt(str2[2]);
add(a,b,c);
}
System.out.println(dijkstra());
}
}
class PIIs implements Comparable<PIIs>{
private int first;
private int second;
public int getFirst() {
return this.first;
}
public int getSecond() {
return this.second;
}
public PIIs(int first,int second) {
this.first=first;
this.second=second;
}
public int compareTo(PIIs o) {
return Integer.compare(first, o.first);
}
}
Floyd求最短路
Floyd求最短路
不同于前面的最短路,属于单元汇,求的是1号点到各个点的最短路。
Floyd求的是**x
号点到y
号点的最短路,属于多元汇**最短路问题。
dist[i][j]
存的是i
号点到j
号点的最短距离。
将邻接矩阵转换为最短距离矩阵,查表即可。
实现代码
3重for循环实现
for(int k=1;k<=n;k++){
//先循环k,i,j顺序可颠倒。
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dist[i][j]=Math.min(dist[i][j],dist[i][k]+dist[k][j]);
}
}
}
其本质是一个动态规划问题:
dist[k,i,j]=dist[k-1,i,k]+dist[k-1,k,j];
理解:
i点到j点经过1到k条边的距离
=i点到k点经过1到k-1条边的距离+k点到j点经过1到k-1条边的距离
由于两段距离都经过1到k-1个点,类似于dp优化,计算时保存的是上一层的值,所以可以将这一重给省略掉。
变为二维:
dist[i][j]=Math.min(dist[i][j],dist[i][k]+dist[k][j]);
时间复杂度
3层for循环:每层循环走n个点
O(n^3)
代码
import java.util.*;
public class Main{
static int n,m,q;
static int INF=0x3f3f3f3f;
static int N=210;
static int dist[][]=new int[N][N];
public static void floyd(){
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dist[i][j]=Math.min(dist[i][j],dist[i][k]+dist[k][j]);
}
}
}
}
public static void main(String []args){
Scanner sc=new Scanner(System.in);
n=sc.nextInt();
m=sc.nextInt();
q=sc.nextInt();
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j)dist[i][j]=0;
//不存在负权回路,处理自环,自己指向自己默认为0
//不影响更新其他点的距离
else{
dist[i][j]=INF;
}
}
}
while(m-->0){
int a=sc.nextInt();
int b=sc.nextInt();
int w=sc.nextInt();
dist[a][b]=Math.min(dist[a][b],w);
//处理重边,保留最小的边权即可
}
floyd();
while(q-->0){
int a=sc.nextInt();
int b=sc.nextInt();
if(dist[a][b]>INF/2)System.out.println("impossible");
//边权为负,存在某些点到点的距离比INF要小一些
//需要大于INF/2
else System.out.println(dist[a][b]);
}
}
}
✨ ✨ ✨
看到这里,不妨点个关注 💖