Dijkstra 算法原理及js實現

1. 用鏈表表示帶權圖

圖片描述

  • 1.1 對鏈表頭節點的描述
/** 節點 */
function V(data) {
    if (!(this instanceof V)) {
        return new V(data);
    }
    this._id = uuid(10, 10); //隨機生成的 uuid
    this.data = data; //頭節點的數據
    this.children = null; //指向鏈表的其它節點
    this.index = null; //節點在數組中的位置(數組下標)
    this.d = Infinity; //從源節點到當前節點的最短距離的估計指(初始位無窮大)
}
  • 1.2 對鏈表的其它節點的描述
function E() {
    if (!(this instanceof E)) {
        return new E();
    }
    this.next = null; //邊指向下一個 E 類型的jie'dian
    this.index = null; // 邊指向的節點在 Adj 中的下標 例如邊 (u,v) 則 index 指向v
    this.w = null; //邊的權重
}
  • 1.3 圖
function G() {
    if (!(this instanceof G)) {
        return new G();
    }
    this.Adj = []; //構成圖 G 的鄰接鏈表
}

G.prototype = {
    constructor: G,
    // 添加邊
    addEdge: function (u, v, w) {
        let e = E();
        e.index = v.index;
        e.w = w;
        let next = u.children;
        if (next == null) {
            u.children = e;
        } else {
            while (true) {
                if (next.next == null) {
                    next.next = e;
                    break;
                } else {
                    next = next.next;
                }
            }
        }
    },
    //獲取節點 u 的全部鄰接節點
    adj: function (u) {
        let next = u.children;
        let list = [];
        while (true) {
            if (next == null) {
                break;
            } else {
                list.push(this.Adj[next.index]);
                next = next.next;
            }
        }
        return list;
    },
    // 權重函數
    w: function (u, v) {
        let next = u.children;
        while (next != null) {
            if (this.Adj[next.index]._id == v._id) {
                return next.w;
            }
            next = next.next;
        }
    },
    // 添加節點
    addVertex: function (v) {
        if (v.index != null) {
            return;
        }
        let index = this.Adj.push(v) - 1; //該節點在數組中的位置
        v.index = index;
    },
    //鬆弛
    relax: function (u, v) {
        if (v.d > u.d + this.w(u, v)) {
            v.d = u.d + this.w(u, v);
            return true;
        }
        return false;
    }
}
  • 1.4 簡單的 uuid
/** 生成一個簡單的uuid len 表示長度, sys 表示進制 能夠位 2, 8, 16 等等*/
function uuid(len, sys) {
    let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
    let uuid = [], i;
    sys = sys || chars.length;

    if (len) {
        for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * sys];
    } else {
        let r;
        uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
        uuid[14] = '4';
        for (i = 0; i < 36; i++) {
            if (!uuid[i]) {
                r = 0 | Math.random() * 16;
                uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
            }
        }
    }

    return uuid.join('');
}

2. Dijkstra 算法

Dijkstra 算法的思想是將圖中的全部節點劃分到兩個集合中,集合Q中存放的是最短路徑未肯定的節點,集合S中存放的是已經肯定最短路徑距離的節點,初始時,全部的節點都在Q中,源節點s的最短路徑距離初始時爲0,集合Q內部經過最小優先隊列來組織節點關係javascript

  1. 從最小優先隊列Q中取出最短估計路徑值最小的節點u
  2. 對u的全部鄰接節點進行鬆弛操做,並更新Q中u的鄰接節點的最短估計路徑值
  3. 重複執行步驟1直至Q爲空集
  • 2.1 鬆弛操做

鬆弛操做是對於節點 v,若是從源節點到v的最短估計路徑距離爲 v.dv.d爲無窮大或爲某一具體數,u也是從源節點可達的節點,而且邊(u, v)存在且距離是w,若是
v.d > u.d + w(u, v), 則 v.d = u.d + w(u, v)java

  • 2.2 最小優先隊列

最小優先隊列參照 二叉堆、堆排序、優先隊列、topK問題詳解及js實現 這裏直接給出實現代碼算法

