本文所有代码基于Java实现图的存储和创建一文所实现的带权无向图
迪杰斯特拉算法
迪杰斯特拉算法(Dijkstra) 是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
Dijkstra算法设置一个集合S
记录已求得最短路径的顶点,初始时把源点v0
放入S
,
集合S
每并入一个新顶点vi
,都要修改源点v0
到集合V-S
中顶点当前的最短路径长度值。
在构造的过程中需要设置两个辅助数组:
dist[]
: 记录从源点v0
到其他各顶点当前的最短路径长度,它的初态为:若从v0
到vi
有边,则dist[i]
为该边的权值;否则设置dist[i]
为∞
。path[]
:path[i]
表示从源点到顶点i
之间的最短路径的前驱结点。在算法结束时,可根据其值追溯得到源点v0
到顶点vi的最短路径。
迪杰斯特拉算法的步骤如下:
- 初始化: 集合
S
初始化为{0}
,dist[]
的初值为dist[i] = getWeight(0,i),i = 1,2,3...n-1
。 - 从顶点集合
V-S
中选出vi
,满足dist[j] = Min{dist[i] | vi∈V-S}
,vj就是当前求得的一条从v0出发的最短路径的终点,领S = S ∪ {j}
。 - 修改从v0出发到集合V-S上任一顶点vk可达的最短路径长度:若
dist[j] + getWeight(j,k) < dist[k]
,则更新dist[k] = dist[j] + getWeight(j,k)
。 - 重复2~3操作共n-1次,知道所有顶点都包含在
S
中。
###实现代码
public int[] Dijkstra(){
//迪杰斯特拉算法求单源最短路径
//为了方便这里我就默认从0号顶点出发,也可以将出发点改为用户输入
//初始化集合S
ArrayList<Integer> S = new ArrayList<>(); //S保存顶点的编号
S.add(0);
//初始化path数组
int[] path = new int[this.vexNum];
for (int i = 0; i < path.length; i++) {
path[i] = -1;
}
//初始化dist数组
int[] dist = new int[this.vexNum];
for (int i = 0; i < this.vexNum; i++) {
dist[i] = this.getWeight(this.vertices.get(0).getName(),this.vertices.get(i).getName());
if(dist[i] < Integer.MAX_VALUE){
path[i] = 0;
}
}
while (S.size() < this.vexNum){
//当集合S中的顶点数量等于图的顶点数量时,算法即可结束
//选择符合条件的Vj,Vj就是当前求得的一条从V0出发的最短路径的重点
int Vj = getNodeForDij(S,dist);
S.add(new Integer(Vj));
//修改从V0出发到集合V-S上任一顶点Vk可达的最短路径长度
for (int k = 0; k < this.vexNum; k++) {
if (!S.contains(k)){
//S中不包含该顶点则说明该顶点∈V-S
//直接这样写会导致当this.getWeight(this.vertices.get(Vj).getName(),this.vertices.get(k).getName())为
//整数最大值时,newDist由于溢出而获得一个负值
// int newDistK = dist[Vj] + this.getWeight(this.vertices.get(Vj).getName(),this.vertices.get(k).getName());
int tmpWeight = this.getWeight(this.vertices.get(Vj).getName(),this.vertices.get(k).getName());
int newDistK = tmpWeight == Integer.MAX_VALUE ? Integer.MAX_VALUE : dist[Vj] + tmpWeight;
if(newDistK < dist[k]){
//如果V0->Vj->Vk这条路径的长度要小于我们之前记录的从V0->Vk的最短路径,那么就要更新V0->Vk的最短路径s
dist[k] = newDistK;
//并且此时Vj就是V0到Vk这条最短路径的前驱节点
path[k] = Vj;
}
}
}
}
return path;
}
public int getWeight(String node1,String node2){
//获取两个顶点之间的边的权值,如果顶点不存在或边不存在或node1和node2是同一顶点,都返回无穷
//此处将Integer.MAX_VALUE视为正无穷
int node1Index = this.containVnode(node1);
if((node1Index < 0 || this.containVnode(node2) < 0)|| node1.equals(node2)){
return Integer.MAX_VALUE;
}
VNode node = this.vertices.get(node1Index);
ArcNode tmpArcNode = new ArcNode(node.first);
while (tmpArcNode!=null){
if(tmpArcNode.node1.getName().equals(node2) || tmpArcNode.node2.getName().equals(node2)){
//由于我们找的是顶点node1的边表,所以无论临时边的node1还是node2与参数node2匹配都说明找到要找的边
return tmpArcNode.weight;
}
tmpArcNode = tmpArcNode.next;
}
//如果经过循环没找到说明不存在该边
return Integer.MAX_VALUE;
}
private int getNodeForDij(ArrayList<Integer> S, int[] dist){
//此方法为Dijkstra算法的辅助函数,用于从顶点集合V-S中选出符合条件的顶点的下标
int res = 0;
for (int i = 0; i < dist.length; i++) {
if ((!S.contains(i))&&dist[i] < dist[res]){
res = i;
}
}
return res;
}
测试运行
所运行的代码如下:
public static void main(String[] args) {
graph_t g = new graph_t();
g.create();
g.showGraph();
int[] path = g.Dijkstra();
System.out.println("迪杰斯特拉算法求得的path数组:");
for (int i = 0; i < path.length; i++) {
System.out.print(path[i] + " ");
}
System.out.println();
}
输入
所输入是这样一张图:
输出
如有错误恳请指正