最短路径算法–无向图[通俗易懂]

最短路径算法–无向图[通俗易懂]最短路径算法Dijkstra算法是最短路径算法中为人熟知的一种,是单起点全路径算法。该算法被称为是“贪心算法”的成功典范。1、表示图的数据结构邻接列表邻接列表:在邻接列表实现中,每一个顶点会存储一个从它这里开始的边的列表。比如,如果顶点A有一条边到B、C和D,那么A的列表中会有3条边邻接…

大家好,又见面了,我是你们的朋友全栈君。

                                         最短路径算法

 Dijkstra算法是最短路径算法中为人熟知的一种,是单起点全路径算法。该算法被称为是“贪心算法”的成功典范。

1、表示图的数据结构 

邻接列表

邻接列表:在邻接列表实现中,每一个顶点会存储一个从它这里开始的边的列表。比如,如果顶点A 有一条边到B、C和D,那么A的列表中会有3条边

最短路径算法--无向图[通俗易懂]

邻接列表只描述了指向外部的边。A 有一条边到B,但是B没有边到A,所以 A没有出现在B的邻接列表中。查找两个顶点之间的边或者权重会比较费时,因为遍历邻接列表直到找到为止。

 邻接矩阵

邻接矩阵:在邻接矩阵实现中,由行和列都表示顶点,由两个顶点所决定的矩阵对应元素表示这里两个顶点是否相连、如果相连这个值表示的是相连边的权重。例如,如果从顶点A到顶点B有一条权重为 5.6 的边,那么矩阵中第A行第B列的位置的元素值应该是5.6:

最短路径算法--无向图[通俗易懂]、、

 

图的邻接矩阵存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储图中的边或弧的信息。

  设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:

最短路径算法--无向图[通俗易懂]

  最短路径算法--无向图[通俗易懂]

从上面可以看出,无向图的边数组是一个对称矩阵。所谓对称矩阵就是n阶矩阵的元满足aij = aji。即从矩阵的左上角到右下角的主对角线为轴,右上角的元和左下角相对应的元全都是相等的。

    从这个矩阵中,很容易知道图中的信息。

    (1)要判断任意两顶点是否有边无边就很容易了;

    (2)要知道某个顶点的度,其实就是这个顶点vi在邻接矩阵中第i行或(第i列)的元素之和;

    (3)求顶点vi的所有邻接点就是将矩阵中第i行元素扫描一遍,arc[i][j]为1就是邻接点;

    而有向图讲究入度和出度,顶点vi的入度为1,正好是第i列各数之和。顶点vi的出度为2,即第i行的各数之和。

算法实现思路

无向图的最短路径实现相对于带权的有向图最短路径实现要简单得多。

源点的最短路径距离为0,从源点开始,采用广度优先的顺序,首先将与源点邻接的顶点的路径求出,然后再依次求解图中其他顶点的最短路径。

由于顶点的最短路径的求解顺序 是一个 广度优先的顺序,因此需要一个辅助队列。初始时,将源点的最短路径距离设置为0,将源点入队列。

然后,在一个while循环中,从队列中弹出顶点,遍历该顶点的邻接点,若该邻接点的距离未被更新过(表示该邻接点未被访问过),更新邻接点的最短路径距离为 该顶点的距离加上1,并将所有的邻接点入队列。

 

该算法的实现与 二叉树的层序遍历,有向图的拓扑排序算法实现都非常的相似。他们都采用了广度的思想在里面。

广度优先的思想就是:处理完某个顶点后,去处理该顶点的所有邻接点,处理完它的邻接点后,再去处理更远(更外层)的顶点。

算法的代码如下:

/*
     * 计算源点s到无向图中各个顶点的最短路径
     * 需要一个队列来保存图中的顶点,初始时,源点入队列,然后以广度的形式向外扩散求解其他顶点的最短路径
     */
    private void unweightedShortestPath(Vertex s){
        //初始化
        Queue<Vertex> queue = new LinkedList<>();
        s.dist = 0;
        queue.offer(s);//将源点dist设置为0并入队列
        
        while(!queue.isEmpty()){
            Vertex v = queue.poll();
            for (Edge e : v.adjEdges) {//扫描v的邻接边(点)
                if(e.endVertex.dist == Integer.MAX_VALUE){//如果这个顶点(e.endVertex)未被访问(每个顶点只会入队列一次)
                    e.endVertex.dist = v.dist + 1;//更新该顶点到源点的距离
                    queue.offer(e.endVertex);
                    e.endVertex.preNode = v;//设置该顶点的前驱顶点
                }//end if
            }//end for
        }//end while
    }

完整代码实现

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

/*
 * 求解无向图的单源最短路径
 */
public class NonDirectedGraph {
    private class Vertex{
        private String vertexLabel;//顶点标识
        private List<Edge> adjEdges;//与该顶点邻接的边(点)
        private int dist;//顶点距离(该顶点到起始顶点的距离)
        private Vertex preNode;
        
