6 最短路径
最短路径,对于图来说,是两顶点之间经过的边数最少的路径;对于网来说,是指两顶点之间经过的边上权值之和最小的路径。路径上第一个顶点为源点,最后一个顶点是终点。
6.1 迪杰斯特拉(Dijkstra)算法
以如下无向图为例:
我们来计算下标为0的顶点,到其他顶点的最短路径,首先定义三个数组,calculated[j]表示0->j的最短路径是否已计算出来,pathVal[j]表示0->j的最短路径,目前还未进行计算,我们设置所有元素值均为无穷大,pre[j]表示0->j中经历的顶点,例如pre[1]=3、pre[3]=2、pre[2]=0,表示的:
(1) pre[1]=3表示0->1需要经过3;
(2) pre[3]=2表示0->3需要经过2;
(3) pre[2]=0表示0->2可以直达;
(4) 因此可以得出结论,0->1的路径是:0->2->3->1;
再定义一个变量min,表示“0->上一个顶点的最小权值”,k表示min对应的顶点的下标,因此初始情况如下所示:
接下来来看0到第0个顶点的最短路径,这个不需要计算,肯定是0,且不需要经过其他顶点,因此有如下情况:
到目前为止,min仍然为0,k仍然为0,接下来看0到其他顶点的最短路径,我们把arc[0][j]与现有的pathVal[j]比较,规则是:
(1) calculated[j]=1表示已找到最短路径,不进行任何处理;
(2) 如果arc[0][j]+min<pathVal[j],则令pathVal[j]=arc[0][j]+min,令pre[j]=k,表示0->j至少要经过顶点k;
(3) 如果arc[0][j]+min>=pathVal[j],则pathVal[j]不变,pre[j]也不变,表示0->j不会经过顶点k;
那么,有以下结果:
(1) calculated[0]=1,不进行任何处理;
(2) arc[0][1]+min=16+0<pathVal[1]=无穷大,令pathVal[1]=arc[0][1]+min=16,令pre[j]=k=0;
(3) arc[0][2]、arc[0][3]与arc[0][1]处理方式一致;
(4) arc[0][4]和arc[0][5]都为无穷大,加上min后等于pathVal[4]和pathVal[5],因此pathVal[4]、pathVal[5]和pre[4]、pre[5]不进行处理;
(5) 这时来看最新的pathVal[j],发现最小的权是pathVal[3],于是令min=pathVal[3]=12,令k=3,这也表示0->3的最短路径已找到,因此令calculated[3]=1,如下:
k=3,即其他未计算出的最短路径,都有可能会经过顶点3,因此我们把arc[3][j]与现有pathVal[j]比较,规则仍与arc[0][j]时一致,特别的,arc[3][2]+min=10+12=26>pathVal[2]=15,因此pathVal[2]和pre[2]不进行变更,当然,arc[3][1]也是同样的:
这时,min=15,k=2,因此处理arc[2][j],如下:
然后继续处理,直到calculated[j]的元素都为1:
我们来看现在的pathVal[j]和pre[j]:
可以知道以下内容:
(1) 顶点5,即E,对应的pathVal[5]是43,pre[4]是4,因此顶点0->5,即C->E的最短路径是43,且中间肯定会经过顶点4即顶点F,即肯定有C->F->E;
(2) 顶点4,即F,对应的pathVal[4]是26,pre[4]是3,因此顶点0->4,即C->F的最短路径是26,且中间肯定会经过顶点3即顶点B,即肯定有C->B->F,结点(1),那么肯定有C->B->F->E;
(3) 顶点3,即B,对应的pathVal[3]是12,pre[3]是0,因此顶点0->3,即C->B的最短路径是12,且无中间顶点,那么,结点(1)和(2),得到结论;
1) C到B的最短路径是12,路径是C->B;
2) C到F的最短路径是26,路径是C->B->F;
3) C到E的最短路径是43,路径是C->B->F->E;
(4) 同样的规则,能分析到:
1) C到A的最短路径是15,路径是C->A;
2) C到D的最短路径是15,路径是C->A->D;
以上即为迪杰斯特拉算法。
由上也可知,邻接矩阵的迪杰斯特拉算法实现代码如下所示:
/**
* 迪杰斯特拉算法获取最短路径
*
* @param fromVertexIndex 起始顶点下标
* @return 起始顶点到各顶点的最短路径及前驱顶点列表
* @author Korbin
* @date 2023-02-20 14:54:35
**/
@SuppressWarnings("unchecked")
public Object[][] shortestPathDijkstra(int fromVertexIndex) {
// 最短路径权值,初始化为fromVertexIndex的权
W[] pathVal = (W[]) Array.newInstance(infinity.getClass(), vertexNum);
// 是否已计算,初始值为false
boolean[] calculated = new boolean[vertexNum];
// 要到达此顶点应先从哪个顶点过来,初始值为fromVertexIndex
Integer[] pre = new Integer[vertexNum];
for (int i = 0; i < vertexNum; i++) {
pathVal[i] = arc[fromVertexIndex][i];
pre[i] = fromVertexIndex;
}
// 最小权值
W minWeight = null;
// 最小权值对应的索引下标
int minWeightIndex = fromVertexIndex;
// 已计算的顶点数量,初始值为0
int calculatedNum;
// 从fromVertexIndex到fromVertexIndex,令pathVal[fromVertexIndex]为0,令pre[fromVertexIndex]为fromVertexIndex
// ,令calculated[fromVertexIndex]为true
pathVal[fromVertexIndex] = (W) Integer.valueOf(0);
pre[fromVertexIndex] = fromVertexIndex;
calculated[fromVertexIndex] = true;
calculatedNum = 1;
while (calculatedNum < vertexNum) {
// 循环到所有顶点都计算完毕
// 处理arc[minWeightIndex][j]
for (int i = 0; i < vertexNum; i++) {
// 跳过calculated[j]为true的顶点
if (!calculated[i]) {
// 与现有pathVal[j]比较
if (infinity instanceof Integer) {
// 对于arc[minWeightIndex][i]为无穷大的也不处理
if (!arc[minWeightIndex][i].equals(infinity)) {
// 只处理权值是Integer的情况,其他类型的类似处理
if (null == minWeight) {
minWeight = (W) Integer.valueOf(0);
}
W arcI = (W) (Integer.valueOf((Integer) arc[minWeightIndex][i] + (Integer) minWeight));
if (arcI.compareTo(pathVal[i]) < 0) {
// 令pathVal[j]=arc[minWeightIndex][j] + minWeight
pathVal[i] = arcI;
// 令pre[j]=minWeightIndex
pre[i] = minWeightIndex;
}
}
}
}
}
// 从pathVal和pre中取出新的minWeight和minWeightIndex
minWeight = null;
for (int i = 0; i < vertexNum; i++) {
// 跳过已计算最短路径的顶点
if (!calculated[i] && (null == minWeight || minWeight.compareTo(pathVal[i]) > 0)) {
minWeight = pathVal[i];
minWeightIndex = i;
}
}
// 设置minWeightIndex对应的顶点为已访问
calculated[minWeightIndex] = true;
calculatedNum++;
// 用于测试
System.out.println("min weight is " + minWeight + ", min weight index is " + minWeightIndex);
StringBuilder builder1 = new StringBuilder("path val is [");
StringBuilder builder2 = new StringBuilder("pre vertex is [");
StringBuilder builder3 = new StringBuilder("calculated is [");
for (int i = 0; i < vertexNum; i++) {
builder1.append(pathVal[i]).append(",");
builder2.append(pre[i]).append(",");
builder3.append(calculated[i]).append(",");
}
builder1.append("]");
builder2.append("]\n\r");
builder3.append("]");
System.out.println(builder3);
System.out.println(builder1);
System.out.println(builder2);
}
Object[][] result = new Object[2][];
result[0] = pathVal;
result[1] = pre;
return result;
}
邻接表代码如下:
/**
* 迪杰斯特拉算法获取最短路径
*
* @param fromVertexIndex 起始顶点下标
* @return 起始顶点到各顶点的最短路径及前驱顶点列表
* @author Korbin
* @date 2023-02-20 14:54:35
**/
@SuppressWarnings("unchecked")
public Object[][] shortestPathDijkstra(int fromVertexIndex) {
// 最短路径权值,初始化为fromVertexIndex的权
W[] pathVal = (W[]) Array.newInstance(infinity.getClass(), vertexNum);
// 是否已计算,初始值为false
boolean[] calculated = new boolean[vertexNum];
// 要到达此顶点应先从哪个顶点过来,初始值为fromVertexIndex
Integer[] pre = new Integer[vertexNum];
for (int i = 0; i < vertexNum; i++) {
pre[i] = fromVertexIndex;
pathVal[i] = infinity;
}
// 处理第一个顶点
VertexNode<T, W> vertexNode = vertexes[fromVertexIndex];
EdgeNode<W> edgeNode = vertexNode.getFirstEdge();
while (null != edgeNode) {
int refIndex = edgeNode.getIndex();
W weight = edgeNode.getWeight();
pathVal[refIndex] = weight;
edgeNode = edgeNode.getNext();
}
// 最小权值
W minWeight = null;
// 最小权值对应的索引下标
int minWeightIndex = fromVertexIndex;
// 已计算的顶点数量,初始值为0
int calculatedNum;
// 从fromVertexIndex到fromVertexIndex,令pathVal[fromVertexIndex]为0,令pre[fromVertexIndex]为fromVertexIndex
// ,令calculated[fromVertexIndex]为true
pathVal[fromVertexIndex] = (W) Integer.valueOf(0);
pre[fromVertexIndex] = fromVertexIndex;
calculated[fromVertexIndex] = true;
calculatedNum = 1;
while (calculatedNum < vertexNum) {
// 循环到所有顶点都计算完毕
// 处理本顶点指向的顶点
vertexNode = vertexes[minWeightIndex];
edgeNode = vertexNode.getFirstEdge();
while (null != edgeNode) {
int refI