function MinBinaryHeap(key) {
    if (!(this instanceof MinBinaryHeap))
        return new MinBinaryHeap(key);
    this.key = key; //key表示用來排序的字段
    this.size = 0; //堆大小 這裏堆大小和數組大小一致
    this.list = []; //用於存放堆元素 存放的是對象
}
MinBinaryHeap.prototype = {
    constructor: MinBinaryHeap,
    //獲取某個節點的父節點
    parent: function (i) {
        let p = Math.floor((i - 1) / 2);
        if (i > this.size - 1 || p < 0) return null;
        return p; //這裏返回的 p 是在數組中的下標,數組是從0開始的
    },
    //獲取某個節點的左孩子
    left: function (i) {
        let l = 2 * i + 1;
        if (l > this.size - 1) return null;
        return l;
    },
    //獲取某個節點的右孩子
    right: function (i) {
        let r = 2 * i + 2;
        if (r > this.size - 1) return null;
        return r;
    },
    minHeapify: function (i) {
        let list = this.list;
        let key = this.key;
        let l = this.left(i);
        let r = this.right(i);
        let smallest = null;
        if (l != null) { //左孩子爲空則右孩子必定爲空
            if (r == null) smallest = l;
            else smallest = list[l][key] < list[r][key] ? l : r;
            if (list[i][key] <= list[smallest][key]) return;
            else {
                let t = list[i];
                list[i] = list[smallest];
                list[smallest] = t;
                this.minHeapify(smallest);
            }
        }
    },
    //元素上浮 對下標爲i的元素進行向上調整,使堆保持其性質
    increase: function (i) {
        let list = this.list;
        let p = this.parent(i);
        while (i > 0 && list[p][this.key] > list[i][this.key]) { //i > 0 必定能保證 p != null
            let t = list[i];
            list[i] = list[p];
            list[p] = t;
            i = this.parent(i);
            p = this.parent(i);
        }
    },
    //構建堆
    buildHeap: function (a) {
        this.list = a;
        this.size = a.length;
        for (let i = Math.floor(a.length / 2) - 1; i > -1; i--) {
            this.minHeapify(i);
        }
    },
    //堆排序 由大到小
    heapSort: function (a) {
        this.buildHeap(a);
        for (let i = this.size - 1; i > 0; i--) {
            let t = this.list[0];
            this.list[0] = this.list[i];
            this.list[i] = t;
            this.size--;
            this.minHeapify(0);
        }
        return this.list;
    },
    //更新排序
    fresh: function() {
        this.size = this.list.length;
        for (let i = Math.floor(this.size / 2) - 1; i > -1; i--) {
            this.minHeapify(i);
        }
    }
}

//最小優先隊列
function MinPriorityQueue(key, a) {
    if (!(this instanceof MinPriorityQueue)) {
        return new MinPriorityQueue(key, a);
    }
    if (a == null) {
        a = [];
    }
    this.minBinaryHeap = MinBinaryHeap(key);
    this.minBinaryHeap.buildHeap(a);
    this.key = key;
}
MinPriorityQueue.prototype = {
    constructor: MinPriorityQueue,
    insert: function (x) { //加入一個元素
        this.minBinaryHeap.size++;
        this.minBinaryHeap.list[this.minBinaryHeap.size - 1] = x;
        //向上調整
        this.minBinaryHeap.increase(this.minBinaryHeap.size - 1);
    },
    //remove 表示獲取後是否刪除 true 刪除 false 不刪除
    min: function (remove) { //獲取最小元素
        let min = this.minBinaryHeap.list[0];
        if (remove) this.removeMin();
        return min;
    },
    removeMin: function () { //移除最小元素
        let list = this.minBinaryHeap.list;
        let size = this.minBinaryHeap.size;
        let min = list[0];
        list[0] = list[size - 1];
        list.shift(size - 1); //刪除
        this.minBinaryHeap.size--;
        this.minBinaryHeap.minHeapify(0);
        return min;
    },
    update: function (i, x) { //更新元素
        this.minBinaryHeap.list[i] = x;
        this.minBinaryHeap.minHeapify(i);
        this.minBinaryHeap.increase(i);
    },
    fresh: function() {
        this.minBinaryHeap.fresh();
    }
}
  • 2.3 Dijkstra算法
function dijkstra(g, s) {
    let minPriorityQueue = MinPriorityQueue('d', null);
    s.d = 0; //初始s的最短路徑距離
    g.Adj.forEach(v => {
        minPriorityQueue.insert(v);
    });
    let u = minPriorityQueue.removeMin(); //取出 d 值最小的節點
    while (u != null) {
        let adj = g.adj(u); //獲取節點u的全部鄰接節點
        adj.forEach(v => { //對u的全部鄰接節點進行鬆弛操做
            let isRelax = g.relax(u, v);
            if(isRelax) {
                minPriorityQueue.fresh(); //刷新最小優先隊列
            }
        });
        u = minPriorityQueue.removeMin();
    }
}

