资讯专栏INFORMATION COLUMN

单源点最短路径(Bellman-Ford)原理及js实现

Michael_Lin / 835人阅读

摘要:说明算法运行结束后,会得到从源节点到其它所有节点的最短路径,同时得到每个节点的前驱节点,不能包含负权回路如图但可以包含图,这里所说的负权环路是指环路的权值总和为正或为负图图松弛操作概念松弛操作针对的操作对象是图中的边,对图中任意一条边,

1. 说明

Bellman-Ford算法运行结束后,会得到从源节点 s 到其它所有节点的最短路径,同时得到每个节点的前驱节点,Bellman-Ford不能包含负权回路如图 1.1 但可以包含图 1.2,这里所说的负权环路是指环路的权值总和为正或为负

图 1.1

图 1.2

2. 松弛操作

2.1. 概念

松弛操作针对的操作对象是图中的边,对图中任意一条边e=(u,v),假设在对e进行松弛之前,已经知道从源节点su的最短估计距离u.d,从源点到v的最短估距离v.d,同时边e的权重为w,松弛操作就是更新节点v的最短估计距离v.d = min{v.d, u.d + w}, 由于初始状态是,所有节点的最短估计路径都设为 Infinity 即无穷大,所以在任意时刻,u.dv.d都是存在的

2.2. 举例

初始时,v1,v2,v3,v4四个节点的最短估计路径都为 Infinity ,求解从v1节点到其它所有节点的最短路径距离,所以将v1.d设置为0

图 2.2

对边(v1,v2)进行松弛 有 v1.d = 0,v2.d = Infinity,w(v1,v2) = 1; 所以v2.d被更新为 v2.d = v1.d + w(v1,v2) = 1;

对边(v1,v3)进行松弛 有 v1.d = 0,v3.d = Infinity,w(v1,v3) = 3; 所以v3.d被更新为 v3.d = v1.d + w(v1,v3) = 3;

对边(v2,v4)进行松弛 有 v2.d = 1,v4.d = Infinity,w(v2,v4) = 5; 所以v4.d被更新为 v4.d = v2.d + w(v2,v4) = 6;

对边(v3,v4)进行松弛 有 v3.d = 3,v4.d = 6,w(v3,v4) = 1; 所以v4.d被更新为 v4.d = v3.d + w(v3,v4) = 4;

3. js中如何表示无穷大

在全局使用 Infinity 来表示正无穷大,用 -Infinity 表示负无穷大,同时可以使用 Number.POSITIVE_INFINITY 表示正无穷,用Number.NEGATIVE_INFINITY 表示负无穷,这几个常量都可以与其它类型的数字比较大小,在 Number中还有其它的常量,读者可以在新版的浏览器控制台 执行 console.dir(Number) 去查看

4. 相关数据结构及初始化算法的输入数据
//节点数据结构
function Vertex() {
    if (!(this instanceof Vertex))
        return new Vertex();
    this.id = null; //用来标识节点
    this.data = null; //节点数据
}

//边数据结构
function Edge() {
    if (!(this instanceof Edge))
        return new Edge();
    this.u = null; //边的起点节点
    this.v = null; //边的终点节点
    this.w = null; //边的权重
}

//图
function Graph() {
    if (!(this instanceof Graph))
        return new Graph();
    this.vertices = []; //图的节点集 作为算法的输入数据
    this.edges = []; //图的边集 作为算法的输入数据
    this.refer = new Map(); //节点标识表
}
Graph.prototype = {
    constructor: Graph,
    initVertices: function(vs) {
        for (let id of vs) {
            let v = Vertex();
            v.id = id;
            this.refer.set(id, v);
            this.vertices.push(v);
        }
    },
    initEdges: function(es) {
        for (let r of es) {
            let e = Edge();
            e.u = this.refer.get(r.u);
            e.v = this.refer.get(r.v);
            e.w = r.w;
            this.edges.push(e);
        }
    }
}

var vertices = ["v1", "v2", "v3", "v4"];
var edges = [
    {u:"v1", v:"v2", w:1},
    {u:"v1", v:"v3", w:3},
    {u:"v2", v:"v4", w:5},
    {u:"v3", v:"v4", w:1},
    {u:"v4", v:"v2", w:-3}
];

