Dijkstra算法不仅是图的经典算法,同时也是对贪心算法比较好的一个实例。
Dijkstra算法
通过Dijkstra计算图G中的最短路径时,需要指定起点vs(即从顶点vs开始计算)。
此外,引进两个集合S和U。S的作用是记录已求出最短路径的顶点,而U则是记录还未求出最短路径的顶点(以及该顶点到起点vs的距离)。
算法步骤
(1) 初始时,S只包含起点vs;U包含除vs外的其他顶点,且U中顶点的距离为"起点vs到该顶点的距离"[例如,U中顶点v的距离为(vs,v)的长度,然后vs和v不相邻,则v的距离为∞]。
(2) 从U中选出"距离最短的顶点k",并将顶点k加入到S中;同时,从U中移除顶点k。
(3) 更新U中各个顶点到起点vs的距离。之所以更新U中顶点的距离,是由于上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其它顶点的距离;例如,(vs,v)的距离可能大于(vs,k)+(k,v)的距离。
(4) 重复步骤(2)和(3),直到遍历完所有顶点。
实例
后续代码会以这个为实例,这里先贴出这个实例,这里为了理解方便,不一次性贴出代码,而是层层解构,最后进行汇总
初始化数据
//初始化
boolean[] flag = new boolean[vertexes.length]; //用于判断是否已经被遍历的标示
int[] U = new int[vertexes.length]; //集合U,记录到各个点的距离
String[] S = new String[vertexes.length]; //集合S,已经计算完成的节点集合
int[] prev = new int[vertexes.length]; //用于记录路径的数组,just 记录而已
//vs表示起始节点的索引,初始化U为vs节点的所有边的权值,flag均为false
for (int i = 0; i < vertexes.length; i++) {
flag[i] = false;
U[i] = matrix[vs][i];
prev[i] = 0;
}
起始节点的处理
//起始节点的处理
S[0] = vertexes[vs]; //起始节点进入S集合
flag[vs] = true; //标记起始节点为已访问
U[vs] = 0; //将U集合中,vs的权值置为0,毕竟从自己到自己权值为0;
核心的Dijkstra整体框架
for (int i = 0; i < vertexes.length; i++) {
//1、找到当前U中最小的元素,并记录下该元素的下标(编程入门难度的逻辑)
//2、将第一步找到的节点加入集合S,并将其标记为已经访问
//3、更新集合U
}
其实结构完成之后,上述步骤好像最难的就是第三步,放心,其实也不难,这里就直接贴出第三步的代码吧
int k = 0;
for (int i = 0; i < vertexes.length; i++) {
//先找到当前U中最小的节点,并记录下标
int min = MAX_WEIGHT;
for (int j = 0; j < vertexes.length; j++) {
if (U[j] < min && flag[j] == false) {
min = U[j];
k = j;
}
}
//找到的节点应该入S集合
S[i] = vertexes[k];
flag[k] = true;//同时标记该最小值的节点为被访问。
//继续更新集合U
for (int j = 0; j < vertexes.length; j++) {
//temp记录为当前的min+当前节点到其他可达节点的权值。
//int temp = min + matrix[k][j];//直接这么写会产生位溢出,毕竟用到了Integer.MAX_VALUE
int temp = matrix[k][j] == MAX_WEIGHT ? MAX_WEIGHT : (min + matrix[k][j]);
//正式开始更新U集合
if (flag[j] == false && temp < U[j]) {
U[j] = temp;
prev[j] = k; //记录一下节点前驱下标。
}
}
}
打印路径
//开始打印路径
System.out.println("起始顶点:" + vertexes[vs]);
for (int i = 0; i < vertexes.length; i++) {
System.out.print("最短路径(" + vertexes[vs] + "," + vertexes[i] + "):" + U[i] + " ");
List<String> path = new ArrayList<>();
int j = i;
while (true) {
path.add(vertexes[j]);
if (j == 0) {
break;
}
j = prev[j];
}
//完成打印工作
for (int x = path.size() - 1; x >= 0; x--) {
if (x == 0) {
System.out.println(path.get(x));
} else {
System.out.print(path.get(x) + "->");
}
}
}
打印的逻辑不是很难,和走链一样,只不是这里是通过数组的下标而已。这里就不解释了,很简单。
扫描二维码关注公众号,回复:
9202285 查看本文章
完整代码
加上测试的代码都在一起,可以直接运行的
import com.learn.graph.MatrixNDG;
import java.util.ArrayList;
import java.util.List;
/**
* autor:liman
* createtime:2020/2/14
* comment:迪杰斯特拉算法
*/
public class ShorestPathDijkstraSelf {
private int[][] matrix;
private int MAX_WEIGHT = Integer.MAX_VALUE;
private String[] vertexes;
/**
* 构建图
*
* @param index
*/
public void createGraph(int index) {
matrix = new int[index][index];
vertexes = new String[index];
int[] v0 = {0, 1, 5, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT};
int[] v1 = {1, 0, 3, 7, 5, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT};
int[] v2 = {5, 3, 0, MAX_WEIGHT, 1, 7, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT};
int[] v3 = {MAX_WEIGHT, 7, MAX_WEIGHT, 0, 2, MAX_WEIGHT, 3, MAX_WEIGHT, MAX_WEIGHT};
int[] v4 = {MAX_WEIGHT, 5, 1, 2, 0, 3, 6, 9, MAX_WEIGHT};
int[] v5 = {MAX_WEIGHT, MAX_WEIGHT, 7, MAX_WEIGHT, 3, 0, MAX_WEIGHT, 5, MAX_WEIGHT};
int[] v6 = {MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 3, 6, MAX_WEIGHT, 0, 2, 7};
int[] v7 = {MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 9, 5, 2, 0, 4};
int[] v8 = {MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, MAX_WEIGHT, 7, 4, 0};
matrix[0] = v0;
matrix[1] = v1;
matrix[2] = v2;
matrix[3] = v3;
matrix[4] = v4;
matrix[5] = v5;
matrix[6] = v6;
matrix[7] = v7;
matrix[8] = v8;
vertexes[0] = "v0";
vertexes[1] = "v1";
vertexes[2] = "v2";
vertexes[3] = "v3";
vertexes[4] = "v4";
vertexes[5] = "v5";
vertexes[6] = "v6";
vertexes[7] = "v7";
vertexes[8] = "v8";
}
public void dijkstra(int vs) {
//初始化
boolean[] flag = new boolean[vertexes.length];
int[] U = new int[vertexes.length];
String[] S = new String[vertexes.length];
int[] prev = new int[vertexes.length];
for (int i = 0; i < vertexes.length; i++) {
flag[i] = false;
U[i] = matrix[vs][i];
prev[i] = 0;
}
//起始节点的处理
S[0] = vertexes[vs];
flag[vs] = true;
U[vs] = 0;
int k = 0;
for (int i = 0; i < vertexes.length; i++) {
//先找到当前U中最小的节点,并记录下标
int min = MAX_WEIGHT;
for (int j = 0; j < vertexes.length; j++) {
if (U[j] < min && flag[j] == false) {
min = U[j];
k = j;
}
}
//找到的节点应该入S集合
S[i] = vertexes[k];
flag[k] = true;//同时标记该最小值的节点为被访问。
//继续更新集合U
for (int j = 0; j < vertexes.length; j++) {
// int temp = min + matrix[k][j];//直接这么写会产生位溢出,毕竟用到了MAX_VALUE
int temp = matrix[k][j] == MAX_WEIGHT ? MAX_WEIGHT : (min + matrix[k][j]);
//正式开始更新U集合
if (flag[j] == false && temp < U[j]) {
U[j] = temp;
prev[j] = k;
}
}
}
//开始打印路径
System.out.println("起始顶点:" + vertexes[vs]);
for (int i = 0; i < vertexes.length; i++) {
System.out.print("最短路径(" + vertexes[vs] + "," + vertexes[i] + "):" + U[i] + " ");
List<String> path = new ArrayList<>();
int j = i;
while (true) {
path.add(vertexes[j]);
if (j == 0) {
break;
}
j = prev[j];
}
//完成打印工作
for (int x = path.size() - 1; x >= 0; x--) {
if (x == 0) {
System.out.println(path.get(x));
} else {
System.out.print(path.get(x) + "->");
}
}
}
//打印一遍S集合
System.out.println("顶点放入到S中的顺序");
for (int i = 0; i < vertexes.length; i++) {
System.out.print(S[i]);
if (i != vertexes.length - 1) {
System.out.print("-->");
}
}
}
public static void main(String[] args) {
ShorestPathDijkstraSelf shorestPathDijkstra = new ShorestPathDijkstraSelf();
shorestPathDijkstra.createGraph(9);
shorestPathDijkstra.dijkstra(0);
}
}
运行结果:
总结
难吗?貌似不太难啊。贪心算法的经典实例