Dijkstra算法原理与步骤
Dijkstra算法是由荷兰计算机科学家Edsger Wybe Dijkstra在1956年发现的一种贪心算法,使用类似广度优先搜索的方法解决赋权图的单源最短路径问题。该算法可固定一个顶点作为源结点然后找到该顶点到图中所有其它结点的最短路径,产生一个最短路径树。但绝大多数Dijkstra算法不能有效处理带有负权边的图[1]。
设一个赋权有向图G=(V,E,W)。其中每条边ei,j := {vi, vj}的权值为一个非负的实数wi,j(ei,j),该权值表示从顶点vi到顶点vj的距离[2]。并设一单源点s∈V。
输入:赋权有向图G = (V,E,W), V = {v1,v2,…,vn}。
输出:从源点s到所有的vj∈V\{s}的最短路径。
- 初始S = {v1};
- 对于vi∈V-S, 计算dist[s,vi](相对于集合S的最短路径的长度);
- 选择minvj∈V dist[s, vi],并将这个vj放进集合S中,更新V-S中的顶点的dist值;
- 重复上述过程,直到S=V。short[s,vi]=dist[s,vi](全局的从源点s到顶点vi的最短路径)。
图 1 Dijkstra算法流程图
Dijkstra算法实例与编程实现
Dijkstra算法实例与计算过程
给定如下带权图,利用Dijkstra算法,分别找出结点“2”~“6”至结点“1”的最短路径,并指出该最短路径经过的结点。
图 2 网络图
第1次迭代:
选择源结点“1”,将1加入S1,源结点“1”到“2”距离(dist[1,2])最短为1,故将结点“2”加入S,成为新的S2[3]。
k | Sk | 1 | 2 | 3 | 4 | 5 | 6 |
1 | {1} | 0 | 1 | ∞ | 3 | 5 | ∞ |
上表还可以表示为下表:
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | F | F | F | F | F |
距离 | 0 | 1 | ∞ | 3 | 5 | ∞ |
父结点 | 1 | 1 | 1 | 1 | 1 | 1 |
第2次迭代:
比较第2次迭代中所有的dist,发现dist[1,2] +w24=2最短,故将结点“4”加入集合S,成为新的S3。注意到源结点“1”到结点“4”有两条路径,一条直达路径距离dist[1,4]=3,另一条经由结点“2”的距离dist[1,2]+w24=2,故将dist[1,4]更新为最短距离2。
k | Sk | 1 | 2 | 3 | 4 | 5 | 6 |
1 | {1} | 0 | 1 | ∞ | 3 | 5 | ∞ |
2 | {1,2} | 6 | 2 | 5 | 8 |
上表还可以表示为下表:
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | T | F | F | F | F |
距离 | 0 | 1 |
|
| 5 |
|
父结点 | 1 | 1 | 1→2 |
| 1 | 1→2 |
第3次迭代:
比较知dist[1,4]+w46=3最小,将结点“6”加入集合S,成为新的集合S4。
k | Sk | 1 | 2 | 3 | 4 | 5 | 6 |
1 | {1} | 0 | 1 | ∞ | 3 | 5 | ∞ |
2 | {1,2} | 6 | 2 | 5 | 8 | ||
3 | {1,2,4} | 6 | 4 | 3 |
上表还可以表示为下表:
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | T | F | T | F | F |
距离 | 0 | 1 | 6 | 2 | 4 | 3 |
父结点 | 1 | 1 | 2 | 2 | 4 | 4 |
第4次迭代:
比较知dist[1,4]+w45=4最小,将结点”5”加入集合S,成为新的集合S5。
k | Sk | 1 | 2 | 3 | 4 | 5 | 6 |
1 | {1} | 0 | 1 | ∞ | 3 | 5 | ∞ |
2 | {1,2} | 6 | 2 | 5 | 8 | ||
3 | {1,2,4} | 6 | 4 | 3 | |||
4 | {1,2,4,6} | 6 | 4 |
上表还可以表示为下表:
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | T | F | T | F | T |
距离 | 0 | 1 | 6 | 2 | 4 | 3 |
父结点 | 1 | 1 | 2 | 2 | 4 | 4 |
第5次迭代:
将最后的结点”3”加入集合S,成为新的集合S5。
k | Sk | 1 | 2 | 3 | 4 | 5 | 6 |
1 | {1} | 0 | 1 | ∞ | 3 | 5 | ∞ |
2 | {1,2} | 6 | 2 | 5 | 8 | ||
3 | {1,2,4} | 6 | 2 | 4 | 3 | ||
4 | {1,2,4,6} | 6 | 4 | 3 | |||
5 | {1,2,4,5,6} | 6 |
上表还可以表示为下表:
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | T | F | T | T | T |
距离 | 0 | 1 | 6 | 2 | 4 | 3 |
父结点 | 1 | 1 | 2 | 2 | 4 | 4 |
第6次迭代:
所有的结点都已加入集合S,即V=S,迭代结束。
k | Sk | 1 | 2 | 3 | 4 | 5 | 6 |
1 | {1} | 0 | 1 | ∞ | 3 | 5 | ∞ |
2 | {1,2} | 6 | 2 | 5 | 8 | ||
3 | {1,2,4} | 6 | 2 | 4 | 3 | ||
4 | {1,2,4,6} | 6 | 4 | 3 | |||
5 | {1,2,4,5,6} | 6 | |||||
6 | {1,2,3,4,5,6} | 0 | 1 | 6 | 2 | 4 | 3 |
上表还可以表示为下表:
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | T | T | T | T | T |
距离 | 0 | 1 | 6 | 2 | 4 | 3 |
父结点 | 1 | 1 | 2 | 2 | 4 | 4 |
综上,对于此网络图,基于Dijkstra算法共进行了6次迭代运算。计算的结点“2”~“6”至结点“1”的最短路径距离及经过的结点如下:
short[1,1]: 1→1 => short[1,1] = 0;
short[1,2]: 1→2 => short[1,2] = w12 = 1;
short[1,3]: 1→2→3 => short[1,3] = w12+w23 = 6;
short[1,4]: 1→2→4 => short[1,4] = w12+w24 = 2;
short[1,5]: 1→2→4→5 => short[1,5] = w12+w24+w45 = 4;
short[1,6]: 1→2→4→6 => short[1,6] = w12+w24+w46 = 3;
Dijkstra算法的编程实现
参考图 1 Dijkstra算法流程图及实例与计算过程,可总结出编程实现Dijkstra算法的核心流程为:
- 初始化G=(V,E,W),可用一矩阵(二维数组)表示一结点到其他各个结点的权值,V是所有结点的集合,E是所有边的集合,W是所有边的权值的集合,设置源结点;
- 创建ChosenVertex类包含上述实例计算过程中所列的第二类表中的所有信息:结点、是否已被选择加入集合S、距离以及父结点。引入三个集合dist, alreadyChoose, parentVertex, 分别表示距离,已选择的结点(即集合S),父结点(即该结点的前一个 结点);
- 初始化三个集合,初始化的alreadyChosen即S集合只有源结点,源结点作为第一次迭代的父结点;
- 计算源点s经由父结点vj到其他未选择的结点vi的距离,若dist[s,vj]+wji<dist[s,vi],则更新其距离dist和父结点parentVertex;
- 选择使dist最小的结点加入S集合,作为下一次迭代的父结点;
- 循环执行步骤4和5,直到V=S即可选择出最短路径。
基于JAVA的Dijkstra算法的编程实现[4]:
- 初始化网格图G = (V,E,W)。这里用邻接矩阵表示带权值的网格图:
- 创建ChosenVertex类:
包含实例计算过程中所列的第二类表中的所有信息:结点、是否已被选择加入集合S、距离以及父结点。
- 初始化操作:
选择源结点”1”:
初始化表格信息:源结点”1”加入S集合,alreadyChosen[0]标记为1,dist[1,1]标记为零,其他均标记为无穷大,父结点标记为“1”,如下表所示:
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | F | F | F | F | F |
距离 | 0 | ∞ | ∞ | ∞ | ∞ | ∞ |
父结点 | 1 |
- 计算源点s经由父结点vj到其他未选择的结点vi的距离,若dist[s,vj]+wji<dist[s,vi],则更新其距离dist和父结点parentVertex。
以第一次迭代为例:
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | F | F | F | F | F |
距离 | 0 | 1 | ∞ | 3 | 5 | ∞ |
父结点 | 1 | 1 | 1 | 1 | 1 | 1 |
源结点source = index = 1作为所有其他未被选择的结点的父结点,计算经由“1”到其他各结点的距离dist[s,vj]+wji,若新计算得到dist大于旧路径的距离则更新距离,同时更新父结点。
- 选择使dist最小的结点加入S集合,作为下一次迭代的父结点;
以第一次迭代为例:
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | F | F | F | F | F |
距离 | 0 | 1 | ∞ | 3 | 5 | ∞ |
父结点 | 1 | 1 | 1 | 1 | 1 | 1 |
比较可知,经由父结点到达结点2的距离dist最小,将结点2加入集合S,同时作为第二次迭代的父结点以更新距离dist。
- 循环执行步骤4和5直到V=S,此时就可得到最短路径距离short=dist以及最短路径经由的结点。
以第二次迭代运算为例:
第一次迭代将结点“2”加入集合S,同时作为第二次迭代运算的父结点,计算从源点“1”出发经由结点“2”到其他结点的距离并比较更新。
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | T | F | F | F | F |
距离 | 0 | 1 |
|
| 5 |
|
父结点 | 1 | 1 | 1→2 |
| 1 | 1→2 |
比较后可见,源点“1”经由结点“2”到结点”3”, ”4”, ”6”的距离小于旧路径的距离,故更新其距离,同时改变父结点为结点“2”;之后比较所有未加入S集合的结点的距离,发现结点4的距离dist[4]=dist[1,4]最小,故将结点“4”加入集合S并作为第三次迭代运算的父结点。
按此步骤迭代6次后的距离即为最短距离:
结点 | 1 | 2 | 3 | 4 | 5 | 6 |
是否加入S集合(T/F) | T | T | T | T | T | T |
距离 | 0 | 1 | 6 | 2 | 4 | 3 |
父结点 | 1 | 1 | 2 | 2 | 4 | 4 |
short[1,1]: 1→1 => short[1,1] = 0;
short[1,2]: 1→2 => short[1,2] = w12 = 1;
short[1,3]: 1→2→3 => short[1,3] = w12+w23 = 6;
short[1,4]: 1→2→4 => short[1,4] = w12+w24 = 2;
short[1,5]: 1→2→4→5 => short[1,5] = w12+w24+w45 = 4;
short[1,6]: 1→2→4→6 => short[1,6] = w12+w24+w46 = 3;
运行结果
以结点“1”作为源结点计算到其他各结点的最短路径如上图所示,可见与上述实例的计算结果相同。
改变源结点的运行结果如下图所示:
经检验,上图运行结果正确,验证了该算法编程的正确性。
源代码
/*已选顶点类——包含上述实例计算过程中第二类表格中所含的信息:结点、结点是否加入集合S、距离和父结点*/
public class ChosenVertex {
/**
* 成员变量
* @param alreadyChosen 已经选择的顶点
* @param distance 距离
* @param parentVertex 父节点 当前结点的前一个结点
* @param path 路径
*/
private int[] alreadyChosen;
private int[] dist;
private int[] parentVertex;
private int[][] path;
public static final int INF = 10000; //表示无穷值
/**
* 构造方法
* @param length 顶点个数
* @param index 源结点
*/
public ChosenVertex(int length, int index) {
this.alreadyChosen = new int[length];
this.parentVertex = new int[length];
this.dist = new int[length];
this.path = new int[length][length];
// 初始化集合S和距离distance
Arrays.fill(alreadyChosen, 0); //S集合为空集 0代表为未被选择 1代表已被选择
Arrays.fill(dist, INF); //距离均初始化为无穷
// 设置源结点已被选择
this.alreadyChosen[index] = 1;
// 设置源结点距离为 dist[s]=dist[s,s]=0
this.dist[index] = 0;
this.parentVertex[index] = index;
}
/**
* 判断index顶点是否已被选择进集合S
* @param index 顶点
* @return 当前顶点若在集合S中 则返回true 否则返回false
*/
public boolean isChosen(int index) {
return alreadyChosen[index] == 1;
}
/**
* 更新距离dist[s,vi]
* @param index 顶点
* @param length 距离
*/
public void updateDist(int index, int length) {
dist[index] = length;
}
/**
* 更新父节点为前一次迭代新选择的顶点
* @param index 新顶点
* @param parent 旧的父节点
*/
public void updateParent(int parent, int index) {
parentVertex[parent] = index;
}
//返回选定结点vi的dist[s,vi]用于比较新获得的dis[s,vj]+wji
public int getDist(int index) {
return this.dist[index];
}
//进行迭代运算 找到使dist[s,vi]最小的vi加入集合S并作为下一次迭代的父节点
public int updateChosen() {
int min = INF;
int index = 0;
for(int i = 0; i < alreadyChosen.length; i++) {
if(alreadyChosen[i] == 0 && dist[i] < min) { //从未被选择的顶点中比较最小的dist
min = dist[i];
index = i;
}
}
alreadyChosen[index] = 1; //将使dist最小的顶点vi选择加入集合S
return index;
}
public void path(int index) {
for(int i = 0; i < alreadyChosen.length; i++) {
path[i][0] = i;
int parent = parentVertex[i];
for(int j = 1; j < alreadyChosen.length; j++) {
path[i][j] = parent;
parent = parentVertex[parent];
}
}
}
//打印运算结果
public void showResult(int index) {
System.out.print("源点: "+(index+1));
System.out.println("\n————————————");
System.out.print("结点: ");
for(int i = 1; i <= alreadyChosen.length; i++) {
System.out.print(i + " ");
}
System.out.println("\n————————————");
System.out.print("选择: ");
for (int i : alreadyChosen) {
System.out.print(i + " ");
}
System.out.println("\n————————————");
System.out.print("距离: ");
for (int i : dist) {
System.out.print(i + " ");
}
System.out.println("\n————————————");
System.out.print("父结点:");
for (int i : parentVertex) {
System.out.print(i+1 + " ");
}
System.out.print("\n————————————");
this.path(index);
for(int i = 0; i < alreadyChosen.length; i++) {
System.out.print("\nshort["+ (i+1) +"]= "+dist[i]+": ");
System.out.print(index+1);
for(int j = (alreadyChosen.length)-1; j >= 0; j--) {
if(path[i][j]!=index) {
System.out.print("->");
System.out.print((path[i][j]+1));
}
else continue;
}
}
}
}
/*网络图类 G = (V,E,W) 包含网格图的顶点、边、权值信息*/
public class Graph {
/**
* 成员变量
* @param vertex 顶点
* @param matrix 邻接矩阵 Adjacency Matrix V和E的集合 邻接矩阵中的值表示边的权值
*/
private char[] vertex;
private int[][] matrix;
private ChosenVertex chosenVertex;
/*构造方法*/
public Graph(char[] vertex, int[][] matrix) {
this.vertex = vertex;
this.matrix = matrix;
}
//Dijkstra算法
public void dijkstra(int index) {
chosenVertex = new ChosenVertex(vertex.length,index);
/**
* 进行第1次迭代运算 这里以源结点"s = index = 1"为父节点计算与其他节点的距离dist[s,vi]
* 并标记vi的前一节点即父节点
* */
update(index);
/*选择使dist最小的vi作为下一依次迭代的父节点*/
for (int j = 1; j < vertex.length; j++) {
//选择使dist最小的vi加入集合S并作为下一次迭代的父节点
index = chosenVertex.updateChosen();
// 更新index为上一次迭代中选出的父节点并更新dist和父节点
update(index);
}
//上述过程共更新了vertex.length次迭代
}
// 更新dist[s,vi]和父节点
// vj := index
public void update(int index) {
int length;
for (int i = 0; i < matrix[index].length; i++) {
// length = dist[s,vj] + wji
length = chosenVertex.getDist(index) + matrix[index][i];
/*如果顶点vi未被选择,并且 length(dist[s,vj]+wji) 小于dist[s,vi]就更新dist和父节点
* 若顶点vi已被选择则保留不更新
* 若经由新父节点得到的dist大于旧有的dist则保留旧有的dist和父节点
*/
if (chosenVertex.isChosen(i) == false && length < chosenVertex.getDist(i)) {
chosenVertex.updateParent(i, index);//将vj标记为父节点即vi的前一节点
chosenVertex.updateDist(i, length);//更新dist[vi]=dist[s,vi]=dist[s,vj]+wji
}
}
}
public void showResult(int index) {
chosenVertex.showResult(index);
}
}
/*Dijkstra算法类 用于创建网格图对象 调用dijskstra算法方法和结果打印方法*/
public class MyDijkstra {
public static final int INF = 10000;
public static void main(String[] args) {
char[] vertex = {'1','2','3','4','5','6'};
int[][] matrix = new int[vertex.length][vertex.length];
matrix[0]= new int[]{0, 1, INF, 3, 5, INF};
matrix[1]= new int[]{1, 0, 5, 1, INF, 7};
matrix[2]= new int[]{INF, 5, 0, INF, INF, 3};
matrix[3]= new int[]{3, 1, INF, 0, 2, 1};
matrix[4]= new int[]{5, INF, INF, 2, 0, 1};
matrix[5]= new int[]{INF, 7, 3, 1, 1, 0};
// 创建Graph对象
Graph graph = new Graph(vertex, matrix);
//选择源结点s
int source = 1; //选择源结点"1"
int index = source-1;//因为数组从零开始 这里减去1
graph.dijkstra(index);
graph.showResult(index);
}
}
参考文献