丹丘生 Java笔记虾 

算法之单源最短路径_Java

本文作者:丹丘生

文末阅读原文获取文中示例代码


一、引言

> 上次两个算法是否沉淀了呢这次我们换一种,我们来学会偷懒~

> 单源最短路径(Dijkstra算法),将偷懒进行到底~

> 大家有兴趣,可以将三个算法对比分析一下。

 

二、小小问题

* Q:如图,小明(标号为A)到其他同学的最短距离是多少呢?(注意箭头方向)  

算法之单源最短路径_Java_02


看着这个图是不是一脸蒙呢? 注意箭头~~

算法之单源最短路径_Java_03

 

Q:两点之间如何存储边的距离呢?

比如B->A的长度为2 length[B][A]=2 这样是不是不错所以我们可以得到下面的表格了。∞ 代表不能到达

算法之单源最短路径_Java_04


那么问题来了

Q:如果用Java来解决,我们输入这些点,这些边的数据,能不能得出A点到其他点的最短距离呢?


通过二维表格我们可以知道从A点初始时到各个点的距离(不一定是最短的距离):

算法之单源最短路径_Java_05


我们根据这个例子,将这个走一遍看看怎么做;

  • 先找距离A最近的顶点, 通过上面的表格可以知道离A最近的是C点。当选择了C点,AC的最短距离就确定了,至于为什么,你们能说出来吗? 因为C点是距离A点最近的点,因为边的长度为正数,所以A可能通过其他点到C,但是长度没C的短。

  • 选择了C,C有两条出路:(C->B)和 (C->F) .这怎么算呢先判断(A->C)+(C->B)这条路能否比(A->B)的路程短 。然后比较发现(A->C)+(C->B) <(A->B) 因此更新 length[A][B] =19;  然后判断(A->C)+(C->F)这条路能否比(A->F)的路程短,之后发现(A->C)+(C->F)< (A->F) 因此更新length[A][F]=12

 

更新完后的长度为:

