From 2f90ba9cfb1434179d3e9cefe7d868111d3e0d4a Mon Sep 17 00:00:00 2001 From: Mauro Bringolf Date: Tue, 15 Aug 2017 14:37:35 +0200 Subject: [PATCH] Raw implementation and test case from wikipedia --- src/graphs/spanning-trees/kruskal.js | 81 ++++++++++++++++++++++ test/graphs/spanning-trees/kruskal.spec.js | 53 ++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 src/graphs/spanning-trees/kruskal.js create mode 100644 test/graphs/spanning-trees/kruskal.spec.js diff --git a/src/graphs/spanning-trees/kruskal.js b/src/graphs/spanning-trees/kruskal.js new file mode 100644 index 00000000..20e031d3 --- /dev/null +++ b/src/graphs/spanning-trees/kruskal.js @@ -0,0 +1,81 @@ +// Kruskal's algorithm for minimal spanning tree implemented with the UnionFind datastructure. + +(function(exports) { + 'use strict'; + + var QuickUnion = require('../../sets/quickunion').QuickUnion; + var mergeSort = require('../../sorting/mergesort').mergeSort; + exports.Vertex = require('../../data-structures/vertex').Vertex; + exports.Edge = require('../../data-structures/edge').Edge; + + exports.Graph = function (edges) { + this.edges = edges || []; + } + + exports.Graph.prototype.kruskal = (function () { + var qunion; + var spanningTree; + var indexes; + + /** + * Used for sorting the edges + * + * @private + * @param {Vertex} a First operand of the comparison. + * @param {Vertex} b Second operand of the comparison. + * @return {number} Number which which is equal, greater or + * less then zero and indicates whether the first vertex is + * "smaller" than the second. + */ + function compareEdges(a, b) { + return a.distance - b.distance; + } + + /** + * Initialize the algorithm. + * + * @private + */ + function init() { + var edge; + var i = 0; + + mergeSort(this.edges, compareEdges); + spanningTree = []; + indexes = {}; + + // Create links from vertices to QuickUnion elements + for (edge of this.edges) { + if (!(edge.from.id in indexes)) { + indexes[edge.from.id] = i; + i += 1; + } + if (!(edge.to.id in indexes)) { + indexes[edge.to.id] = i; + i += 1; + } + } + + qunion = new QuickUnion(i); + } + + return function () { + init.call(this); + + var edge; + + for (edge of this.edges) { + var from = indexes[edge.from.id]; + var to = indexes[edge.to.id]; + if (!qunion.connected(from, to)) { + qunion.union(from, to); + spanningTree.push(edge); + } + } + + return new exports.Graph(spanningTree); + } + + })(); + +})(typeof window === 'undefined' ? module.exports : window); diff --git a/test/graphs/spanning-trees/kruskal.spec.js b/test/graphs/spanning-trees/kruskal.spec.js new file mode 100644 index 00000000..f3094b95 --- /dev/null +++ b/test/graphs/spanning-trees/kruskal.spec.js @@ -0,0 +1,53 @@ +var kruskal = require('../../../src/graphs/spanning-trees/kruskal'); + +describe('Kruskal', function() { + 'use strict'; + + it('should define a function', function () { + expect(kruskal).toBeDefined(); + expect(typeof kruskal).toBe('object'); + expect(typeof kruskal.Graph).toBe('function'); + expect(typeof kruskal.Edge).toBe('function'); + expect(typeof kruskal.Vertex).toBe('function'); + }); + + it('should work with an empty graph', function() { + var graph = new kruskal.Graph([], 0); + var spanningTree = graph.kruskal(); + + expect(spanningTree.edges.length).toEqual(0); + }); + + it('should correctly compute general example', function() { + var nodes = []; + var edges = []; + var i; + for (i = 0; i < 7; i += 1) { + nodes[i] = new kruskal.Vertex(i); + } + + edges.push(new kruskal.Edge(nodes[0], nodes[1], 7)); + edges.push(new kruskal.Edge(nodes[1], nodes[2], 8)); + edges.push(new kruskal.Edge(nodes[2], nodes[4], 5)); + edges.push(new kruskal.Edge(nodes[4], nodes[6], 9)); + edges.push(new kruskal.Edge(nodes[5], nodes[6], 11)); + edges.push(new kruskal.Edge(nodes[3], nodes[5], 6)); + edges.push(new kruskal.Edge(nodes[0], nodes[3], 5)); + edges.push(new kruskal.Edge(nodes[1], nodes[4], 7)); + edges.push(new kruskal.Edge(nodes[1], nodes[3], 9)); + edges.push(new kruskal.Edge(nodes[3], nodes[4], 15)); + edges.push(new kruskal.Edge(nodes[4], nodes[5], 8)); + + var graph = new kruskal.Graph(edges); + var spanningTree = graph.kruskal(); + + expect(spanningTree.edges.length).toEqual(6); + + var sum = spanningTree.edges.reduce(function(acc, edge) { + return acc += edge.distance; + }, 0); + + expect(sum).toEqual(39); + + }) +});