Day63:Bellman_ford队列优化算法 判断负权回路 单源有限最短路

Bellman_ford 算法每次松弛 都是对所有边进行松弛

但真正有效的松弛,是基于已经计算过的节点在做的松弛。

所以 Bellman_ford 算法 每次都是对所有边进行松弛,其实是多做了一些无用功。

只需要对 上一次松弛的时候更新过的节点作为出发节点所连接的边 进行松弛就够了

基于以上思路,用队列来记录上次松弛的时候更新过的节点(其实用栈也行,对元素顺序没有要求)

minDist数组来表达 起点到各个节点的最短距离

基于队列优化的bellman_ford的算法模拟过程:

 


94. 城市间货物运输 I

题目描述

某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。

网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。

请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。如果最低运输成本是一个负数,它表示在遵循最优路径的情况下,运输过程中反而能够实现盈利。

城市 1 到城市 n 之间可能会出现没有路径的情况,同时保证道路网络中不存在任何负权回路。

输入描述

第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。 

接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v (单向图)。

输出描述

如果能够从城市 1 到连通到城市 n, 请输出一个整数,表示运输成本。如果该整数是负数,则表示实现了盈利。如果从城市 1 没有路径可达城市 n,请输出 "unconnected"。

输入示例
6 7
5 6 -2
1 2 1
5 3 1
2 5 2
2 4 -3
4 6 4
1 3 5
输出示例
1
提示信息

示例中最佳路径是从 1 -> 2 -> 5 -> 6,路上的权值分别为 1 2 -2,最终的最低运输成本为 1 + 2 + (-2) = 1。

示例 2:

4 2
1 2 -1
3 4 -1

在此示例中,无法找到一条路径从 1 通往 4,所以此时应该输出 "unconnected"。

数据范围:

1 <= n <= 1000;
1 <= m <= 10000;

-100 <= v <= 100;

基于队列优化的bellman_ford的算法代码:

import java.util.*;

public class Main {
    public static void main(String[] args) {
        int n, m;
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        List<Node>[] edges = new List[n + 1];
        while (m-- > 0) {
            int from = in.nextInt();
            int to = in.nextInt();
            int val = in.nextInt();
            if (edges[from] == null) {
                edges[from] = new LinkedList<>();
            }
            edges[from].add(new Node(to, val));
        }
        int[] minDest = new int[n + 1];
        Arrays.fill(minDest, Integer.MAX_VALUE);
        Queue<Integer> queue = new LinkedList();
        minDest[1] = 0;
        queue.add(1);
        while (!queue.isEmpty()) {
            Integer poll = queue.poll();
            //更新minDest和队列
            List<Node> edge = edges[poll];
            if(edge==null){continue;}
            for(Node temp:edge){
                if(minDest[poll]!=Integer.MAX_VALUE&&minDest[poll]+temp.getVal()<minDest[temp.getTo()]){
                    minDest[temp.getTo()]=minDest[poll]+temp.getVal();
                    queue.add(temp.getTo());
                }
            }
        }
        String result= minDest[n]!=Integer.MAX_VALUE?minDest[n]+"":"unconnected";
        System.out.println(result);

    }
}

class Node {
    int to;
    int val;

    public Node(int to, int val) {
        this.to = to;
        this.val = val;
    }

    public int getTo() {
        return to;
    }

    public int getVal() {
        return val;
    }
}

while (!que.empty()) 队里里 会不会造成死循环? 例如 图中有环,这样一直有元素加入到队列里?

其实有环的情况,要看它是 正权回路 还是 负权回路。

题目描述中,已经说了,本题没有 负权回路 。

但如果本题有 负权回路,那情况就不一样了,我在下一题目讲解中,会重点讲解 负权回路 带来的变化


bellman_ford之判断负权回路

本题是要我们判断 负权回路,也就是图中出现环且环上的边总权值为负数。

如果在这样的图中求最短路的话, 就会在这个环里无限循环 (也是负数+负数 只会越来越小),无法求出最短路径。

所以对于 在有负权值的图中求最短路,都需要先看看这个图里有没有负权回路

在没有负权回路的图中,bellman_ford算法松弛 n 次以上 ,结果不会有变化。

有 负权回路,如果松弛 n 次,结果就会有变化了,因为 有负权回路 就是可以无限最短路径(一直绕圈,就可以一直得到无限小的最短距离)。

使用 队列优化版的bellman_ford(SPFA)

所有节点都与其他节点相连,每个节点的入度为 n-1 (n为节点数量),所以每个节点最多加入 n-1 次队列。

那么如果节点加入队列的次数 超过了 n-1次 ,那么该图就一定有负权回路。


95. 城市间货物运输 II

题目描述

某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。

网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。

