From 445c26baf198f3f50a2da34e6843c79c279bc0a4 Mon Sep 17 00:00:00 2001 From: MuhammadEzzatHBK Date: Mon, 5 May 2025 11:22:45 +0300 Subject: [PATCH 1/8] Adding Karger Minimum Graph Cut Algorithm to the randomized module. --- .../randomized/KargerMinCut.java | 186 ++++++++++++++++++ .../randomized/KargerMinCutTest.java | 139 +++++++++++++ 2 files changed, 325 insertions(+) create mode 100644 src/main/java/com/thealgorithms/randomized/KargerMinCut.java create mode 100644 src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java diff --git a/src/main/java/com/thealgorithms/randomized/KargerMinCut.java b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java new file mode 100644 index 000000000000..2135c29ac94c --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java @@ -0,0 +1,186 @@ +package com.thealgorithms.randomized; + +import java.util.*; + +/** + * Implementation of Karger's Minimum Cut algorithm. + * + *

Karger's algorithm is a randomized algorithm to compute the minimum cut of a connected graph. + * A minimum cut is the smallest set of edges that, if removed, would split the graph into two + * disconnected components. + * + *

The algorithm works by repeatedly contracting random edges in the graph until only two + * nodes remain. The edges between these two nodes represent a cut. By running the algorithm + * multiple times and keeping track of the smallest cut found, the probability of finding the + * true minimum cut increases. + * + *

Key steps of the algorithm: + *

    + *
  1. Randomly select an edge and contract it, merging the two nodes into one.
  2. + *
  3. Repeat the contraction process until only two nodes remain.
  4. + *
  5. Count the edges between the two remaining nodes to determine the cut size.
  6. + *
  7. Repeat the process multiple times to improve the likelihood of finding the true minimum cut.
  8. + *
+ */ +public class KargerMinCut { + + /** + * Output of the Karger algorithm. + * + * @param first The first set of nodes in the cut. + * @param second The second set of nodes in the cut. + * @param minCut The size of the minimum cut. + */ + public record KargerOutput(Set first, Set second, int minCut) {} + + private KargerMinCut() {} + + public static KargerOutput findMinCut(Collection nodeSet, List edges) { + return findMinCut(nodeSet, edges, 100); + } + + /** + * Finds the minimum cut of a graph using Karger's algorithm. + * + * @param nodeSet: Input graph nodes + * @param edges: Input graph edges + * @param iterations: Iterations to run the algorithms for, more iterations = more accuracy + * @return A KargerOutput object containing the two sets of nodes and the size of the minimum cut. + */ + public static KargerOutput findMinCut(Collection nodeSet, + List edges, int iterations) { + Graph graph = new Graph(nodeSet, edges); + KargerOutput minCut = new KargerOutput(new HashSet<>(), new HashSet<>(), Integer.MAX_VALUE); + KargerOutput output; + + // Run the algorithm multiple times to increase the probability of finding + for (int i = 0; i < iterations; i++) { + Graph clone = graph.copy(); + output = clone.findMinCut(); + if (output.minCut < minCut.minCut) { + minCut = output; + } + } + return minCut; + } + + private static class DisjointSetUnion { + private final int[] parent; + public int setCount; + + public DisjointSetUnion(int size) { + parent = new int[size]; + for (int i = 0; i < size; i++) parent[i] = i; + setCount = size; + } + + public int find(int i) { + // If it's not its own parent, then it's not the root of its set + if (parent[i] != i) { + // Recursively find the root of its parent + // and update i's parent to point directly to the root (path compression) + parent[i] = find(parent[i]); + } + + // Return the root (representative) of the set + return parent[i]; + } + + public void union(int u, int v) { + // Find the root of each node + int rootU = find(u); + int rootV = find(v); + + // If they belong to different sets, merge them + if (rootU != rootV) { + // Make rootV point to rootU — merge the two sets + parent[rootV] = rootU; + + // Reduce the count of disjoint sets by 1 + setCount--; + } + } + + + public boolean inSameSet(int u, int v) { + return find(u) == find(v); + } + + /* + This is a verbosity method, it's not a part of the core algorithm, + But it helps us provide more useful output. + */ + public Set getAnySet() { + int aRoot = find(0); //Get one of the two roots + + Set set = new HashSet<>(); + for (int i = 0; i < parent.length; i++) { + if (find(i) == aRoot) { + set.add(i); + } + } + + return set; + } + + } + + + private static class Graph { + private final List nodes; + private final List edges; + + public Graph(Collection nodeSet, List edges) { + this.nodes = new ArrayList<>(nodeSet); + this.edges = new ArrayList<>(); + for (int[] e : edges) { + this.edges.add(new int[]{e[0], e[1]}); + } + } + + public Graph copy() { + return new Graph(this.nodes, this.edges); + } + + public KargerOutput findMinCut() { + DisjointSetUnion dsu = new DisjointSetUnion(nodes.size()); + List workingEdges = new ArrayList<>(edges); + + Random rand = new Random(); + + while (dsu.setCount > 2) { + int[] e = workingEdges.get(rand.nextInt(workingEdges.size())); + if (!dsu.inSameSet(e[0], e[1])) { + dsu.union(e[0], e[1]); + } + } + + int cutEdges = 0; + for (int[] e : edges) { + if (!dsu.inSameSet(e[0], e[1])) { + cutEdges++; + } + } + + return collectResult(dsu, cutEdges); + } + + /* + This is a verbosity method, it's not a part of the core algorithm, + But it helps us provide more useful output. + */ + private KargerOutput collectResult(DisjointSetUnion dsu, int cutEdges) { + Set firstIndices = dsu.getAnySet(); + Set firstSet = new HashSet<>(); + Set secondSet = new HashSet<>(); + for (int i = 0; i < nodes.size(); i++) { + if (firstIndices.contains(i)) { + firstSet.add(nodes.get(i)); + } else { + secondSet.add(nodes.get(i)); + } + } + return new KargerOutput(firstSet, secondSet, cutEdges); + } + } +} diff --git a/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java b/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java new file mode 100644 index 000000000000..aa8af9be52b6 --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java @@ -0,0 +1,139 @@ +package com.thealgorithms.randomized; + +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class KargerMinCutTest { + + @Test + public void testSimpleGraph() { + // Graph: 0 -- 1 + Collection nodes = Arrays.asList(0, 1); + List edges = List.of(new int[]{0, 1}); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(1, result.minCut()); + assertTrue(result.first().contains(0) || result.first().contains(1)); + assertTrue(result.second().contains(0) || result.second().contains(1)); + } + + @Test + public void testTriangleGraph() { + // Graph: 0 -- 1 -- 2 -- 0 + Collection nodes = Arrays.asList(0, 1, 2); + List edges = List.of( + new int[]{0, 1}, + new int[]{1, 2}, + new int[]{2, 0} + ); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(2, result.minCut()); + } + + @Test + public void testSquareGraph() { + // Graph: 0 -- 1 + // | | + // 3 -- 2 + Collection nodes = Arrays.asList(0, 1, 2, 3); + List edges = List.of( + new int[]{0, 1}, + new int[]{1, 2}, + new int[]{2, 3}, + new int[]{3, 0} + ); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(2, result.minCut()); + } + + @Test + public void testDisconnectedGraph() { + // Graph: 0 -- 1 2 -- 3 + Collection nodes = Arrays.asList(0, 1, 2, 3); + List edges = List.of( + new int[]{0, 1}, + new int[]{2, 3} + ); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(0, result.minCut()); + } + + @Test + public void testCompleteGraph() { + // Complete Graph: 0 -- 1 -- 2 -- 3 (all nodes connected to each other) + Collection nodes = Arrays.asList(0, 1, 2, 3); + List edges = List.of( + new int[]{0, 1}, + new int[]{0, 2}, + new int[]{0, 3}, + new int[]{1, 2}, + new int[]{1, 3}, + new int[]{2, 3} + ); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(3, result.minCut()); + } + + @Test + public void testSingleNodeGraph() { + // Graph: Single node with no edges + Collection nodes = List.of(0); + List edges = List.of(); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(0, result.minCut()); + assertTrue(result.first().contains(0)); + assertTrue(result.second().isEmpty()); + } + + @Test + public void testTwoNodesNoEdge() { + // Graph: 0 1 (no edges) + Collection nodes = Arrays.asList(0, 1); + List edges = List.of(); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + assertEquals(0, result.minCut()); + assertTrue(result.first().contains(0) || result.first().contains(1)); + assertTrue(result.second().contains(0) || result.second().contains(1)); + } + + @Test + public void testComplexGraph() { + // Nodes: 0, 1, 2, 3, 4, 5, 6, 7, 8 + // Edges: Fully connected graph with additional edges for complexity + Collection nodes = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8); + List edges = List.of( + new int[]{0, 1}, new int[]{0, 2}, new int[]{0, 3}, new int[]{0, 4}, new int[]{0, 5}, + new int[]{1, 2}, new int[]{1, 3}, new int[]{1, 4}, new int[]{1, 5}, new int[]{1, 6}, + new int[]{2, 3}, new int[]{2, 4}, new int[]{2, 5}, new int[]{2, 6}, new int[]{2, 7}, + new int[]{3, 4}, new int[]{3, 5}, new int[]{3, 6}, new int[]{3, 7}, new int[]{3, 8}, + new int[]{4, 5}, new int[]{4, 6}, new int[]{4, 7}, new int[]{4, 8}, + new int[]{5, 6}, new int[]{5, 7}, new int[]{5, 8}, + new int[]{6, 7}, new int[]{6, 8}, + new int[]{7, 8}, + new int[]{0, 6}, new int[]{1, 7}, new int[]{2, 8} + ); + + KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); + + // The exact minimum cut value depends on the randomization, but it should be consistent + // for this graph structure. For a fully connected graph, the minimum cut is typically + // determined by the smallest number of edges connecting two partitions. + assertTrue(result.minCut() > 0); + } +} From bd04a7d47a71e690f8d80da5351f4168a76a9a59 Mon Sep 17 00:00:00 2001 From: MuhammadEzzatHBK Date: Mon, 5 May 2025 11:28:42 +0300 Subject: [PATCH 2/8] Updating javadocs. --- src/main/java/com/thealgorithms/randomized/KargerMinCut.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/thealgorithms/randomized/KargerMinCut.java b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java index 2135c29ac94c..3117aa89c4d3 100644 --- a/src/main/java/com/thealgorithms/randomized/KargerMinCut.java +++ b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java @@ -21,6 +21,9 @@ *
  • Count the edges between the two remaining nodes to determine the cut size.
  • *
  • Repeat the process multiple times to improve the likelihood of finding the true minimum cut.
  • * + * + * See more: Karger's algorithm + * @author MuhammadEzzatHBK */ public class KargerMinCut { From fbde45f4c6779313b713e9ce7649d03aeaccf6de Mon Sep 17 00:00:00 2001 From: MuhammadEzzatHBK Date: Mon, 5 May 2025 12:15:33 +0300 Subject: [PATCH 3/8] clang formatting. --- .../randomized/KargerMinCut.java | 28 +++++----- .../randomized/KargerMinCutTest.java | 53 +++++-------------- 2 files changed, 30 insertions(+), 51 deletions(-) diff --git a/src/main/java/com/thealgorithms/randomized/KargerMinCut.java b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java index 3117aa89c4d3..34c1a89aac5d 100644 --- a/src/main/java/com/thealgorithms/randomized/KargerMinCut.java +++ b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java @@ -1,6 +1,11 @@ package com.thealgorithms.randomized; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; /** * Implementation of Karger's Minimum Cut algorithm. @@ -21,11 +26,12 @@ *
  • Count the edges between the two remaining nodes to determine the cut size.
  • *
  • Repeat the process multiple times to improve the likelihood of finding the true minimum cut.
  • * - * + *

    * See more: Karger's algorithm + * * @author MuhammadEzzatHBK */ -public class KargerMinCut { +public final class KargerMinCut { /** * Output of the Karger algorithm. @@ -34,9 +40,11 @@ public class KargerMinCut { * @param second The second set of nodes in the cut. * @param minCut The size of the minimum cut. */ - public record KargerOutput(Set first, Set second, int minCut) {} + public record KargerOutput(Set first, Set second, int minCut) { + } - private KargerMinCut() {} + private KargerMinCut() { + } public static KargerOutput findMinCut(Collection nodeSet, List edges) { return findMinCut(nodeSet, edges, 100); @@ -50,8 +58,7 @@ public static KargerOutput findMinCut(Collection nodeSet, List e * @param iterations: Iterations to run the algorithms for, more iterations = more accuracy * @return A KargerOutput object containing the two sets of nodes and the size of the minimum cut. */ - public static KargerOutput findMinCut(Collection nodeSet, - List edges, int iterations) { + public static KargerOutput findMinCut(Collection nodeSet, List edges, int iterations) { Graph graph = new Graph(nodeSet, edges); KargerOutput minCut = new KargerOutput(new HashSet<>(), new HashSet<>(), Integer.MAX_VALUE); KargerOutput output; @@ -104,7 +111,6 @@ public void union(int u, int v) { } } - public boolean inSameSet(int u, int v) { return find(u) == find(v); } @@ -114,7 +120,7 @@ public boolean inSameSet(int u, int v) { But it helps us provide more useful output. */ public Set getAnySet() { - int aRoot = find(0); //Get one of the two roots + int aRoot = find(0); // Get one of the two roots Set set = new HashSet<>(); for (int i = 0; i < parent.length; i++) { @@ -125,10 +131,8 @@ public Set getAnySet() { return set; } - } - private static class Graph { private final List nodes; private final List edges; @@ -137,7 +141,7 @@ public Graph(Collection nodeSet, List edges) { this.nodes = new ArrayList<>(nodeSet); this.edges = new ArrayList<>(); for (int[] e : edges) { - this.edges.add(new int[]{e[0], e[1]}); + this.edges.add(new int[] {e[0], e[1]}); } } diff --git a/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java b/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java index aa8af9be52b6..876b6bf45eaf 100644 --- a/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java +++ b/src/test/java/com/thealgorithms/randomized/KargerMinCutTest.java @@ -1,10 +1,12 @@ package com.thealgorithms.randomized; -import org.junit.jupiter.api.Test; - -import java.util.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.*; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import org.junit.jupiter.api.Test; public class KargerMinCutTest { @@ -12,7 +14,7 @@ public class KargerMinCutTest { public void testSimpleGraph() { // Graph: 0 -- 1 Collection nodes = Arrays.asList(0, 1); - List edges = List.of(new int[]{0, 1}); + List edges = List.of(new int[] {0, 1}); KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); @@ -25,11 +27,7 @@ public void testSimpleGraph() { public void testTriangleGraph() { // Graph: 0 -- 1 -- 2 -- 0 Collection nodes = Arrays.asList(0, 1, 2); - List edges = List.of( - new int[]{0, 1}, - new int[]{1, 2}, - new int[]{2, 0} - ); + List edges = List.of(new int[] {0, 1}, new int[] {1, 2}, new int[] {2, 0}); KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); @@ -42,12 +40,7 @@ public void testSquareGraph() { // | | // 3 -- 2 Collection nodes = Arrays.asList(0, 1, 2, 3); - List edges = List.of( - new int[]{0, 1}, - new int[]{1, 2}, - new int[]{2, 3}, - new int[]{3, 0} - ); + List edges = List.of(new int[] {0, 1}, new int[] {1, 2}, new int[] {2, 3}, new int[] {3, 0}); KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); @@ -58,10 +51,7 @@ public void testSquareGraph() { public void testDisconnectedGraph() { // Graph: 0 -- 1 2 -- 3 Collection nodes = Arrays.asList(0, 1, 2, 3); - List edges = List.of( - new int[]{0, 1}, - new int[]{2, 3} - ); + List edges = List.of(new int[] {0, 1}, new int[] {2, 3}); KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); @@ -72,14 +62,7 @@ public void testDisconnectedGraph() { public void testCompleteGraph() { // Complete Graph: 0 -- 1 -- 2 -- 3 (all nodes connected to each other) Collection nodes = Arrays.asList(0, 1, 2, 3); - List edges = List.of( - new int[]{0, 1}, - new int[]{0, 2}, - new int[]{0, 3}, - new int[]{1, 2}, - new int[]{1, 3}, - new int[]{2, 3} - ); + List edges = List.of(new int[] {0, 1}, new int[] {0, 2}, new int[] {0, 3}, new int[] {1, 2}, new int[] {1, 3}, new int[] {2, 3}); KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); @@ -117,17 +100,9 @@ public void testComplexGraph() { // Nodes: 0, 1, 2, 3, 4, 5, 6, 7, 8 // Edges: Fully connected graph with additional edges for complexity Collection nodes = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8); - List edges = List.of( - new int[]{0, 1}, new int[]{0, 2}, new int[]{0, 3}, new int[]{0, 4}, new int[]{0, 5}, - new int[]{1, 2}, new int[]{1, 3}, new int[]{1, 4}, new int[]{1, 5}, new int[]{1, 6}, - new int[]{2, 3}, new int[]{2, 4}, new int[]{2, 5}, new int[]{2, 6}, new int[]{2, 7}, - new int[]{3, 4}, new int[]{3, 5}, new int[]{3, 6}, new int[]{3, 7}, new int[]{3, 8}, - new int[]{4, 5}, new int[]{4, 6}, new int[]{4, 7}, new int[]{4, 8}, - new int[]{5, 6}, new int[]{5, 7}, new int[]{5, 8}, - new int[]{6, 7}, new int[]{6, 8}, - new int[]{7, 8}, - new int[]{0, 6}, new int[]{1, 7}, new int[]{2, 8} - ); + List edges = List.of(new int[] {0, 1}, new int[] {0, 2}, new int[] {0, 3}, new int[] {0, 4}, new int[] {0, 5}, new int[] {1, 2}, new int[] {1, 3}, new int[] {1, 4}, new int[] {1, 5}, new int[] {1, 6}, new int[] {2, 3}, new int[] {2, 4}, new int[] {2, 5}, new int[] {2, 6}, + new int[] {2, 7}, new int[] {3, 4}, new int[] {3, 5}, new int[] {3, 6}, new int[] {3, 7}, new int[] {3, 8}, new int[] {4, 5}, new int[] {4, 6}, new int[] {4, 7}, new int[] {4, 8}, new int[] {5, 6}, new int[] {5, 7}, new int[] {5, 8}, new int[] {6, 7}, new int[] {6, 8}, new int[] {7, 8}, + new int[] {0, 6}, new int[] {1, 7}, new int[] {2, 8}); KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges); From 164f328c8a0a2433b3362d9c616ebf0a6494b642 Mon Sep 17 00:00:00 2001 From: MuhammadEzzatHBK Date: Mon, 5 May 2025 12:25:09 +0300 Subject: [PATCH 4/8] Removing redundant access modifiers. --- .../randomized/KargerMinCut.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/thealgorithms/randomized/KargerMinCut.java b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java index 34c1a89aac5d..14f1f97450a0 100644 --- a/src/main/java/com/thealgorithms/randomized/KargerMinCut.java +++ b/src/main/java/com/thealgorithms/randomized/KargerMinCut.java @@ -76,15 +76,17 @@ public static KargerOutput findMinCut(Collection nodeSet, List e private static class DisjointSetUnion { private final int[] parent; - public int setCount; + int setCount; - public DisjointSetUnion(int size) { + DisjointSetUnion(int size) { parent = new int[size]; - for (int i = 0; i < size; i++) parent[i] = i; + for (int i = 0; i < size; i++) { + parent[i] = i; + } setCount = size; } - public int find(int i) { + int find(int i) { // If it's not its own parent, then it's not the root of its set if (parent[i] != i) { // Recursively find the root of its parent @@ -96,7 +98,7 @@ public int find(int i) { return parent[i]; } - public void union(int u, int v) { + void union(int u, int v) { // Find the root of each node int rootU = find(u); int rootV = find(v); @@ -111,7 +113,7 @@ public void union(int u, int v) { } } - public boolean inSameSet(int u, int v) { + boolean inSameSet(int u, int v) { return find(u) == find(v); } @@ -119,7 +121,7 @@ public boolean inSameSet(int u, int v) { This is a verbosity method, it's not a part of the core algorithm, But it helps us provide more useful output. */ - public Set getAnySet() { + Set getAnySet() { int aRoot = find(0); // Get one of the two roots Set set = new HashSet<>(); @@ -137,7 +139,7 @@ private static class Graph { private final List nodes; private final List edges; - public Graph(Collection nodeSet, List edges) { + Graph(Collection nodeSet, List edges) { this.nodes = new ArrayList<>(nodeSet); this.edges = new ArrayList<>(); for (int[] e : edges) { @@ -145,11 +147,11 @@ public Graph(Collection nodeSet, List edges) { } } - public Graph copy() { + Graph copy() { return new Graph(this.nodes, this.edges); } - public KargerOutput findMinCut() { + KargerOutput findMinCut() { DisjointSetUnion dsu = new DisjointSetUnion(nodes.size()); List workingEdges = new ArrayList<>(edges); From 74893e1d6efaad248fa9f5935ec68c3147343bfe Mon Sep 17 00:00:00 2001 From: MuhammadEzzatHBK Date: Thu, 8 May 2025 00:22:27 +0300 Subject: [PATCH 5/8] #6219: Add Monte Carlo Integral Approximation --- .../randomized/MonteCarloIntegration.java | 72 +++++++++++++++++++ .../randomized/MonteCarloIntegrationTest.java | 59 +++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java create mode 100644 src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java diff --git a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java new file mode 100644 index 000000000000..45d076c3f574 --- /dev/null +++ b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java @@ -0,0 +1,72 @@ +package com.thealgorithms.randomized; + +import java.util.Random; +import java.util.function.Function; + +/** + * A demonstration of the Monte Carlo integration algorithm in Java. + * + *

    This class estimates the value of definite integrals using randomized sampling, + * also known as the Monte Carlo method. It is particularly effective for: + *

      + *
    • Functions that are difficult or impossible to integrate analytically
    • + *
    • High-dimensional integrals where traditional methods are inefficient
    • + *
    • Simulation and probabilistic analysis tasks
    • + *
    + * + *

    The core idea is to sample random points uniformly from the integration domain, + * evaluate the function at those points, and compute the scaled average to estimate the integral. + * + *

    For a one-dimensional integral over [a, b], the approximation is the function range (b-a), + * multiplied by the function average result for a random sample. + * See more: Monte Carlo Integration + * + * @author: MuhammadEzzatHBK + */ + +public final class MonteCarloIntegration { + + private MonteCarloIntegration() { + } + + /** + * Approximates the definite integral of a given function over a specified + * interval using the Monte Carlo method with a fixed random seed for + * reproducibility. + * + * @param fx the function to integrate + * @param a the lower bound of the interval + * @param b the upper bound of the interval + * @param n the number of random samples to use + * @param seed the seed for the random number generator + * @return the approximate value of the integral + */ + public static double approximate(Function fx, double a, double b, int n, long seed) { + return doApproximate(fx, a, b, n, new Random(seed)); + } + + /** + * Approximates the definite integral of a given function over a specified + * interval using the Monte Carlo method with a random seed based on the + * current system time for more randomness. + * + * @param fx the function to integrate + * @param a the lower bound of the interval + * @param b the upper bound of the interval + * @param n the number of random samples to use + * @return the approximate value of the integral + */ + public static double approximate(Function fx, double a, double b, int n) { + return doApproximate(fx, a, b, n, new Random(System.currentTimeMillis())); + } + + private static double doApproximate(Function fx, double a, double b, int n, Random generator) { + double totalArea = 0.0; + double range = b - a; + for (int i = 0; i < n; i++) { + double x = a + generator.nextDouble() * range; + totalArea += fx.apply(x); + } + return range * totalArea / n; + } +} diff --git a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java new file mode 100644 index 000000000000..78b091ec7696 --- /dev/null +++ b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java @@ -0,0 +1,59 @@ +package com.thealgorithms.randomized; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.function.Function; +import org.junit.jupiter.api.Test; + +class MonteCarloIntegrationTest { + + private static final double EPSILON = 0.03; // Allow 3% error margin + + @Test + void testConstantFunction() { + // Integral of f(x) = 2 from 0 to 1 is 2 + Function constant = x -> 2.0; + double result = MonteCarloIntegration.approximate(constant, 0, 1, 10000); + assertEquals(2.0, result, EPSILON); + } + + @Test + void testLinearFunction() { + // Integral of f(x) = x from 0 to 1 is 0.5 + Function linear = x -> x; + double result = MonteCarloIntegration.approximate(linear, 0, 1, 10000); + assertEquals(0.5, result, EPSILON); + } + + @Test + void testQuadraticFunction() { + // Integral of f(x) = x^2 from 0 to 1 is 1/3 + Function quadratic = x -> x * x; + double result = MonteCarloIntegration.approximate(quadratic, 0, 1, 10000); + assertEquals(1.0 / 3.0, result, EPSILON); + } + + @Test + void testLargeSampleSize() { + // Integral of f(x) = x^2 from 0 to 1 is 1/3 + Function quadratic = x -> x * x; + double result = MonteCarloIntegration.approximate(quadratic, 0, 1, 50000000); + assertEquals(1.0 / 3.0, result, EPSILON / 2); // Larger sample size, smaller error margin + } + + @Test + void testReproducibility() { + Function linear = x -> x; + double result1 = MonteCarloIntegration.approximate(linear, 0, 1, 10000, 42L); + double result2 = MonteCarloIntegration.approximate(linear, 0, 1, 10000, 42L); + assertEquals(result1, result2, 0.0); // Exactly equal + } + + @Test + void testNegativeInterval() { + // Integral of f(x) = x from -1 to 1 is 0 + Function linear = x -> x; + double result = MonteCarloIntegration.approximate(linear, -1, 1, 10000); + assertEquals(0.0, result, EPSILON); + } +} From 1109024f4d5f7f1631ff678b76c6c8c462709a70 Mon Sep 17 00:00:00 2001 From: MuhammadEzzatHBK Date: Thu, 8 May 2025 00:31:36 +0300 Subject: [PATCH 6/8] #6219: Removing package imports --- .../com/thealgorithms/randomized/MonteCarloIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java index 78b091ec7696..0a19339a9cd7 100644 --- a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java +++ b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java @@ -1,6 +1,6 @@ package com.thealgorithms.randomized; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.function.Function; import org.junit.jupiter.api.Test; From 785c8af673e3c19d66cfa454ba99d1f66535b2fe Mon Sep 17 00:00:00 2001 From: MuhammadEzzatHBK Date: Thu, 8 May 2025 00:37:40 +0300 Subject: [PATCH 7/8] #6219: Using Function.identity() instead of an identity lambda. --- .../thealgorithms/randomized/MonteCarloIntegrationTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java index 0a19339a9cd7..eaf2b3290e85 100644 --- a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java +++ b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java @@ -20,7 +20,7 @@ void testConstantFunction() { @Test void testLinearFunction() { // Integral of f(x) = x from 0 to 1 is 0.5 - Function linear = x -> x; + Function linear = Function.identity(); double result = MonteCarloIntegration.approximate(linear, 0, 1, 10000); assertEquals(0.5, result, EPSILON); } @@ -43,7 +43,7 @@ void testLargeSampleSize() { @Test void testReproducibility() { - Function linear = x -> x; + Function linear = Function.identity(); double result1 = MonteCarloIntegration.approximate(linear, 0, 1, 10000, 42L); double result2 = MonteCarloIntegration.approximate(linear, 0, 1, 10000, 42L); assertEquals(result1, result2, 0.0); // Exactly equal @@ -52,7 +52,7 @@ void testReproducibility() { @Test void testNegativeInterval() { // Integral of f(x) = x from -1 to 1 is 0 - Function linear = x -> x; + Function linear = Function.identity(); double result = MonteCarloIntegration.approximate(linear, -1, 1, 10000); assertEquals(0.0, result, EPSILON); } From 33e3d8f3b72632054255f0c51a340b8ebb8121d2 Mon Sep 17 00:00:00 2001 From: MuhammadEzzatHBK Date: Thu, 8 May 2025 10:46:51 +0300 Subject: [PATCH 8/8] #6219: Adding input validation --- .../randomized/MonteCarloIntegration.java | 16 +++++-- .../randomized/MonteCarloIntegrationTest.java | 46 ++++++++++++++++--- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java index 45d076c3f574..05d7abbbcd6c 100644 --- a/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java +++ b/src/main/java/com/thealgorithms/randomized/MonteCarloIntegration.java @@ -61,12 +61,22 @@ public static double approximate(Function fx, double a, double b } private static double doApproximate(Function fx, double a, double b, int n, Random generator) { + if (!validate(fx, a, b, n)) { + throw new IllegalArgumentException("Invalid input parameters"); + } double totalArea = 0.0; - double range = b - a; + double interval = b - a; for (int i = 0; i < n; i++) { - double x = a + generator.nextDouble() * range; + double x = a + generator.nextDouble() * interval; totalArea += fx.apply(x); } - return range * totalArea / n; + return interval * totalArea / n; + } + + private static boolean validate(Function fx, double a, double b, int n) { + boolean isFunctionValid = fx != null; + boolean isIntervalValid = a < b; + boolean isSampleSizeValid = n > 0; + return isFunctionValid && isIntervalValid && isSampleSizeValid; } } diff --git a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java index eaf2b3290e85..2a3a84b5ceea 100644 --- a/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java +++ b/src/test/java/com/thealgorithms/randomized/MonteCarloIntegrationTest.java @@ -1,6 +1,9 @@ package com.thealgorithms.randomized; +import static com.thealgorithms.randomized.MonteCarloIntegration.approximate; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.function.Function; import org.junit.jupiter.api.Test; @@ -13,7 +16,7 @@ class MonteCarloIntegrationTest { void testConstantFunction() { // Integral of f(x) = 2 from 0 to 1 is 2 Function constant = x -> 2.0; - double result = MonteCarloIntegration.approximate(constant, 0, 1, 10000); + double result = approximate(constant, 0, 1, 10000); assertEquals(2.0, result, EPSILON); } @@ -21,7 +24,7 @@ void testConstantFunction() { void testLinearFunction() { // Integral of f(x) = x from 0 to 1 is 0.5 Function linear = Function.identity(); - double result = MonteCarloIntegration.approximate(linear, 0, 1, 10000); + double result = approximate(linear, 0, 1, 10000); assertEquals(0.5, result, EPSILON); } @@ -29,7 +32,7 @@ void testLinearFunction() { void testQuadraticFunction() { // Integral of f(x) = x^2 from 0 to 1 is 1/3 Function quadratic = x -> x * x; - double result = MonteCarloIntegration.approximate(quadratic, 0, 1, 10000); + double result = approximate(quadratic, 0, 1, 10000); assertEquals(1.0 / 3.0, result, EPSILON); } @@ -37,15 +40,15 @@ void testQuadraticFunction() { void testLargeSampleSize() { // Integral of f(x) = x^2 from 0 to 1 is 1/3 Function quadratic = x -> x * x; - double result = MonteCarloIntegration.approximate(quadratic, 0, 1, 50000000); + double result = approximate(quadratic, 0, 1, 50000000); assertEquals(1.0 / 3.0, result, EPSILON / 2); // Larger sample size, smaller error margin } @Test void testReproducibility() { Function linear = Function.identity(); - double result1 = MonteCarloIntegration.approximate(linear, 0, 1, 10000, 42L); - double result2 = MonteCarloIntegration.approximate(linear, 0, 1, 10000, 42L); + double result1 = approximate(linear, 0, 1, 10000, 42L); + double result2 = approximate(linear, 0, 1, 10000, 42L); assertEquals(result1, result2, 0.0); // Exactly equal } @@ -53,7 +56,36 @@ void testReproducibility() { void testNegativeInterval() { // Integral of f(x) = x from -1 to 1 is 0 Function linear = Function.identity(); - double result = MonteCarloIntegration.approximate(linear, -1, 1, 10000); + double result = approximate(linear, -1, 1, 10000); assertEquals(0.0, result, EPSILON); } + + @Test + void testNullFunction() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> approximate(null, 0, 1, 1000)); + assertNotNull(exception); + } + + @Test + void testInvalidInterval() { + Function linear = Function.identity(); + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + approximate(linear, 2, 1, 1000); // b <= a + }); + assertNotNull(exception); + } + + @Test + void testZeroSampleSize() { + Function linear = Function.identity(); + Exception exception = assertThrows(IllegalArgumentException.class, () -> approximate(linear, 0, 1, 0)); + assertNotNull(exception); + } + + @Test + void testNegativeSampleSize() { + Function linear = Function.identity(); + Exception exception = assertThrows(IllegalArgumentException.class, () -> approximate(linear, 0, 1, -100)); + assertNotNull(exception); + } }