1. 迪杰斯特拉算法的介绍
迪杰斯特拉(Dijkstra)算法是典型求两点之间最短路径算法。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止
2. 迪杰斯特拉算法的原理
执行步骤:
- 从开始顶点出发,先将开始顶点标记为已访问。此时开始顶点就是访问顶点
- 得到访问顶点的各个下一个未访问顶点,离开始顶点的权重值。如果新的权重值比该下一个未访问顶点之前的权重值小,则对该下一个未访问顶点的权重值进行替换。表示找到了一条更优的路径
- 从访问顶点的各个下一个未访问顶点中,选取一个离开始顶点的权重值最小的顶点,将该顶点标记为已访问
- 将上一步标记的已访问顶点,作为访问顶点,继续从步骤2开始,直到所有顶点被访问
3. 最短路径问题介绍
问题:有7个村庄A、B、C、D、E、F、G,各个村庄之间的距离(权)用边线表示,比如A -> B距离5公里。现有一个快递需要从C村庄送到D村庄,如何走路径最短。从C村庄送到另外5个村庄类似
程序如下:
import java.util.Arrays;
public class DijkstraAlgorithm {
public static void main(String[] args) {
char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
// 用10000表示距离无限大,不能连通
final int INFINITY = 10000;
// N个顶点形成的N * N二维数组,用来保存顶点之间的距离
// 用INFINITY表示距离无限大,不能连通
int[][] weights = {
{INFINITY, 5, 7, INFINITY, INFINITY, INFINITY, 2},
{5, INFINITY, INFINITY, 9, INFINITY, INFINITY, 3},
{7, INFINITY, INFINITY, INFINITY, 8, INFINITY, INFINITY},
{INFINITY, 9, INFINITY, INFINITY, INFINITY, 4, INFINITY},
{INFINITY, INFINITY, 8, INFINITY, INFINITY, 5, 4},
{INFINITY, INFINITY, INFINITY, 4, 5, INFINITY, 6},
{2, 3, INFINITY, INFINITY, 4, 6, INFINITY}
};
// 创建Graph对象
Graph graph = new Graph(vertexs, weights);
// 输出weights
graph.showWeights();
// 测试迪杰斯特拉算法
graph.dsj(2);
graph.showDijkstra();
}
}
// 各个顶点访问过程中的帮助类
class VertexVisitHelper {
// 标记各个顶点是否已经被访问。0表示未访问,1表示已访问
public int[] visitedVertexs;
// 保存每个顶点在路径上的前一个顶点的index
public int[] preVertexIndexs;
// 保存每个顶点距离开始顶点的权重值
public int[] awayFromStartVertexWeights;
// 构造器
public VertexVisitHelper(int vertexNum, int startVertexIndex) {
this.visitedVertexs = new int[vertexNum];
// 标记开始顶点为已访问
this.visitedVertexs[startVertexIndex] = 1;
this.preVertexIndexs = new int[vertexNum];
// 初始化时,所有顶点在路径上都没有前一个顶点,用-1表示
Arrays.fill(this.preVertexIndexs, -1);
this.awayFromStartVertexWeights = new int[vertexNum];
// 初始化,开始顶点到其它顶点的距离为无限大,到自身的距离为0(已经标记为已访问,距离为0不影响)
Arrays.fill(this.awayFromStartVertexWeights, 10000);
this.awayFromStartVertexWeights[startVertexIndex] = 0;
}
// 根据顶点的index,查询顶点是否被访问
public boolean vertexIsVisited(int vertexIndex) {
return visitedVertexs[vertexIndex] == 1;
}
// 更新顶点在路径上的前一个顶点的index
public void updatePreVertexIndex(int vertexIndex, int preVertexIndex) {
preVertexIndexs[vertexIndex] = preVertexIndex;
}
// 根据顶点的index, 获取该顶点距离开始顶点的权重值
public int getAwayFromStartVertexWeight(int vertexIndex) {
return awayFromStartVertexWeights[vertexIndex];
}
// 根据顶点的index,更新距离开始顶点的权重值
public void updateAwayFromStartVertexWeight(int vertexIndex, int awayFromStartVertexWeight) {
awayFromStartVertexWeights[vertexIndex] = awayFromStartVertexWeight;
}
// 从awayFromStartVertexWeights选取一个值最小的顶点index,并标记为已访问
// 然后返回该顶点的index,以便作为新的访问顶点
public int updateVertexIsVisited() {
int minAwayFromStartVertexWeight = 10000;
int vertexIsVisitedIndex = 0;
for (int i = 0; i < awayFromStartVertexWeights.length; i++) {
// 如果顶点已访问,则不进行处理
if (visitedVertexs[i] == 0 && awayFromStartVertexWeights[i] < minAwayFromStartVertexWeight) {
minAwayFromStartVertexWeight = awayFromStartVertexWeights[i];
vertexIsVisitedIndex = i;
}
}
// 更新vertexIsVisited
visitedVertexs[vertexIsVisitedIndex] = 1;
// 将更新的vertexIsVisitedIndex返回
return vertexIsVisitedIndex;
}
// 显示迪杰斯特拉算法的执行结果
public void show() {
System.out.println("==========================");
// 输出visitedVertexs
System.out.println("各个顶点是否被访问情况:" + Arrays.toString(visitedVertexs));
System.out.println("各个顶点在路径上的前一个顶点index情况:" + Arrays.toString(preVertexIndexs));
System.out.println("各个顶点离开始顶点的最优权重值情况:" + Arrays.toString(awayFromStartVertexWeights));
}
}
class Graph {
// 顶点集合
private char[] vertexs;
// N个顶点形成的N * N二维数组,用来保存顶点之间的距离
private int[][] weights;
// 各个顶点访问过程中的帮助类
private VertexVisitHelper vertexVisitHelper;
// 构造器
public Graph(char[] vertexs, int[][] weights) {
this.vertexs = new char[vertexs.length];
for (int i = 0; i < vertexs.length; i++) {
this.vertexs[i] = vertexs[i];
}
this.weights = new int[vertexs.length][vertexs.length];
for (int i = 0; i < vertexs.length; i++) {
for (int j = 0; j < vertexs.length; j++) {
this.weights[i][j] = weights[i][j];
}
}
}
// 显示图的二维数组,即显示顶点之间的距离
public void showWeights() {
for (int[] line : this.weights) {
System.out.println(Arrays.toString(line));
}
}
// 更新访问顶点的未访问的下一个顶点,到起始顶点的权重值
// 更新访问顶点的未访问的下一个顶点,的前一个顶点index(即访问顶点index)
private void updateNextVertex(int visitVertexIndex) {
int awayFromStartVertexWeight = 10000;
// 遍历访问顶点的相邻顶点
for (int j = 0; j < weights[visitVertexIndex].length; j++) {
// 访问顶点到开始顶点的距离 + 访问顶点的下一个顶点到访问顶点的距离
awayFromStartVertexWeight = vertexVisitHelper.getAwayFromStartVertexWeight(visitVertexIndex) + weights[visitVertexIndex][j];
// 如果访问顶点的下一个顶点没有被访问过,且距离开始顶点的权重值比之前的更小
// 则表示找到了一条更优的路径
if (!vertexVisitHelper.vertexIsVisited(j) && awayFromStartVertexWeight < vertexVisitHelper.getAwayFromStartVertexWeight(j)) {
vertexVisitHelper.updateAwayFromStartVertexWeight(j, awayFromStartVertexWeight);
vertexVisitHelper.updatePreVertexIndex(j, visitVertexIndex);
}
}
}
// 迪杰斯特拉算法实现
public void dsj(int startVertexIndex) {
vertexVisitHelper = new VertexVisitHelper(vertexs.length, startVertexIndex);
// 更新访问顶点的未访问的下一个顶点,到起始顶点的权重值
// 更新访问顶点的未访问的下一个顶点,的前一个顶点index(即访问顶点index)
updateNextVertex(startVertexIndex);
// 不断的进行路径查找,直到最后一个顶点
// 在路径查找的过程中,其它顶点的最短路径也会找到
int visitVertexIndex = 0;
for (int j = 1; j < vertexs.length; j++) {
// 从awayFromStartVertexWeights选取一个值最小的顶点index,并标记为已访问
// 然后返回该顶点的index,以便作为新的访问顶点
visitVertexIndex = vertexVisitHelper.updateVertexIsVisited();
// 更新访问顶点的未访问的下一个顶点,到起始顶点的权重值
// 更新访问顶点的未访问的下一个顶点,的前一个顶点index(即访问顶点index)
// 当最后一个顶点被访问,该方法执行没意义
updateNextVertex(visitVertexIndex);
}
}
// 显示迪杰斯特拉算法的执行结果
public void showDijkstra() {
vertexVisitHelper.show();
}
}
运行程序,结果如下:
[10000, 5, 7, 10000, 10000, 10000, 2]
[5, 10000, 10000, 9, 10000, 10000, 3]
[7, 10000, 10000, 10000, 8, 10000, 10000]
[10000, 9, 10000, 10000, 10000, 4, 10000]
[10000, 10000, 8, 10000, 10000, 5, 4]
[10000, 10000, 10000, 4, 5, 10000, 6]
[2, 3, 10000, 10000, 4, 6, 10000]
==========================
各个顶点是否被访问情况:[1, 1, 1, 1, 1, 1, 1]
各个顶点在路径上的前一个顶点index情况:[2, 0, -1, 5, 2, 4, 0]
各个顶点离开始顶点的最优权重值情况:[7, 12, 0, 17, 8, 13, 9]