最小生成樹原理及Kruskal算法的js實現

1. 生成樹和最小生成樹的概念

設圖G(V,E)連通,則
生成樹:包含圖G(V,E)中的全部節點,及|V|-1條邊的連通圖,一個圖的生成樹能夠有多顆
最小生成樹:最小權重生成樹,在生成樹的概念上加一個限制條件,即生成樹的全部邊的權值總和最小的樹,最小生成樹也能夠有多顆算法

2. 求解最小生成樹的通用方法

因爲最小生成樹包含圖G的全部邊,因此咱們須要作的只是尋找最小生成樹的邊集Asegmentfault

設:邊集A是圖G的任意一顆最小生成樹的邊的子集,初始時A爲空當A不等於G的某個最小生成樹的全部邊集M時循環如下步驟 找到一條屬於M但不屬於A的邊,加入到A中數組

如今問題咱們如何去尋找那條只屬於M但不屬於A的邊數據結構

邊v的尋找方法
當A爲空時,圖G(V,A)是一個有|V|個樹的森林,當A中有n條邊時,n<|V|-1,圖G是一個有|V|-(n+1)個樹的森林,咱們須要尋找的邊v的加入會致使圖G中的森林數目減1邊v是這樣一條邊函數

  • 邊v的兩端的節點屬於兩顆不一樣的樹
  • 邊v的權值是全部知足以上條件中權值最小的

3. Kruskal和Prim 算法

KruskalPrim 算法是最小生成樹經常使用的兩種算法,這兩種算法都是對上述通用方法的細化,不一樣之處就是對邊v的尋找方法上有所差別,Kruskal算法又叫作(邊擴展)算法,適用於邊稀疏的圖,Prim算法叫作(節點擴展算法),適用於邊稠密的圖測試

4. Kruskal算法

  • 4.1. 概念
    Kruskal算法的特色是上述A中的邊能夠屬於多顆不一樣的樹
  • 4.2. 輔助函數 MakeSet(x)

MakeSet操做建立一個包含|V|顆樹的集合,每顆樹只包含一個節點,咱們要爲每一個節點x添加兩個屬性ui

var MakeSet = (function(){
    let set = new Set();
    return function(x) {
        x.parent = x;
        x.rank = 0;
        if(!set.has(x)) set.add(x);
        return set;
    }
})();
  • 4.3. 輔助函數 Find(x)

找到並返回x節點所在的那顆樹的根節點,用於判斷兩個節點是否在同一顆樹中,便是否相交this

function Find(x) {
    if (x.parent != x)
        x.parent = Find(x.parent);
    return x.parent;
}
  • 4.4. 輔助函數 Union(u, v)

Union函數旨在合併兩個節點,應該將這裏的合併和在圖G中的連通區分開,咱們經過不斷調用union來改變MakeSet集合中元素的連通性,被合併的兩個節點會變成一顆數,固然讀者也能夠實現本身的Union,隨意實現都行,只有調用Union操做以後改變了MakeSet,中圖的連通性,是的uv節點處於同一顆樹就行,本文的Union方法採用的思想是 按秩合併(秩 rank)、路徑壓縮 ,經過這種方式建立的樹的節點分佈,會比較均勻,平衡性較高,也就致使操做效率很高spa

function Union(u, v) {
    let uRoot = Find(u);
    let vRoot = Find(v);
    // 若是 u 和 v 在同一顆樹
    if (uRoot == vRoot) return;
    // 若是 u 和 v 不在同一顆樹中,合併它們
    // 若是 uRoot 的層級比 vRoot 的小,將 uRoot 做爲 vRoot 前驅節點
    if (uRoot.rank < vRoot.rank) uRoot.parent = vRoot;
    // 若是 uRoot 的層級比 vRoot 的大,將 vRoot 做爲 uRoot 前驅節點
    else if (uRoot.rank > vRoot.rank) vRoot.parent = uRoot;
    //將 uRoot 設置爲根節點,並將 uRoot 的層級加一
    else {
        vRoot.parent = uRoot;
        uRoot.rank = uRoot.rank + 1;
    }
}
  • 4.5. Kruskal算法

Kruskal算法旨在尋找最小生成數中包含哪些邊,在後面的完整代碼中,該函數的實現會有所不一樣,這裏着重體會原理prototype

