最短路径问题是图论研究中的一个经典的算法问题,旨在寻找图中两个节点之间的最短路径,最经常使用的算法有Dijkstra算法、SPFA算法\Bellman-Ford算法、Floyd算法\Floyd-Warshall算法、Johnson算法等,这篇博客将重点介绍Dijkstra算法。java
迪杰斯特拉算法node
迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,所以又叫狄克斯特拉算法。是从一个顶点到其他各顶点的最短路径算法,解决的是有向图中最短路径问题。迪杰斯特拉算法主要特色是以起始点为中心向外层层扩展,直到扩展到终点为止。具体的计算规则咱们能够经过下图进行查看。算法

经过这幅图(若是图片没法正确显示,请经过百度百科查看)咱们能够简单的理解迪杰斯特拉算法算法的基础思路,下面咱们就经过Java来实现这个算法。数组
先给出一个无向图数据结构

用Dijkstra算法找出以A为起点的单源最短路径步骤以下app

算法实现测试
在具体的实现以前,咱们先有一个基础的约定,就是途中的每个节点咱们都用正整数进行编码,相邻两点之间的距离是正整数,图中两个直接相邻两点的距离咱们保存到map中,也就是求最短距离咱们须要实现这样的一个方法:this
[java] view plain copy编码
print?
spa
- public MinStep getMinStep(int start, int end, final HashMap<Integer, HashMap<Integer, Integer>> stepLength);
第一个参数是起始节点的编号,第二个参数是终点节点的编号,第三个参数是图中直接相邻两个节点的距离组成的map。关于第三个参数,会在后面作详细的介绍。这里咱们定义一个接口,用于计算两点之间的最短路径,具体以下:
[java] view plain copy
print?

- /**
- *@Description:
- */
- package com.lulei.distance;
-
- import java.util.HashMap;
-
- import com.lulei.distance.bean.MinStep;
-
- public interface Distance {
- public static final MinStep UNREACHABLE = new MinStep(false, -1);
- /**
- * @param start
- * @param end
- * @param stepLength
- * @return
- * @Author:lulei
- * @Description: 起点到终点的最短路径
- */
- public MinStep getMinStep(int start, int end, final HashMap<Integer, HashMap<Integer, Integer>> stepLength);
- }
1、方法返回值介绍
上面方法的返回值是咱们自定义的一个数据类型,下面咱们经过代码来看其具体的数据结构:
[java] view plain copy
print?

- /**
- *@Description:
- */
- package com.lulei.distance.bean;
-
- import java.util.List;
-
- public class MinStep {
- private boolean reachable;//是否可达
- private int minStep;//最短步长
- private List<Integer> step;//最短路径
-
- public MinStep() {
- }
-
- public MinStep(boolean reachable, int minStep) {
- this.reachable = reachable;
- this.minStep = minStep;
- }
-
- public boolean isReachable() {
- return reachable;
- }
- public void setReachable(boolean reachable) {
- this.reachable = reachable;
- }
- public int getMinStep() {
- return minStep;
- }
- public void setMinStep(int minStep) {
- this.minStep = minStep;
- }
- public List<Integer> getStep() {
- return step;
- }
- public void setStep(List<Integer> step) {
- this.step = step;
- }
- }
其中最短路径的那个List数组保存了从起点到终点最短路径所经历的节点。
2、每个节点的最优前一节点
在迪杰斯特拉算法中咱们须要保存从起点开始到每个节点最短步长,这也是图中须要比较得出的步长,同时咱们还须要存储该步长下的前一个节点是哪一个,这样咱们就能够经过终点一个一个往前推到起点,这样就出来了完整的最优路径。
[java] view plain copy
print?

