diff --git a/algorithms-miscellaneous-5/pom.xml b/algorithms-miscellaneous-5/pom.xml index 2f530958e3..95036da775 100644 --- a/algorithms-miscellaneous-5/pom.xml +++ b/algorithms-miscellaneous-5/pom.xml @@ -34,6 +34,11 @@ tradukisto ${tradukisto.version} + + com.google.guava + guava + 28.1-jre + org.assertj diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/CycleDetector.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/CycleDetector.java new file mode 100644 index 0000000000..dec0bcdd85 --- /dev/null +++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/CycleDetector.java @@ -0,0 +1,72 @@ +package com.baeldung.algorithms.kruskal; + +import java.util.ArrayList; +import java.util.List; + +public class CycleDetector { + + List nodes; + + public CycleDetector(int totalNodes) { + initDisjointSets(totalNodes); + } + + public boolean detectCycle(Integer u, Integer v) { + Integer rootU = pathCompressionFind(u); + Integer rootV = pathCompressionFind(v); + if (rootU.equals(rootV)) { + return true; + } + unionByRank(rootU, rootV); + return false; + } + + private void initDisjointSets(int totalNodes) { + nodes = new ArrayList<>(totalNodes); + for (int i = 0; i < totalNodes; i++) { + nodes.add(new DisjointSetInfo(i)); + } + } + + private Integer find(Integer node) { + Integer parent = nodes.get(node).getParentNode(); + if (parent.equals(node)) { + return node; + } else { + return find(parent); + } + } + + private Integer pathCompressionFind(Integer node) { + DisjointSetInfo setInfo = nodes.get(node); + Integer parent = setInfo.getParentNode(); + if (parent.equals(node)) { + return node; + } else { + Integer parentNode = find(parent); + setInfo.setParentNode(parentNode); + return parentNode; + } + } + + private void union(Integer rootU, Integer rootV) { + DisjointSetInfo setInfoU = nodes.get(rootU); + setInfoU.setParentNode(rootV); + } + + private void unionByRank(int rootU, int rootV) { + DisjointSetInfo setInfoU = nodes.get(rootU); + DisjointSetInfo setInfoV = nodes.get(rootV); + int rankU = setInfoU.getRank(); + int rankV = setInfoV.getRank(); + if (rankU < rankV) { + setInfoU.setParentNode(rootV); + } else { + setInfoV.setParentNode(rootU); + if (rankU == rankV) { + setInfoU.setRank(rankU + 1); + } + } + } + +} diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/DisjointSetInfo.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/DisjointSetInfo.java new file mode 100644 index 0000000000..ecdc42587a --- /dev/null +++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/DisjointSetInfo.java @@ -0,0 +1,28 @@ +package com.baeldung.algorithms.kruskal; + +public class DisjointSetInfo { + + private Integer parentNode; + private int rank; + + DisjointSetInfo(Integer nodeNumber) { + setParentNode(nodeNumber); + setRank(1); + } + + public Integer getParentNode() { + return parentNode; + } + + public void setParentNode(Integer parentNode) { + this.parentNode = parentNode; + } + + public int getRank() { + return rank; + } + + public void setRank(int rank) { + this.rank = rank; + } +} diff --git a/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/Kruskal.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/Kruskal.java new file mode 100644 index 0000000000..da405679d1 --- /dev/null +++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/Kruskal.java @@ -0,0 +1,53 @@ +package com.baeldung.algorithms.kruskal; + +import com.google.common.graph.EndpointPair; +import com.google.common.graph.MutableValueGraph; +import com.google.common.graph.ValueGraph; +import com.google.common.graph.ValueGraphBuilder; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +public class Kruskal { + + public ValueGraph minSpanningTree(ValueGraph graph) { + + return spanningTree(graph, true); + } + + public ValueGraph maxSpanningTree(ValueGraph graph) { + return spanningTree(graph, false); + } + + private ValueGraph spanningTree(ValueGraph graph, boolean minSpanningTree) { + Set> edges = graph.edges(); + List> edgeList = new ArrayList<>(edges); + + if (minSpanningTree) { + edgeList.sort(Comparator.comparing(e -> graph.edgeValue(e).get())); + } else { + edgeList.sort(Collections.reverseOrder(Comparator.comparing(e -> graph.edgeValue(e).get()))); + } + + int totalNodes = graph.nodes().size(); + CycleDetector cycleDetector = new CycleDetector(totalNodes); + int edgeCount = 0; + + MutableValueGraph spanningTree = ValueGraphBuilder.undirected().build(); + for (EndpointPair edge : edgeList) { + if (cycleDetector.detectCycle(edge.nodeU(), edge.nodeV())) { + continue; + } + spanningTree.putEdgeValue(edge.nodeU(), edge.nodeV(), graph.edgeValue(edge).get()); + edgeCount++; + if (edgeCount == totalNodes - 1) { + break; + } + } + return spanningTree; + } + +} diff --git a/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/kruskal/KruskalUnitTest.java b/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/kruskal/KruskalUnitTest.java new file mode 100644 index 0000000000..a7206c6cd0 --- /dev/null +++ b/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/kruskal/KruskalUnitTest.java @@ -0,0 +1,67 @@ +package com.baeldung.algorithms.kruskal; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Test; +import com.google.common.graph.MutableValueGraph; +import com.google.common.graph.ValueGraph; +import com.google.common.graph.ValueGraphBuilder; +import com.baeldung.algorithms.kruskal.Kruskal; + +public class KruskalUnitTest { + + private MutableValueGraph graph; + + @Before + public void setup() { + graph = ValueGraphBuilder.undirected().build(); + graph.putEdgeValue(0, 1, 8.0); + graph.putEdgeValue(0, 2, 5.0); + graph.putEdgeValue(1, 2, 9.0); + graph.putEdgeValue(1, 3, 11.0); + graph.putEdgeValue(2, 3, 15.0); + graph.putEdgeValue(2, 4, 10.0); + graph.putEdgeValue(3, 4, 7.0); + } + + @Test + public void givenGraph_whenMinimumSpanningTree_thenOutputCorrectResult() { + final Kruskal kruskal = new Kruskal(); + ValueGraph spanningTree = kruskal.minSpanningTree(graph); + + assertTrue(spanningTree.hasEdgeConnecting(0, 1)); + assertTrue(spanningTree.hasEdgeConnecting(0, 2)); + assertTrue(spanningTree.hasEdgeConnecting(2, 4)); + assertTrue(spanningTree.hasEdgeConnecting(3, 4)); + assertEquals(graph.edgeValue(0, 1), spanningTree.edgeValue(0, 1)); + assertEquals(graph.edgeValue(0, 2), spanningTree.edgeValue(0, 2)); + assertEquals(graph.edgeValue(2, 4), spanningTree.edgeValue(2, 4)); + assertEquals(graph.edgeValue(3, 4), spanningTree.edgeValue(3, 4)); + + assertFalse(spanningTree.hasEdgeConnecting(1, 2)); + assertFalse(spanningTree.hasEdgeConnecting(1, 3)); + assertFalse(spanningTree.hasEdgeConnecting(2, 3)); + } + + @Test + public void givenGraph_whenMaximumSpanningTree_thenOutputCorrectResult() { + final Kruskal kruskal = new Kruskal(); + ValueGraph spanningTree = kruskal.maxSpanningTree(graph); + + assertTrue(spanningTree.hasEdgeConnecting(0, 1)); + assertTrue(spanningTree.hasEdgeConnecting(1, 3)); + assertTrue(spanningTree.hasEdgeConnecting(2, 3)); + assertTrue(spanningTree.hasEdgeConnecting(2, 4)); + assertEquals(graph.edgeValue(0, 1), spanningTree.edgeValue(0, 1)); + assertEquals(graph.edgeValue(1, 3), spanningTree.edgeValue(1, 3)); + assertEquals(graph.edgeValue(2, 3), spanningTree.edgeValue(2, 3)); + assertEquals(graph.edgeValue(2, 4), spanningTree.edgeValue(2, 4)); + + assertFalse(spanningTree.hasEdgeConnecting(0, 2)); + assertFalse(spanningTree.hasEdgeConnecting(1, 2)); + assertFalse(spanningTree.hasEdgeConnecting(3, 4)); + } +}