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
This commit is contained in:
parent
99fa431fcc
commit
7a2c941024
|
@ -39,6 +39,16 @@
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
<version>${guava.version}</version>
|
<version>${guava.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
<version>${jackson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.platform</groupId>
|
||||||
|
<artifactId>junit-platform-commons</artifactId>
|
||||||
|
<version>${junit.platform.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.assertj</groupId>
|
<groupId>org.assertj</groupId>
|
||||||
|
@ -66,6 +76,8 @@
|
||||||
<commons-codec.version>1.11</commons-codec.version>
|
<commons-codec.version>1.11</commons-codec.version>
|
||||||
<commons-math3.version>3.6.1</commons-math3.version>
|
<commons-math3.version>3.6.1</commons-math3.version>
|
||||||
<guava.version>28.1-jre</guava.version>
|
<guava.version>28.1-jre</guava.version>
|
||||||
|
<jackson.version>2.10.2</jackson.version>
|
||||||
|
<junit.platform.version>1.6.0</junit.platform.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
</project>
|
</project>
|
|
@ -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<Edge> getMST() {
|
||||||
|
return mst;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalWeight() {
|
||||||
|
return totalWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "MST: " + mst.toString() + " | Total Weight: " + totalWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<Edge> iterableTree(int i) {
|
||||||
|
return trees[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<Edge> getAllEdges() {
|
||||||
|
Iterable<Edge> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.baeldung.algorithms.boruvka;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Input {
|
||||||
|
private int nodes;
|
||||||
|
private int edges;
|
||||||
|
private List<E> 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<E> getEdgeList() {
|
||||||
|
return edgeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEdgeList(List<E> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.baeldung.algorithms.boruvka;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
public class Tree implements Iterable<Edge> {
|
||||||
|
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<Edge> iterator() {
|
||||||
|
return new LinkedIterator(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LinkedIterator implements Iterator<Edge> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue