算法学习记录 8.16 最短路(Dijkstra算法 Bellman-Ford算法 SPFA算法)

 1.朴素Dijkstra算法

 基本步骤:

1.定义一个数组dist表示源点到其余各个结点的距离,dist[i]表示源点到结点i的距离,数组dist内各个元素初始为无穷大

用一个状态数组state标记是否找到源点到该结点的最短距离,如果为真,则表示找到了源点到该结点的最短距离,如果为假,则表示还没有找到源点到该点的最短距离 状态数组state初始为假

2.源点到源点的距离为0 则dist[1]=0

3.遍历dist数组,找到一个未确定为最短路径的所有结点中离源点最近的结点。假设该结点编号为i,则更新对应的state数组:state[i]=true

4.遍历i所有可能到达的结点。如果dist[j]大于dist[i]加上i到j的距离,即dist[j]>dist[i]+w[i][j],更新dist[j]=dist[i]+w[i][j]

5.重复 3 4 直至所有的结点的状态更新成true

6.此时dist数组中保存了源点到各结点的最短路径

import java.util.*;

public class Main{
    static int N=510;
    static int n,m;
    static int max=0x3f3f3f3f;//定义max为无穷大 10^9
    static int[][] g=new int[N][N];//存两个点之间的距离
    static int[] dist=new int[N];//存每个点到起点之间的距离
    static boolean[] st=new boolean[N];//状态数组 标记源点到该结点是否已经是最短路径
    public static int dijkstra(){
            //初始化 将dist数组全部赋最大值
            Arrays.fill(dist,max);
            dist[1]=0;//源点到源点的距离为0
            //遍历dist数组
            for(int i=0;i<n;i++){
                int t=-1;//临时变量
                //遍历所有未标记为最短路径的结点中dist最小的点
                for(int j=1;j<=n;j++){
                    if(!st[j]&&(t==-1||dist[j]<dist[t]))
                        t=j;
                }
                //找到了距离源点最近的点 并更新它的state
                st[t]=true;
                //遍历所有与i结点相连的结点 更新最短路径
                for(int j=1;j<=n;j++){
                    dist[j]=Math.min(dist[j],dist[t]+g[t][j]);
                }
            }
            //源点到n的长度没有改变 输出-1 若改变 则输出最短路径长度
            if(dist[n]==max) return -1;
            else return dist[n];
    }
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        n=sc.nextInt();
        m=sc.nextInt();
        for(int i=1;i<=n;i++){
            Arrays.fill(g[i],max);
        }
        while(m-->0){
            int a=sc.nextInt();
            int b=sc.nextInt();
            int c=sc.nextInt();
            //如果出现重边 更新边为较小值
            g[a][b]=Math.min(g[a][b],c);
        }
        int res=dijkstra();
        System.out.println(res);
    }
}

2.堆优化的Dijkstra算法

优化细节见AcWing 850. Dijkstra求最短路 II - AcWing

import java.io.*;
import java.util.*;

public class Main{
    static int n,m,idx;
    static int N=150010;
    static int max=0x3f3f3f3f;
    static int[] h=new int[N];
    static int[] w=new int[N];//存取a到b之间的距离
    static int[] e=new int[N];
    static int[] ne=new int[N];
    static int[] dist=new int[N];
    static boolean[] st=new boolean[N];
    public static void add(int a,int b,int c){
        e[idx]=b;
        w[idx]=c;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    public static int dijkstra(){
        //定义一个优先级队列
        PriorityQueue<Pair> queue=new PriorityQueue<Pair>();
        Arrays.fill(dist,max);
        dist[1]=0;
        //将初始结点入队
        queue.add(new Pair(0,1));
        while(!queue.isEmpty()){
            //队列非空 取出非空结点
            Pair p=queue.poll();
            int t=p.getSecond();
            int distance=p.getFirst();
            if(st[t]) continue;
            st[t]=true;
            //遍历所有与当前结点相邻的结点 并更新它们对应的的dist 更新后加入队列
            for(int i=h[t];i!=-1;i=ne[i]){
                int j=e[i];
                if(dist[j]>distance+w[i]){
                    dist[j]=distance+w[i];
                    queue.add(new Pair(dist[j],j));
                }
            }
        }
        if(dist[n]==max) return -1;
        else return dist[n];
    }
    public static void main(String[] args)throws IOException{
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        String[] s=br.readLine().split(" ");
        n=Integer.parseInt(s[0]);
        m=Integer.parseInt(s[1]);
        Arrays.fill(h,-1);
        while(m-->0){
            String[] str=br.readLine().split(" ");
            int a=Integer.parseInt(str[0]);
            int b=Integer.parseInt(str[1]);
            int c=Integer.parseInt(str[2]);
            add(a,b,c);
        }
        int res=dijkstra();
        System.out.println(res);
    }
}
//定义一个类pair用来存储位置以及源点到它的最短距离
class Pair implements Comparable<Pair>{
    private int first;
    private int second;
    public int getFirst(){
        return this.first;
    }
    public int getSecond(){
        return this.second;
    }
    public Pair(int first,int second){
        this.first=first;
        this.second=second;
    }
    @Override
    public int compareTo(Pair o){
        return Integer.compare(first,o.first);
    }
}

