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
algorithms-miscellaneous-5
|
@ -39,6 +39,16 @@
|
|||
<artifactId>guava</artifactId>
|
||||
<version>${guava.version}</version>
|
||||
</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>
|
||||
<groupId>org.assertj</groupId>
|
||||
|
@ -66,6 +76,8 @@
|
|||
<commons-codec.version>1.11</commons-codec.version>
|
||||
<commons-math3.version>3.6.1</commons-math3.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>
|
||||
|
||||
</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