function Kruskal(G, w) {
    let A = []; //A用於存放最小生成數所包含的邊
    for(let x of G.V) {
        MakeSet(x);
    }
    //對G.E按照邊的權中從小到大排序
    for(let e of G.E) {
        quickSort(0, G.E.length-1, G.E, 'w');
    }
    //因爲邊已經按照從小到大的順序有序,因此這裏只須要尋找不相交的邊(邊所在的樹不相交),
    for(let e of G.E) {
        if(Find(e.u)!=Find(e.v)) {
            A.push(e);
            Union(e.u, e.v); //改變連通性
        }
    }
    return A;
}
  • 4.6. 圖,頂點,邊,的數據結構

這裏的數據結構及如何建圖參照 BFS,DFS 算法原理及js實現,這裏不作詳細說明

//頂點數據結構
function Vertex() {
    if (!(this instanceof Vertex))
        return new Vertex();
    this.edges = null; //由頂點發出的全部邊
    this.id = null; //節點的惟一標識
    this.data = null; //存放節點的數據
}

//數據結構 鄰接鏈表-邊
function Edge() {
    if (!(this instanceof Edge))
        return new Edge();
    this.index = null; //邊所依附的節點的位置
    this.sibling = null;
    this.w = null; //保存邊的權值
}

//數據結構 圖-G
function Graph() {
    if (!(this instanceof Graph))
        return new Graph();
    this.V = []; //節點集
    this.E = []; //邊集
    this.refer = new Map(); //字典 用來映射標節點的識符和數組中的位置
}
Graph.prototype = {
    constructor: Graph,
    //這裏加進來的已經具有了邊的關係
    //建立圖的 節點
    initVertex: function(vertexs) {
        //建立節點並初始化節點屬性 id
        for (let v of vertexs) {
            let vertex = Vertex();
            vertex.id = v.id;
            this.V.push(vertex);
        }
        //初始化 字典
        for (let i in this.V) {
            this.refer.set(this.V[i].id, i);
        }
    },
    //創建圖中 邊 的關係
    initEdge: (function() {
        //建立鏈表,返回鏈表的第一個節點
        function createLink(index, len, edges, refer) {
            if (index >= len) return null;
            let edgeNode = Edge();
            edgeNode.index = refer.get(edges[index].id); //邊鏈接的節點 用在數組中的位置表示 參照字典
            edgeNode.w = edges[index].w; //邊的權值
            edgeNode.sibling = createLink(++index, len, edges, refer); //經過遞歸實現 回溯
            return edgeNode;
        }
        return function(edges) {
            for (let field in edges) {
                let index = this.refer.get(field); //從字典表中找出節點在 V 中的位置
                let vertex = this.V[index]; //獲取節點
                vertex.edges = createLink(0, edges[field].length, edges[field], this.refer);
            }
        }
    }()),
    storageEdge: function(edges) {
        this.E = edges;
    }
}

var vertexs = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}];
var edges = [
    {u:'a',v:'b',w:3},
    {u:'a',v:'c',w:1},
    {u:'b',v:'a',w:3},
    {u:'b',v:'c',w:4},
    {u:'b',v:'d',w:5},
    {u:'c',v:'a',w:1},
    {u:'c',v:'b',w:4},
    {u:'c',v:'d',w:6},
    {u:'c',v:'e',w:7},
    {u:'d',v:'b',w:5},
    {u:'d',v:'c',w:6},
    {u:'d',v:'e',w:2},
    {u:'e',v:'c',w:7},
    {u:'e',v:'d',w:6}
]

var g = Graph();
g.initVertex(vertexs);
g.storageEdge(edges);

運行這部分代碼,生成了用於Kruskal算法輸入的圖

  • 4.7. 完整代碼及測試

圖片描述

測試的算法的輸入圖爲上圖,紅色的邊爲最終最小生成樹包含的邊,出現順序依次爲 ac,de,ab,bd,這裏的輸入圖爲無向圖

//快速排序 數組a由對象組成 key爲排序的參照指標 quickSort(0,a.length-1,a,'key')
function quickSort(left, right, a, key) {
    if (left > right)
        return;
    var i = left;
    var j = right;
    var benchMark = a[i];
    var temp;
    while (i != j) {
        //移動 j
        while (a[j][key] >= benchMark[key] && i < j)
            j--;
        //移動 i
        while (a[i][key] <= benchMark[key] && i < j)
            i++;
        if (i < j) {
            temp = a[i];
            a[i] = a[j];
            a[j] = temp;
        }
    }

    a[left] = a[i];
    a[i] = benchMark;
    quickSort(left, i - 1, a, key);
    quickSort(i + 1, right, a, key);
}

var MakeSet = (function() {
    let set = new Set();
    return function(x) {
        x.parent = x;
        x.rank = 0;
        if (!set.has(x)) set.add(x);
        return set;
    }
})();

