算法-贝尔曼-福特算法

算法-贝尔曼-福特算法

注:该文是本博主记录学习之用,没有太多详细的讲解,敬请谅解!

一、简介

贝尔曼-福特算法(Bellman–Ford algorithm )用于计算出起点到各个节点的最短距离,支持存在负权重的情况
  • 它的原理是对图进行最多V-1次松弛操作,得到所有可能的最短路径。其优于狄克斯特拉算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达O(VE)。但算法可以进行若干种优化,提高了效率。

  • 贝尔曼-福特算法每次对所有的边进行松弛,每次松弛都会得到一条最短路径,所以总共需要要做的松弛操作是V - 1次。在完成这么多次松弛后如果还是可以松弛的话,那么就意味着,其中包含负环。

二、场景

  • 从起点出发是否存在到达各个节点的路径。
  • 从起点出发到达各个节点最短路径。
  • 是否存在负环路。

三、实现思路

  • 初始化时将起点s到各个顶点v的距离dist(s->v)赋值为∞,dist(s->s)赋值为0

  • 后续进行最多n-1次遍历操作,对所有的边进行松弛操作,假设:

    所谓的松弛,以边ab为例,若dist(a)代表起点s到达a点所需要花费的总数,
    dist(b)代表起点s到达b点所需要花费的总数,weight(ab)代表边ab的权重, 若存在:

    (dist(a) +weight(ab)) < dist(b)

    则说明存在到b的更短的路径,s->…->a->b,更新b点的总花费为(dist(a) +weight(ab)),父节点为a

  • 遍历都结束后,若再进行一次遍历,还能得到s到某些节点更短的路径的话,则说明存在负环路

四、java实现

如下图,求出A到各节点的最短路径

在这里插入图片描述

public class BellmanFord {

    /**
     * 实现思路:
     * 1、初始化时将起点起点到各个顶点的距离赋值为(无穷大)∞,当前起点距离赋值为0
     * 2、后续进行最多n-1次遍历操作
     * @param args
     */
    public static void main(String[] args){

        //创建图
        Edge ab = new Edge("A", "B", -1);
        Edge ac = new Edge("A", "C", 4);
        Edge bc = new Edge("B", "C", 3);
        Edge be = new Edge("B", "E", 2);
        Edge ed = new Edge("E", "D", -3);
        Edge dc = new Edge("D", "C", 5);
        Edge bd = new Edge("B", "D", 2);
        Edge db = new Edge("D", "B", 1);

        //需要按图中的步骤步数顺序建立数组,否则就是另外一幅图了,
        //从起点A出发,步骤少的排前面
        Edge[] edges = new Edge[] {ab,ac,bc,be,bd,ed,dc,db};

        //存放到各个节点所需要消耗的时间
        HashMap<String,Integer> costMap = new HashMap<String,Integer>();
        //到各个节点对应的父节点
        HashMap<String,String> parentMap = new HashMap<String,String>();


        //初始化各个节点所消费的,当然也可以再遍历的时候判断下是否为Null
        //i=0的时候
        costMap.put("A", 0); //源点
        costMap.put("B", Integer.MAX_VALUE);
        costMap.put("C", Integer.MAX_VALUE);
        costMap.put("D", Integer.MAX_VALUE);
        costMap.put("E", Integer.MAX_VALUE);

        //进行节点数n-1次循环
        for(int i =1; i< costMap.size();i++) {
            boolean hasChange = false;
            for(int j =0; j< edges.length;j++) {
                Edge edge = edges[j];
                //该边起点目前总的路径大小
                int startPointCost = costMap.get(edge.getStartPoint()) == null ? 0:costMap.get(edge.getStartPoint());
                //该边终点目前总的路径大小
                int endPointCost = costMap.get(edge.getEndPoint()) == null ? Integer.MAX_VALUE : costMap.get(edge.getEndPoint());
                //如果该边终点目前的路径大小 > 该边起点的路径大小 + 该边权重 ,说明有更短的路径了
                if(endPointCost > (startPointCost + edge.getWeight())) {
                    costMap.put(edge.getEndPoint(), startPointCost + edge.getWeight());
                    parentMap.put(edge.getEndPoint(), edge.getStartPoint());
                    hasChange = true;
                }
            }
            if (!hasChange) {
                //经常还没达到最大遍历次数便已经求出解了,此时可以优化为提前退出循环
                break;
            }
        }

        //在进行一次判断是否存在负环路
        boolean hasRing = false;
        for(int j =0; j< edges.length;j++) {
            Edge edge = edges[j];
            int startPointCost = costMap.get(edge.getStartPoint()) == null ? 0:costMap.get(edge.getStartPoint());
            int endPointCost = costMap.get(edge.getEndPoint()) == null ? Integer.MAX_VALUE : costMap.get(edge.getEndPoint());
            if(endPointCost > (startPointCost + edge.getWeight())) {
                System.out.print("\n图中存在负环路,无法求解\n");
                hasRing = true;
                break;
            }
        }

        if(!hasRing) {
            //打印出到各个节点的最短路径
            for(String key : costMap.keySet()) {
                System.out.print("\n到目标节点"+key+"最低耗费:"+costMap.get(key));
                if(parentMap.containsKey(key)) {
                    List<String> pathList = new ArrayList<String>();
                    String parentKey = parentMap.get(key);
                    while (parentKey!=null) {
                        pathList.add(0, parentKey);
                        parentKey = parentMap.get(parentKey);
                    }
                    pathList.add(key);
                    String path="";
                    for(String k:pathList) {
                        path = path.equals("") ? path : path + " --> ";
                        path = path +  k ;
                    }
                    System.out.print(",路线为"+path);
                }
            }
        }


    }



    /**
     *  代表"一条边"的信息对象
     *
     * @author Administrator
     *
     */
    static class Edge{
        //起点id
        private String startPoint;
        //结束点id
        private String endPoint;
        //该边的权重
        private int weight;
        public Edge(String startPoint,String endPoint,int weight) {
            this.startPoint = startPoint;
            this.endPoint = endPoint;
            this.weight = weight;
        }
        public String getStartPoint() {
            return startPoint;
        }

        public String getEndPoint() {
            return endPoint;
        }

        public int getWeight() {
            return weight;
        }
    }

}

五、小结

1、贝尔曼-福特算法Bellman–Ford主要用于存在负权重的方向图中(没有负权重也可以用,但是效率比狄克斯特拉算法低很多),搜索出源点到各个节点的最短路径

2、Bellman–Ford可以判断出图是否存在负环路,但存在负环路的情况下不支持计算出各个节点的最短路径。只需要在结束(节点数目-1)次遍历后,再执行一次遍历,若还可以更新数据则说明存在负环路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值