Skip to content

Commit 9e1105a

Browse files
committed
feat: Add Stoer-Wagner Algorithm for Minimum Cut
1 parent 69d8406 commit 9e1105a

File tree

2 files changed

+197
-0
lines changed

2 files changed

+197
-0
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package com.thealgorithms.graph;
2+
3+
/**
4+
* An implementation of the Stoer-Wagner algorithm to find the global minimum cut of an undirected, weighted graph.
5+
* A minimum cut is a partition of the graph's vertices into two disjoint sets with the minimum possible edge weight
6+
* sum connecting the two sets.
7+
*
8+
* Wikipedia: https://en.wikipedia.org/wiki/Stoer%E2%80%93Wagner_algorithm
9+
* Time Complexity: O(V^3) where V is the number of vertices.
10+
*/
11+
public class StoerWagner {
12+
13+
/**
14+
* Finds the minimum cut in the given undirected, weighted graph.
15+
*
16+
* @param graph An adjacency matrix representing the graph. graph[i][j] is the weight of the edge between i and j.
17+
* @return The weight of the minimum cut.
18+
*/
19+
public int findMinCut(int[][] graph) {
20+
int n = graph.length;
21+
if (n < 2) {
22+
return 0;
23+
}
24+
25+
// Make a working copy of the adjacency matrix so we can merge vertices
26+
int[][] g = new int[n][n];
27+
for (int i = 0; i < n; i++) {
28+
System.arraycopy(graph[i], 0, g[i], 0, n);
29+
}
30+
31+
// vertices contains the list of active vertex indices (initially 0..n-1)
32+
int[] vertices = new int[n];
33+
for (int i = 0; i < n; i++) {
34+
vertices[i] = i;
35+
}
36+
37+
int bestCut = Integer.MAX_VALUE;
38+
39+
// Repeat n-1 phases; in each phase we reduce number of active vertices by 1
40+
for (int m = n; m > 1; m--) {
41+
int[] weights = new int[n]; // accumulated weights for selection
42+
boolean[] added = new boolean[n]; // which original vertices have been added in this phase
43+
int prev = -1; // previously added vertex id in the growing set
44+
45+
for (int i = 0; i < m; i++) {
46+
// Select the not-yet-added vertex (among the first m active vertices) with maximum weight
47+
int sel = -1;
48+
for (int j = 0; j < m; j++) {
49+
int v = vertices[j];
50+
if (!added[v] && (sel == -1 || weights[v] > weights[sel])) {
51+
sel = v;
52+
}
53+
}
54+
55+
// If sel is -1 it means the graph is disconnected in the remaining part;
56+
// the minimum cut value is 0 in that case.
57+
if (sel == -1) {
58+
return 0;
59+
}
60+
61+
added[sel] = true;
62+
63+
// If this is the last vertex added in this phase, weights[sel] is the cut weight between sel and the rest.
64+
if (i == m - 1) {
65+
// Update best cut
66+
if (weights[sel] < bestCut) {
67+
bestCut = weights[sel];
68+
}
69+
70+
// Merge 'sel' into 'prev' (combine their edges) to reduce vertex count
71+
if (prev != -1) {
72+
for (int k = 0; k < n; k++) {
73+
// accumulate edges from sel into prev
74+
g[prev][k] += g[sel][k];
75+
g[k][prev] = g[prev][k];
76+
}
77+
78+
// Remove 'sel' from vertices[] by replacing it with last active vertex
79+
int selIndex = -1;
80+
for (int j = 0; j < m; j++) {
81+
if (vertices[j] == sel) {
82+
selIndex = j;
83+
break;
84+
}
85+
}
86+
// replace position selIndex with last active vertex (m-1)
87+
vertices[selIndex] = vertices[m - 1];
88+
}
89+
// Phase done
90+
} else {
91+
// not last: update weights of remaining active vertices
92+
for (int j = 0; j < m; j++) {
93+
int v = vertices[j];
94+
if (!added[v]) {
95+
weights[v] += g[sel][v];
96+
}
97+
}
98+
prev = sel;
99+
}
100+
}
101+
}
102+
103+
return bestCut == Integer.MAX_VALUE ? 0 : bestCut;
104+
}
105+
}
106+
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
/**
8+
* Unit tests for the StoerWagner global minimum cut algorithm.
9+
*
10+
* These tests verify correctness of the implementation across
11+
* several graph configurations: simple, complete, disconnected,
12+
* and small edge cases.
13+
*/
14+
public class StoerWagnerTest {
15+
16+
@Test
17+
public void testSimpleGraph() {
18+
int[][] graph = {
19+
{0, 3, 2, 0},
20+
{3, 0, 1, 4},
21+
{2, 1, 0, 5},
22+
{0, 4, 5, 0}
23+
};
24+
StoerWagner algo = new StoerWagner();
25+
assertEquals(5, algo.findMinCut(graph)); // Correct minimum cut = 5
26+
}
27+
28+
@Test
29+
public void testTriangleGraph() {
30+
int[][] graph = {
31+
{0, 2, 3},
32+
{2, 0, 4},
33+
{3, 4, 0}
34+
};
35+
StoerWagner algo = new StoerWagner();
36+
assertEquals(5, algo.findMinCut(graph)); // min cut = 5
37+
}
38+
39+
@Test
40+
public void testDisconnectedGraph() {
41+
int[][] graph = {
42+
{0, 0, 0},
43+
{0, 0, 0},
44+
{0, 0, 0}
45+
};
46+
StoerWagner algo = new StoerWagner();
47+
assertEquals(0, algo.findMinCut(graph)); // Disconnected graph => cut = 0
48+
}
49+
50+
@Test
51+
public void testCompleteGraph() {
52+
int[][] graph = {
53+
{0, 1, 1, 1},
54+
{1, 0, 1, 1},
55+
{1, 1, 0, 1},
56+
{1, 1, 1, 0}
57+
};
58+
StoerWagner algo = new StoerWagner();
59+
assertEquals(3, algo.findMinCut(graph)); // Each vertex connected to all others
60+
}
61+
62+
@Test
63+
public void testSingleVertex() {
64+
int[][] graph = {{0}};
65+
StoerWagner algo = new StoerWagner();
66+
assertEquals(0, algo.findMinCut(graph)); // Only one vertex
67+
}
68+
69+
@Test
70+
public void testTwoVertices() {
71+
int[][] graph = {
72+
{0, 7},
73+
{7, 0}
74+
};
75+
StoerWagner algo = new StoerWagner();
76+
assertEquals(7, algo.findMinCut(graph)); // Only one edge, cut weight = 7
77+
}
78+
79+
@Test
80+
public void testSquareGraphWithDiagonal() {
81+
int[][] graph = {
82+
{0, 2, 0, 2},
83+
{2, 0, 3, 0},
84+
{0, 3, 0, 4},
85+
{2, 0, 4, 0}
86+
};
87+
StoerWagner algo = new StoerWagner();
88+
assertEquals(4, algo.findMinCut(graph)); // verified manually
89+
}
90+
}
91+

0 commit comments

Comments
 (0)