- /**
- *@Description:
- */
- package com.lulei.distance.bean;
-
- public class PreNode {
- private int preNodeNum;// 最优 前一个节点
- private int nodeStep;// 最小步长
-
- public PreNode(int preNodeNum, int nodeStep) {
- this.preNodeNum = preNodeNum;
- this.nodeStep = nodeStep;
- }
-
- public int getPreNodeNum() {
- return preNodeNum;
- }
- public void setPreNodeNum(int preNodeNum) {
- this.preNodeNum = preNodeNum;
- }
- public int getNodeStep() {
- return nodeStep;
- }
- public void setNodeStep(int nodeStep) {
- this.nodeStep = nodeStep;
- }
- }
3、迪杰斯特拉算法计算过程当中须要关注的变量
从介绍迪杰斯特拉算法的图中,咱们知道在计算的过程当中咱们须要保存起点到各个节点的最短距离、已经计算过的节点、下次须要计算节点队列和图中相邻两个节点的距离。咱们经过代码来看下具体的定义:
[java] view plain copy
print?

- //key1节点编号,key2节点编号,value为key1到key2的步长
- private HashMap<Integer, HashMap<Integer, Integer>> stepLength;
- //非独立节点个数
- private int nodeNum;
- //移除节点
- private HashSet<Integer> outNode;
- //起点到各点的步长,key为目的节点,value为到目的节点的步长
- private HashMap<Integer, PreNode> nodeStep;
- //下一次计算的节点
- private LinkedList<Integer> nextNode;
- //起点、终点
- private int startNode;
- private int endNode;
咱们这里看下stepLength这个属性,它保存了图中相邻两个节点之间的距离,好比key1=1;key2=3;value=9;这表明的意义就是从节点1到节点3的距离是9。经过这样的存储,咱们就须要把图中每两个相邻的点保存到这个类型的map中。
4、属性初始化
在开始计算以前,咱们须要对这些属性进行初始化,具体以下:
[java] view plain copy
print?

- private void initProperty(int start, int end) {
- outNode = new HashSet<Integer>();
- nodeStep = new HashMap<Integer, PreNode>();
- nextNode = new LinkedList<Integer>();
- nextNode.add(start);
- startNode = start;
- endNode = end;
- }
这一步咱们须要把起点添加到下一次须要计算的节点队列中。
5、迪杰斯特拉算法
这一步也就是迪杰斯特拉算法的核心部分,在计算的过程当中,咱们须要进行以下步骤:
1)判断是否达到终止条件,若是达到终止条件,结束本次算法,若是没有达到,执行下一步;(终止条件:下一次须要计算的节点队列没有数据或已经计算过的节点数等于节点总数)
2)获取下一次计算的节点A;
3)从起点到各节点之间的最短距离map中获取到达A点的最小距离L;
4)获取A节点的可达节点B,计算从起点先到A再到B是否优于已有的其余方式到B,若是优于,则更新B节点,不然不更新;
5)判断B是不是已经移除的节点,若是不是移除的节点,把B添加到下一次须要计算的节点队列中,不然不作操做;
6)判断A节点是否还有除B之外的其余节点,若是有,执行第4)步,不然执行下一步;
7)将A节点从下一次须要计算的节点中移除添加到已经计算过的节点中;
8)执行第一步。
咱们来看下具体的代码实现:
[java] view plain copy
print?

- private void step() {
- if (nextNode == null || nextNode.size() < 1) {
- return;
- }
- if (outNode.size() == nodeNum) {
- return;
- }
- //获取下一个计算节点
- int start = nextNode.removeFirst();
- //到达该节点的最小距离
- int step = 0;
- if (nodeStep.containsKey(start)) {
- step = nodeStep.get(start).getNodeStep();
- }
- //获取该节点可达节点
- HashMap<Integer,Integer> nextStep = stepLength.get(start);
- Iterator<Entry<Integer, Integer>> iter = nextStep.entrySet().iterator();
- while (iter.hasNext()) {
- Entry<Integer, Integer> entry = iter.next();
- Integer key = entry.getKey();
- //若是是起点到起点,不计算之间的步长
- if (key == startNode) {
- continue;
- }
- //起点到可达节点的距离
- Integer value = entry.getValue() + step;
- if ((!nextNode.contains(key)) && (!outNode.contains(key))) {
- nextNode.add(key);
- }
- if (nodeStep.containsKey(key)) {
- if (value < nodeStep.get(key).getNodeStep()) {
- nodeStep.put(key, new PreNode(start, value));
- }
- } else {
- nodeStep.put(key, new PreNode(start, value));
- }
- }
- //将该节点移除
- outNode.add(start);
- //计算下一个节点
- step();
- }
6、组装最短路径返回结果
经过前面的计算,已经计算出了起点到各个节点的最短路径,下面就须要组装起点到终点的最短路径,在计算最短距离下的路径方式,咱们须要从终点依次往前推,即到达终点最短距离下的前一个节点是A,到达A节点最短距离下的前一节点是B,直到找到起点终止。
[java] view plain copy
print?

