From fc53c7de5da94336027201f16ff38f5fd9fa1c68 Mon Sep 17 00:00:00 2001 From: Oleksii Trekhleb Date: Mon, 7 May 2018 13:53:13 +0300 Subject: [PATCH] Add Kruskal. --- README.md | 5 +- src/algorithms/graph/kruskal/README.md | 49 ++++++++++ .../graph/kruskal/__test__/kruskal.test.js | 91 +++++++++++++++++++ src/algorithms/graph/kruskal/kruskal.js | 62 +++++++++++++ 4 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 src/algorithms/graph/kruskal/README.md create mode 100644 src/algorithms/graph/kruskal/__test__/kruskal.test.js create mode 100644 src/algorithms/graph/kruskal/kruskal.js diff --git a/README.md b/README.md index 1ca21bd6..73e23a13 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ * [Bellman-Ford Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/bellman-ford) - finding shortest path to all graph vertices * [Detect Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/detect-cycle) - for both directed and undirected graphs (DFS and Disjoint Set based versions) * [Prim’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph - * Kruskal’s Algorithm - finding Minimum Spanning Tree (MST) + * [Kruskal’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/kruskal) - finding Minimum Spanning Tree (MST) for weighted undirected graph * Topological Sorting * Eulerian path, Eulerian circuit * Strongly Connected Component algorithm @@ -85,7 +85,8 @@ * **Greedy** * [Unbound Knapsack Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/knapsack-problem) * [Dijkstra Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/dijkstra) - finding shortest path to all graph vertices - * [Prim’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) + * [Prim’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/prim) - finding Minimum Spanning Tree (MST) for weighted undirected graph + * [Kruskal’s Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/kruskal) - finding Minimum Spanning Tree (MST) for weighted undirected graph * **Divide and Conquer** * [Euclidean Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/math/euclidean-algorithm) - calculate the Greatest Common Divisor (GCD) * [Permutations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/permutations) (with and without repetitions) diff --git a/src/algorithms/graph/kruskal/README.md b/src/algorithms/graph/kruskal/README.md new file mode 100644 index 00000000..5ed128e7 --- /dev/null +++ b/src/algorithms/graph/kruskal/README.md @@ -0,0 +1,49 @@ +# Kruskal's Algorithm + +Kruskal's algorithm is a minimum-spanning-tree algorithm which +finds an edge of the least possible weight that connects any two +trees in the forest. It is a greedy algorithm in graph theory +as it finds a minimum spanning tree for a connected weighted +graph adding increasing cost arcs at each step. This means it +finds a subset of the edges that forms a tree that includes every +vertex, where the total weight of all the edges in the tree is +minimized. If the graph is not connected, then it finds a +minimum spanning forest (a minimum spanning tree for each +connected component). + +![Kruskal Algorithm](https://upload.wikimedia.org/wikipedia/commons/5/5c/MST_kruskal_en.gif) + +![Kruskal Demo](https://upload.wikimedia.org/wikipedia/commons/b/bb/KruskalDemo.gif) + +A demo for Kruskal's algorithm based on Euclidean distance. + +## Minimum Spanning Tree + +A **minimum spanning tree** (MST) or minimum weight spanning tree +is a subset of the edges of a connected, edge-weighted +(un)directed graph that connects all the vertices together, +without any cycles and with the minimum possible total edge +weight. That is, it is a spanning tree whose sum of edge weights +is as small as possible. More generally, any edge-weighted +undirected graph (not necessarily connected) has a minimum +spanning forest, which is a union of the minimum spanning +trees for its connected components. + +![Minimum Spanning Tree](https://upload.wikimedia.org/wikipedia/commons/d/d2/Minimum_spanning_tree.svg) + +A planar graph and its minimum spanning tree. Each edge is +labeled with its weight, which here is roughly proportional +to its length. + +![Minimum Spanning Tree](https://upload.wikimedia.org/wikipedia/commons/c/c9/Multiple_minimum_spanning_trees.svg) + +This figure shows there may be more than one minimum spanning +tree in a graph. In the figure, the two trees below the graph +are two possibilities of minimum spanning tree of the given graph. + +## References + +- [Minimum Spanning Tree on Wikipedia](https://en.wikipedia.org/wiki/Minimum_spanning_tree) +- [Kruskal's Algorithm on Wikipedia](https://en.wikipedia.org/wiki/Kruskal%27s_algorithm) +- [Kruskal's Algorithm on YouTube by Tushar Roy](https://www.youtube.com/watch?v=fAuF0EuZVCk) +- [Kruskal's Algorithm on YouTube by Michael Sambol](https://www.youtube.com/watch?v=71UQH7Pr9kU) diff --git a/src/algorithms/graph/kruskal/__test__/kruskal.test.js b/src/algorithms/graph/kruskal/__test__/kruskal.test.js new file mode 100644 index 00000000..da71d137 --- /dev/null +++ b/src/algorithms/graph/kruskal/__test__/kruskal.test.js @@ -0,0 +1,91 @@ +import GraphVertex from '../../../../data-structures/graph/GraphVertex'; +import GraphEdge from '../../../../data-structures/graph/GraphEdge'; +import Graph from '../../../../data-structures/graph/Graph'; +import kruskal from '../kruskal'; + +describe('kruskal', () => { + it('should fire an error for directed graph', () => { + function applyPrimToDirectedGraph() { + const graph = new Graph(true); + + kruskal(graph); + } + + expect(applyPrimToDirectedGraph).toThrowError(); + }); + + it('should find minimum spanning tree', () => { + const vertexA = new GraphVertex('A'); + const vertexB = new GraphVertex('B'); + const vertexC = new GraphVertex('C'); + const vertexD = new GraphVertex('D'); + const vertexE = new GraphVertex('E'); + const vertexF = new GraphVertex('F'); + const vertexG = new GraphVertex('G'); + + const edgeAB = new GraphEdge(vertexA, vertexB, 2); + const edgeAD = new GraphEdge(vertexA, vertexD, 3); + const edgeAC = new GraphEdge(vertexA, vertexC, 3); + const edgeBC = new GraphEdge(vertexB, vertexC, 4); + const edgeBE = new GraphEdge(vertexB, vertexE, 3); + const edgeDF = new GraphEdge(vertexD, vertexF, 7); + const edgeEC = new GraphEdge(vertexE, vertexC, 1); + const edgeEF = new GraphEdge(vertexE, vertexF, 8); + const edgeFG = new GraphEdge(vertexF, vertexG, 9); + const edgeFC = new GraphEdge(vertexF, vertexC, 6); + + const graph = new Graph(); + + graph + .addEdge(edgeAB) + .addEdge(edgeAD) + .addEdge(edgeAC) + .addEdge(edgeBC) + .addEdge(edgeBE) + .addEdge(edgeDF) + .addEdge(edgeEC) + .addEdge(edgeEF) + .addEdge(edgeFC) + .addEdge(edgeFG); + + expect(graph.getWeight()).toEqual(46); + + const minimumSpanningTree = kruskal(graph); + + expect(minimumSpanningTree.getWeight()).toBe(24); + expect(minimumSpanningTree.getAllVertices().length).toBe(graph.getAllVertices().length); + expect(minimumSpanningTree.getAllEdges().length).toBe(graph.getAllVertices().length - 1); + expect(minimumSpanningTree.toString()).toBe('E,C,A,B,D,F,G'); + }); + + it('should find minimum spanning tree for simple graph', () => { + const vertexA = new GraphVertex('A'); + const vertexB = new GraphVertex('B'); + const vertexC = new GraphVertex('C'); + const vertexD = new GraphVertex('D'); + + const edgeAB = new GraphEdge(vertexA, vertexB, 1); + const edgeAD = new GraphEdge(vertexA, vertexD, 3); + const edgeBC = new GraphEdge(vertexB, vertexC, 1); + const edgeBD = new GraphEdge(vertexB, vertexD, 3); + const edgeCD = new GraphEdge(vertexC, vertexD, 1); + + const graph = new Graph(); + + graph + .addEdge(edgeAB) + .addEdge(edgeAD) + .addEdge(edgeBC) + .addEdge(edgeBD) + .addEdge(edgeCD); + + expect(graph.getWeight()).toEqual(9); + + const minimumSpanningTree = kruskal(graph); + + expect(minimumSpanningTree.getWeight()).toBe(3); + expect(minimumSpanningTree.getAllVertices().length).toBe(graph.getAllVertices().length); + expect(minimumSpanningTree.getAllEdges().length).toBe(graph.getAllVertices().length - 1); + expect(minimumSpanningTree.toString()).toBe('A,B,C,D'); + }); +}); diff --git a/src/algorithms/graph/kruskal/kruskal.js b/src/algorithms/graph/kruskal/kruskal.js new file mode 100644 index 00000000..3f0ac44f --- /dev/null +++ b/src/algorithms/graph/kruskal/kruskal.js @@ -0,0 +1,62 @@ +import Graph from '../../../data-structures/graph/Graph'; +import QuickSort from '../../sorting/quick-sort/QuickSort'; +import DisjointSet from '../../../data-structures/disjoint-set/DisjointSet'; + +/** + * @param {Graph} graph + * @return {Graph} + */ +export default function kruskal(graph) { + // It should fire error if graph is directed since the algorithm works only + // for undirected graphs. + if (graph.isDirected) { + throw new Error('Prim\'s algorithms works only for undirected graphs'); + } + + // Init new graph that will contain minimum spanning tree of original graph. + const minimumSpanningTree = new Graph(); + + // Sort all graph edges in increasing order. + const sortingCallbacks = { + /** + * @param {GraphEdge} graphEdgeA + * @param {GraphEdge} graphEdgeB + */ + compareCallback: (graphEdgeA, graphEdgeB) => { + if (graphEdgeA.weight === graphEdgeB.weight) { + return 1; + } + + return graphEdgeA.weight <= graphEdgeB.weight ? -1 : 1; + }, + }; + const sortedEdges = new QuickSort(sortingCallbacks).sort(graph.getAllEdges()); + + // Create disjoint sets for all graph vertices. + const keyCallback = graphVertex => graphVertex.getKey(); + const disjointSet = new DisjointSet(keyCallback); + + graph.getAllVertices().forEach((graphVertex) => { + disjointSet.makeSet(graphVertex); + }); + + // Go through all edges started from the minimum one and try to add them + // to minimum spanning tree. The criteria of adding the edge would be whether + // it is forms the cycle or not (if it connects two vertices from one disjoint + // set or not). + for (let edgeIndex = 0; edgeIndex < sortedEdges.length; edgeIndex += 1) { + /** @var {GraphEdge} currentEdge */ + const currentEdge = sortedEdges[edgeIndex]; + + // Check if edge forms the cycle. If it does then skip it. + if (!disjointSet.inSameSet(currentEdge.startVertex, currentEdge.endVertex)) { + // Unite two subsets into one. + disjointSet.union(currentEdge.startVertex, currentEdge.endVertex); + + // Add this edge to spanning tree. + minimumSpanningTree.addEdge(currentEdge); + } + } + + return minimumSpanningTree; +}