最短路-dijkstra算法:朴素版&堆优化版(java详解)(代码片段)

ZSYL ZSYL     2022-12-11     161

关键词:

朴素版Dijkstra算法

1. 目标

找到从一个点到其他点的最短距离

2. 算法

  • 初始化距离dist数组,将起点dist距离设为0,其他点的距离设为无穷(就是很大的值)
  • for循环遍历n次,每层循环里找出不在S集合中,且距离最近的点,然后用该点去更新其他点的距离,算法复杂度是O(n2),适合稠密图.
  • vis数组,标记是否在S集合中,即是否已经找出最短距离的点集合.

3. 关键代码

//用来记录,不在S集合中,距离最近的点
int t=-1;
for(j=1;j<=n;j++)

    if(!st[j]&&(t==-1||dist[t]>dist[j]))
        t=j;

//加入到集合S中
st[t]=true;
//更新新加入的点,到其他点的距离
for(j=1;j<=n;j++)

    dist[j]=min(dist[j],dist[t]+g[t][j]);

4. 例题

洛谷 P1339 Heat Wave

洛谷 P1339 Heat Wave(Java版)

5. 参考代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.Arrays;

public class Main 
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    public static void main(String[] args) 
        int n = nextInt();
        int m = nextInt();
        int s = nextInt();
        int t = nextInt();
        int[][] map = new int[n+1][n+1];
        for (int i = 0; i <= n; i++) 
            Arrays.fill(map[i], 5000000);
        
        while (m-- > 0) 
            int u = nextInt();
            int v = nextInt();
            int w = nextInt();
            // 保留长度最短的重边
            map[u][v] = map[v][u] = Math.min(map[u][v], w);
        
        boolean[] vis = new boolean[n+1];  // 标记数组
        int[] value = new int[n+1];  // 起点s到其他点的距离
        Arrays.fill(value, 0x3f);  // 初始化为最大值

        // 初始化起点s
        value[s] = 0;  // 起点s到s距离是0

		// 循环n次,就可以将所有点都加入到集合里
        for (int i = 1; i <= n; i++) 
            // 寻找当前最短路径
            // 在未获取的顶点中心找到vs
            int k = 0;  // 用来记录,不在S集合中,距离最近的点
            for (int j = 1; j <= n; j++)   // 在没有确定最短路中的所有点找出距离最短的那个点
                if (!vis[j] && (k == 0 || value[j] < value[k])) 
                    k = j;
                
            
            // 循环结束k存的是没有确定节点中离起点s最近点的编号
            // 标记k为获取的最短路径,加入集合S中
            vis[k] = true;

            // 修正当前最短路径和前驱顶点
            // 当已经得到顶点k的最短路径之后,更新未获取的顶点的最短路径和前驱顶点
            for (int j = 1; j <= n; j++) 
                value[j] = Math.min(value[j], value[k]+map[k][j]);
            
        
        System.out.println(value[t]);
    
    static int nextInt() 
        try 
            in.nextToken();
         catch (IOException e) 
            e.printStackTrace();
        
        return (int)in.nval;
    

堆优化版Dijkstra算法

1. 原理

找寻不在S中,且距离最近的点的方法进行优化,采用最小堆(优先队列的方法),时间复杂度为mlog(n),就可以解决.

2. 算法

对于稀疏图,我们可以用邻接表结构来进行存储(节省空间)

先上数据,如下:

4 5
1 4 9
4 3 8
1 2 5
2 4 6
1 3 7

第一行两个整数n m。

n表示顶点个数(顶点编号为1~n),m表示边的条数。接下来m行表示,每行有3个数x y z,表示顶点x到顶点y的边的权值为z。

下图就是一种使用链表来实现邻接表的方法。


上面这种实现方法为图中的每一个顶点(左边部分)都建立了一个单链表(右边部分)。这样我们就可以通过遍历每个顶点的链表,从而得到该顶点所有的边了。使用链表来实现邻接表对于痛恨指针的的朋友来说,这简直就是噩梦。

Java版邻接表,数据结构展示:

