最短路径-图-----练习力扣787. K 站中转内最便宜的航班

首先还是搞定三种最短路径的算法。

参考:https://www.cnblogs.com/Halburt/p/10756572.html
https://www.cnblogs.com/tahitian-fang/p/5754946.html

先总结一下:

  1. floyd算法,任意两点的最短路径(多源最短路径),可负权边,
  2. dijkstra算法,一点的最短路径(单源最短路径),不可负权边
  3. bellman-ford算法,允许负权边的单源最短路径算法
  4. spfa,其实是bellman-ford+队列优化,其实和bfs的关系更密一点

Floyd

对源点 目标点 桥接点 进行三层循环。

遍历图中任意两个顶点的两两组合vi和vj, 对每一个组合 再对所有顶点进行遍历,针对某一顶点vm ,比较vi和vj当前的最短连接和通过vm的连接的大小,并且把新的当前最短连接重置为其中更小的那个值;这样一圈遍历下来,就可以保证得到图中任意两个顶点之间的最小距离。

原理证明: 待补充

Dijkstra

对从源点的所有边上,挑出未选中的,并且是最短路径的 一条边。该边的终点就被选中了,找到了最短路径。

由于每次是挑出路径最短的,但是一旦有了负边,当前最短的可能在经过负边后,还可以降低,根本无法确定最短路径了

Bellman-Ford

原理证明: 待补充 ,干不动了

NC158 单源最短路

建议先去牛客练习下,各个算法。
为了搞定力扣的787题,该题使用Bellman-Ford 解决

package NC158_单源最短路;

import java.util.Arrays;

public class Main {
    public int findShortestPath (int n, int m, int[][] graph) {
        //dis 记录从1节点开始的到其他顶点的最短距离
    	int dis[]=new int [n+1];
    	//最初赋值为无穷大
    	Arrays.fill(dis, Integer.MAX_VALUE);
    	//起点到起点 肯定是0 啦
    	dis[1]=0;
    	//然后存储起点是1 的其他所有边  的距离保存到dis中
    	for (int[] each : graph) {
			if(each[0]==1)
				dis[each[1]]=each[2];
		}
		//开始了Bellman-Ford   算法
		//迭代n次
    	for(int k=0;k<n;k++){
    		//遍历所有边,使用k次边进行放缩
    		for (int[] each : graph) {
    			//如果1到 边的起点 是无穷大,不可达到,没必要进行了
				if(dis[each[0]]==Integer.MAX_VALUE)
					continue;
				//否则该边可以缩短距离
				if(dis[each[1]]>dis[each[0]]+each[2]){
					dis[each[1]]=dis[each[0]]+each[2];
				}
			}
    	}
    	return dis[n];
    }
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

	}

}

787 K 站中转内最便宜的航班

有 n 个城市通过 m 个航班连接。每个航班都从城市 u 开始,以价格 w 抵达 v。

现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到从 src 到 dst 最多经过 k 站中转的最便宜的价格。 如果没有这样的路线,则输出 -1。

示例 1:

输入:
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 1
输出: 200
解释:
城市航班图如下
在这里插入图片描述

从城市 0 到城市 2 在 1 站中转以内的最便宜价格是 200,如图中红色所示。
示例 2:

输入:
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 0
输出: 500
解释:
城市航班图如下

从城市 0 到城市 2 在 0 站中转以内的最便宜价格是 500,如图中蓝色所示。
在这里插入图片描述

提示:

n 范围是 [1, 100],城市标签从 0 到 n - 1
航班数量范围是 [0, n * (n - 1) / 2]
每个航班的格式 (src, dst, price)
每个航班的价格范围是 [1, 10000]
k 范围是 [0, n - 1]
航班没有重复,且不存在自环

这题是最短路径的变种,看着就感觉Bellman-Ford 最巴适啦。

唯一的问题在于 如何表示这个 k个中转。想想之前牛客那个题,最后的两层循环时候,外层循环就可以表示当前第几次遍历所有的边,概念上很接近啊。

那么再仔细想想,针对这个题,有个问题,
比如下方的数据
4, { { 0, 1, 1 },{ 0, 2, 5 }, { 1, 2, 1 }, { 2, 3, 1 } }, 0, 3, 1)

简单的画个图 框起来的是顶点。
在这里插入图片描述
上面这个图我们在遍历边的时候会出现个什么问题。 跟着代码试试就知道了,我们会先利用(1->2)遍更新 0->2 的距离为2 然后,才能利用(0-2 长度为2 )更新 0-3 距离为3。 这就是在一次遍历所有边的时候,直接求出了最短距离了,这不太行啊。

解决方案:
这咋办,题目要求我们必须知道到达顶点3 的中转次数,此时我们利用外层的循环k,然后在里面循环 利用边进行中转(这个词更符合题意)的时候,我们确保 当前k次循环时候的中转 只利用k-1次的中转结果进行 中转,有点绕
同时 为了配合这样的使用 我们需要记录经过几次中转时源点 到其他顶点的距离了,所以需要一个二维数组记录dis啦。
这个dis二维数组 第一位表示节点 第二维表示中转几次 例如

dis[ i ] [ k ] 表示 源点到顶点i 经过最多k次中转 后的最短距离。

package 建物流中转站;

import java.util.Arrays;

public class Main2 {
	public static int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
		// 一个从src开始的距离数组
		//一个边的数组
		int [][]dis=new int[n][n];
		for(int i=0;i<n;i++){
			if(i==src){
				Arrays.fill(dis[i], 0);
				continue;
			}
			Arrays.fill(dis[i], Integer.MAX_VALUE);
		}
		//加载现有的 出发是src的边
		for(int [] edge : flights){
			if(edge[0]==src){
				dis[edge[1]][0]=edge[2];
			}
		}
		for(int time=1;time<=k;time++){
			for(int[] edge :flights){
				if(dis[edge[0]][time-1]==Integer.MAX_VALUE){
					continue;
				}
				//先传递k-1次中转的最短距离
				dis[edge[1]][time]=Math.min(dis[edge[1]][time], dis[edge[1]][time-1]);
				
				//看看k次中转后的而距离是不是最短
				dis[edge[1]][time]=Math.min(dis[edge[0]][time-1]+edge[2], dis[edge[1]][time]);

				
			}
		}
		
		return dis[dst][k]==Integer.MAX_VALUE?-1:dis[dst][k];
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
//System.out.println(findCheapestPrice(4,new int[][]{{0,1,1},{0,2,5},{1,2,1},{2,3,1}},0,3,1));
System.out.println(findCheapestPrice(3,new int[][]{{0,1,100},{1,2,100},{0,2,100}},0,2,1));

	}

}


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值