From 0df902da92720c0da367a3fa0ae444f99b2def2d Mon Sep 17 00:00:00 2001 From: Gang Date: Mon, 16 Dec 2019 23:46:22 -0700 Subject: [PATCH 1/2] =?UTF-8?q?BAEL-3406=20Kruskal=E2=80=99s=20Algorithm?= =?UTF-8?q?=20for=20Spanning=20Trees?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- algorithms-miscellaneous-5/pom.xml | 5 ++ .../baeldung/algorithms/kruskal/Kruskal.java | 88 +++++++++++++++++++ .../algorithms/kruskal/KruskalUnitTest.java | 66 ++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/Kruskal.java create mode 100644 algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/kruskal/KruskalUnitTest.java diff --git a/algorithms-miscellaneous-5/pom.xml b/algorithms-miscellaneous-5/pom.xml index 2f530958e3..83a30f420c 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/Kruskal.java b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/Kruskal.java new file mode 100644 index 0000000000..62b260c71e --- /dev/null +++ b/algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/Kruskal.java @@ -0,0 +1,88 @@ +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 totalEdges = edgeList.size(); + int totalNodes = graph.nodes().size(); + int edgeCount = 0; + List roots = new ArrayList<>(totalNodes); + List sizes = new ArrayList<>(totalNodes); + for (int i = 0; i < totalNodes; i++) { + roots.add(i); + sizes.add(1); + } + + MutableValueGraph spanningTree = ValueGraphBuilder.undirected().build(); + for (int i = 0; i < totalEdges; i++) { + EndpointPair edge = edgeList.get(i); + if (detectCycle(edge.nodeU(), edge.nodeV(), roots, sizes)) { + continue; + } + spanningTree.putEdgeValue(edge.nodeU(), edge.nodeV(), graph.edgeValue(edge).get()); + edgeCount++; + if (edgeCount == totalNodes - 1) { + break; + } + } + return spanningTree; + } + + private Integer find(Integer x, List roots) { + Integer root = roots.get(x); + if (!root.equals(x)) { + roots.set(x, find(root, roots)); + } + return roots.get(x); + } + + private void unionBySize(Integer rootU, Integer rootV, List roots, List sizes) { + Integer total = sizes.get(rootU) + sizes.get(rootV); + if (sizes.get(rootU) < sizes.get(rootV)) { + roots.set(rootU, rootV); + sizes.set(rootV, total); + } else { + roots.set(rootV, rootU); + sizes.set(rootU, total); + } + } + + private boolean detectCycle(Integer u, Integer v, List roots, List sizes) { + Integer rootU = find(u, roots); + Integer rootV = find(v, roots); + if (rootU.equals(rootV)) { + return true; + } + unionBySize(rootU, rootV, roots, sizes); + return false; + } +} 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..e944d2894c --- /dev/null +++ b/algorithms-miscellaneous-5/src/test/java/com/baeldung/algorithms/kruskal/KruskalUnitTest.java @@ -0,0 +1,66 @@ +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; + +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)); + } +} From 103b04cb8e94dc17702a10b4a343cf89d930e55f Mon Sep 17 00:00:00 2001 From: Gang Date: Sun, 22 Dec 2019 18:53:01 -0700 Subject: [PATCH 2/2] BAEL-3406, refactor Kruskal algorithm by using more classes. --- algorithms-miscellaneous-5/pom.xml | 6 +- .../algorithms/kruskal/CycleDetector.java | 72 +++++++++++++++++++ .../algorithms/kruskal/DisjointSetInfo.java | 28 ++++++++ .../baeldung/algorithms/kruskal/Kruskal.java | 43 ++--------- .../algorithms/kruskal/KruskalUnitTest.java | 1 + 5 files changed, 108 insertions(+), 42 deletions(-) create mode 100644 algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/CycleDetector.java create mode 100644 algorithms-miscellaneous-5/src/main/java/com/baeldung/algorithms/kruskal/DisjointSetInfo.java diff --git a/algorithms-miscellaneous-5/pom.xml b/algorithms-miscellaneous-5/pom.xml index 83a30f420c..95036da775 100644 --- a/algorithms-miscellaneous-5/pom.xml +++ b/algorithms-miscellaneous-5/pom.xml @@ -35,9 +35,9 @@ ${tradukisto.version} - com.google.guava - guava - 28.1-jre + com.google.guava + guava + 28.1-jre 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 index 62b260c71e..da405679d1 100644 --- 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 @@ -14,7 +14,7 @@ import java.util.Set; public class Kruskal { public ValueGraph minSpanningTree(ValueGraph graph) { - + return spanningTree(graph, true); } @@ -32,20 +32,13 @@ public class Kruskal { edgeList.sort(Collections.reverseOrder(Comparator.comparing(e -> graph.edgeValue(e).get()))); } - int totalEdges = edgeList.size(); int totalNodes = graph.nodes().size(); + CycleDetector cycleDetector = new CycleDetector(totalNodes); int edgeCount = 0; - List roots = new ArrayList<>(totalNodes); - List sizes = new ArrayList<>(totalNodes); - for (int i = 0; i < totalNodes; i++) { - roots.add(i); - sizes.add(1); - } MutableValueGraph spanningTree = ValueGraphBuilder.undirected().build(); - for (int i = 0; i < totalEdges; i++) { - EndpointPair edge = edgeList.get(i); - if (detectCycle(edge.nodeU(), edge.nodeV(), roots, sizes)) { + for (EndpointPair edge : edgeList) { + if (cycleDetector.detectCycle(edge.nodeU(), edge.nodeV())) { continue; } spanningTree.putEdgeValue(edge.nodeU(), edge.nodeV(), graph.edgeValue(edge).get()); @@ -57,32 +50,4 @@ public class Kruskal { return spanningTree; } - private Integer find(Integer x, List roots) { - Integer root = roots.get(x); - if (!root.equals(x)) { - roots.set(x, find(root, roots)); - } - return roots.get(x); - } - - private void unionBySize(Integer rootU, Integer rootV, List roots, List sizes) { - Integer total = sizes.get(rootU) + sizes.get(rootV); - if (sizes.get(rootU) < sizes.get(rootV)) { - roots.set(rootU, rootV); - sizes.set(rootV, total); - } else { - roots.set(rootV, rootU); - sizes.set(rootU, total); - } - } - - private boolean detectCycle(Integer u, Integer v, List roots, List sizes) { - Integer rootU = find(u, roots); - Integer rootV = find(v, roots); - if (rootU.equals(rootV)) { - return true; - } - unionBySize(rootU, rootV, roots, sizes); - return false; - } } 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 index e944d2894c..a7206c6cd0 100644 --- 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 @@ -9,6 +9,7 @@ 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 {