// 边结点的类型定义
class ArcNode 
    int adjVex;  // 存放邻接的点的序号
    ArcNode nextArc;  // 指向Vi下一个邻接点的边结点
    int weight; // 权值

    // 初始化边结点
    public ArcNode() 
        adjVex = 0;
        weight = 0;
        nextArc = null;
    


// 顶点类型的定义
class VNode<T> 
    T data;  // 存储顶点的名称或其他相关信息
    ArcNode firstArc;  // 指向顶点Vi的第一个邻接点的边结点
    public VNode() 
        data = null; firstArc = null;
    

还是挺麻烦的了o(╥﹏╥)o

这里我将为大家介绍另一种使用数组来实现的邻接表,这是一种在实际应用中非常容易实现的方法。这种方法为每个顶点i(i从1~n)也都保存了一个类似“链表”的东西,里面保存的是从顶点i出发的所有的边,具体如下。

首先我们按照读入的顺序为每一条边进行编号(1~m)。比如第一条边“1 4 9”的编号就是1,“1 3 7”这条边的编号是5。

这里用u、v和w三个数组用来记录每条边的具体信息,即u[i]、v[i]和w[i]表示第i条边是从第u[i]号顶点到v[i]号顶点(u[i]v[i]),且权值为w[i]。


再用一个first数组来存储每个顶点其中一条边的编号。以便待会我们来枚举每个顶点所有的边(你可能会问:存储其中一条边的编号就可以了?不可能吧,每个顶点都需要存储其所有边的编号才行吧!甭着急,继续往下看)。比如1号顶点有一条边是 “1 4 9”(该条边的编号是1),那么就将first[1]的值设为1。如果某个顶点i没有以该顶点为起始点的边,则将first[i]的值设为-1。现在我们来看看具体如何操作,初始状态如下。


咦?上图中怎么多了一个next数组,有什么作用呢?不着急,待会再解释,现在先读入第一条边“1 4 9”。

读入第1条边(1 4 9),将这条边的信息存储到u[1]、v[1]和w[1]中。同时为这条边赋予一个编号,因为这条边是最先读入的,存储在u、v和w数组下标为1的单元格中,因此编号就是1。这条边的起始点是1号顶点,因此将first[1]的值设为1。(貌似的这个next数组很神秘啊⊙_⊙)。

读入第2条边(4 3 8),将这条边的信息存储到u[2]、v[2]和w[2]中,这条边的编号为2。这条边的起始顶点是4号顶点,因此将first[4]的值设为2。另外这条“编号为2的边”是我们发现以4号顶点为起始点的第一条边,所以将next[2]的值设为-1


读入第3条边(1 2 5),将这条边的信息存储到u[3]、v[3]和w[3]中,这条边的编号为3,起始顶点是1号顶点。我们发现1号顶点已经有一条“编号为1 的边”了,如果此时将first[1]的值设为3,那“编号为1的边”岂不是就丢失了?

我有办法,此时只需将 next[3] 的值设为1即可。现在你知道next数组是用来做什么的吧!

next[i]存储的是“编号为i的边”的“前一条边”的编号。(注:next数组的大小由边的数目决定,first数组的大小由顶点的个数来决定)


读入第4条边(2 4 6),将这条边的信息存储到u[4]、v[4]和w[4]中,这条边的编号为4,起始顶点是2号顶点,因此将first[2]的值设为4。另外这条“编号为4的边”是我们发现以2号顶点为起始点的第一条边,所以将next[4]的值设为-1。

读入第5条边(1 3 7),将这条边的信息存储到u[5]、v[5]和w[5]中,这条边的编号为5,起始顶点又是1号顶点。此时需要将first[1]的值设为5,并将next[5]的值改为3(编号为5的边的前一条边的编号为3)。


此时,如果我们想遍历1号顶点的每一条边就很简单了。1号顶点的其中一条边的编号存储在first[1]中。其余的边则可以通过next数组寻找到。请看下图。


细心的同学会发现,此时遍历边某个顶点边的时候的遍历顺序正好与读入时候的顺序相反。因为在为每个顶点插入边的时候都直接插入“链表”的首部而不是尾部。不过这并不会产生任何问题,这正是这种方法的其妙之处。

3. 关键代码