算法之单源最短路径_Java_06

  • 然后继续在除了C以外的点,找距离A点最近的点。通过上面的表格,我们可以知道接下来最近的顶点是F。当选择了F点,(A->F)的最短距离就确定了,至于为什么?能说出来吗?

  • 选择了F,F有两条出路:(F->D)(F->E),同样的,怎么算呢?先判断(A->F)+(F->D)这条路是否比(A->D)的路程短,之后比较发现((A->F)+(F->D))(长度25)< (A->D)(长度28,因此更新length[A][D]=25;然后判断(A->F)+(F->E)是否比(A->E)路程短,之后发现(A->F)+(F->E)<(A->E). 因此更新length[A][E]=30;


更新完后的长度为:

算法之单源最短路径_Java_07

  • 然后继续在除了C,F以外的点,找距离A点最近的点。通过上面的表格,我们知道最近的顶点是B。当选择了B(A->B)的最短距离就确定了,再问一遍,是为什么呢?

  • 选择B,B有两条出路:(B->E)(B->A),同样的,怎么算呢?先判断(A->B)+(B->E) 这条路是否比(A->E)的路程短。然后比较发现((A->B)+(B->E))(29) < A->E(30),因此更新length[A][E]=29.之后判断(A->B)+(B->A)是否比(A->A)的路程短。然后发现(A->B)+(B->A)>(A->A); 因此更新length[A][A] =0(其实不需要更新)


更新完后的长度为:

算法之单源最短路径_Java_08

  • 然后继续在除了C,F,B以外的点,找距离A点最近的点。通过上面的表格,我们知道最近的点是D。当选择了DA->D的最短距离就确定了。选择D,不需要做任何处理。继续找距离A点最近的点,接下来最近的点是E;E只有一条出路:E->D;先判断A->D+E->D这条路是否比A->D的路程短。然后比较发现((A->E)+(E->D))(长度33) > (A->D)(29)


更新完后的长度为:

算法之单源最短路径_Java_09

  • 接着找距离A点近的,但是发现所有的点都已经找过,最短距离已经计算完毕。

 

思考:这个过程的代码如何写呢?似乎就是循环(这层循环点)里面嵌套循环(找寻与起始点距离近的点),然后循环找点(距离近的点到其他点的距离)然后判断距离是否为最短。


让我们试下

package dijkstra;
import java.util.Scanner;
/**
* Created by Administrator on 2018/5/20.
* target: Dijkstra算法示例
* 输入:
6 9
0 1 1
0 2 12
1 2 9
1 3 3
2 4 5
3 2 4
3 4 13
3 5 15
4 5 4
* 输出:
* 0 1 8 4 13 17
*
@author danqiusheng
*/

public class Dijkstra {
   static int INF = Integer.MAX_VALUE;//无法到达

   public static void main(String[] args) {
       Scanner read = new Scanner(System.in);
       // 输入points, edges 顶点数和边树
       int points = read.nextInt();
       int edges = read.nextInt();


       int[][] length = new int[points][points];

       // 初始化边(当前点到当前点为0)
       for (int i = 0; i < points; i++) {
           for (int j = 0; j < points; j++) {
               if (i == j) length[i][j] = 0;
               else length[i][j] = INF;
           }
       }

       read.nextLine();

       //输入顶点关系
       for (int i = 0; i < edges; i++) {
           String[] data = read.nextLine().split(" ");
           length[Integer.parseInt(data[0])][Integer.parseInt(data[1])] = Integer.parseInt(data[2]);
       }

       // 用一个一维数组存储当前点到其他点的距离,因为要根据二维表获取点到点的距离,不更改二维表的数据
       int[] distance = new int[points];

       // 初始化1点点到其余各个顶点的初始路程
       for (int i = 0; i < points; i++) {
           distance[i] = length[0][i];
       }

       // 定义一个flag数组。这个数组标记距离1最近的点被选择的数据
       boolean[] flag = new boolean[points];
       flag[0] = true;// 1点是最近的点,默认选择

       int min;
       int current = 0;
       for (int i = 0; i < points; i++) {
           min = INF;
           // 找寻距离1最短的点
           for (int j = 0; j < points; j++) {
               if (!flag[j] && distance[j] < min) {
                   min = distance[j];
                   current = j;
               }
           }
           // 设置这个距离1最短的点被选择
           flag[current] = true;

           // 获取这个最短的点到其他点的距离
           for (int m = 0; m < points; m++) {
               if (length[current][m] < INF) { // 判断是否为不可达
                   if (distance[m] > distance[current] + length[current][m]) {// 判断是否小于最短路径
                       distance[m] = distance[current] + length[current][m];
                   }
               }
           }
       }

       //输出最终的结果
       for (int i = 0; i < points; i++) {
           System.out.print(distance[i]+" ");
       }
   }
}


三、概念

 >单源最短路径问题,即在图中求出给定顶点到其它任一顶点的最短路径。 Dijkstra提出按各顶点与源点v间的路径长度的递增次序,生成到各顶点的最短路径的算法。既先求出长度最短的一条最短路径,再参照它求出长度次短的一条最短路径,依次类推,直到从源点到其它各顶点的最短路径全部求出为止。

 

四、总结

> 这个算法,看起来很简单,但是需要理解。参考书《啊哈,算法》,这本书讲算法不错。

> 上面的示例代码并不是最优的算法,相信大家看出来里面的时间复杂度是O(N*N),在哪里可以优化,这里我先留个坑,日后来填~

 

 

五、小试牛刀

> 题目要求的比较多,我就不贴出来,在git上。

 

- [Question](https://github.com/danqiusheng/algorithm_practice/blob/master/src/dijkstra/Question1.md)

 

 

六、示例代码


- [DijkstraDemo1](https://github.com/danqiusheng/algorithm_practice/blob/master/src/dijkstra/DijkstraDemo1.java)