图-Dijkster最短路径
1.题目:过年回家
题目描述:
输入包含多组数据,每组数据第一行包含两个正整数m(1≤m≤500)和n(1≤n≤30),其中n表示有n个收费站,编号依次为1、2、…、n。出发地的编号为0,终点的编号为n,即需要从0到n。
紧接着m行,每行包含三个整数f、t、c,(0≤f, t≤n; 1≤c≤10),分别表示从编号为f的地方开到t,需要交c元的过路费。
输出描述:
对应每组数据,请输出至少需要交多少过路费。
8 4
0 1 10
0 2 5
1 2 2
1 3 1
2 1 3
2 3 9
2 4 2
3 4 4
算法的思路
Dijkstra算法采用的是一种贪心的策略,声明一个数组dist来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T,初始时,原点 s 的路径权重被赋为 0 (dist[s] = 0)。若对于顶点 s 存在能直接到达的边(s,m),则把dist[m]设为w(s, m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。初始时,集合T只有顶点s。
然后,从dist数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该点加入到T中,OK,此时完成一个顶点,
然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,如果是,那么就替换这些顶点在dist中的值。
然后,又从dist中找出最小值,重复上述动作,直到T中包含了图的所有顶点。
算法的演示:
下面我求下图,从顶点v1到其他各个顶点的最短路径
首先第一步,我们先声明一个dis数组,该数组初始化的值为:
我们的顶点集T的初始化为:T={v1}
既然是求 v1顶点到其余各个顶点的最短路程,那就先找一个离 1 号顶点最近的顶点。通过数组 dis 可知当前离v1顶点最近是 v3顶点。当选择了 2 号顶点后,dis[2](下标从0开始)的值就已经从“估计值”变为了“确定值”,即 v1顶点到 v3顶点的最短路程就是当前 dis[2]值。将V3加入到T中。
为什么呢?因为目前离 v1顶点最近的是 v3顶点,并且这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得 v1顶点到 v3顶点的路程进一步缩短了。因为 v1顶点到其它顶点的路程肯定没有 v1到 v3顶点短.
OK,既然确定了一个顶点的最短路径,下面我们就要根据这个新入的顶点V3会有出度,发现以v3 为弧尾的有: < v3,v4 >,那么我们看看路径:v1–v3–v4的长度是否比v1–v4短,其实这个已经是很明显的了,因为dis[3]代表的就是v1–v4的长度为无穷大,而v1–v3–v4的长度为:10+50=60,所以更新dis[3]的值,得到如下结果:
因此 dis[3]要更新为 60。这个过程有个专业术语叫做“松弛”。即 v1顶点到 v4顶点的路程即 dis[3],通过 < v3,v4> 这条边松弛成功。这便是 Dijkstra 算法的主要思想:通过“边”来松弛v1顶点到其余各个顶点的路程。
然后,我们又从除dis[2]和dis[0]外的其他值中寻找最小值,发现dis[4]的值最小,通过之前是解释的原理,可以知道v1到v5的最短距离就是dis[4]的值,然后,我们把v5加入到集合T中,然后,考虑v5的出度是否会影响我们的数组dis的值,v5有两条出度:< v5,v4>和 < v5,v6>,然后我们发现:v1–v5–v4的长度为:50,而dis[3]的值为60,所以我们要更新dis[3]的值.另外,v1-v5-v6的长度为:90,而dis[5]为100,所以我们需要更新dis[5]的值。更新后的dis数组如下图:
然后,继续从dis中选择未确定的顶点的值中选择一个最小的值,发现dis[3]的值是最小的,所以把v4加入到集合T中,此时集合T={v1,v3,v5,v4},然后,考虑v4的出度是否会影响我们的数组dis的值,v4有一条出度:< v4,v6>,然后我们发现:v1–v5–v4–v6的长度为:60,而dis[5]的值为90,所以我们要更新dis[5]的值,更新后的dis数组如下图:
然后,我们使用同样原理,分别确定了v6和v2的最短路径,最后dis的数组的值如下:
代码:
1 import java.util.Scanner; 2 public class Main{ 3 public static void main(String[]args){ 4 Scanner in=new Scanner(System.in); 5 while(in.hasNext()){ 6 int m=in.nextInt(); 7 int n=in.nextInt(); 8 int[][] Edge=new int[n+1][n+1];//通过临接矩阵来保存图 9 int[] dist=new int[n+1];//dist用来保存从起点到每个点的最短路径,到不了的点距离设置为无穷大 10 int[]path=new int[n+1];//path用来保存每个点的前一个顶点号,如path[3]=2,则有顶点2->3 11 boolean[] s=new boolean[n+1];//s用来记录该点是否已经确定了,初始化都为false,当把该点计入最短路径后,设为true; 12 for(int i=0;i<n+1;i++){ //初始化临街矩阵 13 for(int j=0;j<n+1;j++){ 14 Edge[i][j]=Integer.MAX_VALUE; 15 } 16 } 17 for(int i=0;i<m;i++){ //输入临接矩阵 18 Edge[in.nextInt()][in.nextInt()]=in.nextInt(); 19 } 20 Dijkster(Edge,dist,path,s,m,n); 21 } 22 } 23 private static void Dijkster(int[][]Edge,int[]dist,int[]path,boolean[]s,int m,int n){ 24 for(int i=0;i<n+1;i++){//初始化dist,path 25 dist[i]=Edge[0][i]; 26 if(i!=0&&dist[i]<Integer.MAX_VALUE){ 27 path[i]=0; 28 }else{ 29 path[i]=-1; 30 } 31 } 32 s[0]=true;//从第一个顶点开始计算 33 dist[0]=0; 34 for(int i=0;i<n;i++){ 35 int min=Integer.MAX_VALUE; 36 int u=0; 37 //选择当前未确定顶点中具有最短路径(离起点最近)的顶点u 38 for(int j=0;j<n+1;j++){ 39 if(!s[j]&&dist[j]<min){ 40 u=j; 41 min=dist[j]; 42 } 43 } 44 //此时u已经在最短路径上了,更新s 45 s[u]=true; 46 for(int k=0;k<n+1;k++){ 47 if(!s[k]&&Edge[u][k]<Integer.MAX_VALUE&&dist[u]+Edge[u][k]<dist[k]){ 48 dist[k]=dist[u]+Edge[u][k]; 49 path[k]=u; 50 } 51 } 52 } 53 System.out.println(dist[n]); 54 } 55 }
一般而言,最短路径的题就是这样的解法,学习一下排名第一的解法,算法方面都差不多,但是他使用了java IO流,使得整个程序的执行时间比第二名快了3倍,看来数据量大的时候,Scanner扫描器就不是那么合适了。
1 import java.io.BufferedReader; 2 import java.io.IOException; 3 import java.io.InputStreamReader; 4 import java.io.OutputStreamWriter; 5 import java.io.PrintWriter; 6 import java.io.StreamTokenizer; 7 8 public class Main { 9 10 public static void main(String[] args) throws IOException { 11 StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in))); 12 PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out)); 13 14 while(in.nextToken() != StreamTokenizer.TT_EOF){ 15 int m = (int) in.nval; 16 in.nextToken(); 17 int n = (int) in.nval; 18 int [][] a = new int [n + 1][n + 1]; // 收费站数目加上起点 19 for(int i = 0; i <= n; i++){ 20 for(int j = 0; j <= n; j++){ 21 a[i][j] = Integer.MAX_VALUE; 22 } 23 } 24 25 for(int k = 1; k <= m; k++){ // 用二维数组存储两地通过需要教的路费 26 in.nextToken(); int i = (int)in.nval; 27 in.nextToken(); int j = (int)in.nval; 28 in.nextToken(); a[i][j] = (int)in.nval; 29 } 30 int s = dijk(a); 31 out.println(s); 32 out.flush(); 33 } 34 35 } 36 37 private static int dijk(int [][] graph){ 38 39 boolean [] sign = new boolean[graph.length]; 40 int [] dist = new int [graph.length]; 41 42 // 遍历第 起始位置能到达的下一个位置, 收费为当前收费站的收费 43 for(int i = 0; i< graph.length; i++){ 44 dist[i] = graph[0][i]; 45 } 46 sign[0] = true; 47 // 遍历其他位置、 48 for(int i = 1; i < graph.length; i++){ 49 int min = Integer.MAX_VALUE; 50 int u = 0; 51 for(int j = 1; j < graph.length; j++){ 52 if(!sign[j] && dist[j] < min){ 53 min = dist[j]; 54 u = j; 55 } 56 } 57 58 sign[u] = true; 59 for(int j = 1; j < graph.length; j++){ 60 if(!sign[j] && graph[u][j] < Integer.MAX_VALUE){ // 没被访问过、同时有路可以走、 61 int v = dist[u] + graph[u][j]; 62 if(v < dist[j]){ 63 dist[j] = v; 64 } 65 } 66 } 67 } 68 69 return dist[graph.length - 1]; 70 } 71 }
,再来看一题
2.题目:牛牛取快递
题目描述:牛牛的快递到了,他迫不及待地想去取快递,但是天气太热了,以至于牛牛不想在烈日下多走一步。他找来了你,请你帮他规划一下,他最少要走多少距离才能取回快递。
输入描述:
每个输入包含一个测试用例。
输入的第一行包括四个正整数,表示位置个数N(2<=N<=10000),道路条数M(1<=M<=100000),起点位置编号S(1<=S<=N)和快递位置编号T(1<=T<=N)。位置编号从1到N,道路是单向的。数据保证S≠T,保证至少存在一个方案可以从起点位置出发到达快递位置再返回起点位置。
接下来M行,每行包含三个正整数,表示当前道路的起始位置的编号U(1<=U<=N),当前道路通往的位置的编号V(1<=V<=N)和当前道路的距离D(1<=D<=1000)。
输出描述:
对于每个用例,在单独的一行中输出从起点出发抵达快递位置再返回起点的最短距离。
输入例子1:
3 3 1 3 1 2 3 2 3 3 3 1 1
输出例子:
7
可以看出,这个题也是在考察最短路径,
如果将上一个题的答案稍加改进,得到
1 import java.util.Scanner; 2 public class Dijkster{ 3 public static void main(String[]args){ 4 Scanner in=new Scanner(System.in); 5 while(in.hasNext()){ 6 int n=in.nextInt(); 7 int m=in.nextInt(); 8 int a=in.nextInt(); 9 int b=in.nextInt(); 10 int[][] Edge=new int[n+1][n+1];//通过临接矩阵来保存图 11 12 13 for(int i=0;i<n+1;i++){ //初始化临街矩阵 14 for(int j=0;j<n+1;j++){ 15 Edge[i][j]=Integer.MAX_VALUE; 16 } 17 } 18 for(int i=0;i<m;i++){ //输入临接矩阵 19 int x=in.nextInt(); 20 int y=in.nextInt(); 21 int z=in.nextInt(); 22 if(Edge[x][y]<Integer.MAX_VALUE){ 23 if(z<Edge[x][y]) 24 Edge[x][y]=z; 25 } 26 27 else 28 Edge[x][y]=z; 29 } 30 int p= Dijkster(Edge,n,a,b); 31 int q= Dijkster(Edge,n,b,a); 32 System.out.println(p+q); 33 } 34 in.close(); 35 } 36 private static int Dijkster(int[][]Edge,int n,int start,int end){ 37 int[] dist=new int[n+1];//dist用来保存从起点到每个点的最短路径,到不了的点距离设置为无穷大 38 int[]path=new int[n+1];//path用来保存每个点的前一个顶点号,如path[3]=2,则有顶点2-3 39 boolean[] s=new boolean[n+1];//s用来记录该点是否已经确定了,初始化都为false,当把该点计入最短路径后,设为true; 40 for(int i=1;i<=n;i++){//初始化dist,path 41 dist[i]=Edge[start][i]; 42 if(i!=start&&dist[i]<Integer.MAX_VALUE){ 43 path[i]=start; 44 }else{ 45 path[i]=-1; 46 } 47 } 48 s[start]=true;//从第一个顶点开始计算 49 dist[start]=0; 50 for(int i=0;i<n;i++){ 51 int min=Integer.MAX_VALUE; 52 int u=0; 53 //选择当前未确定顶点中具有最短路径(离起点最近)的顶点u 54 for(int j=1;j<=n;j++){ 55 if(!s[j]&&dist[j]<min){ 56 u=j; 57 min=dist[j]; 58 } 59 } 60 //此时u已经在最短路径上了,更新s 61 s[u]=true; 62 for(int k=1;k<=n;k++){ 63 if(!s[k]&&Edge[u][k]<Integer.MAX_VALUE&&dist[u]+Edge[u][k]<dist[k]){ 64 dist[k]=dist[u]+Edge[u][k]; 65 path[k]=u; 66 } 67 } 68 } 69 return dist[end]; 70 71 } 72 }
运行测试了下,发现在数据量小的时候,代码是没问题的,也就是当端点数n<=100 算法均能正确运行
当n>100后,运行结果显示:
在int[][] Edge=new int[n+1][n+1];这句处
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
也就是内存超出了
也就说明这个时候应该换用 邻接表来表示图了