var g = Graph();
g.initVertices(vertices);
g.initEdges(edges);

5. Bellman-Ford算法

5.1. 算法介绍

BellmanFord算法的原理就是对输入的所有边都进行 |V| - 1次松弛操作,为什么是 |V| - 1次见 5.3.

5.2. 算法的js实现

 function BellmanFord(vertices, edges, source) {
    let distance = new Map(); //用来记录从原节点 source 到某个节点的最短路径估计值
    let predecessor = new Map(); //用来记录某个节点的前驱节点

    // 第一步: 初始化图
    for (let v of vertices) {
        distance.set(v, Infinity); // 初始化最短估计距离 默认无穷大
        predecessor.set(v, null); // 初始化前驱节点 默认为空
    }
    distance.set(source, 0); // 将源节点的最短路径估计距离 初始化为0

    // 第二步: 重复松弛边
    for (let i = 1, len = vertices.length - 1; i < len; i++) {
        for (let e of edges) {
            if (distance.get(e.u) + e.w < distance.get(e.v)) {
                distance.set(e.v, distance.get(e.u) + e.w);
                predecessor.set(e.v, e.u);
            }
        }
    }

    // 第三步: 检查是否有负权回路 第三步必须在第二步后面
    for (let e of edges) {
        if (distance.get(e.u) + e.w < distance.get(e.v))
            return null; //返回null表示包涵负权回路
    }

    return {
        distance: distance,
        predecessor: predecessor
    }
}

5.3. 为什么第二步中的要加最外层循环,并且是 |V| - 1

最外层增加循环且次数为|V| - 1次,原因是对输入的边的顺序是没有限制的,在 2.2.节 中,我们用了四次松弛操作就找到了从节点v1到其它所有节点的最短路径,是因为 2.2.节 中边是按照一定的顺序选取的,开始时选取的是与源节点直接相领的边,接下来选取边的起始节点是已经被松弛过的边连接的终止节点,如果对边的选取顺序为 (v2,v4),(v3,v4),(v1,v2),(v1,v3) 这种情况就需要最外层的循环,并且需要两次,考虑最坏的情况,如图

图 5.3

并且边的选取顺序为(v3,v4),(v2,v3),(v1,v2),这样对于四个节点需要三次最外层的循环,即|V| - 1

在《算法导论》中,有这样的描述:
当进行第 i 次循环时,一定包含边 (v[i-1],v[i]), 这句话的意思时,如果存在从源节点sv的最短路径,那么在第i次循环结束后,节点 v[i-1].d和节点v[i].d一定不为 Infinity ,为一个具体的值

6. 完整代码

输入图为 图 1.2 从 节点v1到其它所有节点的最短路径

//节点数据结构
function Vertex() {
    if (!(this instanceof Vertex))
        return new Vertex();
    this.id = null; //用来标识节点
    this.data = null; //节点数据
}

//边数据结构
function Edge() {
    if (!(this instanceof Edge))
        return new Edge();
    this.u = null; //边的起点节点
    this.v = null; //边的终点节点
    this.w = null; //边的权重
}

//图
function Graph() {
    if (!(this instanceof Graph))
        return new Graph();
    this.vertices = []; //图的节点集
    this.edges = []; //图的边集
    this.refer = new Map(); //节点标识表
}
Graph.prototype = {
    constructor: Graph,
    initVertices: function(vs) {
        for (let id of vs) {
            let v = Vertex();
            v.id = id;
            this.refer.set(id, v);
            this.vertices.push(v);
        }
    },
    initEdges: function(es) {
        for (let r of es) {
            let e = Edge();
            e.u = this.refer.get(r.u);
            e.v = this.refer.get(r.v);
            e.w = r.w;
            this.edges.push(e);
        }
    }
}

