Karger's Min Cut Algorithm
Description
Karger's algorithm is a randomized algorithm to compute a minimum cut of a connected graph. A cut is a partition of the vertices of a graph into two disjoint subsets, and the minimum cut is the cut with the fewest number of crossing edges.
This algorithm was invented by David Karger in 1993 and is notable for its simplicity and the fact that it's a Monte Carlo algorithm - a randomized algorithm that may produce incorrect results with some probability.
How It Works
The algorithm works by repeatedly contracting random edges in the graph until only two vertices remain. These two vertices represent a cut in the original graph, and the algorithm returns the number of edges between them as the cut value.
Algorithm Steps:
- Start with the input graph G = (V, E)
- While there are more than 2 vertices:
- Choose a random edge (u, v)
- Contract edge (u, v) into a single vertex
- Remove self-loops
- Return the number of edges between the two remaining vertices
Edge Contraction:
When we contract an edge (u, v), we:
- Replace vertices u and v with a single vertex uv
- Replace any edge (u, w) or (v, w) with an edge (uv, w)
- Remove any self-loops created in the process
Visualization
Success Probability
The probability that a single run of Karger's algorithm finds a minimum cut is at least 2/(n(n-1)), where n is the number of vertices. This is because:
- There are n(n-1)/2 possible edges in a complete graph
- A minimum cut has at least 2 vertices on each side
- The algorithm must avoid contracting any edge in the minimum cut until the very end
To increase the probability of finding the minimum cut, we can run the algorithm multiple times and take the minimum of all the cuts found. If we run the algorithm n²log(n) times, the probability of finding the minimum cut approaches 1.
Pseudocode
function kargerMinCut(graph):
while number of vertices > 2:
randomly select an edge (u, v)
contract edge (u, v):
merge vertices u and v into a new vertex uv
replace edges to u or v with edges to uv
remove self-loops
return number of edges between the two remaining vertices
function findMinCut(graph, iterations):
minCut = infinity
for i = 1 to iterations:
cut = kargerMinCut(copy of graph)
minCut = min(minCut, cut)
return minCut
Implementation
import random
import copy
def karger_min_cut(graph):
# Create a working copy of the graph
working_graph = copy.deepcopy(graph)
# Keep track of which original vertices are in each contracted vertex
vertex_map = {v: [v] for v in graph}
# Continue until only 2 vertices remain
while len(working_graph) > 2:
# Select a random edge
u = random.choice(list(working_graph.keys()))
if not working_graph[u]: # Skip isolated vertices
continue
v = random.choice(working_graph[u])
# Merge v into u
vertex_map[u].extend(vertex_map[v])
# Redirect edges
for neighbor in working_graph[v]:
if neighbor != u: # Avoid self-loops
working_graph[u].append(neighbor)
# Replace v with u in neighbor's edge list
working_graph[neighbor] = [u if x == v else x for x in working_graph[neighbor]]
# Remove v
del working_graph[v]
del vertex_map[v]
# Remove self-loops
working_graph[u] = [w for w in working_graph[u] if w != u]
# Return the number of edges between the two remaining vertices
vertices = list(working_graph.keys())
if len(vertices) < 2:
return 0
return len(working_graph[vertices[0]])
def find_min_cut(graph, iterations=100):
min_cut = float('inf')
for _ in range(iterations):
cut = karger_min_cut(graph)
min_cut = min(min_cut, cut)
return min_cut
# Example usage
if __name__ == "__main__":
# Example graph represented as an adjacency list
# Each vertex has a list of its neighbors
example_graph = {
0: [1, 2, 3],
1: [0, 3],
2: [0, 3, 4],
3: [0, 1, 2, 4],
4: [2, 3]
}
# Run the algorithm multiple times to increase the probability of finding the min cut
min_cut = find_min_cut(example_graph, iterations=100)
print(f"Minimum cut value: {min_cut}")
function kargerMinCut(graph) {
// Create a deep copy of the graph
const workingGraph = JSON.parse(JSON.stringify(graph));
// Keep track of which original vertices are in each contracted vertex
const vertexMap = {};
for (const v in graph) {
vertexMap[v] = [v];
}
// Continue until only 2 vertices remain
while (Object.keys(workingGraph).length > 2) {
// Select a random vertex
const vertices = Object.keys(workingGraph);
const u = vertices[Math.floor(Math.random() * vertices.length)];
// Skip if no edges
if (workingGraph[u].length === 0) continue;
// Select a random neighbor
const v = workingGraph[u][Math.floor(Math.random() * workingGraph[u].length)];
// Merge v into u
vertexMap[u] = [...vertexMap[u], ...vertexMap[v]];
// Redirect edges
for (const neighbor of workingGraph[v]) {
if (neighbor !== u) { // Avoid self-loops
workingGraph[u].push(neighbor);
// Replace v with u in neighbor's edge list
workingGraph[neighbor] = workingGraph[neighbor].map(x => x === v ? u : x);
}
}
// Remove v
delete workingGraph[v];
delete vertexMap[v];
// Remove self-loops
workingGraph[u] = workingGraph[u].filter(w => w !== u);
}
// Return the number of edges between the two remaining vertices
const vertices = Object.keys(workingGraph);
if (vertices.length < 2) return 0;
return workingGraph[vertices[0]].length;
}
function findMinCut(graph, iterations = 100) {
let minCut = Infinity;
for (let i = 0; i < iterations; i++) {
const cut = kargerMinCut(graph);
minCut = Math.min(minCut, cut);
}
return minCut;
}
// Example usage
const exampleGraph = {
'0': ['1', '2', '3'],
'1': ['0', '3'],
'2': ['0', '3', '4'],
'3': ['0', '1', '2', '4'],
'4': ['2', '3']
};
const minCut = findMinCut(exampleGraph, 100);
console.log(`Minimum cut value: ${minCut}`);
#include
#include
#include
#include
#include
#include
#include
using namespace std;
// Structure to represent a graph
struct Graph {
int V; // Number of vertices
vector> adj; // Adjacency list
// Constructor
Graph(int vertices) : V(vertices) {
adj.resize(V);
}
// Add an edge
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
// Create a copy of the graph
Graph copy() {
Graph g(V);
for (int i = 0; i < V; i++) {
g.adj[i] = adj[i];
}
return g;
}
};
// Function to find the minimum cut using Karger's algorithm
int kargerMinCut(Graph& g) {
// Random number generator
random_device rd;
mt19937 gen(rd());
// Create a working copy
Graph working = g.copy();
// Keep track of which vertices have been contracted
vector> contracted(g.V);
for (int i = 0; i < g.V; i++) {
contracted[i].push_back(i);
}
// Continue until only 2 vertices remain
int remainingVertices = g.V;
while (remainingVertices > 2) {
// Pick a random edge
int u = uniform_int_distribution<>(0, remainingVertices - 1)(gen);
// Skip if no edges
if (working.adj[u].empty()) continue;
int v = working.adj[u][uniform_int_distribution<>(0, working.adj[u].size() - 1)(gen)];
// Contract edge (u, v)
// Merge v's edges into u
for (int neighbor : working.adj[v]) {
if (neighbor != u) { // Avoid self-loops
working.adj[u].push_back(neighbor);
// Replace v with u in neighbor's adjacency list
replace(working.adj[neighbor].begin(), working.adj[neighbor].end(), v, u);
}
}
// Remove all edges to v
for (int i = 0; i < remainingVertices; i++) {
working.adj[i].erase(
remove(working.adj[i].begin(), working.adj[i].end(), v),
working.adj[i].end()
);
}
// Remove self-loops
working.adj[u].erase(
remove(working.adj[u].begin(), working.adj[u].end(), u),
working.adj[u].end()
);
// Move the last vertex to position v and decrease count
if (v != remainingVertices - 1) {
working.adj[v] = working.adj[remainingVertices - 1];
// Update references to the moved vertex
for (int i = 0; i < remainingVertices; i++) {
replace(working.adj[i].begin(), working.adj[i].end(), remainingVertices - 1, v);
}
}
remainingVertices--;
}
// Return the number of edges between the two remaining vertices
return working.adj[0].size();
}
// Function to find the minimum cut by running Karger's algorithm multiple times
int findMinCut(Graph& g, int iterations = 100) {
int minCut = INT_MAX;
for (int i = 0; i < iterations; i++) {
int cut = kargerMinCut(g);
minCut = min(minCut, cut);
}
return minCut;
}
int main() {
// Example graph
Graph g(5);
g.addEdge(0, 1);
g.addEdge(0, 2);
g.addEdge(0, 3);
g.addEdge(1, 3);
g.addEdge(2, 3);
g.addEdge(2, 4);
g.addEdge(3, 4);
int minCut = findMinCut(g, 100);
cout << "Minimum cut value: " << minCut << endl;
return 0;
}
Applications
Karger's algorithm has several practical applications:
- Network Reliability: Finding the minimum cut helps identify the most vulnerable points in a network.
- Image Segmentation: In computer vision, min-cut algorithms can be used to separate foreground from background.
- Clustering: Identifying natural divisions in data by finding minimum cuts between clusters.
- Community Detection: Finding communities in social networks by identifying sparse connections between dense subgraphs.
Improvements
Several improvements to Karger's algorithm have been developed:
- Karger-Stein Algorithm: A recursive variant that achieves a success probability of Ω(1/log n) with O(n² log n) running time.
- Weighted Contraction: Choosing edges with probability proportional to their weights can improve performance for certain graphs.
- Parallel Implementation: The algorithm is naturally parallelizable, as multiple runs can be executed independently.
References
- Karger, D. R. (1993). "Global Min-cuts in RNC, and Other Ramifications of a Simple Min-Cut Algorithm". Proceedings of the 4th Annual ACM-SIAM Symposium on Discrete Algorithms.
- Karger, D. R., & Stein, C. (1996). "A new approach to the minimum cut problem". Journal of the ACM, 43(4), 601-640.
- Stoer, M., & Wagner, F. (1997). "A simple min-cut algorithm". Journal of the ACM, 44(4), 585-591.