蓝桥杯上岸必背!!!(第七期 最短路算法)

第七期:最短路算法🔥 🔥 🔥

蓝桥杯热门考点模板总结来啦✨

你绝绝绝绝绝绝对不能错过的常考最短路算法模板 💥

❗️ ❗️ ❗️

大家好 我是寸铁✨

还没背熟模板的伙伴们背起来 💪 💪 💪

祝大家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]);
        }
    }
}

✨ ✨ ✨
看到这里,不妨点个关注 💖

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寸 铁

感谢您的支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值