- private MinStep changeToMinStep() {
- MinStep minStep = new MinStep();
- minStep.setMinStep(nodeStep.get(endNode).getNodeStep());
- minStep.setReachable(true);
- LinkedList<Integer> step = new LinkedList<Integer>();
- minStep.setStep(step);
- int nodeNum = endNode;
- step.addFirst(nodeNum);
- while (nodeStep.containsKey(nodeNum)) {
- int node = nodeStep.get(nodeNum).getPreNodeNum();
- step.addFirst(node);
- nodeNum = node;
- }
- return minStep;
- }
7、接口定义方法实现
[java] view plain copy
print?

- public MinStep getMinStep(int start, int end, final HashMap<Integer, HashMap<Integer, Integer>> stepLength) {
- this.stepLength = stepLength;
- this.nodeNum = this.stepLength != null ? this.stepLength.size() : 0;
- //起点、终点不在目标节点内,返回不可达
- if (this.stepLength == null || (!this.stepLength.containsKey(start)) || (!this.stepLength.containsKey(end))) {
- return UNREACHABLE;
- }
- initProperty(start, end);
- step();
- if (nodeStep.containsKey(end)) {
- return changeToMinStep();
- }
- return UNREACHABLE;
- }
8、迪杰斯特拉算法完整代码
[java] view plain copy
print?

- /**
- *@Description:
- */
- package com.lulei.distance.dijkstra;
-
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.LinkedList;
- import java.util.Map.Entry;
-
- import com.lulei.distance.Distance;
- import com.lulei.distance.bean.MinStep;
- import com.lulei.distance.bean.PreNode;
-
- public class DistanceDijkstraImpl implements Distance{
- //key1节点编号,key2节点编号,value为key1到key2的步长
- private HashMap<Integer, HashMap<Integer, Integer>> stepLength;
- //非独立节点个数
- private int nodeNum;
- //移除节点
- private HashSet<Integer> outNode;
- //起点到各点的步长,key为目的节点,value为到目的节点的步长
- private HashMap<Integer, PreNode> nodeStep;
- //下一次计算的节点
- private LinkedList<Integer> nextNode;
- //起点、终点
- private int startNode;
- private int endNode;
-
- /**
- * @param start
- * @param end
- * @param stepLength
- * @return
- * @Author:lulei
- * @Description: start 到 end 的最短距离
- */
- public MinStep getMinStep(int start, int end, final HashMap<Integer, HashMap<Integer, Integer>> stepLength) {
- this.stepLength = stepLength;
- this.nodeNum = this.stepLength != null ? this.stepLength.size() : 0;
- //起点、终点不在目标节点内,返回不可达
- if (this.stepLength == null || (!this.stepLength.containsKey(start)) || (!this.stepLength.containsKey(end))) {
- return UNREACHABLE;
- }
- initProperty(start, end);
- step();
- if (nodeStep.containsKey(end)) {
- return changeToMinStep();
- }
- return UNREACHABLE;
- }
-
- /**
- * 返回最短距离以及路径
- */
- private MinStep changeToMinStep() {
- MinStep minStep = new MinStep();
- minStep.setMinStep(nodeStep.get(endNode).getNodeStep());
- minStep.setReachable(true);
- LinkedList<Integer> step = new LinkedList<Integer>();
- minStep.setStep(step);
- int nodeNum = endNode;
- step.addFirst(nodeNum);
- while (nodeStep.containsKey(nodeNum)) {
- int node = nodeStep.get(nodeNum).getPreNodeNum();
- step.addFirst(node);
- nodeNum = node;
- }
- return minStep;
- }
-
- /**
- * @param start
- * @Author:lulei
- * @Description: 初始化属性
- */
- private void initProperty(int start, int end) {
- outNode = new HashSet<Integer>();
- nodeStep = new HashMap<Integer, PreNode>();
- nextNode = new LinkedList<Integer>();
- nextNode.add(start);
- startNode = start;
- endNode = end;
- }
-
- /**
- * @param end
- * @Author:lulei
- * @Description:
- */
- private void step() {
- if (nextNode == null || nextNode.size() < 1) {
- return;
- }
- if (outNode.size() == nodeNum) {
- return;
- }
- //获取下一个计算节点
- int start = nextNode.removeFirst();
- //到达该节点的最小距离
- int step = 0;
- if (nodeStep.containsKey(start)) {
- step = nodeStep.get(start).getNodeStep();
- }
- //获取该节点可达节点
- HashMap<Integer,Integer> nextStep = stepLength.get(start);
- Iterator<Entry<Integer, Integer>> iter = nextStep.entrySet().iterator();
- while (iter.hasNext()) {
- Entry<Integer, Integer> entry = iter.next();
- Integer key = entry.getKey();
- //若是是起点到起点,不计算之间的步长
- if (key == startNode) {
- continue;
- }
- //起点到可达节点的距离
- Integer value = entry.getValue() + step;
- if ((!nextNode.contains(key)) && (!outNode.contains(key))) {
- nextNode.add(key);
- }
- if (nodeStep.containsKey(key)) {
- if (value < nodeStep.get(key).getNodeStep()) {
- nodeStep.put(key, new PreNode(start, value));
- }
- } else {
- nodeStep.put(key, new PreNode(start, value));
- }
- }
- //将该节点移除
- outNode.add(start);
- //计算下一个节点
- step();
- }
- }
代码测试
对于上述代码的测试,咱们仍是使用咱们事例图形中的例子,计算从节点1到节点5的最短距离。
[java] view plain copy
print?