// 遍历与当前结点相连的链表其他结点
for (int i = h[ver]; i != -1; i = next[i]) 
    int j = e[i];  // 获取相连边的终点
    if (dist[j] > distance + w[i]) 
        dist[j] = distance + w[i];
        pq.add(new int[]j, dist[j]);
    

4. 例题

洛谷 P1339 Heat Wave

洛谷 P1339 Heat Wave(Java版)

5. 参考代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.Arrays;
import java.util.PriorityQueue;

public class digkstraMinHeap 
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    public static void main(String[] args) 
        int n = nextInt();
        int m = nextInt();
        int s = nextInt();
        int t = nextInt();
        int[] e = new int[m+1];  // 第idx条边对应的终点
        int[] w = new int[m+1];  // 第idx条边对应的权值
        int[] h = new int[n+1];  // 以顶点x为起点的边的编号
        int[] next = new int[m+1];  // 第idx条边对应前一条边的idx
        int[] dist = new int[n+1];  // 起点s到第i个点的距离
        boolean[] vis = new boolean[n+1];  // 第i个点的最短路是否确定,是否需要更新,标记数组
        int idx = 1;  // 第几条边
        Arrays.fill(h, -1);  // 初始为-1
        while (m-- > 0) 
            int u = nextInt();
            int v = nextInt();
            int W = nextInt();
            e[idx] = v;
            w[idx] = W;
            next[idx] = h[u];
            h[u] = idx++;
        

        Arrays.fill(dist, 0x3f);  // 初始化为最大值


        // 初始化第一个点
        dist[s] = 0;  // 第一个点到起点的距离为0,int[0]是点编号,int[1]是距离起点的距离
        PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2)->(o1[1]-o2[1]));  // 按照距离升序排序
        pq.add(new int[]s, 0);  // 把起点s点放入堆中

        while (!pq.isEmpty()) 
            int[] node = pq.poll();
            int ver = node[0];
            int distance = node[1];
            if (vis[ver])
                continue;

            vis[ver] = true;  // 标记当前最短距离结点已访问
            // 遍历与当前结点相连的链表其他结点
            for (int i = h[ver]; i != -1; i = next[i]) 
                int j = e[i];  // 获取相连边的终点
                if (dist[j] > distance + w[i]) 
                    dist[j] = distance + w[i];
                    pq.add(new int[]j, dist[j]);
                
            
        
        if (dist[n] == 0x3f3f3f3f) 
            System.out.println(-1);
         else 
            System.out.println(dist[t]);
        

    
    static int nextInt() 
        try 
            in.nextToken();
         catch (IOException e) 
            e.printStackTrace();
        
        return (int)in.nval;
    

参考博客link

衷心感谢!

加油!

感谢!

努力!

(算法基础)朴素版的dijkstra算法

...xff08;n^2~m)。时间复杂度O(N^2)算法解释(朴素版的Dijkstra)首先是关于这个图的存储,图的话主要是分为稠密图与稀疏图。稠密图就是说n的平方与m是一个量级的,对于稠密图的话,用邻接矩阵来存;稀... 查看详情

算法之最短路

...路我跟你讲SPFA已经死了好吧,SPFA+堆优又太难打,那就用dijkstra吧。(负权?我管它呢)不加任何优化的裸dijkstra一般般快吧,N^2,N=10000时可以卡过,很好打。#include<bits/stdc++.h>#defineMAXN1005usingnamespacestd;longlongLIS[MAXN][MAXN];//LIS... 查看详情

最短路径-dijkstra算法(代码片段)

