(Java)数据结构——图(第六节)Dijkstra实现单源最短路径

前言

本博客是博主用于复习数据结构以及算法的博客,如果疏忽出现错误,还望各位指正。

Dijkstra算法(Dijkstra的实现原理)

迪杰斯特拉算法的实现,很像Prim,基本原理是:

我先找到距离集合路径最短的邻接点,连接,然后看看加入这个新的点后,其他没有遍历过的结点是不是能够更短地到达,有的话就更新。

还是这个图,不过要注意的是,为了凸显出效果,我把AB之间的距离由7改为了8

来走一遍过程:我们以B开始吧

B遍历找到最短距离D权重为3

之后我们进入D,用{3(最短距离)+ D这一行未遍历到的结点}与 {dist中记录的权重}进行比较。A不通INF(注意Java中这时候如果“不通”用Integer.MAX_VALUE表示的话,不能再加,会变成负数的,直接忽略即可),B已经遍历,CD不通,E为3+8=11>5,不更新。结束

然后我们返回dist数组,选取最新的最短路径E权重为5

之后我们进入E,同理

再返回dist数组,选择C权重为6

之后我们进入C,注意,B到A是8,B到C再到A是7,于是进行更新dist和path中对应的值,之后继续……返回dist,最后选择A权重7

以上就是Dijkstra的原理。

Dijkstra的代码实现

主要的实现部分

理解是要格外注意Integer.MAX_VALUE+会变成负数,所以忽略。

public int[] Dijkstra(int begin){
        int N = vertexList.size();
        //到其他点的最短路径,动态更新,直到最后返回
        int[] dist = new int[N];
        for(int i=0;i<N;i++){ //进行一下拷贝,以免更改核心数据
            dist[i] = edges[begin][i];
        }
        //System.out.println(Arrays.toString(dist));
        isVisited[begin] = true;    //该点已经遍历过
        int[] path = new int[N];    //记录这个点从哪来的,到时候在path里寻找就行
        //比如path 2 1 1 1 1,那么A就是从C来的,然后去C,C是从B来的,B是从B来的,OK结束,路径出来
        Arrays.fill(path,begin);
        for(int i = 0;i<N-1;i++){//遍历N-1次即可
            int min = Integer.MAX_VALUE;
            int index = 0;
            for(int j = 0;j<N;j++){//寻找当前的最短路径
                if(!isVisited[j]){
                    if(dist[j] < min){
                        min = dist[j];
                        index = j;
                    }
                }
            }
            isVisited[index] = true;    //置为遍历过
            for(int k = 0;k<N;k++){//新加入点后,看min+edges[新加点的那一行],看是不是比以前的小,小就换了
                if(!isVisited[k] && edges[index][k]!=Integer.MAX_VALUE){//此处格外注意,因为Integer.MAX_VALUE再+就变成负的了,所以一定要注意
                    if(dist[index]+edges[index][k] < dist[k]){
                        dist[k] = dist[index]+edges[index][k];
                        path[k] = index;
                    }
                }
            }
            //找到了之后呢
        }
        //System.out.println(Arrays.toString(dist));
        //System.out.println(Arrays.toString(path));
        return dist;     //返回的是到每个点的最小路径
    }

之后是完整的实现代码(包含其他一些代码)

如果要实现每个点到其他点的最短路径,那么只需要遍历一遍即可,不过别忘了重置isVisited!

//package GraphTest.Demo;

import java.util.*;

