From 7a2c9410249efb879bc37f28eb0b373713be1d1b Mon Sep 17 00:00:00 2001
From: Sampada <46674082+sampada07@users.noreply.github.com>
Date: Fri, 6 Mar 2020 21:19:00 +0530
Subject: [PATCH] BAEL-3883: Boruvka's Algorithm for Minimum-Spanning Trees
(#8816)
* BAEL-3883: Boruvka's Algorithm for Minimum-Spanning Trees
* BAEL-3883: checking in pom.xml
---
algorithms-miscellaneous-5/pom.xml | 12 ++++
.../algorithms/boruvka/BoruvkaMST.java | 61 +++++++++++++++++
.../algorithms/boruvka/DisjointSet.java | 49 ++++++++++++++
.../com/baeldung/algorithms/boruvka/Edge.java | 40 ++++++++++++
.../baeldung/algorithms/boruvka/Graph.java | 64 ++++++++++++++++++
.../baeldung/algorithms/boruvka/Input.java | 65 +++++++++++++++++++
.../com/baeldung/algorithms/boruvka/Tree.java | 63 ++++++++++++++++++
.../algorithms/boruvka/BoruvkaUnitTest.java | 48 ++++++++++++++
.../src/test/resources/input.json | 41 ++++++++++++
9 files changed, 443 insertions(+)
create mode 100644 algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/BoruvkaMST.java
create mode 100644 algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/DisjointSet.java
create mode 100644 algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Edge.java
create mode 100644 algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Graph.java
create mode 100644 algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Input.java
create mode 100644 algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Tree.java
create mode 100644 algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/boruvka/BoruvkaUnitTest.java
create mode 100644 algorithms-miscellaneous-5/src/test/resources/input.json
diff --git a/algorithms-miscellaneous-5/pom.xml b/algorithms-miscellaneous-5/pom.xml
index 4f9cc8b711..2799c39971 100644
--- a/algorithms-miscellaneous-5/pom.xml
+++ b/algorithms-miscellaneous-5/pom.xml
@@ -39,6 +39,16 @@
guava
${guava.version}
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+
+
+ org.junit.platform
+ junit-platform-commons
+ ${junit.platform.version}
+
org.assertj
@@ -66,6 +76,8 @@
1.11
3.6.1
28.1-jre
+ 2.10.2
+ 1.6.0
\ No newline at end of file
diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/BoruvkaMST.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/BoruvkaMST.java
new file mode 100644
index 0000000000..0f8bc5b296
--- /dev/null
+++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/BoruvkaMST.java
@@ -0,0 +1,61 @@
+package com.baeldung.algorithms.boruvka;
+
+public class BoruvkaMST {
+
+ private static Tree mst = new Tree();
+ private static int totalWeight;
+
+ public BoruvkaMST(Graph graph) {
+ DisjointSet dSet = new DisjointSet(graph.getNodes());
+
+ // repeat at most log N times or until we have N-1 edges
+ for (int t = 1; t < graph.getNodes() && mst.getEdgeCount() < graph.getNodes() - 1; t = t + t) {
+
+ // foreach tree in forest, find closest edge
+ Edge[] closestEdgeArray = new Edge[graph.getNodes()];
+ for (Edge edge : graph.getAllEdges()) {
+ int first = edge.getFirst();
+ int second = edge.getSecond();
+ int firstParent = dSet.getParent(first);
+ int secondParent = dSet.getParent(second);
+ if (firstParent == secondParent) {
+ continue; // same tree
+ }
+ if (closestEdgeArray[firstParent] == null || edge.getWeight() < closestEdgeArray[firstParent].getWeight()) {
+ closestEdgeArray[firstParent] = edge;
+ }
+ if (closestEdgeArray[secondParent] == null || edge.getWeight() < closestEdgeArray[secondParent].getWeight()) {
+ closestEdgeArray[secondParent] = edge;
+ }
+ }
+
+ // add newly discovered edges to MST
+ for (int i = 0; i < graph.getNodes(); i++) {
+ Edge edge = closestEdgeArray[i];
+ if (edge != null) {
+ int first = edge.getFirst();
+ int second = edge.getSecond();
+ // don't add the same edge twice
+ if (dSet.getParent(first) != dSet.getParent(second)) {
+ mst.addEdge(edge);
+ totalWeight += edge.getWeight();
+ dSet.union(first, second);
+ }
+ }
+ }
+ }
+ }
+
+ public Iterable getMST() {
+ return mst;
+ }
+
+ public int getTotalWeight() {
+ return totalWeight;
+ }
+
+ public String toString() {
+ return "MST: " + mst.toString() + " | Total Weight: " + totalWeight;
+ }
+
+}
diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/DisjointSet.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/DisjointSet.java
new file mode 100644
index 0000000000..7769686e36
--- /dev/null
+++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/DisjointSet.java
@@ -0,0 +1,49 @@
+package com.baeldung.algorithms.boruvka;
+
+import java.util.Arrays;
+
+public class DisjointSet {
+
+ private int[] nodeParents;
+ private int[] nodeRanks;
+
+ public DisjointSet(int n) {
+ nodeParents = new int[n];
+ nodeRanks = new int[n];
+ for (int i = 0; i < n; i++) {
+ nodeParents[i] = i;
+ nodeRanks[i] = 0;
+ }
+ }
+
+ public int getParent(int node) {
+ while (node != nodeParents[node]) {
+ node = nodeParents[node];
+ }
+ return node;
+ }
+
+ public void union(int node1, int node2) {
+ int node1Parent = getParent(node1);
+ int node2Parent = getParent(node2);
+ if (node1Parent == node2Parent) {
+ return;
+ }
+
+ if (nodeRanks[node1Parent] < nodeRanks[node2Parent]) {
+ nodeParents[node1Parent] = node2Parent;
+ }
+ else if (nodeRanks[node1Parent] > nodeRanks[node2Parent]) {
+ nodeParents[node2Parent] = node1Parent;
+ }
+ else {
+ nodeParents[node2Parent] = node1Parent;
+ nodeRanks[node1Parent]++;
+ }
+ }
+
+ public String toString() {
+ return "Parent: " + Arrays.toString(nodeParents) + "Rank: " + Arrays.toString(nodeRanks);
+ }
+
+}
diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Edge.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Edge.java
new file mode 100644
index 0000000000..6ee136fc48
--- /dev/null
+++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Edge.java
@@ -0,0 +1,40 @@
+package com.baeldung.algorithms.boruvka;
+
+public class Edge {
+
+ private final int first;
+ private final int second;
+ private final int weight;
+
+ public Edge(int first, int second, int weight) {
+ this.first = first;
+ this.second = second;
+ this.weight = weight;
+ }
+
+ public double getWeight() {
+ return weight;
+ }
+
+ public int getFirst() {
+ return first;
+ }
+
+ public int getSecond() {
+ return second;
+ }
+
+ public int getOtherNode(int firstNode) {
+ int secondNode = 0;
+ if (firstNode == first)
+ secondNode = second;
+ else if (firstNode == second)
+ secondNode = first;
+ return secondNode;
+ }
+
+ public String toString() {
+ return String.format("%d-%d %d", first, second, weight);
+ }
+
+}
diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Graph.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Graph.java
new file mode 100644
index 0000000000..e899007dfa
--- /dev/null
+++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Graph.java
@@ -0,0 +1,64 @@
+package com.baeldung.algorithms.boruvka;
+
+import java.io.IOException;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+
+public class Graph {
+
+ private int nodes;
+ private int edges;
+ private Tree[] trees;
+
+ public Graph(Input jsonGraph) throws JsonParseException, JsonMappingException, IOException {
+ nodes = jsonGraph.getNodes();
+ trees = (Tree[]) new Tree[nodes];
+ for (int i = 0; i < nodes; i++) {
+ trees[i] = new Tree();
+ }
+
+ int edgesFromInput = jsonGraph.getEdges();
+ for (int i = 0; i < edgesFromInput; i++) {
+ int first = jsonGraph.getEdgeList()
+ .get(i)
+ .getFirst();
+ int second = jsonGraph.getEdgeList()
+ .get(i)
+ .getSecond();
+ int weight = jsonGraph.getEdgeList()
+ .get(i)
+ .getWeight();
+ Edge edge = new Edge(first, second, weight);
+
+ trees[first].addEdge(edge);
+ trees[second].addEdge(edge);
+ edges++;
+ }
+
+ }
+
+ public int getNodes() {
+ return nodes;
+ }
+
+ public int getEdges() {
+ return edges;
+ }
+
+ public Iterable iterableTree(int i) {
+ return trees[i];
+ }
+
+ public Iterable getAllEdges() {
+ Iterable list = new Tree();
+ for (int i = 0; i < nodes; i++) {
+ for (Edge edge : iterableTree(i)) {
+ if (edge.getOtherNode(i) > i) {
+ ((Tree) list).addEdge(edge);
+ }
+ }
+ }
+ return list;
+ }
+}
diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Input.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Input.java
new file mode 100644
index 0000000000..b52720d5e9
--- /dev/null
+++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Input.java
@@ -0,0 +1,65 @@
+package com.baeldung.algorithms.boruvka;
+
+import java.util.List;
+
+public class Input {
+ private int nodes;
+ private int edges;
+ private List edgeList;
+
+ public int getNodes() {
+ return nodes;
+ }
+
+ public void setNodes(int nodes) {
+ this.nodes = nodes;
+ }
+
+ public int getEdges() {
+ return edges;
+ }
+
+ public void setEdges(int edges) {
+ this.edges = edges;
+ }
+
+ public List getEdgeList() {
+ return edgeList;
+ }
+
+ public void setEdgeList(List edgeList) {
+ this.edgeList = edgeList;
+ }
+
+ static class E {
+ private int first;
+ private int second;
+ private int weight;
+
+ public int getFirst() {
+ return first;
+ }
+
+ public void setFirst(int first) {
+ this.first = first;
+ }
+
+ public int getSecond() {
+ return second;
+ }
+
+ public void setSecond(int second) {
+ this.second = second;
+ }
+
+ public int getWeight() {
+ return weight;
+ }
+
+ public void setWeight(int weight) {
+ this.weight = weight;
+ }
+
+ }
+
+}
diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Tree.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Tree.java
new file mode 100644
index 0000000000..cc28233e07
--- /dev/null
+++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/boruvka/Tree.java
@@ -0,0 +1,63 @@
+package com.baeldung.algorithms.boruvka;
+
+import java.util.Iterator;
+
+public class Tree implements Iterable {
+ private Node root;
+ private int edgeCount;
+
+ private static class Node {
+ private Edge edge;
+ private Node next;
+
+ public String toString() {
+ String nextStr = next != null ? next.toString() : "";
+ return edge.toString() + " | " + nextStr;
+ }
+ }
+
+ public Tree() {
+ root = null;
+ edgeCount = 0;
+ }
+
+ public int getEdgeCount() {
+ return edgeCount;
+ }
+
+ public void addEdge(Edge edge) {
+ Node oldRoot = root;
+ root = new Node();
+ root.edge = edge;
+ root.next = oldRoot;
+ edgeCount++;
+ }
+
+ public String toString() {
+ String rootStr = root != null ? root.toString() : "";
+ return "Tree: " + rootStr + "Size: " + edgeCount;
+ }
+
+ public Iterator iterator() {
+ return new LinkedIterator(root);
+ }
+
+ private class LinkedIterator implements Iterator {
+ private Node current;
+
+ public LinkedIterator(Node root) {
+ current = root;
+ }
+
+ public boolean hasNext() {
+ return current != null;
+ }
+
+ public Edge next() {
+ Edge edge = current.edge;
+ current = current.next;
+ return edge;
+ }
+ }
+
+}
diff --git a/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/boruvka/BoruvkaUnitTest.java b/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/boruvka/BoruvkaUnitTest.java
new file mode 100644
index 0000000000..1d03d2d4d9
--- /dev/null
+++ b/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/boruvka/BoruvkaUnitTest.java
@@ -0,0 +1,48 @@
+package com.baeldung.algorithms.boruvka;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class BoruvkaUnitTest {
+
+ private Input input;
+ private static String INPUT_JSON = "/input.json";
+
+ @Before
+ public void convertInputJsonToObject() throws JsonParseException, JsonMappingException, IOException {
+ ObjectMapper mapper = new ObjectMapper();
+ StringBuilder jsonStr = new StringBuilder();
+ try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(BoruvkaMST.class.getResourceAsStream(INPUT_JSON)))) {
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ jsonStr.append(line)
+ .append("\n");
+ }
+ }
+
+ input = mapper.readValue(jsonStr.toString(), Input.class);
+
+ }
+
+ @Test
+ public void givenInputGraph_whenBoruvkaPerformed_thenMinimumSpanningTree() throws JsonParseException, JsonMappingException, IOException {
+ Graph graph = new Graph(input);
+ BoruvkaMST boruvkaMST = new BoruvkaMST(graph);
+
+ Tree mst = (Tree) boruvkaMST.getMST();
+
+ assertEquals(30, boruvkaMST.getTotalWeight());
+ assertEquals(4, mst.getEdgeCount());
+ }
+
+}
diff --git a/algorithms-miscellaneous-5/src/test/resources/input.json b/algorithms-miscellaneous-5/src/test/resources/input.json
new file mode 100644
index 0000000000..0151f83618
--- /dev/null
+++ b/algorithms-miscellaneous-5/src/test/resources/input.json
@@ -0,0 +1,41 @@
+{
+ "nodes": 5,
+ "edges": 7,
+ "edgeList": [
+ {
+ "first": 0,
+ "second": 1,
+ "weight": 8
+ },
+ {
+ "first": 0,
+ "second": 2,
+ "weight": 5
+ },
+ {
+ "first": 1,
+ "second": 2,
+ "weight": 9
+ },
+ {
+ "first": 1,
+ "second": 3,
+ "weight": 11
+ },
+ {
+ "first": 2,
+ "second": 3,
+ "weight": 15
+ },
+ {
+ "first": 2,
+ "second": 4,
+ "weight": 10
+ },
+ {
+ "first": 3,
+ "second": 4,
+ "weight": 7
+ }
+ ]
+}
\ No newline at end of file