        public Vertex(String vertexLabel) {
            this.vertexLabel = vertexLabel;
            adjEdges = new LinkedList<>();
            dist = Integer.MAX_VALUE;
            preNode = null;
        }
    }
    private class Edge{
        private Vertex endVertex;
        public Edge(Vertex endVertex) {
            this.endVertex = endVertex;
        }
    }
    
    private Map<String, Vertex> nonDirectedGraph;//保存了图中所有的顶点,边的关系以List形式保存在Vertex类中
    private Vertex startVertex;//图的起始顶点
    
    public NonDirectedGraph(String graphContent) {
        nonDirectedGraph = new LinkedHashMap<>();
        buildGraph(graphContent);
    }
    
    private void buildGraph(String graphContent){
        String[] lines = graphContent.split("\n");
        
        String startNodeLabel, endNodeLabel;
        Vertex startNode, endNode;
        for(int i = 0; i < lines.length; i++){
            String[] nodesInfo = lines[i].split(",");
            startNodeLabel = nodesInfo[1];
            endNodeLabel = nodesInfo[2];
            
            endNode = nonDirectedGraph.get(endNodeLabel);
            if(endNode == null){
                endNode = new Vertex(endNodeLabel);
                nonDirectedGraph.put(endNodeLabel, endNode);
            }
            
            startNode = nonDirectedGraph.get(startNodeLabel);
            if(startNode == null){
                startNode = new Vertex(startNodeLabel);
                nonDirectedGraph.put(startNodeLabel, startNode);
            }
            Edge e = new Edge(endNode);
            //对于无向图而言,起点和终点都要添加边
            endNode.adjEdges.add(e);
            startNode.adjEdges.add(e);
        }
        startVertex = nonDirectedGraph.get(lines[0].split(",")[1]);//总是以文件中第一行第二列的那个标识顶点作为源点
    }
    
    public void unweightedShortestPath(){
        unweightedShortestPath(startVertex);
    }
    
    
    /*
     * 计算源点s到无向图中各个顶点的最短路径
     * 需要一个队列来保存图中的顶点,初始时,源点入队列,然后以广度的形式向外扩散求解其他顶点的最短路径
     */
    private void unweightedShortestPath(Vertex s){
        //初始化
        Queue<Vertex> queue = new LinkedList<>();
        s.dist = 0;
        queue.offer(s);//将源点dist设置为0并入队列
        
        while(!queue.isEmpty()){
            Vertex v = queue.poll();
            for (Edge e : v.adjEdges) {//扫描v的邻接边(点)
                if(e.endVertex.dist == Integer.MAX_VALUE){//如果这个顶点(e.endVertex)未被访问(每个顶点只会入队列一次)
                    e.endVertex.dist = v.dist + 1;//更新该顶点到源点的距离
                    queue.offer(e.endVertex);
                    e.endVertex.preNode = v;//设置该顶点的前驱顶点
                }//end if
            }//end for
        }//end while
    }
    
    //打印图中所有顶点到源点的距离及路径
    public void showDistance(){
        Collection<Vertex> vertexs = nonDirectedGraph.values();
        for (Vertex vertex : vertexs) {
            System.out.print(vertex.vertexLabel + "<--");
            Vertex tmpPreNode = vertex.preNode;
            while(tmpPreNode != null){
                System.out.print(tmpPreNode.vertexLabel + "<--");
                tmpPreNode = tmpPreNode.preNode;
            }
            System.out.println("distance=" + vertex.dist);
        }
    }
}

图理解

参考链接

https://blog.csdn.net/yu121380/article/details/79810233

这里写图片描述

 

对于求最短路径的问题一般都会给出一幅图,或者边与边的关系。如上图。

假设我们起点是A,我们要求到F的最短距离,我们会怎么做? 
首先,因为A是起点,所以我们把对于每个点都有个参数,相对于A的距离,默认除了A到A为0,其他都是无穷大。 

这里写图片描述

从起点A开始,我们更新与A相连通的点到A的距离,并把A点标记。如图: 
这里写图片描述

我们遍历一次所有点与A的距离,找到最小的,这里是点B。 
以它为起点,把它周围未被标记的点拿来做比较,显然,像F这种没有与A练过的点,当前距离就会变成min(dis[B]+maze[B][F],INF),就是B到起点的距离加上BF之间的距离。而像c这种与A直接相连的点,当前距离就会变成min(dis[B]+maze[B][C],dis[c]),所以按照我们每次只需要比较当前点到当前状态起点的和与当前点到起点的距离就可以了。 
这里写图片描述

然后我们遍历发现,当前未被标记且到起点距离最小点的点是C点。我们更新连接了C的所有点。同样利用上面的min公式。 

这里写图片描述

同理,更新D点的邻点。 

这里写图片描述

再把更新E点的邻点。  

这里写图片描述

最后再更新F点。发现F点周围所有点都被标记了,不做更新。 
再遍历,发现图中所有点都被遍历了,算法结束。 
这个时候,我们已经求出了所有点到起点的最小距离。 
可以直接输出dis[F]求得A到F的最短路径。

注意: 
上面的图重点是看每次变化找的起点和与出发点路径的变化。 
当前起点是当前未被标记且到出发点距离最近的点。 
更新的点都是与该起点直接相连并未被标记的点。

因为并没有无穷大这个数,所以我们把INF设为一个我们计算不能到达的数,这里的数接近int的上限,可以近似看作正无穷。
————————————————
 

#include"iostream"
#include"cstring"
#include"cstdio"
 
using namespace std;
#define INF 0x7f7f7f7f
 
const int N = 105; //点的个数上限
 
int maze[N][N];
int dis[N];
bool vis[N];
 
//点的个数和边的条数
int n,m;
 
void init()
{
    memset(maze,INF,sizeof(maze));
    memset(dis,INF,sizeof(dis));
    memset(vis,false,sizeof(vis));
}
 
void dijkstra(int st)
{
    dis[st]=0;
    for(int i=1; i<=n; i++)
    {
    //找到和起点距离最短的点
        int minx=INF;
        int minmark;
        for(int j=1; j<=n; j++)
        {
            if(vis[j]==false&&dis[j]<=minx)
            {
                minx=dis[j];
                minmark=j;
            }
        }
        //并标记
        vis[minmark]=true;
        //更新所有和它连接的点的距离
        for(int j=1; j<=n; j++)
        {
            if(vis[j]==false&&dis[j]>dis[minmark]+maze[minmark][j])
                dis[j]=dis[minmark]+maze[minmark][j];
        }
    }
}
 
 
int main()
{
    while(scanf("%d %d",&n,&m)!=EOF)
    {
        if(n==0&&m==0) break;
        //每组数据都要初始化
        init();
        for(int i=1; i<=m; i++)
        {
            int x,y,len;
            scanf("%d %d %d",&x,&y,&len);
            if(x!=y&&maze[x][y]>len)
            {
                maze[y][x]=len;
                maze[x][y]=len;
            }
        }
        //以1为起点跑一次dij
        dijkstra(1);
        //输出到n的距离
        printf("%d\n",dis[n]);
    }
}

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/142072.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • OGG安装配置_ogg是什么格式的文件

    OGG安装配置_ogg是什么格式的文件OGG简介(GoldenGate)OGG是一种基于日志的结构化数据复制软件OGG能够实现大量交易数据的实时捕捉,变换和投递,实现源数据库与目标数据库的数据同步,保持最少10ms的数据延迟。OGG安装1.使用Oracle用户,且加入oinstall用户组,GoldenGate安装在Oracle用户所在/home/oracle/app/OGG_linux/ggs目录下。2.源…

    2022年10月26日
  • RPC基本原理_基本原理是什么意思

    RPC基本原理_基本原理是什么意思RPC非常重要,很多人面试的时候都挂在了这个地方!你要是还不懂RPC是什么?他的基本原理是什么?你一定要把下边的内容记起来!好好研究一下!特别是文中给出的一张关于RPC的基本流程图,重点中的重点,Du

  • javah 详解_java entity

    javah 详解_java entity1javah–help帮助说明乱码说明javah–help输出内容采用的是utf-8编码,在cmd打开可能出现乱码,因此执行指令chcp936,指定编码字符集(cmd默认的字符编码集是GBK)2javah参数说明javah–help用法:javah[options]<classes>其中,…

  • bp神经网络的设计方法_bp神经网络例子

    bp神经网络的设计方法_bp神经网络例子基于BP神经网络的室内声源定位算法的实现(附有程序)问题描述现在有一个安静的房子,有一个人在房间里走动,我要利用屋里的麦克风接收这个人的脚步声,然后对这个人进行定位。问题的意义声源定位,这个问题的研究意义重大,它能克服视觉定位的缺点(即只能对看得到的地方进行定位)。问题的研究方法本文只讨论基于麦克风阵列的声源定位(即利用麦克风收集声源信息)。目前解决这个问题的主流方法有三个,分别是基于最大输出功率的可控波束形成技术、基于高分辨率谱估计技术、基于声达时间差的定位技术。这三种方法都是通过研究声音的

  • OpenWrt添加启动脚本

    OpenWrt添加启动脚本

  • Java四种引用类型_JAVA引用数据类型

    Java四种引用类型_JAVA引用数据类型今天看代码,里面有一个类java.lang.ref.SoftReference把小弟弄神了,试想一下,接触java已经有3年了哇,连lang包下面的类都不了解,怎么混。后来在网上查资料,感觉收获颇多,现记录如下。    对象的强、软、弱和虚引用在JDK1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(reachabl

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号