然而,在评估从城市 1 到城市 n 的所有可能路径中综合政府补贴后的最低运输成本时,存在一种情况:图中可能出现负权回路。负权回路是指一系列道路的总权值为负,这样的回路使得通过反复经过回路中的道路,理论上可以无限地减少总成本或无限地增加总收益。为了避免货物运输商采用负权回路这种情况无限的赚取政府补贴,算法还需检测这种特殊情况。

请找出从城市 1 到城市 n 的所有可能路径中,综合政府补贴后的最低运输成本。同时能够检测并适当处理负权回路的存在。

城市 1 到城市 n 之间可能会出现没有路径的情况

输入描述

第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。 

接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。

输出描述

如果没有发现负权回路,则输出一个整数,表示从城市 1 到城市 n 的最低运输成本(包括政府补贴)。如果该整数是负数,则表示实现了盈利。如果发现了负权回路的存在,则输出 "circle"。如果从城市 1 无法到达城市 n,则输出 "unconnected"。

输入示例
4 4
1 2 -1
2 3 1
3 1 -1 
3 4 1
输出示例
circle
提示信息

路径中存在负权回路,从 1 -> 2 -> 3 -> 1,总权值为 -1,理论上货物运输商可以在该回路无限循环赚取政府补贴,所以输出 "circle" 表示已经检测出了该种情况。

数据范围:

1 <= n <= 1000;
1 <= m <= 10000;

-100 <= v <= 100;

import java.util.*;

public class Main {
    public static void main(String[] args) {
        int n, m;
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        m = in.nextInt();
        List<Node>[] edges = new List[n + 1];
        while (m-- > 0) {
            int from = in.nextInt();
            int to = in.nextInt();
            int val = in.nextInt();
            if (edges[from] == null) {
                edges[from] = new LinkedList<>();
            }
            edges[from].add(new Node(to, val));
        }
        int[] minDest = new int[n + 1];
        int[] count = new int[n + 1];
        Arrays.fill(minDest, Integer.MAX_VALUE);
        Queue<Integer> queue = new LinkedList();
        minDest[1] = 0;
        queue.add(1);
        count[1]++;
        while (!queue.isEmpty()) {
            Integer poll = queue.poll();
            //更新minDest和队列
            List<Node> edge = edges[poll];
            if (edge == null) {
                continue;
            }
            for (Node temp : edge) {
                if (minDest[poll] != Integer.MAX_VALUE && minDest[poll] + temp.getVal() < minDest[temp.getTo()]) {
                    minDest[temp.getTo()] = minDest[poll] + temp.getVal();
                    queue.add(temp.getTo());
                    count[temp.getTo()]++;
                    if(count[temp.getTo()]>=n){//如果加入队列次数超过 n-1次 就说明该图与负权回路
                        System.out.println("circle");
                        return;
                    }
                }
            }
        }
        String result = minDest[n] != Integer.MAX_VALUE ? minDest[n] + "" : "unconnected";
        System.out.println(result);

    }
}

class Node {
    int to;
    int val;

    public Node(int to, int val) {
        this.to = to;
        this.val = val;
    }

    public int getTo() {
        return to;
    }

    public int getVal() {
        return val;
    }
}

bellman_ford之单源有限最短路

题目:

96. 城市间货物运输 III

题目描述

某国为促进城市间经济交流,决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市,通过道路网络连接,网络中的道路仅允许从某个城市单向通行到另一个城市,不能反向通行。

网络中的道路都有各自的运输成本和政府补贴,道路的权值计算方式为:运输成本 - 政府补贴。权值为正表示扣除了政府补贴后运输货物仍需支付的费用;权值为负则表示政府的补贴超过了支出的运输成本,实际表现为运输过程中还能赚取一定的收益。

请计算在最多经过 k 个城市的条件下,从城市 src 到城市 dst 的最低运输成本。

输入描述

第一行包含两个正整数,第一个正整数 n 表示该国一共有 n 个城市,第二个整数 m 表示这些城市中共有 m 条道路。

接下来为 m 行,每行包括三个整数,s、t 和 v,表示 s 号城市运输货物到达 t 号城市,道路权值为 v。

最后一行包含三个正整数,src、dst、和 k,src 和 dst 为城市编号,从 src 到 dst 经过的城市数量限制。

输出描述

输出一个整数,表示从城市 src 到城市 dst 的最低运输成本,如果无法在给定经过城市数量限制下找到从 src 到 dst 的路径,则输出 "unreachable",表示不存在符合条件的运输方案。

输入示例
6 7
1 2 1
2 4 -3
2 5 2
1 3 5
3 5 1
4 6 4
5 6 -2
2 6 1
输出示例
0
提示信息

从 2 -> 5 -> 6 中转一站,运输成本为 0。 

