BAEL-3883: Boruvka's minimum spanning tree, moved code to newer module (#8949)

algorithms-miscellaneous-6
This commit is contained in:
Sampada 2020-03-24 08:37:48 +05:30 committed by GitHub
parent 1062b4fef6
commit 060557608c
12 changed files with 172 additions and 396 deletions

View File

@ -41,11 +41,6 @@
<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>
@ -78,7 +73,6 @@
<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>

View File

@ -1,61 +0,0 @@
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;
}
}

View File

@ -1,49 +0,0 @@
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);
}
}

View File

@ -1,40 +0,0 @@
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);
}
}

View File

@ -1,64 +0,0 @@
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;
}
}

View File

@ -1,65 +0,0 @@
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;
}
}
}

View File

@ -1,63 +0,0 @@
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;
}
}
}

View File

@ -1,48 +0,0 @@
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());
}
}

View File

@ -11,5 +11,17 @@
<artifactId>parent-modules</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>
<properties>
<guava.version>28.1-jre</guava.version>
</properties>
</project>

View File

@ -0,0 +1,84 @@
package com.baeldung.algorithms.boruvka;
import com.google.common.graph.EndpointPair;
import com.google.common.graph.MutableValueGraph;
import com.google.common.graph.ValueGraphBuilder;
public class BoruvkaMST {
private static MutableValueGraph<Integer, Integer> mst = ValueGraphBuilder.undirected()
.build();
private static int totalWeight;
public BoruvkaMST(MutableValueGraph<Integer, Integer> graph) {
int size = graph.nodes().size();
UnionFind uf = new UnionFind(size);
// repeat at most log N times or until we have N-1 edges
for (int t = 1; t < size && mst.edges().size() < size - 1; t = t + t) {
EndpointPair<Integer>[] closestEdgeArray = new EndpointPair[size];
// foreach tree in graph, find closest edge
for (EndpointPair<Integer> edge : graph.edges()) {
int u = edge.nodeU();
int v = edge.nodeV();
int uParent = uf.find(u);
int vParent = uf.find(v);
if (uParent == vParent) {
continue; // same tree
}
int weight = graph.edgeValueOrDefault(u, v, 0);
if (closestEdgeArray[uParent] == null) {
closestEdgeArray[uParent] = edge;
}
if (closestEdgeArray[vParent] == null) {
closestEdgeArray[vParent] = edge;
}
int uParentWeight = graph.edgeValueOrDefault(closestEdgeArray[uParent].nodeU(), closestEdgeArray[uParent].nodeV(), 0);
int vParentWeight = graph.edgeValueOrDefault(closestEdgeArray[vParent].nodeU(), closestEdgeArray[vParent].nodeV(), 0);
if (weight < uParentWeight) {
closestEdgeArray[uParent] = edge;
}
if (weight < vParentWeight) {
closestEdgeArray[vParent] = edge;
}
}
// add newly discovered edges to MST
for (int i = 0; i < size; i++) {
EndpointPair<Integer> edge = closestEdgeArray[i];
if (edge != null) {
int u = edge.nodeU();
int v = edge.nodeV();
int weight = graph.edgeValueOrDefault(u, v, 0);
// don't add the same edge twice
if (uf.find(u) != uf.find(v)) {
mst.putEdgeValue(u, v, weight);
totalWeight += weight;
uf.union(u, v);
}
}
}
}
}
public MutableValueGraph<Integer, Integer> getMST() {
return mst;
}
public int getTotalWeight() {
return totalWeight;
}
public String toString() {
return "MST: " + mst.toString() + " | Total Weight: " + totalWeight;
}
}

View File

@ -0,0 +1,39 @@
package com.baeldung.algorithms.boruvka;
public class UnionFind {
private int[] parents;
private int[] ranks;
public UnionFind(int n) {
parents = new int[n];
ranks = new int[n];
for (int i = 0; i < n; i++) {
parents[i] = i;
ranks[i] = 0;
}
}
public int find(int u) {
while (u != parents[u]) {
u = parents[u];
}
return u;
}
public void union(int u, int v) {
int uParent = find(u);
int vParent = find(v);
if (uParent == vParent) {
return;
}
if (ranks[uParent] < ranks[vParent]) {
parents[uParent] = vParent;
} else if (ranks[uParent] > ranks[vParent]) {
parents[vParent] = uParent;
} else {
parents[vParent] = uParent;
ranks[uParent]++;
}
}
}

View File

@ -0,0 +1,37 @@
package com.baeldung.algorithms.boruvka;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.Before;
import org.junit.Test;
import com.google.common.graph.MutableValueGraph;
import com.google.common.graph.ValueGraphBuilder;
public class BoruvkaUnitTest {
private MutableValueGraph<Integer, Integer> graph;
@Before
public void setup() {
graph = ValueGraphBuilder.undirected()
.build();
graph.putEdgeValue(0, 1, 8);
graph.putEdgeValue(0, 2, 5);
graph.putEdgeValue(1, 2, 9);
graph.putEdgeValue(1, 3, 11);
graph.putEdgeValue(2, 3, 15);
graph.putEdgeValue(2, 4, 10);
graph.putEdgeValue(3, 4, 7);
}
@Test
public void givenInputGraph_whenBoruvkaPerformed_thenMinimumSpanningTree() {
BoruvkaMST boruvkaMST = new BoruvkaMST(graph);
MutableValueGraph<Integer, Integer> mst = boruvkaMST.getMST();
assertEquals(30, boruvkaMST.getTotalWeight());
assertEquals(4, mst.edges().size());
}
}