最短路径算法【dijkstra、floyd、bellmen-ford、spfa】

例题 :leetcode 743.网络延迟时间

简介:
一、单源最短路
dijkstra适用于仅有正权的图
带有负权的图:bellmen-ford、spfa
二、多源最短路
floyd:动态规划

1、dijkstra 【O(n^2)】 【O(mlogn)】

dijkstra处理不了负权图。因为如果源点到终点距离比较短,但是有另外一条距离(一开始距离较长)由于有负权路,导致最后到终点距离反而更短,从而导致错误。
如 A->C【5】 A->B【6】->C【-2】,会先选择A->C的路径,但是实际最短路是A->B->C。

贪心思想:
1.找到已经确定的最短距离的点,去更新相邻点的距离
2.不再更新已经确定的点

枚举:【O(n^2)】

public int networkDelayTime(int[][] times, int n, int k) {
        int[][] g=new int[n][n];
        int max=Integer.MAX_VALUE/2;
        for (int i = 0; i < n; i++) {
            Arrays.fill(g[i],max);
        }
        int[] dist=new int[n];
        Arrays.fill(dist,max);
        boolean[] vis=new boolean[n];
        for (int[] time : times) {
            g[time[0]-1][time[1]-1]=time[2];
        }
        dist[k-1]=0;
        for (int i = 0; i < n; i++) {
            int t=-1;
            for (int j = 0; j < n; j++) {
                if(!vis[j]&&(t==-1||dist[t]>dist[j])){
                    t=j;
                }
            }
            vis[t]=true;
            for (int j = 0; j < n; j++) {
                dist[j]=Math.min(dist[j],dist[t]+g[t][j]);
            }
        }
        int ans = Arrays.stream(dist).max().getAsInt();
        return ans==max?-1:ans;
    }

堆:【O(mlogn)】

public int networkDelayTime(int[][] times, int n, int k) {
        int max=Integer.MAX_VALUE/2;
        int[] dist=new int[n];
        Arrays.fill(dist,max);
        boolean[] vis=new boolean[n];
        Map<Integer,List<int[]>> map=new HashMap<>();
        for (int[] time : times) {
            if(!map.containsKey(time[0]-1)){
                map.put(time[0]-1,new ArrayList<>());
            }
            map.get(time[0]-1).add(new int[]{time[1]-1,time[2]});
        }
        dist[k-1]=0;
        PriorityQueue<Integer> pq=new PriorityQueue<>((o1, o2) -> dist[o1]-dist[o2]);
        pq.add(k-1);
        while (!pq.isEmpty()){
            Integer t = pq.poll();
            if(vis[t])    continue;
            vis[t]=true;
            List<int[]> temp = map.getOrDefault(t, new ArrayList<>());
            for (int[] arr : temp) {
                if(dist[t]+arr[1]<dist[arr[0]]){
                    dist[arr[0]]=dist[t]+arr[1];
                    if(!vis[arr[0]]) pq.add(arr[0]);
                }
            }
        }
        int ans = Arrays.stream(dist).max().getAsInt();
        return ans==max?-1:ans;
    }

2、floyd 【O(n^3)】

思想:三层循环,枚举中间节点k,去计算i点、j点中间的最短距离

public int networkDelayTime(int[][] times, int n, int k) {
        int[][] g=new int[n][n];
        int max=Integer.MAX_VALUE/2;
        for (int i = 0; i < n; i++) {
            Arrays.fill(g[i],max);
        }
        for (int[] time : times) {
            g[time[0]-1][time[1]-1]=time[2];
        }
        for (int t = 0; t < n; t++) {
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if(g[i][t]+g[t][j]<g[i][j]){
                        g[i][j]=g[i][t]+g[t][j];
                    }
                }
            }
        }
        g[k-1][k-1]=0;  //剔除自己
        int ans = Arrays.stream(g[k-1]).max().getAsInt();
        return ans==max?-1:ans;
    }

3、bellmen-ford 【O(nm)】

n个节点,最差的情况是一条直线,这时最多有n-1次松弛,所以当第n次还能松弛时,说明图里有负环。
主要用于判断负环。
思想:广度搜索,得到一个节点,去松弛相邻的节点,再根据这些节点,去松弛相应相邻的节点。

