迪杰斯特拉算法求图的最短路径(Java)

本文所有代码基于Java实现图的存储和创建一文所实现的带权无向图


迪杰斯特拉算法

迪杰斯特拉算法(Dijkstra) 是由荷兰计算机科学家狄克斯特拉于1959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

Dijkstra算法设置一个集合S记录已求得最短路径的顶点,初始时把源点v0放入S,
集合S每并入一个新顶点vi,都要修改源点v0到集合V-S中顶点当前的最短路径长度值。
在构造的过程中需要设置两个辅助数组:

  • dist[]: 记录从源点v0到其他各顶点当前的最短路径长度,它的初态为:若从v0vi有边,则dist[i]为该边的权值;否则设置dist[i]
  • path[]: path[i]表示从源点到顶点i之间的最短路径的前驱结点。在算法结束时,可根据其值追溯得到源点v0到顶点vi的最短路径。

迪杰斯特拉算法的步骤如下:

  1. 初始化: 集合S初始化为{0},dist[]的初值为dist[i] = getWeight(0,i),i = 1,2,3...n-1
  2. 从顶点集合V-S中选出vi,满足dist[j] = Min{dist[i] | vi∈V-S},vj就是当前求得的一条从v0出发的最短路径的终点,领S = S ∪ {j}
  3. 修改从v0出发到集合V-S上任一顶点vk可达的最短路径长度:若dist[j] + getWeight(j,k) < dist[k],则更新dist[k] = dist[j] + getWeight(j,k)
  4. 重复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();
    }
输入

所输入是这样一张图:
在这里插入图片描述

输出

在这里插入图片描述

如有错误恳请指正

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值