文章内容选自尚硅谷数据结构和算法,eclipse环境,jdk11
算法思想
课程里说,dijkstra算法要具备三个数组,一个数组是already_arr,用来记录已经访问了的结点,另一个数组是dis数组,用来记录出发结点或者访问结点到某结点距离的最短路径。还有一个pre_visited数组,假如说pre_visited[i]=j,说明第i个结点的前驱结点是第j结点。
最后dis数组内的元素就记录了从出发点到其余各结点的最短距离。
我觉得 pre_visited数组在该算法中并没有实际价值,它只能记录最后一轮遍历,各结点的前驱结点是谁,其实在多轮遍历中,前驱结点经常被覆盖。如果把pre_visited改为二维数组,或者链表形式,记录最优路径,则更为恰当。
声明一个概念,出发结点和访问节点,出发结点只有一个,就是最开始遍历的节点,访问节点就是除了出发结点的其他节点,在算法中,如果有n个结点,就要有n-1轮遍历,每一轮遍历都要遍历所有结点,每一轮遍历都是从访问节点开始的。
- 该算法首先要初始化,选定一个出发结点,然后设置already_arr中出发结点的位置为1,其余全为0;dis数组中该出发结点的距离为0,其余距离都为65535,pre_visited为全0.
- 随后进行第一轮遍历(此时还并没有进入for循环内部),即执行update函数。这儿有个细节:先设立already_arr出发(访问)结点为1,再进行(for循环内部)update的遍历。这是在dij函数内进行的,dij函数可谓 是主函数。
- 遍历每完成一轮,选出该轮dis距离中最短的一个位置,把already_arr该位置设置为1,以这个位置作为下次遍历的访问节点,开始下一轮的遍历。对应upgradeArr函数
- 遍历的过程中,运用到了动态规划的思想,这一点难以说明,其最重要的语句莫过于update函数中的
int len = vv.getDis(index) + matrix[index][i]; //访问结点的路径长度加上访问结点到其他结点的路径长度
if(!vv.isVisited(i) && len < vv.dis[i]) {
vv.setDis(i, len);
vv.setPre(i, index);
}
意思是说如果出发(访问)结点记录的dis距离加上出发(访问)结点到i结点的距离,短于此时dis数组中记录i结点的距离,说明找到了一条更优的路径,短于当前记录i的距离(当前出发结点到i的距离),因此进行更新。之所以说用到了动态规划dp算法,是因为每一轮的更新,都用到了上一轮dis记录的信息。
代码演示
package com.atguigu.dijkstra;
import java.util.Arrays;
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;
matrix[0]=new int[]{N,5,7,N,N,N,2};
matrix[1]=new int[]{5,N,N,9,N,N,3};
matrix[2]=new int[]{7,N,N,N,8,N,N};
matrix[3]=new int[]{N,9,N,N,N,4,N};
matrix[4]=new int[]{N,N,8,N,N,5,4};
matrix[5]=new int[]{N,N,N,4,5,N,6};
matrix[6]=new int[]{2,3,N,N,4,6,N};
Graph graph = new Graph(vertex,matrix,2);
graph.showGraph(matrix);
graph.dij(2);
graph.showRes();
}
}
class Graph{
private char[] vertex;
private int[][] matrix;
private VisitedVertex vv;
public Graph(char[] vertex,int matrix[][],int index) {
this.vertex = vertex;
this.matrix = matrix;
vv = new VisitedVertex(vertex.length,index);
}
public void showGraph(int[][] matrix) {
for(int[] tmp:matrix) {
System.out.println(Arrays.toString(tmp));
}
}
public void update(int index) {
for(int i = 0;i < vertex.length;i++) {
int len = vv.getDis(index) + matrix[index][i]; //访问节点的路径长度加上访问节点到其他结点的路径长度
if(!vv.isVisited(i) && len < vv.dis[i]) {
vv.setDis(i, len);
vv.setPre(i, index);
}
}
}
public void dij(int index) {
update(index);
for(int i = 1;i<vertex.length;i++) {
int id = vv.upgradeArr();
update(id);
}
}
public void showRes() {
vv.showRes();
}
}
class VisitedVertex{
private int already_arr[];
private int pre_visited[];
int dis[];
public VisitedVertex(int length,int index) {
this.already_arr = new int[length];
this.pre_visited = new int[length];
this.dis = new int[length];
Arrays.fill(dis,65535);
dis[index] = 0;
already_arr[index] = 1;
}
public int getDis(int index) {
return dis[index];
}
public boolean isVisited(int index) {
return already_arr[index] == 1;
}
public int upgradeArr() {
int index=0;
int min = 65535;
for(int i = 0;i < dis.length;i++) {
if(already_arr[i] == 0 && dis[i] < min) {
min = dis[i];
index = i;
}
}
already_arr[index] = 1;
return index;
}
public void setDis(int index,int len) {
dis[index] = len;
}
public void setPre(int pre,int index) {
pre_visited[pre] = index;
}
public void showRes() {
System.out.println("======================================");
System.out.println("already_arr" + Arrays.toString(already_arr));
System.out.println("pre_visited" + Arrays.toString(pre_visited));
System.out.println("dis" + Arrays.toString(dis));
char[] vertex = {'A','B','C','D','E','F','G'};
int count = 0;
for(int i:dis) {
System.out.printf(vertex[count] + "(" + dis[count] + ")" + "\t");
count++;
}
}
}
演示结果为
[65535, 5, 7, 65535, 65535, 65535, 2]
[5, 65535, 65535, 9, 65535, 65535, 3]
[7, 65535, 65535, 65535, 8, 65535, 65535]
[65535, 9, 65535, 65535, 65535, 4, 65535]
[65535, 65535, 8, 65535, 65535, 5, 4]
[65535, 65535, 65535, 4, 5, 65535, 6]
[2, 3, 65535, 65535, 4, 6, 65535]
======================================
already_arr[1, 1, 1, 1, 1, 1, 1]
pre_visited[2, 0, 0, 5, 2, 4, 0]
dis[7, 12, 0, 17, 8, 13, 9]
A(7) B(12) C(0) D(17) E(8) F(13) G(9)
如果改为从G位置为出发结点,则同时设置设置初始化 Graph graph = new Graph(vertex,matrix,6); 和 dij(6)
Graph graph = new Graph(vertex,matrix,6);
graph.showGraph(matrix);
graph.dij(6);
graph.showRes();
运行结果为
[65535, 5, 7, 65535, 65535, 65535, 2]
[5, 65535, 65535, 9, 65535, 65535, 3]
[7, 65535, 65535, 65535, 8, 65535, 65535]
[65535, 9, 65535, 65535, 65535, 4, 65535]
[65535, 65535, 8, 65535, 65535, 5, 4]
[65535, 65535, 65535, 4, 5, 65535, 6]
[2, 3, 65535, 65535, 4, 6, 65535]
======================================
already_arr[1, 1, 1, 1, 1, 1, 1]
pre_visited[6, 6, 0, 5, 6, 6, 0]
dis[2, 3, 9, 10, 4, 6, 0]
A(2) B(3) C(9) D(10) E(4) F(6) G(0)
Ps:我复现的代码不够简洁,在变换出发结点时需要改两处,而视频源码只需改一处,即直接把VisitedVertex对象的建立放在dij函数中,就实现了只改dij函数即可。