3. 測試及總體實現

圖片描述

  • 3.1 測試代碼
let g = G();
let v1 = V('v1');
let v2 = V('v2');
let v3 = V('v3');
let v4 = V('v4');
let v5 = V('v5');
g.addVertex(v1);
g.addVertex(v2);
g.addVertex(v3);
g.addVertex(v4);
g.addVertex(v5);
g.addEdge(v1, v2, 10);
g.addEdge(v1, v3, 5);
g.addEdge(v2, v4, 1);
g.addEdge(v2, v3, 2);
g.addEdge(v3, v4, 9);
g.addEdge(v3, v5, 2);
g.addEdge(v3, v2, 3);
g.addEdge(v4, v5, 4);
g.addEdge(v5, v4, 6);
g.addEdge(v5, v1, 7);

let a = dijkstra(g, v1);
console.log(g);
  • 3.2 總體代碼及實現
function G() {
    if (!(this instanceof G)) {
        return new G();
    }
    this.Adj = [];
}

G.prototype = {
    constructor: G,
    // 添加邊
    addEdge: function (u, v, w) {
        let e = E();
        e.index = v.index;
        e.w = w;
        let next = u.children;
        if (next == null) {
            u.children = e;
        } else {
            while (true) {
                if (next.next == null) {
                    next.next = e;
                    break;
                } else {
                    next = next.next;
                }
            }
        }
    },
    adj: function (u) {
        let next = u.children;
        let list = [];
        while (true) {
            if (next == null) {
                break;
            } else {
                list.push(this.Adj[next.index]);
                next = next.next;
            }
        }
        return list;
    },
    // 權重函數
    w: function (u, v) {
        let next = u.children;
        while (next != null) {
            if (this.Adj[next.index]._id == v._id) {
                return next.w;
            }
            next = next.next;
        }
    },
    // 添加節點
    addVertex: function (v) {
        if (v.index != null) {
            return;
        }
        let index = this.Adj.push(v) - 1; //該節點在數組中的位置
        v.index = index;
    },
    //鬆弛
    relax: function (u, v) {
        if (v.d > u.d + this.w(u, v)) {
            v.d = u.d + this.w(u, v);
            return true;
        }
        return false;
    }
}

/** 節點 */
function V(data) {
    if (!(this instanceof V)) {
        return new V(data);
    }
    this._id = uuid(10, 10);
    this.data = data;
    this.children = null;
    this.index = null; //節點在數組中的位置
    this.d = Infinity;
}

/** 邊 */
function E() {
    if (!(this instanceof E)) {
        return new E();
    }
    this.next = null;
    this.index = null; // 邊指向的節點 在Adj中的下標 例如邊(u,v) 則 index 指向v
    this.w = null;
}

/** 生成一個簡單的uuid */
function uuid(len, sys) {
    let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
    let uuid = [], i;
    sys = sys || chars.length;

    if (len) {
        for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * sys];
    } else {
        let r;
        uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
        uuid[14] = '4';
        for (i = 0; i < 36; i++) {
            if (!uuid[i]) {
                r = 0 | Math.random() * 16;
                uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
            }
        }
    }

    return uuid.join('');
}