//體會兩個 Find 方法的不一樣
// function Find(x) {
//     if (x.parent != x)
//         Find(x.parent);
//     return x.parent;
// }

function Find(x) {
    if (x.parent != x)
        x.parent = Find(x.parent);
    return x.parent;
}

function Union(u, v) {
    let uRoot = Find(u);
    let vRoot = Find(v);
    // 若是 u 和 v 在同一顆樹
    if (uRoot == vRoot) return;
    // 若是 u 和 v 不在同一顆樹中,合併它們
    // 若是 uRoot 的層級比 vRoot 的小,將 uRoot 做爲 vRoot 前驅節點
    if (uRoot.rank < vRoot.rank) uRoot.parent = vRoot;
    // 若是 uRoot 的層級比 vRoot 的大,將 vRoot 做爲 uRoot 前驅節點
    else if (uRoot.rank > vRoot.rank) vRoot.parent = uRoot;
    //任選一個做爲根節點
    else {
        vRoot.parent = uRoot;
        uRoot.rank = uRoot.rank + 1;
    }
}

function Kruskal(G) {
    let A = []; //A用於存放最小生成數所包含的邊
    for(let x of G.V) {
        MakeSet(x);
    }
    //對G.E按照邊的權中從小到大排序
    for(let e of G.E) {
        quickSort(0, G.E.length-1, G.E, 'w');
    }
    for(let e of G.E) {
        let u = G.V[G.refer.get(e.u)];
        let v = G.V[G.refer.get(e.v)];
        if(Find(u)!=Find(v)) {
            A.push(e);
            Union(u, v);
        }
    }
    return A;
}

function Vertex() {
    if (!(this instanceof Vertex))
        return new Vertex();
    this.edges = null; //由頂點發出的全部邊
    this.id = null; //節點的惟一標識
    this.data = null; //存放節點的數據
}

//數據結構 鄰接鏈表-邊
function Edge() {
    if (!(this instanceof Edge))
        return new Edge();
    this.index = null; //邊所依附的節點的位置
    this.sibling = null;
    this.w = null; //保存邊的權值
}

//數據結構 圖-G
function Graph() {
    if (!(this instanceof Graph))
        return new Graph();
    this.V = []; //節點集
    this.E = [];
    this.refer = new Map(); //字典 用來映射標節點的識符和數組中的位置
}
Graph.prototype = {
    constructor: Graph,
    //這裏加進來的已經具有了邊的關係
    //建立圖的 節點
    initVertex: function(vertexs) {
        //建立節點並初始化節點屬性 id
        for (let v of vertexs) {
            let vertex = Vertex();
            vertex.id = v.id;
            this.V.push(vertex);
        }
        //初始化 字典
        for (let i in this.V) {
            this.refer.set(this.V[i].id, i);
        }
    },
    //創建圖中 邊 的關係
    initEdge: (function() {
        //建立鏈表,返回鏈表的第一個節點
        function createLink(index, len, edges, refer) {
            if (index >= len) return null;
            let edgeNode = Edge();
            edgeNode.index = refer.get(edges[index].id); //邊鏈接的節點 用在數組中的位置表示 參照字典
            edgeNode.w = edges[index].w; //邊的權值
            edgeNode.sibling = createLink(++index, len, edges, refer); //經過遞歸實現 回溯
            return edgeNode;
        }
        return function(edges) {
            for (let field in edges) {
                let index = this.refer.get(field); //從字典表中找出節點在 V 中的位置
                let vertex = this.V[index]; //獲取節點
                vertex.edges = createLink(0, edges[field].length, edges[field], this.refer);
            }
        }
    }()),
    storageEdge: function(edges) {
        this.E = edges;
    }
}

//測試數據
var vertexs = [{id:'a'}, {id:'b'}, {id:'c'}, {id:'d'}, {id:'e'}];
var edges = [
    {u:'a',v:'b',w:3},
    {u:'a',v:'c',w:1},
    {u:'b',v:'a',w:3},
    {u:'b',v:'c',w:4},
    {u:'b',v:'d',w:5},
    {u:'c',v:'a',w:1},
    {u:'c',v:'b',w:4},
    {u:'c',v:'d',w:6},
    {u:'c',v:'e',w:7},
    {u:'d',v:'b',w:5},
    {u:'d',v:'c',w:6},
    {u:'d',v:'e',w:2},
    {u:'e',v:'c',w:7},
    {u:'e',v:'d',w:6}
]

var g = Graph();
g.initVertex(vertexs);
g.storageEdge(edges);
var A = Kruskal(g);
console.log(A);
相關文章
相關標籤/搜索