引言
最近在复习离散数学的相关知识,于是便想用java实现一下离散数学中著名的最短路径算法Dijkstra。这个算法虽然简单,但还是可以从中学习到很多知识。那咱废话不多说,直接上解析!
逻辑介绍
Dijkstra算法是根据一张有权无向图来展开的,我们可以在无向图中选择一个节点,将它作为源节点使用更新路径算法来获得图中每个节点对于该节点的最短路径。
引例
为了让大家理解的更加清晰,本案例中的无向图就使用如下作为示范。但算法覆盖面可不局限于这次的用例:
如图所示,我们选中【编号0】为源节点,然后每次计算与它相邻节点到达该节点的距离。注意我们每次计算节点只能计算与之相邻的节点路径,而不能间隔计算。接着,我们选择每一次计算中最小的值将其标记,此标记就是该节点到达源节点的最小值。
这是我们手动计算后可以获得的结果,【在结果中我们用(x,y)表示,其中x表示该节点最短路径经过的最后一个节点,y表示距离源节点的距离值】:
计算次数/节点 | 0 | 1 | 2 | 3 | 4 |
第一次 | (0,0)* | — | — | — | — |
第二次 | (0,12) | (0,6) | (0,9) | (0,3)* | |
第三次 | (2,8) | (0.6)* | (0,9) | ||
第四次 | (4,6)* | (0,9) | |||
第五次 | (2,8)* |
我们理解了Dijkstra算法的计算过程就可以完成算法设计啦!
程序设计
设计介绍
由于Dijkstra算法是根据无向图进行操作的,无向图的根本就是节点和边。那么我们就可以设计一个Node类来存储无向图中的每一个节点(包括节点编号和当前到达源节点的距离)。
之后我们可以将创建好的节点类进行组图,于是就需要创建一个Graph类来表示无向图,其中存储节点和边,以及编写最为重要的Dijkstra计算方法。
最后我们就可以在Main中调用这些方法来完成我们整个Dijkstra的计算历程了。
代码演示
Node类
import java.util.Comparator;
//这里使用Comparator<Object>接口可以在优先队列集合中自定义排序方式
public class Node implements Comparator<Node> {
private int node;//节点编号
private int cost;//从源节点到该节点的距离
//初始化节点数据
public Node(int node, int cost) {
this.node = node;
this.cost = cost;
}
//构造空构造函数用于之后的优先队列自定义排序算法使用
public Node(){}
//重写Comparator接口中定义的抽象方法
@Override
public int compare(Node node1, Node node2) {
return Integer.compare(node1.cost, node2.cost);
}
public int getNode() {
return node;
}
public int getCost(){
return cost;
}
}
Graph类
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.PriorityQueue;
//创建一个加权无向图
public class Graph {
private final int V;//图顶点数量
private final List<List<Node>> adj;//存储图中边和顶点
//初始化无向图
public Graph(int V) {
this.V = V;
adj=new ArrayList<>(V);
//为每个集合元素赋予初始空对象
for(int i=0;i<V;i++){
adj.add(new ArrayList<>());
}
}
//添加点和边
public void addEdge(int u,int v,int weight){
adj.get(u).add(new Node(v,weight));
}
public void dijkstra(int src){
PriorityQueue<Node> pq=new PriorityQueue<>(V,new Node());//创建一个优先队列
int[] dist =new int[V];//用于存储每个节点到源节点的最短距离
int[] prev=new int[V];
Arrays.fill(prev,-1);
Arrays.fill(dist,Integer.MAX_VALUE);//为初始最短距离初始化最大值
pq.add(new Node(src,0));//将源节点添加到队列中,距离为0
dist[src]=0;//源节点到自身的距离为0
while(!pq.isEmpty()){
int minNode=pq.poll().getNode();//从队列中取出距离最短的节点
//遍历优先队列
for(Node n:adj.get(minNode)){
int neiNode=n.getNode();//获得与取出节点相邻的节点编号
int weight=n.getCost();//获得相邻节点的距离值
//判断相邻节点到该点的距离是否小于当前存储的距离
if(dist[minNode]+weight<dist[neiNode]){
//如果小于就更新邻节点的距离值
dist[neiNode]=dist[minNode]+weight;
//更新前驱节点编号
prev[neiNode]=minNode;
//添加到优先队列中继续筛选
pq.add(new Node(neiNode,dist[neiNode]));
}
}
}
printNode(src,dist,prev);
}
//打印最后信息
public void printNode(int src,int[] dist,int[] prev){
System.out.println("根节点:");
System.out.println("("+src+","+dist[src]+")");
System.out.println("各节点到根节点的最短路径");
for(int i=0;i<V;i++){
if(i!=src) {
System.out.print(i + ":" + "(" + prev[i] + "," + dist[i] + ")");
}
}
}
}
Main类(测试数据)
public class Main {
public static void main(String[] args) {
System.out.println("测试数据:");
int V=5;
Graph graph = new Graph(V); // 创建一个图
// 添加边到图中
graph.addEdge(0, 1, 12);
graph.addEdge(0, 2, 6);
graph.addEdge(0, 3, 9);
graph.addEdge(0, 4, 3);
graph.addEdge(2, 1, 2);
graph.addEdge(2, 3, 2);
graph.addEdge(4, 1, 3);
graph.dijkstra(0); // 执行Dijkstra算法,从节点0开始
}
}
测试结果
测试结果如图:
可以看到,测试结果显示的和原先我们手动计算的结果一致,证明编写正确!
总结
Dijkstra算法是离散数学中的一个重要内容,在日常生活中的运用也很广泛,虽然这个算法简单,但是如果大家能够掌握也算是大家在java学习中的一个小小的成果了吧!
最后,我自己也是一个初学java的小白,在CSDN中我也在关注许多优质的博主的博文,他们也给了我很大的帮助,撰写这篇博文同样也是为了帮助更多的热爱编程的学者们能够更好的理解计算机程序,也希望在今后的日子里我们能够携手并进,冲向自己期待的美好明天吧!