public class Graph{//这是一个图类
    /***基础属性***/
    int[][] edges;    //邻接矩阵存储边
    ArrayList<EData> to = new ArrayList<>();    //EData包含start,end,weight三个属性,相当于另一种存储方式,主要是为了实现kruskal算法定义的
    ArrayList<String> vertexList = new ArrayList<>();    //存储结点名称,当然你若想用Integer也可以,这个是我自己复习用的
    int numOfEdges;    //边的个数
    boolean[] isVisited;
    //构造器
    Graph(int n){
        this.edges = new int[n][n];
        //为了方便,决定讲结点初始化为INF,这也方便后续某些操作
        int INF = Integer.MAX_VALUE;
        for(int i=0;i<n;i++){
            Arrays.fill(edges[i],INF);
        }
        this.numOfEdges = 0;
        this.isVisited = new boolean[n];
    }
    //插入点
    public void insertVertex(String vertex){//看自己个人喜好,我这边是一个一个在主方法里插入点的名称
        vertexList.add(vertex);
    }
    //点的个数
    public int getNumOfVertex(){
        return vertexList.size();
    }
    //获取第i个节点的名称
    public String getVertexByIndex(int i){
        return vertexList.get(i);
    }
    //获取该节点的下标
    public int getIndexOfVertex(String vertex){
        return vertexList.indexOf(vertex);
    }
    //插入边
    public void insertEdge(int v1,int v2,int weight){
        //注意,这里是无向图
        edges[v1][v2] = weight;
        edges[v2][v1] = weight;
        this.numOfEdges++;
        //如果要用Kruskal算法的话这里+
        to.add(new EData(v1,v2,weight));    //加入from to这种存储方式
    }
    //边的个数
    public int getNumOfEdge(){
        return this.numOfEdges;
    }
    //得到点到点的权值
    public int getWeight(int v1,int v2){//获取v1和v2边的权重
        return edges[v1][v2];
    }
    //打印图
    public void showGraph(){
        for(int[] line:edges){
            System.out.println(Arrays.toString(line));
        }
    }
    //获取index行 第一个邻接结点
    public int getFirstNeighbor(int index){
        for(int i = 0;i < vertexList.size();i++){
            if(edges[index][i] != Integer.MAX_VALUE){
                return i;    //找到就返回邻接结点的坐标
            }
        }
        return -1;    //没找到的话,返回-1
    }
    //获取row行 column列之后的第一个邻接结点
    public int getNextNeighbor(int row,int column){
        for(int i = column + 1;i < vertexList.size();i++){
            if(edges[row][i] != Integer.MAX_VALUE){
                return i;    //找到就返回邻接结点的坐标
            }
        }
        return -1;    //没找到的话,返回-1
    }
    //DFS实现,先定义一个isVisited布尔数组确认该点是否遍历过

    public void DFS(int index,boolean[] isVisited){
        System.out.print(getVertexByIndex(index)+" ");    //打印当前结点
        isVisited[index] = true;
        //查找index的第一个邻接结点f
        int f = getFirstNeighbor(index);
        //
        while(f != -1){//说明有
            if(!isVisited[f]){//f没被访问过
                DFS(f,isVisited);    //就进入该节点f进行遍历
            }
            //如果f已经被访问过,从当前 i 行的 f列 处往后找
            f = getNextNeighbor(index,f);
        }
    }
    //考虑到连通分量,需要对所有结点进行一次遍历,因为有Visited,所以不用考虑冲突情况
    public void DFS(){
        for(int i=0;i<vertexList.size();i++){
            if(!isVisited[i]){
                DFS(i,isVisited);
            }
        }
    }

    public void BFS(int index,boolean[] isVisited){
        //BFS是由队列实现的,所以我们先创建一个队列
        LinkedList<Integer> queue = new LinkedList<>();
        System.out.print(getVertexByIndex(index)+" ");    //打印当前结点
        isVisited[index] =true;    //遍历标志ture
        queue.addLast(index);    //队尾加入元素
        int cur,neighbor;    //队列头节点cur和邻接结点neighbor
        while(!queue.isEmpty()){//如果队列不为空的话,就一直进行下去
            //取出队列头结点下标
            cur = queue.removeFirst();    //可以用作出队
            //得到第一个邻接结点的下标
            neighbor = getFirstNeighbor(cur);
            //之后遍历下一个
            while(neighbor != -1){//邻接结点存在
                //是否访问过
                if(!isVisited[neighbor]){
                    System.out.print(getVertexByIndex(neighbor)+" ");
                    isVisited[neighbor] = true;
                    queue.addLast(neighbor);
                }
                //在cur行找neighbor列之后的下一个邻接结点
                neighbor = getNextNeighbor(cur,neighbor);
            }
        }
    }
    //考虑到连通分量,需要对所有结点进行一次遍历,因为有Visited,所以不用考虑冲突情况
    public void BFS(){
        for(int i=0;i<vertexList.size();i++){
            if(!isVisited[i]){
                BFS(i,isVisited);
            }
        }
    }