1 <= n <= 1000; 

1 <= m <= 10000; 

-100 <= v <= 100;

思路:

题目中描述是 最多经过 k 个城市的条件下,而不是一定经过k个城市,也可以经过的城市数量比k小,但要最短的路径

从图中看:从2到6经过一站的最佳路线是 2->5->6,  最低成本为0;

在bellman_ford算法中,节点数量为n,起点1到终点n,最多是 n-1 条边相连。 那么对所有边松弛 n-1 次 就一定能得到 起点到达 终点的最短距离。 

故从src 到dest经过k站,则对所有边松弛k+1次就一定能得到单源有限最短路径

代码版本1:

import java.util.*;

public class Main {

    public static void main(String[] args) {
        int n, l;
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        l = in.nextInt();
        List<Node> edges= new LinkedList<>();
        while (l-- > 0) {
            int x = in.nextInt();
            int y = in.nextInt();
            int val = in.nextInt();
            edges.add(new Node(x,y,val));
        }
        //初始化
        int start = in.nextInt();
        int end = in.nextInt();
        int k=in.nextInt();
        int[] minDest = new int[n + 1];
        Arrays.fill(minDest,Integer.MAX_VALUE);
        minDest[start] = 0;
        //每条边处理n-1次
        for(int i=1;i<=k+1;i++){
            //取出每边
            for(Node node:edges){
                int from=node.getFrom();
                int to=node.getTo();
                int val=node.getVal();
                if(minDest[from]!=Integer.MAX_VALUE&& minDest[from]+val<minDest[to]){
                    minDest[to]=minDest[from]+val;
                }
            }
        }
        String result = minDest[end] == Integer.MAX_VALUE ? "unconnected" : minDest[end] + "";
        System.out.println(result);

    }

}

class Node {
    int from;
    int to;
    int val;

    public int getFrom() {
        return from;
    }

    public void setFrom(int from) {
        this.from = from;
    }

    public int getTo() {


        return to;
    }



    public Node(int from, int to, int val) {
        this.from = from;
        this.to = to;
        this.val = val;
    }

    public void setTo(int to) {
        this.to = to;
    }

    public int getVal() {
        return val;
    }

    public void setVal(int val) {
        this.val = val;
    }
}

版本1的代码没通过 

图中 1->4成本最低路径 1->2->3->1->4 ,最低成本为1

为什么会出现-2的情况?打印minDest数组看看!!! 

注意:如果示例是有负权回路的

对所有边松弛第一次的过程中,大家会发现,不仅仅 与起点一条边相连的节点更新了,所有节点都更新了。

这样就造成了一个情况,即:计算minDist数组的时候,基于了本次松弛的 minDist数值,而不是上一次 松弛时候minDist的数值。
所以在每次计算 minDist 时候,要基于 对所有边上一次松弛的 minDist 数值才行,所以我们要记录上一次松弛的minDist。

代码版本二:

import java.util.*;

public class Main {

    public static void main(String[] args) {
        int n, l;
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        l = in.nextInt();
        List<Node> edges= new LinkedList<>();
        while (l-- > 0) {
            int x = in.nextInt();
            int y = in.nextInt();
            int val = in.nextInt();
            edges.add(new Node(x,y,val));
        }
        //初始化
        int start = in.nextInt();
        int end = in.nextInt();
        int k=in.nextInt();
        int[] minDest = new int[n + 1];
        int[] copy_minDest  = Arrays.copyOf(minDest, minDest.length);
        Arrays.fill(minDest,Integer.MAX_VALUE);
        minDest[start] = 0;
        //每条边处理n-1次
        for(int i=1;i<=k+1;i++){
            copy_minDest=Arrays.copyOf(minDest,minDest.length);
            //取出每边
            for(Node node:edges){
                int from=node.getFrom();
                int to=node.getTo();
                int val=node.getVal();
                 注意使用copy_minDest 来计算 minDist
                if(copy_minDest[from]!=Integer.MAX_VALUE&& copy_minDest[from]+val<minDest[to]){
                    minDest[to]=copy_minDest[from]+val;
                }
            }

        }
        String result = minDest[end] == Integer.MAX_VALUE ? "unreachable" : minDest[end] + "";
        System.out.println(result);

    }

}

class Node {
    int from;
    int to;
    int val;

    public int getFrom() {
        return from;
    }

    public void setFrom(int from) {
        this.from = from;
    }

    public int getTo() {


        return to;
    }



    public Node(int from, int to, int val) {
        this.from = from;
        this.to = to;
        this.val = val;
    }

    public void setTo(int to) {
        this.to = to;
    }

    public int getVal() {
        return val;
    }

    public void setVal(int val) {
        this.val = val;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值