From 9e1105a2302037eb9413280f0e306e54eb6582ee Mon Sep 17 00:00:00 2001 From: sharan Date: Sun, 12 Oct 2025 13:11:19 +0530 Subject: [PATCH 1/4] feat: Add Stoer-Wagner Algorithm for Minimum Cut --- .../com/thealgorithms/graph/StoerWagner.java | 106 ++++++++++++++++++ .../thealgorithms/graph/StoerWagnerTest.java | 91 +++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 src/main/java/com/thealgorithms/graph/StoerWagner.java create mode 100644 src/test/java/com/thealgorithms/graph/StoerWagnerTest.java diff --git a/src/main/java/com/thealgorithms/graph/StoerWagner.java b/src/main/java/com/thealgorithms/graph/StoerWagner.java new file mode 100644 index 000000000000..49a0e6ebd19f --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/StoerWagner.java @@ -0,0 +1,106 @@ +package com.thealgorithms.graph; + +/** + * An implementation of the Stoer-Wagner algorithm to find the global minimum cut of an undirected, weighted graph. + * A minimum cut is a partition of the graph's vertices into two disjoint sets with the minimum possible edge weight + * sum connecting the two sets. + * + * Wikipedia: https://en.wikipedia.org/wiki/Stoer%E2%80%93Wagner_algorithm + * Time Complexity: O(V^3) where V is the number of vertices. + */ +public class StoerWagner { + + /** + * Finds the minimum cut in the given undirected, weighted graph. + * + * @param graph An adjacency matrix representing the graph. graph[i][j] is the weight of the edge between i and j. + * @return The weight of the minimum cut. + */ + public int findMinCut(int[][] graph) { + int n = graph.length; + if (n < 2) { + return 0; + } + + // Make a working copy of the adjacency matrix so we can merge vertices + int[][] g = new int[n][n]; + for (int i = 0; i < n; i++) { + System.arraycopy(graph[i], 0, g[i], 0, n); + } + + // vertices contains the list of active vertex indices (initially 0..n-1) + int[] vertices = new int[n]; + for (int i = 0; i < n; i++) { + vertices[i] = i; + } + + int bestCut = Integer.MAX_VALUE; + + // Repeat n-1 phases; in each phase we reduce number of active vertices by 1 + for (int m = n; m > 1; m--) { + int[] weights = new int[n]; // accumulated weights for selection + boolean[] added = new boolean[n]; // which original vertices have been added in this phase + int prev = -1; // previously added vertex id in the growing set + + for (int i = 0; i < m; i++) { + // Select the not-yet-added vertex (among the first m active vertices) with maximum weight + int sel = -1; + for (int j = 0; j < m; j++) { + int v = vertices[j]; + if (!added[v] && (sel == -1 || weights[v] > weights[sel])) { + sel = v; + } + } + + // If sel is -1 it means the graph is disconnected in the remaining part; + // the minimum cut value is 0 in that case. + if (sel == -1) { + return 0; + } + + added[sel] = true; + + // If this is the last vertex added in this phase, weights[sel] is the cut weight between sel and the rest. + if (i == m - 1) { + // Update best cut + if (weights[sel] < bestCut) { + bestCut = weights[sel]; + } + + // Merge 'sel' into 'prev' (combine their edges) to reduce vertex count + if (prev != -1) { + for (int k = 0; k < n; k++) { + // accumulate edges from sel into prev + g[prev][k] += g[sel][k]; + g[k][prev] = g[prev][k]; + } + + // Remove 'sel' from vertices[] by replacing it with last active vertex + int selIndex = -1; + for (int j = 0; j < m; j++) { + if (vertices[j] == sel) { + selIndex = j; + break; + } + } + // replace position selIndex with last active vertex (m-1) + vertices[selIndex] = vertices[m - 1]; + } + // Phase done + } else { + // not last: update weights of remaining active vertices + for (int j = 0; j < m; j++) { + int v = vertices[j]; + if (!added[v]) { + weights[v] += g[sel][v]; + } + } + prev = sel; + } + } + } + + return bestCut == Integer.MAX_VALUE ? 0 : bestCut; + } +} + diff --git a/src/test/java/com/thealgorithms/graph/StoerWagnerTest.java b/src/test/java/com/thealgorithms/graph/StoerWagnerTest.java new file mode 100644 index 000000000000..5179a33f62e5 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/StoerWagnerTest.java @@ -0,0 +1,91 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the StoerWagner global minimum cut algorithm. + * + * These tests verify correctness of the implementation across + * several graph configurations: simple, complete, disconnected, + * and small edge cases. + */ +public class StoerWagnerTest { + + @Test + public void testSimpleGraph() { + int[][] graph = { + {0, 3, 2, 0}, + {3, 0, 1, 4}, + {2, 1, 0, 5}, + {0, 4, 5, 0} + }; + StoerWagner algo = new StoerWagner(); + assertEquals(5, algo.findMinCut(graph)); // Correct minimum cut = 5 + } + + @Test + public void testTriangleGraph() { + int[][] graph = { + {0, 2, 3}, + {2, 0, 4}, + {3, 4, 0} + }; + StoerWagner algo = new StoerWagner(); + assertEquals(5, algo.findMinCut(graph)); // min cut = 5 + } + + @Test + public void testDisconnectedGraph() { + int[][] graph = { + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0} + }; + StoerWagner algo = new StoerWagner(); + assertEquals(0, algo.findMinCut(graph)); // Disconnected graph => cut = 0 + } + + @Test + public void testCompleteGraph() { + int[][] graph = { + {0, 1, 1, 1}, + {1, 0, 1, 1}, + {1, 1, 0, 1}, + {1, 1, 1, 0} + }; + StoerWagner algo = new StoerWagner(); + assertEquals(3, algo.findMinCut(graph)); // Each vertex connected to all others + } + + @Test + public void testSingleVertex() { + int[][] graph = {{0}}; + StoerWagner algo = new StoerWagner(); + assertEquals(0, algo.findMinCut(graph)); // Only one vertex + } + + @Test + public void testTwoVertices() { + int[][] graph = { + {0, 7}, + {7, 0} + }; + StoerWagner algo = new StoerWagner(); + assertEquals(7, algo.findMinCut(graph)); // Only one edge, cut weight = 7 + } + + @Test + public void testSquareGraphWithDiagonal() { + int[][] graph = { + {0, 2, 0, 2}, + {2, 0, 3, 0}, + {0, 3, 0, 4}, + {2, 0, 4, 0} + }; + StoerWagner algo = new StoerWagner(); + assertEquals(4, algo.findMinCut(graph)); // verified manually + } +} + From 7e86329750be2c57613c8591b46dde5b65b67823 Mon Sep 17 00:00:00 2001 From: sharan Date: Sun, 12 Oct 2025 13:20:50 +0530 Subject: [PATCH 2/4] fix: Correct Stoer-Wagner implementation --- .../com/thealgorithms/graph/StoerWagner.java | 96 +++++++------------ .../thealgorithms/graph/StoerWagnerTest.java | 41 ++------ 2 files changed, 42 insertions(+), 95 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/StoerWagner.java b/src/main/java/com/thealgorithms/graph/StoerWagner.java index 49a0e6ebd19f..8cf4c04ce69c 100644 --- a/src/main/java/com/thealgorithms/graph/StoerWagner.java +++ b/src/main/java/com/thealgorithms/graph/StoerWagner.java @@ -1,5 +1,7 @@ package com.thealgorithms.graph; +import java.util.Arrays; + /** * An implementation of the Stoer-Wagner algorithm to find the global minimum cut of an undirected, weighted graph. * A minimum cut is a partition of the graph's vertices into two disjoint sets with the minimum possible edge weight @@ -22,85 +24,57 @@ public int findMinCut(int[][] graph) { return 0; } - // Make a working copy of the adjacency matrix so we can merge vertices - int[][] g = new int[n][n]; + int[][] currentGraph = new int[n][n]; for (int i = 0; i < n; i++) { - System.arraycopy(graph[i], 0, g[i], 0, n); + System.arraycopy(graph[i], 0, currentGraph[i], 0, n); } - // vertices contains the list of active vertex indices (initially 0..n-1) - int[] vertices = new int[n]; - for (int i = 0; i < n; i++) { - vertices[i] = i; - } + int minCut = Integer.MAX_VALUE; + boolean[] merged = new boolean[n]; - int bestCut = Integer.MAX_VALUE; + for (int phase = 0; phase < n - 1; phase++) { + boolean[] inSetA = new boolean[n]; + int[] weights = new int[n]; + int prev = -1; + int last = -1; - // Repeat n-1 phases; in each phase we reduce number of active vertices by 1 - for (int m = n; m > 1; m--) { - int[] weights = new int[n]; // accumulated weights for selection - boolean[] added = new boolean[n]; // which original vertices have been added in this phase - int prev = -1; // previously added vertex id in the growing set + for (int i = 0; i < n - phase; i++) { + int maxWeight = -1; + int currentVertex = -1; - for (int i = 0; i < m; i++) { - // Select the not-yet-added vertex (among the first m active vertices) with maximum weight - int sel = -1; - for (int j = 0; j < m; j++) { - int v = vertices[j]; - if (!added[v] && (sel == -1 || weights[v] > weights[sel])) { - sel = v; + for (int j = 0; j < n; j++) { + if (!merged[j] && !inSetA[j] && weights[j] > maxWeight) { + maxWeight = weights[j]; + currentVertex = j; } } - // If sel is -1 it means the graph is disconnected in the remaining part; - // the minimum cut value is 0 in that case. - if (sel == -1) { + if (currentVertex == -1) { + // This can happen if the graph is disconnected. return 0; } - added[sel] = true; + prev = last; + last = currentVertex; + inSetA[last] = true; - // If this is the last vertex added in this phase, weights[sel] is the cut weight between sel and the rest. - if (i == m - 1) { - // Update best cut - if (weights[sel] < bestCut) { - bestCut = weights[sel]; + for (int j = 0; j < n; j++) { + if (!merged[j] && !inSetA[j]) { + weights[j] += currentGraph[last][j]; } + } + } - // Merge 'sel' into 'prev' (combine their edges) to reduce vertex count - if (prev != -1) { - for (int k = 0; k < n; k++) { - // accumulate edges from sel into prev - g[prev][k] += g[sel][k]; - g[k][prev] = g[prev][k]; - } + minCut = Math.min(minCut, weights[last]); - // Remove 'sel' from vertices[] by replacing it with last active vertex - int selIndex = -1; - for (int j = 0; j < m; j++) { - if (vertices[j] == sel) { - selIndex = j; - break; - } - } - // replace position selIndex with last active vertex (m-1) - vertices[selIndex] = vertices[m - 1]; - } - // Phase done - } else { - // not last: update weights of remaining active vertices - for (int j = 0; j < m; j++) { - int v = vertices[j]; - if (!added[v]) { - weights[v] += g[sel][v]; - } - } - prev = sel; - } + // Merge 'last' vertex into 'prev' vertex + for (int i = 0; i < n; i++) { + currentGraph[prev][i] += currentGraph[last][i]; + currentGraph[i][prev] = currentGraph[prev][i]; } + merged[last] = true; } - return bestCut == Integer.MAX_VALUE ? 0 : bestCut; + return minCut; } } - diff --git a/src/test/java/com/thealgorithms/graph/StoerWagnerTest.java b/src/test/java/com/thealgorithms/graph/StoerWagnerTest.java index 5179a33f62e5..894d99687d1d 100644 --- a/src/test/java/com/thealgorithms/graph/StoerWagnerTest.java +++ b/src/test/java/com/thealgorithms/graph/StoerWagnerTest.java @@ -6,7 +6,7 @@ /** * Unit tests for the StoerWagner global minimum cut algorithm. - * + * * These tests verify correctness of the implementation across * several graph configurations: simple, complete, disconnected, * and small edge cases. @@ -15,46 +15,28 @@ public class StoerWagnerTest { @Test public void testSimpleGraph() { - int[][] graph = { - {0, 3, 2, 0}, - {3, 0, 1, 4}, - {2, 1, 0, 5}, - {0, 4, 5, 0} - }; + int[][] graph = {{0, 3, 2, 0}, {3, 0, 1, 4}, {2, 1, 0, 5}, {0, 4, 5, 0}}; StoerWagner algo = new StoerWagner(); assertEquals(5, algo.findMinCut(graph)); // Correct minimum cut = 5 } @Test public void testTriangleGraph() { - int[][] graph = { - {0, 2, 3}, - {2, 0, 4}, - {3, 4, 0} - }; + int[][] graph = {{0, 2, 3}, {2, 0, 4}, {3, 4, 0}}; StoerWagner algo = new StoerWagner(); assertEquals(5, algo.findMinCut(graph)); // min cut = 5 } @Test public void testDisconnectedGraph() { - int[][] graph = { - {0, 0, 0}, - {0, 0, 0}, - {0, 0, 0} - }; + int[][] graph = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; StoerWagner algo = new StoerWagner(); assertEquals(0, algo.findMinCut(graph)); // Disconnected graph => cut = 0 } @Test public void testCompleteGraph() { - int[][] graph = { - {0, 1, 1, 1}, - {1, 0, 1, 1}, - {1, 1, 0, 1}, - {1, 1, 1, 0} - }; + int[][] graph = {{0, 1, 1, 1}, {1, 0, 1, 1}, {1, 1, 0, 1}, {1, 1, 1, 0}}; StoerWagner algo = new StoerWagner(); assertEquals(3, algo.findMinCut(graph)); // Each vertex connected to all others } @@ -68,24 +50,15 @@ public void testSingleVertex() { @Test public void testTwoVertices() { - int[][] graph = { - {0, 7}, - {7, 0} - }; + int[][] graph = {{0, 7}, {7, 0}}; StoerWagner algo = new StoerWagner(); assertEquals(7, algo.findMinCut(graph)); // Only one edge, cut weight = 7 } @Test public void testSquareGraphWithDiagonal() { - int[][] graph = { - {0, 2, 0, 2}, - {2, 0, 3, 0}, - {0, 3, 0, 4}, - {2, 0, 4, 0} - }; + int[][] graph = {{0, 2, 0, 2}, {2, 0, 3, 0}, {0, 3, 0, 4}, {2, 0, 4, 0}}; StoerWagner algo = new StoerWagner(); assertEquals(4, algo.findMinCut(graph)); // verified manually } } - From 7bffe7a7bc8589f9b0b7cd5c2d9791f62e03ac8e Mon Sep 17 00:00:00 2001 From: sharan Date: Sun, 12 Oct 2025 13:27:31 +0530 Subject: [PATCH 3/4] fix: Remove unused import --- src/main/java/com/thealgorithms/graph/StoerWagner.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/graph/StoerWagner.java b/src/main/java/com/thealgorithms/graph/StoerWagner.java index 8cf4c04ce69c..b647a2f03f61 100644 --- a/src/main/java/com/thealgorithms/graph/StoerWagner.java +++ b/src/main/java/com/thealgorithms/graph/StoerWagner.java @@ -1,6 +1,5 @@ package com.thealgorithms.graph; -import java.util.Arrays; /** * An implementation of the Stoer-Wagner algorithm to find the global minimum cut of an undirected, weighted graph. From 3464a2793dc863ef2d20b2dbeffca473dfc1a110 Mon Sep 17 00:00:00 2001 From: sharan Date: Sun, 12 Oct 2025 13:31:50 +0530 Subject: [PATCH 4/4] fix: Apply clang-format --- src/main/java/com/thealgorithms/graph/StoerWagner.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/graph/StoerWagner.java b/src/main/java/com/thealgorithms/graph/StoerWagner.java index b647a2f03f61..b204834c431a 100644 --- a/src/main/java/com/thealgorithms/graph/StoerWagner.java +++ b/src/main/java/com/thealgorithms/graph/StoerWagner.java @@ -1,6 +1,5 @@ package com.thealgorithms.graph; - /** * An implementation of the Stoer-Wagner algorithm to find the global minimum cut of an undirected, weighted graph. * A minimum cut is a partition of the graph's vertices into two disjoint sets with the minimum possible edge weight