設圖G(V,E)
連通,則
生成樹:包含圖G(V,E)
中的全部節點,及|V|-1
條邊的連通圖,一個圖的生成樹能夠有多顆
最小生成樹:最小權重生成樹,在生成樹的概念上加一個限制條件,即生成樹的全部邊的權值總和最小的樹,最小生成樹也能夠有多顆算法
因爲最小生成樹包含圖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是這樣一條邊函數
Kruskal
和Prim
算法是最小生成樹經常使用的兩種算法,這兩種算法都是對上述通用方法的細化,不一樣之處就是對邊v的尋找方法上有所差別,Kruskal
算法又叫作(邊擴展)算法,適用於邊稀疏的圖,Prim
算法叫作(節點擴展算法),適用於邊稠密的圖測試
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; } })();
Find(x)
找到並返回x節點所在的那顆樹的根節點,用於判斷兩個節點是否在同一顆樹中,便是否相交this
function Find(x) { if (x.parent != x) x.parent = Find(x.parent); return x.parent; }
Union(u, v)
Union
函數旨在合併兩個節點,應該將這裏的合併和在圖G中的連通區分開,咱們經過不斷調用union
來改變MakeSet
集合中元素的連通性,被合併的兩個節點會變成一顆數,固然讀者也能夠實現本身的Union
,隨意實現都行,只有調用Union
操做以後改變了MakeSet
,中圖的連通性,是的u
,v
節點處於同一顆樹就行,本文的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; } }
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; }
這裏的數據結構及如何建圖參照 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算法輸入的圖
測試的算法的輸入圖爲上圖,紅色的邊爲最終最小生成樹包含的邊,出現順序依次爲 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);