Skip to content

Commit 445c26b

Browse files
Adding Karger Minimum Graph Cut Algorithm to the randomized module.
1 parent d866fbd commit 445c26b

2 files changed

Lines changed: 325 additions & 0 deletions

File tree

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package com.thealgorithms.randomized;
2+
3+
import java.util.*;
4+
5+
/**
6+
* Implementation of Karger's Minimum Cut algorithm.
7+
*
8+
* <p>Karger's algorithm is a randomized algorithm to compute the minimum cut of a connected graph.
9+
* A minimum cut is the smallest set of edges that, if removed, would split the graph into two
10+
* disconnected components.
11+
*
12+
* <p>The algorithm works by repeatedly contracting random edges in the graph until only two
13+
* nodes remain. The edges between these two nodes represent a cut. By running the algorithm
14+
* multiple times and keeping track of the smallest cut found, the probability of finding the
15+
* true minimum cut increases.
16+
*
17+
* <p>Key steps of the algorithm:
18+
* <ol>
19+
* <li>Randomly select an edge and contract it, merging the two nodes into one.</li>
20+
* <li>Repeat the contraction process until only two nodes remain.</li>
21+
* <li>Count the edges between the two remaining nodes to determine the cut size.</li>
22+
* <li>Repeat the process multiple times to improve the likelihood of finding the true minimum cut.</li>
23+
* </ol>
24+
*/
25+
public class KargerMinCut {
26+
27+
/**
28+
* Output of the Karger algorithm.
29+
*
30+
* @param first The first set of nodes in the cut.
31+
* @param second The second set of nodes in the cut.
32+
* @param minCut The size of the minimum cut.
33+
*/
34+
public record KargerOutput(Set<Integer> first, Set<Integer> second, int minCut) {}
35+
36+
private KargerMinCut() {}
37+
38+
public static KargerOutput findMinCut(Collection<Integer> nodeSet, List<int[]> edges) {
39+
return findMinCut(nodeSet, edges, 100);
40+
}
41+
42+
/**
43+
* Finds the minimum cut of a graph using Karger's algorithm.
44+
*
45+
* @param nodeSet: Input graph nodes
46+
* @param edges: Input graph edges
47+
* @param iterations: Iterations to run the algorithms for, more iterations = more accuracy
48+
* @return A KargerOutput object containing the two sets of nodes and the size of the minimum cut.
49+
*/
50+
public static KargerOutput findMinCut(Collection<Integer> nodeSet,
51+
List<int[]> edges, int iterations) {
52+
Graph graph = new Graph(nodeSet, edges);
53+
KargerOutput minCut = new KargerOutput(new HashSet<>(), new HashSet<>(), Integer.MAX_VALUE);
54+
KargerOutput output;
55+
56+
// Run the algorithm multiple times to increase the probability of finding
57+
for (int i = 0; i < iterations; i++) {
58+
Graph clone = graph.copy();
59+
output = clone.findMinCut();
60+
if (output.minCut < minCut.minCut) {
61+
minCut = output;
62+
}
63+
}
64+
return minCut;
65+
}
66+
67+
private static class DisjointSetUnion {
68+
private final int[] parent;
69+
public int setCount;
70+
71+
public DisjointSetUnion(int size) {
72+
parent = new int[size];
73+
for (int i = 0; i < size; i++) parent[i] = i;
74+
setCount = size;
75+
}
76+
77+
public int find(int i) {
78+
// If it's not its own parent, then it's not the root of its set
79+
if (parent[i] != i) {
80+
// Recursively find the root of its parent
81+
// and update i's parent to point directly to the root (path compression)
82+
parent[i] = find(parent[i]);
83+
}
84+
85+
// Return the root (representative) of the set
86+
return parent[i];
87+
}
88+
89+
public void union(int u, int v) {
90+
// Find the root of each node
91+
int rootU = find(u);
92+
int rootV = find(v);
93+
94+
// If they belong to different sets, merge them
95+
if (rootU != rootV) {
96+
// Make rootV point to rootU — merge the two sets
97+
parent[rootV] = rootU;
98+
99+
// Reduce the count of disjoint sets by 1
100+
setCount--;
101+
}
102+
}
103+
104+
105+
public boolean inSameSet(int u, int v) {
106+
return find(u) == find(v);
107+
}
108+
109+
/*
110+
This is a verbosity method, it's not a part of the core algorithm,
111+
But it helps us provide more useful output.
112+
*/
113+
public Set<Integer> getAnySet() {
114+
int aRoot = find(0); //Get one of the two roots
115+
116+
Set<Integer> set = new HashSet<>();
117+
for (int i = 0; i < parent.length; i++) {
118+
if (find(i) == aRoot) {
119+
set.add(i);
120+
}
121+
}
122+
123+
return set;
124+
}
125+
126+
}
127+
128+
129+
private static class Graph {
130+
private final List<Integer> nodes;
131+
private final List<int[]> edges;
132+
133+
public Graph(Collection<Integer> nodeSet, List<int[]> edges) {
134+
this.nodes = new ArrayList<>(nodeSet);
135+
this.edges = new ArrayList<>();
136+
for (int[] e : edges) {
137+
this.edges.add(new int[]{e[0], e[1]});
138+
}
139+
}
140+
141+
public Graph copy() {
142+
return new Graph(this.nodes, this.edges);
143+
}
144+
145+
public KargerOutput findMinCut() {
146+
DisjointSetUnion dsu = new DisjointSetUnion(nodes.size());
147+
List<int[]> workingEdges = new ArrayList<>(edges);
148+
149+
Random rand = new Random();
150+
151+
while (dsu.setCount > 2) {
152+
int[] e = workingEdges.get(rand.nextInt(workingEdges.size()));
153+
if (!dsu.inSameSet(e[0], e[1])) {
154+
dsu.union(e[0], e[1]);
155+
}
156+
}
157+
158+
int cutEdges = 0;
159+
for (int[] e : edges) {
160+
if (!dsu.inSameSet(e[0], e[1])) {
161+
cutEdges++;
162+
}
163+
}
164+
165+
return collectResult(dsu, cutEdges);
166+
}
167+
168+
/*
169+
This is a verbosity method, it's not a part of the core algorithm,
170+
But it helps us provide more useful output.
171+
*/
172+
private KargerOutput collectResult(DisjointSetUnion dsu, int cutEdges) {
173+
Set<Integer> firstIndices = dsu.getAnySet();
174+
Set<Integer> firstSet = new HashSet<>();
175+
Set<Integer> secondSet = new HashSet<>();
176+
for (int i = 0; i < nodes.size(); i++) {
177+
if (firstIndices.contains(i)) {
178+
firstSet.add(nodes.get(i));
179+
} else {
180+
secondSet.add(nodes.get(i));
181+
}
182+
}
183+
return new KargerOutput(firstSet, secondSet, cutEdges);
184+
}
185+
}
186+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package com.thealgorithms.randomized;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.*;
6+
7+
import static org.junit.jupiter.api.Assertions.*;
8+
9+
public class KargerMinCutTest {
10+
11+
@Test
12+
public void testSimpleGraph() {
13+
// Graph: 0 -- 1
14+
Collection<Integer> nodes = Arrays.asList(0, 1);
15+
List<int[]> edges = List.of(new int[]{0, 1});
16+
17+
KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges);
18+
19+
assertEquals(1, result.minCut());
20+
assertTrue(result.first().contains(0) || result.first().contains(1));
21+
assertTrue(result.second().contains(0) || result.second().contains(1));
22+
}
23+
24+
@Test
25+
public void testTriangleGraph() {
26+
// Graph: 0 -- 1 -- 2 -- 0
27+
Collection<Integer> nodes = Arrays.asList(0, 1, 2);
28+
List<int[]> edges = List.of(
29+
new int[]{0, 1},
30+
new int[]{1, 2},
31+
new int[]{2, 0}
32+
);
33+
34+
KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges);
35+
36+
assertEquals(2, result.minCut());
37+
}
38+
39+
@Test
40+
public void testSquareGraph() {
41+
// Graph: 0 -- 1
42+
// | |
43+
// 3 -- 2
44+
Collection<Integer> nodes = Arrays.asList(0, 1, 2, 3);
45+
List<int[]> edges = List.of(
46+
new int[]{0, 1},
47+
new int[]{1, 2},
48+
new int[]{2, 3},
49+
new int[]{3, 0}
50+
);
51+
52+
KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges);
53+
54+
assertEquals(2, result.minCut());
55+
}
56+
57+
@Test
58+
public void testDisconnectedGraph() {
59+
// Graph: 0 -- 1 2 -- 3
60+
Collection<Integer> nodes = Arrays.asList(0, 1, 2, 3);
61+
List<int[]> edges = List.of(
62+
new int[]{0, 1},
63+
new int[]{2, 3}
64+
);
65+
66+
KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges);
67+
68+
assertEquals(0, result.minCut());
69+
}
70+
71+
@Test
72+
public void testCompleteGraph() {
73+
// Complete Graph: 0 -- 1 -- 2 -- 3 (all nodes connected to each other)
74+
Collection<Integer> nodes = Arrays.asList(0, 1, 2, 3);
75+
List<int[]> edges = List.of(
76+
new int[]{0, 1},
77+
new int[]{0, 2},
78+
new int[]{0, 3},
79+
new int[]{1, 2},
80+
new int[]{1, 3},
81+
new int[]{2, 3}
82+
);
83+
84+
KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges);
85+
86+
assertEquals(3, result.minCut());
87+
}
88+
89+
@Test
90+
public void testSingleNodeGraph() {
91+
// Graph: Single node with no edges
92+
Collection<Integer> nodes = List.of(0);
93+
List<int[]> edges = List.of();
94+
95+
KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges);
96+
97+
assertEquals(0, result.minCut());
98+
assertTrue(result.first().contains(0));
99+
assertTrue(result.second().isEmpty());
100+
}
101+
102+
@Test
103+
public void testTwoNodesNoEdge() {
104+
// Graph: 0 1 (no edges)
105+
Collection<Integer> nodes = Arrays.asList(0, 1);
106+
List<int[]> edges = List.of();
107+
108+
KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges);
109+
110+
assertEquals(0, result.minCut());
111+
assertTrue(result.first().contains(0) || result.first().contains(1));
112+
assertTrue(result.second().contains(0) || result.second().contains(1));
113+
}
114+
115+
@Test
116+
public void testComplexGraph() {
117+
// Nodes: 0, 1, 2, 3, 4, 5, 6, 7, 8
118+
// Edges: Fully connected graph with additional edges for complexity
119+
Collection<Integer> nodes = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8);
120+
List<int[]> edges = List.of(
121+
new int[]{0, 1}, new int[]{0, 2}, new int[]{0, 3}, new int[]{0, 4}, new int[]{0, 5},
122+
new int[]{1, 2}, new int[]{1, 3}, new int[]{1, 4}, new int[]{1, 5}, new int[]{1, 6},
123+
new int[]{2, 3}, new int[]{2, 4}, new int[]{2, 5}, new int[]{2, 6}, new int[]{2, 7},
124+
new int[]{3, 4}, new int[]{3, 5}, new int[]{3, 6}, new int[]{3, 7}, new int[]{3, 8},
125+
new int[]{4, 5}, new int[]{4, 6}, new int[]{4, 7}, new int[]{4, 8},
126+
new int[]{5, 6}, new int[]{5, 7}, new int[]{5, 8},
127+
new int[]{6, 7}, new int[]{6, 8},
128+
new int[]{7, 8},
129+
new int[]{0, 6}, new int[]{1, 7}, new int[]{2, 8}
130+
);
131+
132+
KargerMinCut.KargerOutput result = KargerMinCut.findMinCut(nodes, edges);
133+
134+
// The exact minimum cut value depends on the randomization, but it should be consistent
135+
// for this graph structure. For a fully connected graph, the minimum cut is typically
136+
// determined by the smallest number of edges connecting two partitions.
137+
assertTrue(result.minCut() > 0);
138+
}
139+
}

0 commit comments

Comments
 (0)