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

news2024/11/24 12:06:54

前言

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

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.coloradmin.cn/o/1588940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day93:云上攻防-云服务篇对象存储Bucket桶任意上传域名接管AccessKey泄漏

目录 云服务-对象存储-权限配置不当 权限控制-公共读或公共读写&#xff1a;可完整访问但不显示完整结构目录 权限控制-Bucket授权策略&#xff1a;设置ListObject显示完整结构目录 权限控制-Bucket公共读写权限&#xff1a;公共读写直接PUT文件任意上传 云服务-对象存储-…

秋招算法刷题7

20240410 1.接雨水 方法一&#xff0c;动态规划&#xff0c;时间复杂度O&#xff08;n^2&#xff09;&#xff0c;空间复杂度O&#xff08;n&#xff09; public int trap(int[] height) { int nheight.length; if(n0){ return 0; } …

【报错】TypeError: Cannot read property ‘meta‘ of undefined

&#x1f608;解决思路 首先这里很明显我们能看到是缺少该参数&#xff1a;meta。 但是经过查找后发现和该参数无关。 &#x1f608;解决方法 后来我上网搜了下&#xff0c;网上的回答大部分偏向于是package.json这个文件中的tabBar.list数组对象只有一条的问题。 网上的大…

Nginx小册(博客笔记迁移)

nginx基础 1.常用命令 nginx -v #查看版本 ps -ef | grep nginx #输出linux进程、 nginx #启动nginx进程 nginx -s reload #重载配置 nginx -s stop # 停止进程 nginx -t # 检查是否有语法错误&#xff0c;以及配置文件地址2.nginx的配置文件 # 用户组的设置 windows上不生…

Llama2模型本地部署(Mac M1 16G)

环境准备 环境&#xff1a;Mac M1 16G、Conda Conda创建环境配置 使用Anaconda-Navigator创建python 3.8环境 切换到新建的conda环境&#xff1a; conda activate llama38 llama.cpp 找一个目录&#xff0c;下载llama.cpp git clone https://github.com/ggerganov/llama.…

Java后端平台的搭建

后端开发准备工作(配置Tomcat) 安装tomcat安装jdk 配置JAVA HONE(到java目录),path(到 bin 目录)解压Tomcat进入到bin目录,双击startup.bat启动tomcat访问 ip端口在conf目录的 server.xml配置端口 后端平台的搭建 创建Web项目(前提搭建好Tomcat配置) 注:一定要提前配置好Ma…

Android-NDK的linux交叉编译环境

NDK工具包下载 NDK 下载 | Android NDK | Android Developers https://github.com/android/ndk/wiki/Unsupported-Downloads 以android-ndk-r26c下载为例&#xff0c;下载后将压缩包解压至/usr目录下 CMakeLists编译选项设置 编译平台变量判断条件中增加一下android条件…

Okhttp全链路监控

目标&#xff1a; 1&#xff09;.监控网络请求的各个阶段 2&#xff09;获取每一个阶段的耗时和性能&#xff0c;用于性能分析。包括dns解析&#xff0c;socket连接时间&#xff0c;tls连接时间&#xff0c;请求发送时间&#xff0c;服务器接口处理时间&#xff0c;应答传输时…

Windows搭建LightPicture图片管理网站结合内网穿透实现公网访问本地图片

文章目录 1.前言2. Lightpicture网站搭建2.1. Lightpicture下载和安装2.2. Lightpicture网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 现在的手机越来越先进&#xff0c;功能也越来越多&#xff0c;而手机…

数字证书在网络安全中的关键作用与日常应用

在当今数字化的时代&#xff0c;网络安全问题日益凸显&#xff0c;保护数据安全和用户隐私成为了人们关注的焦点。数字证书作为一种重要的网络安全技术&#xff0c;其在网络安全中扮演着关键的角色&#xff0c;并且在我们的日常生活中有着广泛的应用。现在给大家介绍简单介绍下…

数据结构——链表变形

数据结构——链表变形 带尾指针的链表尾插的变化 循环双向双向循环 我们在上次已经了解了单链表&#xff0c;今天我们来了解一下链表的各种变形&#xff0c;如果还没有了解过上面单链表的小伙伴可以点击这里&#xff1a; https://blog.csdn.net/qq_67693066/article/details/13…

避坑指南: “低代码开发平台”怎么选看这篇!附案例参考

从事TOB行业 9 年&#xff0c;深入体验过 10 余款低代码产品。 下面基于我的个人认知给大家讲一讲&#xff1a; 低代码开发平台该如何选&#xff1f;从哪些维度可以判断低代码平台适合你的企业&#xff1f;多家企业采用低代码平台的实践案例&#xff01; 目前市面上有超过 2…

vue快速入门(十)v-bind动态属性绑定

注释很详细&#xff0c;直接上代码 上一篇 新增内容 图片切换逻辑动态绑定的完整写法与简写方法 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice…

IDM激活步骤-亲测可用

前言&#xff1a;我试了3种方法&#xff0c;仅以下方法激活成功&#xff0c;其他都是30天试用 使用步骤&#xff1a; 1.从官网下载IDM并安装&#xff1a;https://www.internetdownloadmanager.com/ 2.下载激活工具&#xff1a;https://wwif.lanzouw.com/iSY2N16s81xi &#…

2024年第十四届MathorCup数学应用挑战赛B题解题思路

B题https://mbd.pub/o/bread/ZZ6Wm5dx 问题1:对于附件I(Pre_test文件夹)给定的三张甲骨文原始拓片图 片进行图像预处理&#xff0c;提取图像特征&#xff0c;建立甲骨文图像预处理模型&#xff0c;实现对 甲骨文图像干扰元素的初步判别和处理。 针对问题1&#xff0c;对于附件…

备战蓝桥杯---刷杂题2

显然我们直接看前一半&#xff0c;然后我们按照斜行看&#xff0c;我们发现斜行是递增的&#xff0c;而同一行从左向右也是递增的&#xff0c;因此我们可以直接二分&#xff0c;同时我们发现对称轴的数为Ck,2k. 我们从16斜行枚举即可 #include<bits/stdc.h> using name…

文心一言 VS 讯飞星火 VS chatgpt (235)-- 算法导论17.2 3题

三、假定我们不仅对计数器进行增 1 操作&#xff0c;还会进行置 0 操作(即将所有位复位)。设检测或修改一个位的时间为 Θ(1) &#xff0c;说明如何用一个位数组来实现计数器&#xff0c;使得对一个初值为 0 的计数器执行一个由任意 n 个 INCREMENT 和 RESET 操作组成的序列花费…

【每日练习】二叉树

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

程序员需要考证吗?程序员考证真的有用吗?

程序员需要考证吗?程序员考证真的有用吗? 软考简介 软考&#xff0c;即软件设计师考试&#xff0c;是由中国人事部主管的一项专业技术资格考试。通过软考&#xff0c;考生可以获得软件设计师等级证书&#xff0c;这是国家认可的专业技术资格证书之一。软考作为我国软件行业的…

docker安装华为高斯-opengauss

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…