    public  void prim(int begin){
        //Prim原理:从当前集合选出权重最小的邻接结点加入集合,构成新的集合,重复步骤,直到N-1条边
        int N = vertexList.size();
        //当前的集合 与其他邻接结点的最小值
        int[] lowcost = edges[begin];
        //记录该结点是从哪个邻接结点过来的
        int[] adjvex = new int[N];
        Arrays.fill(adjvex,begin);
        //表示已经遍历过了,isVisited置true
        isVisited[begin] = true;

        for(int i =0;i<N-1;i++){//进行N-1次即可,因为只需要联通N-1条边
            //寻找当前集合最小权重邻接结点的操作
            int index = 0;
            int mincost = Integer.MAX_VALUE;
            for(int j = 0;j<N;j++){
                if(isVisited[j]) continue;
                if(lowcost[j] < mincost){//寻找当前松弛点
                    mincost = lowcost[j];
                    index = j;
                }
            }
            System.out.println("选择节点"+index+"权重为:"+mincost);
            isVisited[index] = true;
            System.out.println(index);
            //加入集合后更新的操作,看最小邻接结点是否更改
            for(int k = 0;k<N;k++){
                if(isVisited[k]) continue;//如果遍历过就跳过
                if(edges[index][k] < lowcost[k]){ //加入新的节点之后更新,检查原图的index节点,加入后,是否有更新的
                    lowcost[k] = (edges[index][k]);
                    adjvex[k] = index;
                }
            }
        }
    }
    //用于对之前定义的to进行排序
    public void toSort(){
        Collections.sort(this.to, new Comparator<EData>() {
            @Override
            public int compare(EData o1, EData o2) {
                //顺序对就是升序,顺序不对就是降序,比如我现在是o1和o2传进来,比较时候o1在前就是升序,o1在后就是降序
                int result = Integer.compare(o1.weight,o2.weight);
                return result;
            }
        });
    }
    //并查集查找
    public int find(int x,int[] f){
        while(x!=f[x]){
            x = f[x];
        }
        return x;
    }
    //并查集合并
    public int union(int x,int y,int[] f){
        find(x,f);
        find(y,f);
        if(x!=y){
            f[x] = y;
            return y;
        }
        return -1;    //如果一样的集合就返回-1
    }
    public  ArrayList<Integer> kruskal(){
        //kruskal是对form to weight这种结构的类进行排序,然后选取最短的一条边,看是否形成回路,加入
        toSort();    //调用toSort进行排序
        //由于要判断是否形成回路,我们可以用DFS或者BFS,因为之前都写过,所以我们在这用并查集
        //初始化并查集
        int[] f = new int[vertexList.size()];
        for(int i = 0;i<vertexList.size();i++){
            f[i] = i;
        }
        ArrayList<Integer> res = new ArrayList<>();
        int count = 0;
        for(int i = 0;count != vertexList.size()-1 && i<this.to.size();i++){
            //之后就是开始取边了
            EData temp = this.to.get(i);
            if(union(temp.start,temp.end,f)!=-1){//如果查到不是一个集,就可以添加
                //这里都加进来是方便看哪个边
                res.add(temp.start);
                res.add(temp.end);
                count++;
            }
        }
        return res;    //最后把集合返回去就行
    }
    public int[] Dijkstra(int begin){
        int N = vertexList.size();
        //到其他点的最短路径,动态更新,直到最后返回
        int[] dist = new int[N];
        for(int i=0;i<N;i++){ //进行一下拷贝,以免更改核心数据
            dist[i] = edges[begin][i];
        }
        //System.out.println(Arrays.toString(dist));
        isVisited[begin] = true;    //该点已经遍历过
        int[] path = new int[N];    //记录这个点从哪来的,到时候在path里寻找就行
        //比如path 2 1 1 1 1,那么A就是从C来的,然后去C,C是从B来的,B是从B来的,OK结束,路径出来
        Arrays.fill(path,begin);
        for(int i = 0;i<N-1;i++){//遍历N-1次即可
            int min = Integer.MAX_VALUE;
            int index = 0;
            for(int j = 0;j<N;j++){//寻找当前的最短路径
                if(!isVisited[j]){
                    if(dist[j] < min){
                        min = dist[j];
                        index = j;
                    }
                }
            }
            isVisited[index] = true;    //置为遍历过
            for(int k = 0;k<N;k++){//新加入点后,看min+edges[新加点的那一行],看是不是比以前的小,小就换了
                if(!isVisited[k] && edges[index][k]!=Integer.MAX_VALUE){//此处格外注意,因为Integer.MAX_VALUE再+就变成负的了,所以一定要注意
                    if(dist[index]+edges[index][k] < dist[k]){
                        dist[k] = dist[index]+edges[index][k];
                        path[k] = index;
                    }
                }
            }
            //找到了之后呢
        }
        //System.out.println(Arrays.toString(dist));
        //System.out.println(Arrays.toString(path));
        return dist;     //返回的是到每个点的最小路径
    }