public int networkDelayTime(int[][] times, int n, int k) {
        int max=Integer.MAX_VALUE/2,m= times.length;
        int[] dist=new int[n];
        Arrays.fill(dist,max);
        dist[k-1]=0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                int from=times[j][0]-1,to=times[j][1]-1,val=times[j][2];
                if(dist[from]+val<dist[to]){
                    dist[to]=dist[from]+val;
//                    if(i==n-1){
//                        return -1;  //表示有负环
//                    }
                }
            }
        }
        int ans = Arrays.stream(dist).max().getAsInt();
        return ans==max?-1:ans;
    }

4、spfa 最好[O(n)] 最坏 [O(nm)]

最好的情况:所有点与一个点相连
最坏的情况:一条直线
思想:是bellmen-ford算法的优化,通过队列储存节点,因为bellmen每次都要去循环每条边,然后看是否是当前节点可以松弛的,而且spfa通过队列存储节点,直接通过这些节点去松弛相邻的节点。

可以通过cnt[]数组来判断是否有负环,当cnt[v]>=n时,边数>=n,经过的点数>=n+1,有负环。

public int networkDelayTime(int[][] times, int n, int k) {
        int max=Integer.MAX_VALUE/2,m= times.length;
        int[] dist=new int[n];
        boolean[] inq=new boolean[n];
        Arrays.fill(dist,max);
        dist[k-1]=0;
        Map<Integer,List<int[]>> map=new HashMap<>();
        for (int[] time : times) {
            if(!map.containsKey(time[0]-1)){
                map.put(time[0]-1,new ArrayList<>());
            }
            map.get(time[0]-1).add(new int[]{time[1]-1,time[2]});
        }
        Queue<Integer> q=new LinkedList<>();
        q.add(k-1);
        inq[k-1]=true;
        while(!q.isEmpty()){
            Integer t = q.poll();
            inq[t]=false;
            List<int[]> temp = map.getOrDefault(t, new ArrayList<>());
            for (int[] arr : temp) {
                if(dist[t]+arr[1]<dist[arr[0]]){
                    dist[arr[0]]=dist[t]+arr[1];
                    if(!inq[arr[0]])    {
                        inq[arr[0]]=true;
                        q.add(arr[0]);
                    }
                }
            }
        }
        int ans = Arrays.stream(dist).max().getAsInt();
        return ans==max?-1:ans;
    }

判断是否有负环:

public int networkDelayTime(int[][] times, int n, int k) {
        int max=Integer.MAX_VALUE/2,m= times.length;
        int[] dist=new int[n];
        boolean[] inq=new boolean[n];
        Arrays.fill(dist,max);
        dist[k-1]=0;
        Map<Integer,List<int[]>> map=new HashMap<>();
        for (int[] time : times) {
            if(!map.containsKey(time[0]-1)){
                map.put(time[0]-1,new ArrayList<>());
            }
            map.get(time[0]-1).add(new int[]{time[1]-1,time[2]});
        }
        // TODO 核心思想
        Queue<Integer> q=new LinkedList<>();
        //记录更新过的边数
        int[] cnt=new int[n];
        //所有节点入队列,负环可能存在所有节点出发的最短路上
        for (int i = 0; i < n; i++) {
            q.add(i);
            inq[i]=true;
        }
        while(!q.isEmpty()){
            Integer t = q.poll();
            inq[t]=false;
            List<int[]> temp = map.getOrDefault(t, new ArrayList<>());
            for (int[] arr : temp) {
                if(dist[t]+arr[1]<dist[arr[0]]){
                    dist[arr[0]]=dist[t]+arr[1];
                    cnt[arr[0]]=cnt[t]+1;
                    if(cnt[arr[0]]>=n)  return -1; //表示有负环
                    if(!inq[arr[0]])    {
                        inq[arr[0]]=true;
                        q.add(arr[0]);
                    }
                }
            }
        }
        int ans = Arrays.stream(dist).max().getAsInt();
        return ans==max?-1:ans;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值