forked from TheAlgorithms/Java
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathKargerMinCut.java
More file actions
195 lines (168 loc) · 6.45 KB
/
KargerMinCut.java
File metadata and controls
195 lines (168 loc) · 6.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package com.thealgorithms.randomized;
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.
*
* <p>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.
*
* <p>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.
*
* <p>Key steps of the algorithm:
* <ol>
* <li>Randomly select an edge and contract it, merging the two nodes into one.</li>
* <li>Repeat the contraction process until only two nodes remain.</li>
* <li>Count the edges between the two remaining nodes to determine the cut size.</li>
* <li>Repeat the process multiple times to improve the likelihood of finding the true minimum cut.</li>
* </ol>
* <p>
* See more: <a href="https://en.wikipedia.org/wiki/Karger%27s_algorithm">Karger's algorithm</a>
*
* @author MuhammadEzzatHBK
*/
public final 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<Integer> first, Set<Integer> second, int minCut) {
}
private KargerMinCut() {
}
public static KargerOutput findMinCut(Collection<Integer> nodeSet, List<int[]> 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<Integer> nodeSet, List<int[]> 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;
int setCount;
DisjointSetUnion(int size) {
parent = new int[size];
for (int i = 0; i < size; i++) {
parent[i] = i;
}
setCount = size;
}
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];
}
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--;
}
}
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.
*/
Set<Integer> getAnySet() {
int aRoot = find(0); // Get one of the two roots
Set<Integer> 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<Integer> nodes;
private final List<int[]> edges;
Graph(Collection<Integer> nodeSet, List<int[]> edges) {
this.nodes = new ArrayList<>(nodeSet);
this.edges = new ArrayList<>();
for (int[] e : edges) {
this.edges.add(new int[] {e[0], e[1]});
}
}
Graph copy() {
return new Graph(this.nodes, this.edges);
}
KargerOutput findMinCut() {
DisjointSetUnion dsu = new DisjointSetUnion(nodes.size());
List<int[]> 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<Integer> firstIndices = dsu.getAnySet();
Set<Integer> firstSet = new HashSet<>();
Set<Integer> 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);
}
}
}