- /**
- *@Description:
- */
- package com.lulei.distance.test;
-
- import java.util.HashMap;
-
- import com.lulei.distance.Distance;
- import com.lulei.distance.bean.MinStep;
- import com.lulei.distance.dijkstra.DistanceDijkstraImpl;
- import com.lulei.util.JsonUtil;
-
- public class DistanceTest {
-
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- HashMap<Integer, HashMap<Integer,Integer>> stepLength = new HashMap<Integer, HashMap<Integer,Integer>>();
- HashMap<Integer,Integer> step1 = new HashMap<Integer, Integer>();
- stepLength.put(1, step1);
- step1.put(6, 14);
- step1.put(3, 9);
- step1.put(2, 7);
-
- HashMap<Integer,Integer> step2 = new HashMap<Integer, Integer>();
- stepLength.put(2, step2);
- step2.put(1, 7);
- step2.put(3, 10);
- step2.put(4, 15);
-
- HashMap<Integer,Integer> step3 = new HashMap<Integer, Integer>();
- stepLength.put(3, step3);
- step3.put(1, 9);
- step3.put(2, 10);
- step3.put(4, 11);
- step3.put(6, 2);
-
- HashMap<Integer,Integer> step4 = new HashMap<Integer, Integer>();
- stepLength.put(4, step4);
- step4.put(2, 15);
- step4.put(5, 5);
- step4.put(3, 11);
-
- HashMap<Integer,Integer> step5 = new HashMap<Integer, Integer>();
- stepLength.put(5, step5);
- step5.put(6, 9);
- step5.put(4, 5);
-
- HashMap<Integer,Integer> step6 = new HashMap<Integer, Integer>();
- stepLength.put(6, step6);
- step6.put(1, 14);
- step6.put(5, 9);
- step6.put(3, 2);
-
- Distance distance = new DistanceDijkstraImpl();
- MinStep step = distance.getMinStep(1, 5, stepLength);
- System.out.println(JsonUtil.parseJson(step));
- }
-
- }
这里组装相邻两个节点之间的距离用了大量的代码,咱们看下输出结果:
[java] view plain copy
print?

- {"reachable":true,"minStep":20,"step":[1,3,6,5]}
最后的思考
最短路径算法在现实生活中其实有不少的用处,好比迷宫解法、路径规划、路由寻址等等,这些问题看似很复杂,其实只须要作对应的转化后仍是能够用最基础的算法解决的。