function BellmanFord(vertices, edges, source) {
    let distance = new Map(); //用来记录从原节点 source 到某个节点的最短路径估计值
    let predecessor = new Map(); //用来记录某个节点的前驱节点

    // 第一步: 初始化图
    for (let v of vertices) {
        distance.set(v, Infinity); // 初始化最短估计距离 默认无穷大
        predecessor.set(v, null); // 初始化前驱节点 默认为空
    }
    distance.set(source, 0); // 将源节点的最短路径估计距离 初始化为0

    // 第二步: 重复松弛边
    for (let i = 1, len = vertices.length - 1; i < len; i++) {
        for (let e of edges) {
            if (distance.get(e.u) + e.w < distance.get(e.v)) {
                distance.set(e.v, distance.get(e.u) + e.w);
                predecessor.set(e.v, e.u);
            }
        }
    }

    // 第三步: 检查是否有负权回路 第三步必须在第二步后面
    for (let e of edges) {
        if (distance.get(e.u) + e.w < distance.get(e.v))
            return null; //返回null表示包涵负权回路
    }

    return {
        distance: distance,
        predecessor: predecessor
    }
}

var vertices = ["v1", "v2", "v3", "v4"];
var edges = [
    {u:"v1", v:"v2", w:1},
    {u:"v1", v:"v3", w:3},
    {u:"v2", v:"v4", w:5},
    {u:"v3", v:"v4", w:1},
    {u:"v4", v:"v2", w:-3}
];

var g = Graph();
g.initVertices(vertices);
g.initEdges(edges);

var r = BellmanFord(g.vertices, g.edges, g.vertices[0]);
console.log(r);

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90650.html

相关文章

  • JS实现源点短路、动态规划分段图算法

    摘要:最近在上算法课大三,因为自己是写的,不想用去写。在网上百度用实现单源点最短路径动态规划分段图算法这两个算法,发现并没有。。。 最近在上算法课(大三),因为自己是写js+php的,不想用c去写。在网上百度用js实现单源点最短路径、动态规划分段图算法这两个算法,发现并没有。。。于是自己xjb写了下,c里的带指针的结构体按我的理解换成了对象数组,写的不好请各位大牛给点改进的建议。。。 动态规...

    simon_chen 评论0 收藏0
  • 王者编程大赛之五 — 短路

    摘要:由于是从顶点到的最短路径,则有。算法流程根据最短路径的最优子结构性质,提出了以最短路径长度递增,逐次生成最短路径的算法。相关文章王者编程大赛之一王者编程大赛之二蓄水池王者编程大赛之三背包王者编程大赛之四约瑟夫环 首发于 樊浩柏科学院 自如年底就会拥有 50W 间房子,大家知道每间房房子都是需要配置完才能出租给自如客的,整个房租的配置过程是很复杂的,每天都需要大量的物流师傅将家电、家具...

    yuanzhanghu 评论0 收藏0
  • 面试算法实践与国外大厂习题指南

    摘要:面试算法实践与国外大厂习题指南翻译自维护的仓库,包含了在线练习算法概述与大厂习题实战等内容。面试算法实践与国外大厂习题指南在线练习在线面试编程数据结构链表即是由节点组成的线性集合,每个节点可以利用指针指向其他节点。 面试算法实践与国外大厂习题指南 翻译自 Kevin Naughton Jr. 维护的仓库 interviews,包含了在线练习、算法概述与大厂习题实战等内容。笔者发现正好和...

    genedna 评论0 收藏0
  • 算法(第4版) Chapter 4.4 短路

    摘要:相关操作就是判断的不等号符号改反,初始值设为负无穷副本的最短路径即为原图的最长路径。方法是同上面一样构造图,同时会添加负权重边,再将所有边取反,然后求最短路径最短路径存在则可行没有负权重环就是可行的调度。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 谢路云Chapter ...

    leap_frog 评论0 收藏0
  • 网络协议 6 -路由协议

    摘要:动态路由协议基于链路状态路由算法的开放式最短路径优先协议,广泛应用在数据中心的协议。基于距离矢量路由算法的针对网络之间的路由协议,称为外网路由协议,简称每个数据中心都有自己的路由配置。     前面例子中,我们都是在一个局域网内折腾。今天就让我们扩大范围,在多个局域网甚至到广阔的互联网世界中遨游,看看这中间会发生什么。     这个过程中,跨网关访问是我们要了解的第一个内容。 跨网关访...

    Drinkey 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<