    public static void main(String[] args) {
        int n = 5;
        String[] Vertexs ={"A","B","C","D","E"};
        //创建图对象
        Graph graph = new Graph(n);
        for(String value:Vertexs){
            graph.insertVertex(value);
        }
        graph.insertEdge(0,1,8);
        graph.insertEdge(0,2,1);
        graph.insertEdge(1,2,6);
        graph.insertEdge(1,3,3);
        graph.insertEdge(1,4,5);
        graph.insertEdge(3,4,8);
//        graph.showGraph();
//
//        graph.DFS(1, graph.isVisited);
//        System.out.println();
//        graph.DFS();//再求求所有的,看有没有剩下的
//        System.out.println();
//        Arrays.fill(graph.isVisited,false);
//        graph.BFS(1, graph.isVisited);
//        System.out.println();
//        Arrays.fill(graph.isVisited,false);
//        graph.BFS();
//        System.out.println();
//        Arrays.fill(graph.isVisited,false);
//        graph.prim(2);
//        graph.toSort();
//        for(EData i : graph.to){
//            System.out.println(i.toString());
//        }
//        System.out.println(graph.kruskal().toString());;
//        Arrays.fill(graph.isVisited,false);
        for(int i = 0;i<graph.getNumOfVertex();i++){//每个点的到其他点的最短路径只需要遍历一遍即可,时间复杂度On3
            Arrays.fill(graph.isVisited,false);
            System.out.println(Arrays.toString(graph.Dijkstra(i)));
        }
    }
}

