單源點最短路徑(Bellman-Ford)原理及js實現

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都是存在的this

  • 2.2. 舉例

初始時,v1,v2,v3,v4四個節點的最短估計路徑都爲 Infinity ,求解從v1節點到其它全部節點的最短路徑距離,因此將v1.d設置爲0spa

圖 2.2 prototype

圖片描述

  • 對邊(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) 去查看3d

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.code

  • 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.3blog

圖片描述

而且邊的選取順序爲(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);
相關文章
相關標籤/搜索