问题描述:
求图(单源简单图)的最短在实际生活中有许多应用,比如说在你在一个景区的某个景点,参观完后,要怎么走最少的路程到你想参观的下个景点,这就利用到了求图最短路径的算法。求图的最短路径有很多算法,这里介绍一种迪杰斯特拉(Dijkstra)算法来求图的最短路径。
迪杰斯特拉算法采用贪心算法的策略,将所有顶点分为已标记点和未标记点两个集合,从起始点开始,不断在未标记点中寻找距离起始点路径最短的顶点,并将其标记,直到所有顶点都被标记为止。
记录已标记点距离起点最近距离,这个可以用数组或者list结构(结点是顺序排列的),或者map结构。
有些场景下需要知道路径的具体信息,可以用一个数组或者list来存储。
path[ ]:path[ i ]表示从源点到顶点 i 之间的最短路径的前驱结点。在算法结束时,可以根据其值追溯到源点 V0 到 Vi
的最短路径。
参考:Dijkstra 算法最详解
代码实现:
import java.util.Arrays;
/**
* @author haiYang
* @create 2022-02-03 10:33
*/
public class DijkstraAlgorithm {
public static void main(String[] args) {
char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
//邻接矩阵
int[][] matrix = new int[vertex.length][vertex.length];
final int N = 65535;// 表示不可以连接
/* 1 2 3 4 5 */
matrix[0] = new int[]{N, 10, N, N, 5};/*1*/
matrix[1] = new int[]{N, N, 1, N, 2};/*2*/
matrix[2] = new int[]{N, N, N, 4, N};/*3*/
matrix[3] = new int[]{7, N, 6, N, N};/*4*/
matrix[4] = new int[]{N, 3, 9, 2, N};/*5*/
//创建 Graph对象
DGraph graph = new DGraph(vertex, matrix);
//测试, 看看图的邻接矩阵是否ok
graph.showGraph();
//测试迪杰斯特拉算法
graph.dijkstra(0);
graph.showDijkstra('A', 'C');
}
}
//已访问顶点集合
class VisitedVertex {
//记录各个顶点是否访问,1表示访问过,0表示未访问过
public int[] alreadyVertex;
//表示从源点到顶点i之间的最短路径的前驱结点
public int[] path;
//记录从源点到其他各个顶点当前的最短路径长度
public int[] dist;
/**
* 构造器
*
* @param vertexNum 顶点数目
* @param vertexIndex 顶点索引(顶点数组对应的下标)
*/
public VisitedVertex(int vertexNum, int vertexIndex) {
this.alreadyVertex = new int[vertexNum];
this.path = new int[vertexNum];
this.dist = new int[vertexNum];
//初始化dist数组,顶点i到其他顶点的距离初始为65536,到自己的距离初始为0。
Arrays.fill(dist, 65535);
dist[vertexIndex] = 0;
//初始顶点已访问
this.alreadyVertex[vertexIndex] = 1;
}
/**
* 判断该顶点是否已经访问过
*
* @param vertexIndex 顶点索引
* @return
*/
public boolean isVisited(int vertexIndex) {
return alreadyVertex[vertexIndex] == 1;
}
/**
* 更新源点到目标顶点的最短路径长度
*
* @param objectiveVertexIndex 目标顶点索引
* @param objectiveVertexLength 目标顶点长度
*/
public void updateDist(int objectiveVertexIndex, int objectiveVertexLength) {
dist[objectiveVertexIndex] = objectiveVertexLength;
}
/**
* 更新源点到该顶点最短路径下,该顶点的前驱顶点
*
* @param preVertexIndex 前驱顶点
* @param VertexIndex 该顶点
*/
public void updatePath(int VertexIndex, int preVertexIndex) {
path[VertexIndex] = preVertexIndex;
}
/**
* 返回源点到该顶点的上一次更新的最短路径
* 用于判断此次是否更新最短路径长度
*
* @param vertexIndex
* @return
*/
public int getDist(int vertexIndex) {
return dist[vertexIndex];
}
/**
* 寻找与源点之间最短距离且未访问顶点的索引
*
* @return
*/
public int updateArr() {
int min = 65536, index = 0;
for (int i = 0; i < alreadyVertex.length; i++) {
if (alreadyVertex[i] == 0 && dist[i] < min) {
min = dist[i];
index = i;
}
}
alreadyVertex[index] = 1;
return index;
}
public void show(char begin, char end) {
System.out.println("===================");
int beginIndex = 0;
int endIndex = 0;
char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
for (int i = 0; i < vertex.length; i++) {
if (vertex[i] == begin) {
beginIndex = i;
}
if (vertex[i] == end) {
endIndex = i;
}
}
System.out.println(begin + " -> " + end + "的最短距离为:" + dist[endIndex]);
System.out.print(begin + " -> " + end + "的最短路径为:");
showPath(beginIndex, endIndex);
System.out.println(vertex[endIndex]);
}
/**
* 通过递归遍历先驱数组path返回最短路径
*
* @param beginIndex
* @param endIndex
*/
private void showPath(int beginIndex, int endIndex) {
char[] vertex = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
if (path[endIndex] == beginIndex) {
System.out.print(vertex[beginIndex] + " -> ");
return;
} else {
showPath(beginIndex, path[endIndex]);
}
System.out.print(vertex[path[endIndex]] + " -> ");
}
}
class DGraph {
private char[] vertex;//顶点数组
private int[][] arcs;//邻接矩阵
private VisitedVertex visitedVertex;
public DGraph(char[] vertex, int[][] arcs) {
this.vertex = vertex;
this.arcs = arcs;
}
public void showGraph() {
for (int[] link : arcs) {
System.out.println(Arrays.toString(link));
}
}
/**
* dijkstra算法
*
* @param index 出发顶点的下标
*/
public void dijkstra(int index) {
visitedVertex = new VisitedVertex(vertex.length, index);
update(index);
for (int i = 1; i < vertex.length; i++) {
index = visitedVertex.updateArr();
update(index);
}
}
//更新index下标顶点到周围顶点的距离和周围顶点的前驱顶点
public void update(int index) {
int len = 0;
//根据邻接矩阵找到邻接顶点
for (int i = 0; i < arcs[index].length; i++) {
//从出发顶点到index顶点的距离+ 从index顶点到i顶点的距离的和
len = visitedVertex.getDist(index) + arcs[index][i];
if (!visitedVertex.isVisited(i) && len < visitedVertex.getDist(i)) {
visitedVertex.updatePath(i, index);//更新前驱顶点
visitedVertex.updateDist(i, len); //更新最短距离
}
}
}
public void showDijkstra(char begin, char end) {
visitedVertex.show(begin, end);
}
}