一直以来对于Dijkstra算法都是只知道其大致步骤,至于为什么该算法能保证找到的都是最短路径却一直似懂非懂。今天花费了半天功夫仔细思考了其中的原理,感觉有些收获。为了防止像之前一样一边捡一边丢,决定记录一下~~~
问题 :
设
G(V,E)
为简单无向赋权连通图,
V
为其顶点集,
准备知识:
- 如果
P(i,j)=Vi...Vk...Vs...Vj
是从顶点
i
到
j 的最短路径, k 和s 是这条路径上的一个中间顶点,那么 P(k,s) 必定是从 k 到s 的最短路径; - 要表示从图
G
中的任一顶点
R 到顶点 P 的最短路径,只需记录图中每一个顶点R 的前继顶点即可;
Dijkstra算法步骤:
符号说明
我搜到的大多数博客文章均把顶点集分成两个子集:已被选中的顶点集
U
和未被选中的顶点集
- 集合
A
: 已被选中的顶点集合,即已经确定了到起始顶点
P 的最短路径的顶点集合; - 集合 B : 与A中至少一个顶点邻接且未被选中的顶点集合;
- 集合
C : 未被选中且不与 A 中任何顶点邻接的顶点集合; D(Vi) : 图 G 中任一顶点Vi 到起始顶点 P 的距离;Pre(Vi) : 顶点 P 到顶点Vi 的最短路径中顶点 Vi 的前继顶点;
算法步骤
准备:计算与起始顶点
P
邻接的顶点到起始顶点的距离,并将它们放入集合
第一步:比较集合
B
中所有顶点到起始顶点的距离,选中其中距离起始顶点路径最短的顶点
第二步:重新计算集合
B
和集合
+ 若
R
在集合
+ 若
R
在集合
重复执行第一步和第二步,直到
G
中所有顶点均移入集合
算法浅析
要弄清楚Dijkstra算法为什么能够确保找到的都是最短路径。主要需要弄清楚一个问题:
1.
G
的子图
问题1
A,B,C
三个集合中顶点在图
G
中的邻接关系如下图所示:
在步骤二中每次集合
Java实现
// 图数据结构
public class Graph {
private int vertex_num;
private Vertex[] vertices;
private double[][] adjacent_array;
public Graph(int vertex_num) {
this.vertex_num = vertex_num;
this.adjacent_array = new double[vertex_num][vertex_num];
this.vertices = new Vertex[vertex_num];
for (int i = 0; i < vertex_num; i++) {
this.vertices[i] = new Vertex();
}
}
public int getVertex_num() {
return vertex_num;
}
public void setVertex_num(int vertex_num) {
this.vertex_num = vertex_num;
}
public double[][] getAdjacent_array() {
return adjacent_array;
}
public void setAdjacent_array(double[][] adjacent_array) {
this.adjacent_array = adjacent_array;
}
public Vertex[] getVertices() {
return vertices;
}
public void setVertices(Vertex[] vertices) {
this.vertices = vertices;
}
}
// 顶点数据结构
public class Vertex {
private int preIndex = -1;
private double dist = Double.MAX_VALUE;
private boolean isSelected = false;
public double getDist() {
return dist;
}
public void setDist(double dist) {
this.dist = dist;
}
public boolean isSelected() {
return isSelected;
}
public void setSelected(boolean selected) {
isSelected = selected;
}
public int getPreIndex() {
return preIndex;
}
public void setPreIndex(int preIndex) {
this.preIndex = preIndex;
}
}
// 算法实现
public class Dijkstra {
public static Graph calMinimumPath(Graph g, int startIndex) {
//init
for (int i = 0; i < g.getVertex_num(); i++) {
if (i == startIndex) {
g.getVertices()[i].setDist(0);
g.getVertices()[i].setPreIndex(-1);
// 将起点加入集合A
g.getVertices()[i].setSelected(true);
continue;
} else {
if (g.getAdjacent_array()[i][startIndex] < Double.MAX_VALUE) {
g.getVertices()[i].setDist(g.getAdjacent_array()[i][startIndex]);
g.getVertices()[i].setPreIndex(startIndex);
}
}
}
for (int i = 0; i < g.getVertex_num(); i++) {
// 从备选集合B中选择一个点加入集合A
double min = Double.MAX_VALUE;
int selected = Integer.MAX_VALUE;
for (int j = 0; j < g.getVertex_num(); j++) {
if (!g.getVertices()[j].isSelected() && g.getVertices()[j].getDist() < min) {
selected = j;
min = g.getVertices()[j].getDist();
}
}
if (Double.MAX_VALUE == min) return g;
// 将被选中的点加入集合A
g.getVertices()[selected].setSelected(true);
for (int k = 0; k < g.getVertex_num(); k++) {
// 观察与被选中点S邻接的点R到目标点P的距离与R先到S再到P的距离的大小, 用小的替换
if (!g.getVertices()[k].isSelected() && g.getAdjacent_array()[k][selected] < Double.MAX_VALUE) {
if (g.getAdjacent_array()[k][selected] + g.getVertices()[selected].getDist() < g.getVertices()[k].getDist()) {
// 说明加入点S之后R到P的距离比之前更近,需更新R点到P点的路径和距离. 该步骤也将集合C中与S邻接的点移入了集合B中
g.getVertices()[k].setDist(g.getAdjacent_array()[k][selected] + g.getVertices()[selected].getDist());
g.getVertices()[k].setPreIndex(selected);
}
}
}
}
return g;
}
}
// 程序入口
public class MinimumPath {
public static void main(String[] args) {
int startIndex = 0;
Graph g = new Graph(5);
g.setAdjacent_array(new double[][]{{0, 100, 30, Double.MAX_VALUE, 10},
{100, 0, 60, 10, Double.MAX_VALUE},
{30, 60, 0, 60, Double.MAX_VALUE},
{Double.MAX_VALUE, 10, 60, 0, 50},
{10, Double.MAX_VALUE, Double.MAX_VALUE, 50, 0}});
g = Dijkstra.calMinimumPath(g, startIndex);
for (int i = 0; i < g.getVertex_num(); i++) {
int current = i;
while (g.getVertices()[current].getPreIndex() != -1) {
System.out.print(current + " -> ");
current = g.getVertices()[current].getPreIndex();
}
System.out.println(startIndex + " length: " + g.getVertices()[i].getDist());
}
}
}
输出结果:
0 length: 0.0
1 -> 3 -> 4 -> 0 length: 70.0
2 -> 0 length: 30.0
3 -> 4 -> 0 length: 60.0
4 -> 0 length: 10.0
有些地方可能想的太复杂了,导致语言表达不清晰,后面再慢慢改进。欢迎拍砖!!