dijkstra大神发明的算法朴素的dijkstra算法时间复杂度为O(nn),只能处理包含正权边的图。使用优先级队列或堆优化过的dijkstra算法时间复杂度为O(Nlog(N))下面是优先级队列优化的dijkstra代码源码/*input:点数N,边数M,起点S,终点T,以... 查看详情

最短路算法——dijkstra算法(代码片段)

用途:解决单源最短路径问题(已固定一个起点,求它到其他所有点的最短路问题)算法核心(广搜):(1)确定的与起点相邻的点的最短距离,再根据已确定最短距离的点更新其他与之相邻的点的最短距离。(2)之后的更新... 查看详情

图中最短路径算法(dijkstra算法)(转)

1.Dijkstra1)      适用条件&范围:a)   单源最短路径(从源点s到其它所有顶点v);b)   有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图)c)   所有边权非负(任取(i,... 查看详情

求解最短路的四个算法及其优化(代码片段)

目录求解最短路的四个算法及其优化1.Dijkstra算法<1.朴素Dijkstra算法:<2:堆优化的Dijkstra算法2.Floyd算法3.Bellman-Ford算法4.SPFA算法求解最短路的四个算法及其优化1.Dijkstra算法Dijkstra很好的运用了贪心算法,其思想是一直找离已加... 查看详情

小航的算法日记图论

...路问题:​​​​1.Floyd「多源汇最短路」​​​​2.朴素Dijkstra「单源最短路」​​​​3.堆优化Dijkstra「单源最短路」​​​​4.BellmanFord「单源最短路」​​​​5.SPFA「单源最短路」​​​​最小生成树:​​​​1.Krusk 查看详情

训练指南uvalive-4080(最短路dijkstra+边修改+最短路树)(代码片段)

layout:posttitle:训练指南UVALive-4080(最短路Dijkstra+边修改+最短路树)author:"luowentaoaa"catalog:truemathjax:truetags:-Dijkstra-最短路树-图论-训练指南WarfareAndLogisticsUVALive-4080题意①先求任意两点间的最短路径累加和,其中不连通的边权... 查看详情

dijkstra算法求单源最短路

参考技术Adijkstra算法用于求解单源最短路问题,只能求解正权图,图中有负边求出来的结果会有问题。算法的思想就是先确定一个起点(源点),然后寻找这个点到其他所有点的距离最小值,找到一条距离最短的线路。第一次查... 查看详情

最短路径—dijkstra算法

Dijkstra算法1.定义概览Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法是很有代表性的最短路径算... 查看详情

最短路径算法——dijkstra算法

...法。最出名的求最短路径算法有两个,即Bellman-Ford算法和Dijkstra算法。这两种算法的思路不同,但得出的结果是相同的。    下面只介绍Dijkstra算法,它的已知条件是整个网络拓扑和各链路的长度。  应注意到,若将已知... 查看详情

最短路学习笔记:floyd&&dijkstra

谨以此笔记记录jjw高三党四个月学习NOI的历程..如转载请标记出处Floyd算法:  默认是业界最短路最简单的写法,并且只有五行。时间复杂度为O(N3),空间复杂度为O(N2)。1for(intk=1;k<=n;k++){2for(inti=1;i<=n;i++){3for(intj=1;j<=n;j++){4... 查看详情

最短路径算法-dijkstra

Dijkstra是解决单源最短路径的一般方法,属于一种贪婪算法。所谓单源最短路径是指在一个赋权有向图中,从某一点出发,到另一点的最短路径。 以python代码为例,实现Dijkstra算法1、数据结构设计假设图以单边列表的方式进... 查看详情

最短路径-dijkstra算法(转载)

注意:以下代码只是描述思路,没有测试过!! Dijkstra算法1.定义概览Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展... 查看详情

最短路径之dijkstra算法

  Dijkstra(迪杰斯特拉)算法是典型的最短路径路由算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。Dijkstra算法能得出最短路径的最优解,但由于它遍历... 查看详情

最短路径——dijkstra算法(java)

...(有权图)中的问题,我们解决网中的最短路径常常使用dijkstra算法来求解。 dijkstra算法是一种贪心的思想,具体其正确性的证明,这里就不再赘述。下面来直接讲解如何使用dijkstra算法:1.我们在使用dijkstra算法时为了其编... 查看详情

最短路径—dijkstra算法和floyd算法

转自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.htmlDijkstra算法1.定义概览Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径。主要特点是以起始点为中心向外层层扩展,直到... 查看详情

最短路径——dijkstra算法

最短路径——Dijkstra算法BFS算法的局限性Dijkstra算法第一轮:循环遍历所有结点,找到还没确定最短路径,且dist最小的顶点vi,令final[i]=true;检查所有邻接自vi的顶点,若其final值为false,则更新dist和path的信息第二轮:循环遍历所... 查看详情