function MinBinaryHeap(key) {
    if (!(this instanceof MinBinaryHeap))
        return new MinBinaryHeap(key);
    this.key = key; //key表示用來排序的字段
    this.size = 0; //堆大小 這裏堆大小和數組大小一致
    this.list = []; //用於存放堆元素 存放的是對象
}
MinBinaryHeap.prototype = {
    constructor: MinBinaryHeap,
    //獲取某個節點的父節點
    parent: function (i) {
        let p = Math.floor((i - 1) / 2);
        if (i > this.size - 1 || p < 0) return null;
        return p; //這裏返回的 p 是在數組中的下標,數組是從0開始的
    },
    //獲取某個節點的左孩子
    left: function (i) {
        let l = 2 * i + 1;
        if (l > this.size - 1) return null;
        return l;
    },
    //獲取某個節點的右孩子
    right: function (i) {
        let r = 2 * i + 2;
        if (r > this.size - 1) return null;
        return r;
    },
    minHeapify: function (i) {
        let list = this.list;
        let key = this.key;
        let l = this.left(i);
        let r = this.right(i);
        let smallest = null;
        if (l != null) { //左孩子爲空則右孩子必定爲空
            if (r == null) smallest = l;
            else smallest = list[l][key] < list[r][key] ? l : r;
            if (list[i][key] <= list[smallest][key]) return;
            else {
                let t = list[i];
                list[i] = list[smallest];
                list[smallest] = t;
                this.minHeapify(smallest);
            }
        }
    },
    //元素上浮 對下標爲i的元素進行向上調整,使堆保持其性質
    increase: function (i) {
        let list = this.list;
        let p = this.parent(i);
        while (i > 0 && list[p][this.key] > list[i][this.key]) { //i > 0 必定能保證 p != null
            let t = list[i];
            list[i] = list[p];
            list[p] = t;
            i = this.parent(i);
            p = this.parent(i);
        }
    },
    //構建堆
    buildHeap: function (a) {
        this.list = a;
        this.size = a.length;
        for (let i = Math.floor(a.length / 2) - 1; i > -1; i--) {
            this.minHeapify(i);
        }
    },
    //堆排序 由大到小
    heapSort: function (a) {
        this.buildHeap(a);
        for (let i = this.size - 1; i > 0; i--) {
            let t = this.list[0];
            this.list[0] = this.list[i];
            this.list[i] = t;
            this.size--;
            this.minHeapify(0);
        }
        return this.list;
    },
    //更新排序
    fresh: function() {
        this.size = this.list.length;
        for (let i = Math.floor(this.size / 2) - 1; i > -1; i--) {
            this.minHeapify(i);
        }
    }
}

//最小優先隊列
function MinPriorityQueue(key, a) {
    if (!(this instanceof MinPriorityQueue)) {
        return new MinPriorityQueue(key, a);
    }
    if (a == null) {
        a = [];
    }
    this.minBinaryHeap = MinBinaryHeap(key);
    this.minBinaryHeap.buildHeap(a);
    this.key = key;
}
MinPriorityQueue.prototype = {
    constructor: MinPriorityQueue,
    insert: function (x) { //加入一個元素
        this.minBinaryHeap.size++;
        this.minBinaryHeap.list[this.minBinaryHeap.size - 1] = x;
        //向上調整
        this.minBinaryHeap.increase(this.minBinaryHeap.size - 1);
    },
    //remove 表示獲取後是否刪除 true 刪除 false 不刪除
    min: function (remove) { //獲取最小元素
        let min = this.minBinaryHeap.list[0];
        if (remove) this.removeMin();
        return min;
    },
    removeMin: function () { //移除最小元素
        let list = this.minBinaryHeap.list;
        let size = this.minBinaryHeap.size;
        let min = list[0];
        list[0] = list[size - 1];
        list.shift(size - 1); //刪除
        this.minBinaryHeap.size--;
        this.minBinaryHeap.minHeapify(0);
        return min;
    },
    update: function (i, x) { //更新元素
        this.minBinaryHeap.list[i] = x;
        this.minBinaryHeap.minHeapify(i);
        this.minBinaryHeap.increase(i);
    },
    fresh: function() {
        this.minBinaryHeap.fresh();
    }
}

function dijkstra(g, s) {
    let minPriorityQueue = MinPriorityQueue('d', null);
    s.d = 0;
    g.Adj.forEach(v => {
        minPriorityQueue.insert(v);
    });
    let u = minPriorityQueue.removeMin();
    while (u != null) {
        let adj = g.adj(u);
        adj.forEach(v => {
            let isRelax = g.relax(u, v);
            if(isRelax) {
                minPriorityQueue.fresh();
            }
        });
        u = minPriorityQueue.removeMin();
    }
}

let g = G();
let v1 = V('v1');
let v2 = V('v2');
let v3 = V('v3');
let v4 = V('v4');
let v5 = V('v5');
g.addVertex(v1);
g.addVertex(v2);
g.addVertex(v3);
g.addVertex(v4);
g.addVertex(v5);
g.addEdge(v1, v2, 10);
g.addEdge(v1, v3, 5);
g.addEdge(v2, v4, 1);
g.addEdge(v2, v3, 2);
g.addEdge(v3, v4, 9);
g.addEdge(v3, v5, 2);
g.addEdge(v3, v2, 3);
g.addEdge(v4, v5, 4);
g.addEdge(v5, v4, 6);
g.addEdge(v5, v1, 7);

let a = dijkstra(g, v1);
console.log(g);
  • 3.3 運行結果

將以上代碼複製粘貼到瀏覽器控制檯運行後結果爲
圖片描述segmentfault

相關文章
相關標籤/搜索