Quick Info

Category: Randomized
Time Complexity: O(V²E)
Space Complexity: O(V+E)
Success Probability: 1/n for a single run

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:

  1. Start with the input graph G = (V, E)
  2. While there are more than 2 vertices:
    • Choose a random edge (u, v)
    • Contract edge (u, v) into a single vertex
    • Remove self-loops
  3. 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

Current Vertices: 0
Current Edges: 0
Min Cut Value: -

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.