 时间复杂度O(mlogn)

补充:优先级队列相关Java【优先级队列】详细图解 / 模拟实现 + 【PriorityQueue】常用方法介绍_java优先级队列_灵魂相契的树的博客-CSDN博客

3.Bellman-Ford算法

假设 1 号点到 n 号点是可达的,每一个点同时向指向的方向出发,更新相邻的点的最短距离,通过循环 n-1 次操作,若图中不存在负环,则 1 号点一定会到达 n 号点,若图中存在负环,则在 n-1 次松弛后一定还会更新

基本步骤:

for n次
     for 所有边 a,b,w (松弛操作)
           dist[b] = min(dist[b],backup[a] + w)

注意:backup[] 数组是上一次迭代后 dist[] 数组的备份,由于是每个点同时向外出发,因此需要对 dist[] 数组进行备份,若不进行备份会因此发生串联效应,影响到下一个点

 题解

import java.util.*;
import java.io.*;

public class Main{
    static int n,m,k;
    static int N=510,M=10010;
    static int max=0x3f3f3f3f;
    static Node[] list=new Node[M];//定义数组存储每个三元组
    static int[] dist=new int[N];
    static int[] back=new int[N];
    public static void bellman_ford(){
        //dist数组初始赋最大值
        Arrays.fill(dist,max);
        dist[1]=0;
        for(int i=0;i<k;i++){
            back=Arrays.copyOf(dist,n+1);//数组备份 防止串连
            for(int j=0;j<m;j++){
                Node edg=list[j];
                int a=edg.a;
                int b=edg.b;
                int c=edg.c;
                dist[b]=Math.min(dist[b],back[a]+c);//更新dist数组
            }
        }
        //这里dist[n]需要和max/2进行对比 防止因为负权边而导致dist[n]不等于max 但仍为无穷大
        if(dist[n]>max/2) System.out.println("impossible");
        else System.out.println(dist[n]);
    }
    public static void main(String[] args)throws IOException{
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        String[] s=br.readLine().split(" ");
        n=Integer.parseInt(s[0]);
        m=Integer.parseInt(s[1]);
        k=Integer.parseInt(s[2]);
        for(int i=0;i<m;i++){
            String[] str=br.readLine().split(" ");
            int a=Integer.parseInt(str[0]);
            int b=Integer.parseInt(str[1]);
            int c=Integer.parseInt(str[2]);
            list[i]=new Node(a,b,c);
        }
        bellman_ford();
    }
}
//定义一个类用来存储三元组 从a到b 权重为c
class Node{
    int a,b,c;
    public Node(int a,int b,int c){
        this.a=a;
        this.b=b;
        this.c=c;
    }
}

时间复杂度O(nm)

4.SPFA算法

题意描述同Bellman-Ford算法

题解

此处的数组st用于表示当前位置是否在队列当中 如果是为true 否为false

import java.io.*;
import java.util.*;

public class Main{
    static int n,m,idx,hh,tt;
    static int N=100010;
    static int max=0x3f3f3f3f;
    static int[] q=new int[N];
    static int[] h=new int[N];
    static int[] w=new int[N];
    static int[] e=new int[N];
    static int[] ne=new int[N];
    static int[] dist=new int[N];
    static boolean[] st=new boolean[N];
    public static void add(int a,int b,int c){
        e[idx]=b;
        w[idx]=c;
        ne[idx]=h[a];
        h[a]=idx++;
    }
    public static void spfa(){
        hh=0;
        tt=-1;
        Arrays.fill(dist,max);
        dist[1]=0;
        q[++tt]=1;//更新位置1上的结点信息并入队
        st[1]=true;
        while(hh<=tt){
            int t=q[hh++];//取出第一个队列第一个结点
            st[t]=false;
            for(int i=h[t];i!=-1;i=ne[i]){
                int j=e[i];
                if(dist[j]>dist[t]+w[i]){
                    dist[j]=dist[t]+w[i];//更新最短路径长度
                    if(!st[j]){//如果此时的j并不在队列中 入队
                        q[++tt]=j;
                        st[j]=true;
                    }
                }
            }
        }
        if(dist[n]==max) System.out.println("impossible");
        else System.out.println(dist[n]);
    }
    public static void main(String[] args)throws IOException{
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        String[] s=br.readLine().split(" ");
        n=Integer.parseInt(s[0]);
        m=Integer.parseInt(s[1]);
        Arrays.fill(h,-1);
        while(m-->0){
            String[] str=br.readLine().split(" ");
            int a=Integer.parseInt(str[0]);
            int b=Integer.parseInt(str[1]);
            int c=Integer.parseInt(str[2]);
            add(a,b,c);
        }
        spfa();
    }
}

时间复杂度O(m) 最坏为O(nm)

如何判断是否有负环?

开辟一个数组cnt 记录从结点a到b经过的边数 如果边数大于等于n 则表示有环

需要调整初始状态 初始将所有的结点都加入队列 环未必在1到n的路上

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值