class EData{
    //当然,这是为了方便,直接记录结点下标,而不记录像"A"这种
    int start;
    int end;
    int weight;
    EData(int start,int end,int weight){
        this.start = start;
        this.end = end;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "EData{" +
                "start=" + start +
                ", end=" + end +
                ", weight=" + weight +
                '}';
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/537815.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java:字符集和字符流

字符集 规定了字符和二进制之间对应关系的一张表 字节是计算机最基本的存储单位 字符则是通过字符组成和编码而成的文本 常见字符集 1,ASCII字符集 基础字符编码标准,包含128个字符,只包括英文字母,数字和一些常见的符号 一个字节表示一个字符 所有的字符集均兼容ASCII…

Oracle 正则表达式

一、Oracle 正则表达式相关函数 (1) regexp_like &#xff1a;同 like 功能相似&#xff08;模糊 匹配&#xff09; (2) regexp_instr &#xff1a;同 instr 功能相似&#xff08;返回字符所在 下标&#xff09; (3) regexp_substr &#xff1a; 同 substr 功能相似&…

七项上榜!通付盾十一度荣登安全牛《中国网络安全行业全景图》

2024年4月12日&#xff0c;安全牛第十一版《中国网络安全行业全景图》&#xff08;以下简称“全景图”&#xff09;正式发布。通付盾凭借领先的技术实力与优秀的市场表现&#xff0c;成功入选身份与访问安全、应用与业务安全、移动安全、安全支撑技术与体系四大安全领域。 身份…

宝塔面板部署腾讯云的域名

一、腾讯云&#xff0c;搜索我的证书&#xff0c;点击打开如图所示&#xff0c;点击下砸 二、点击宝塔的证书&#xff0c;然后下载到桌面 三、解压 四、打开宝塔&#xff0c;网站》自己的项目列表中要绑定的ssl 五、对应的文件内容复制进去&#xff0c;保存并启用证书 六、有了…

基于SSM校园招聘信息管理系统的设计与实现说明(内附设计LW + PPT+ 源码下载)

摘 要 随着我国近年来高校不断的进行扩招&#xff0c;2022年全国高校的毕业生人数已经超过一千万人&#xff0c;而在这个时代的大学生早已不像上世纪八九十年代一样&#xff0c;毕业就可以分配工作&#xff0c;所以在当今这个时代毕业生找工作是个非常困难的事情。再加上近几…

[管理者与领导者-158] :团队管理 - 高效执行力 -1- 总体架构、策略、方法、情与法

目录 一、总体架构 二、管理者目标&#xff1a;管理者通过团队获得结果 2.1 考核目标 2.2 高效执行力的指标&#xff1a;时间、成本、质量、数量、满意度 三、管理者的管理策略&#xff1a;结果&#xff08;头与尾&#xff09; VS 过程 四、管理手段和方法 五、关于情与…

TinyEMU源码分析之中断处理

TinyEMU源码分析之中断处理 1 触发中断2 查询中断2.1 查询中断使能与pending状态&#xff08;mie和mip&#xff09;2.2 查询中断总开关与委托&#xff08;mstatus和mideleg&#xff09;2.2.1 M模式2.2.2 S模式2.2.3 U模式 3 处理中断3.1 获取中断编号3.2 检查委托3.3 进入中断3…

【教程】7代核显直通HDMI成功输出 PVE下玩AIO最有性价比的机器

大家好&#xff0c;我是村雨Mura&#xff0c;好久没写教程了&#xff0c;本期是7代核显直通&#xff0c;重点在于HDMI输出画面 本教程理论上适用于4代以后intel带核显CPU&#xff0c;如果你有直通成功经验欢迎评论区分享 前面有点啰嗦&#xff0c;想直接看教程&#xff0c;直…

【每日练习】二叉树

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;二叉树 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 文章目录 一、100. 相同的树1. 题目简介2.…

Go语言中channel和互斥锁的应用场景

面对一个并发问题,我们的解决方案是使用channel还是互斥锁来实现并不总是很清晰。因为Go提倡使用通信来共享内存,所以一个常见的错误就是总是强制使用channel,不管实际情况如何。但是我们应该把这两种选择作为互补手段。 首先,简单回顾一下Go语言中的channel:channel是一种交…

同步检查继电器DT-13/200额定电压100V柜内安装板前接线JOSEF约瑟

系列型号 DT-13/200同步检查继电器; DT-13/160同步检查继电器; DT-13/130同步检查继电器; DT-13/120同步检查继电器; DT-13/90同步检查继电器; DT-13/254同步检查继电器; 同步检查继电器DT-13/200 用途 DT-13型同步检查继电器用于两端供电线路的自动重合闸线路中&…

2024 年第十四届 Mathorcup 数学应用挑战赛题目C 题 物流网络分拣中心货量预测及人员排班完整思路以及源代码分享,仅供学习

电商物流网络在订单履约中由多个环节组成&#xff0c;图1是一个简化的物流网络示意图。其中&#xff0c;分拣中心作为网络的中间环节&#xff0c;需要将包裹按照不同流向进行分拣并发往下一个场地&#xff0c;最终使包赛到达消费者手中。分拣中心管理效率的提升&#xff0c;对整…

物联网SaaS平台

在信息化、智能化浪潮席卷全球的今天&#xff0c;物联网SaaS平台作为推动工业数字化转型的重要工具&#xff0c;正日益受到广泛关注。那么&#xff0c;物联网SaaS平台究竟是什么&#xff1f;HiWoo Cloud作为物联网SaaS平台又有哪些独特优势&#xff1f;更重要的是&#xff0c;它…

使用unicloud-map 无法展示poi的天坑

天坑&#xff01;天坑&#xff01;天坑 使用unicloud-map的天坑 202404121722&#xff0c;昨天晚上发现uni-admin中导入了unicloud-map管理端之后在chrome浏览器由于地图定位失败&#xff0c;一直没有办法新增poi,不过后面发现safari浏览器是可以定位出来的&#xff0c;所以今…

上网行为管理软件怎么选择?哪个软件好?| 三款热门行为审计软件分享

上网行为监控系统是一种用于监控和管理互联网使用行为的系统。 这种系统主要用于企业和学校等机构&#xff0c;以控制和管理员工或学生在工作时间或学习时间内对互联网的使用。 而现在的企业越来越信息化&#xff0c;随之而来的信息危机也丛生不断&#xff0c;企业管理软件也…

VXWorks6.9 + Workbench3.3 Simulation 代码调试

VxWorks系列传送门 本章是基于前一篇《VXWorks6.9 Workbench3.3 开发环境部署》来进行讲解的&#xff0c;在上一篇我们创建了一个Hello World 的项目&#xff0c;并将编译后的可执行文件放到了VxWorks - FTP共享文件目录下&#xff0c;顺利的在VxWin 系统中跑起来。 本篇着重讲…

【学习】Spring IoCDI

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 Spring 是什么&#xff1f; 什么是 IoC容器&#xff1f; 传统开发模式 loC开发模式 IoC的优势 IoC 的使用 Bean的…

TMS320F280049 EPWM模块--ET子模块(7)

下图是ET子模块在EPWM中的位置。可以看到ET子模块相对较独立。接收多种信号&#xff0c;处理后传递给PIE和ADC。 下图是ET的内部框图&#xff0c;可以更具体的看到输入和输出信号。 ET内部也可以软件force产生事件信号。ET输出时可以做分频&#xff0c;也就是接收n次输入后才输…

外贸开发信必知技巧:高回复率不再是梦

外贸行业在Zoho的客户群体中占比较高。因为我们的国际化背景、丰富的产品组合、多语言多币种跨时区、高性价比等特点&#xff0c;成为外贸企业开展业务的选择。在和外贸客户沟通中&#xff0c;发现无论是外贸大拿还是新手小白&#xff0c;大家遇到一个共同的问题——发出去的开…

5252DG 外场通信测试仪范围:9kHz~6.3GHz/9GHz/20GHz

5252DG 外场通信测试仪 率范围&#xff1a;9kHz~6.3GHz/9GHz/20GHz 简述 5252DG外场通信测试仪是集合高性能信号分析模块、多制式解析算法软件于一体的手持式测试仪表&#xff0c;是为满足运营商对移动通信的测试而推出的全新平台。其拥有更高测试频率、更大解析带宽、更快扫…
最新文章