Minimum cut - Stoer-Wagner algorithm (Data Structures & Algorithms)
Learn Minimum cut - Stoer-Wagner algorithm (Data Structures & Algorithms) step by step with clear examples and exercises.
Why This Matters
The Minimum Cut problem is a fundamental concept in graph theory with wide applications in various fields such as computer networks, image segmentation, data clustering, and more. Solving it efficiently can help you excel in competitive programming contests and better understand complex graph algorithms like Ford-Fulkerson's algorithm for Maximum Flow problems.
Prerequisites
To grasp the Minimum Cut - Stoer-Wagner algorithm, you should have a solid understanding of:
- Basic Graph Data Structures and Algorithms (Breadth-First Search, Depth-First Search)
- Maximum Flow Problem and Ford-Fulkerson's algorithm
- Basic Python Programming concepts
- Understanding of Dynamic Programming algorithms
- Familiarity with Union-Find Data Structures
Core Concept
The Minimum Cut - Stoer-Wagner algorithm is a dynamic programming approach for finding the minimum cut in an undirected graph with non-negative edge weights. The algorithm iteratively merges small connected components (also called sets) of vertices until only two sets remain, which correspond to the partition defining the minimum cut.
Key Data Structures and Variables
graph: An adjacency list representation of the input graph.n: The number of vertices in the graph.m: The number of edges in the graph.sets: A list of sets, where each set is a connected component of the graph. Initially, each vertex forms its own set.cut_value: The current minimum cut value. It starts as zero and gets updated during the execution of the algorithm.component_size: A list storing the size of each set in thesetslist.max_set: The index of the largest set in thesetslist.min_cut: The final minimum cut value found by the algorithm.parent: An array of sizen+1, used for finding the parent of a vertex in the union-find data structure.edge_weights: A list storing the weights of all edges in the graph, sorted in non-decreasing order.
Main Algorithm Steps
- Initialize the data structures and variables as described above.
- While there are more than two sets, perform the following steps:
a. Find the two largest sets A and B.
b. Compute the cut value between A and B, denoted as cut_ab.
c. If cut_ab is less than or equal to cut_value, then update cut_value with cut_ab, merge A and B into a single set, and update the data structures accordingly.
- The final minimum cut value is stored in the
min_cutvariable.
Union-Find Data Structure
The union-find data structure is used to efficiently find the parent of each vertex and merge sets when needed. It supports two main operations:
find(x): Returns the representative (root) of the set containing vertexx.union(x, y): Merges the sets containing verticesxandy.
The union-find data structure is implemented using either a disjoint set forest or a compression tree.
Worked Example
Let's consider the following undirected graph with 7 vertices and 9 edges:
1---2---3
| |
4---5---6---7
With edge weights as follows:
- (1, 2): 2
- (1, 3): 3
- (2, 3): 5
- (2, 4): 8
- (3, 4): 4
- (3, 5): 7
- (4, 5): 6
- (4, 6): 9
- (5, 6): 10
- (5, 7): 2
- (6, 7): 1
Initialization
graph = {
1: [(2, 2), (3, 3)],
2: [(1, 2), (3, 5), (4, 8)],
3: [(1, 3), (2, 5), (4, 4)],
4: [(2, 4), (3, 4), (5, 6), (6, 9)],
5: [(3, 7), (4, 6)],
6: [(4, 10), (7, 1)],
7: [(5, 2)]
}
n = len(graph)
m = sum([len(edges) for edges in graph.values()])
sets = [set([i]) for i in range(1, n+1)]
cut_value = 0
component_size = [len(s) for s in sets]
max_set = max(sets, key=len)
parent = list(range(n+1))
edge_weights = sorted([weight for (u, v), weight in graph.values()])
Main Algorithm Steps
while len(sets) > 2:
largest_set_a, largest_set_b = max(sets, key=lambda s: component_size[s]), None
cut_value_candidate = float('inf')
for set_b in sets:
if set_b != largest_set_a and set_b not in (None, largest_set_a):
cut_ab = sum(weight for (u, v), weight in graph[find(u)][find(v)] if u in largest_set_a and v in set_b)
if cut_ab < cut_value_candidate:
cut_value_candidate = cut_ab
largest_set_b = set_b
if cut_value_candidate <= cut_value:
cut_value = cut_value_candidate
merge(largest_set_a, largest_set_b)
Union Operation
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
root_x = find(x)
root_y = find(y)
if root_x == root_y:
return
if component_size[root_x] > component_size[root_y]:
parent[root_y] = root_x
component_size[root_x] += component_size[root_y]
else:
parent[root_x] = root_y
component_size[root_y] += component_size[root_x]
Merge Operation
def merge(a, b):
a_root = find(max(a))
b_root = find(min(b))
if a_root == b_root:
return
if component_size[a_root] > component_size[b_root]:
parent[b_root] = a_root
component_size[a_root] += component_size[b_root]
else:
parent[a_root] = b_root
component_size[b_root] += component_size[a_root]
Finding the Minimum Cut Value
To find the minimum cut value, we first need to compute the total weight of all edges in the graph. Then, for each edge (u, v), if u and v belong to different sets, add its weight to the current minimum cut value. Finally, return the final minimum cut value.
def min_cut():
total_weight = sum([edge[1] for edge in graph.values()])
min_cut_value = 0
for u, v in edge_weights:
if find(u) != find(v):
min_cut_value += u
return min_cut_value
Final Minimum Cut Value
The final minimum cut value is stored in the min_cut() function.
Common Mistakes
- Not correctly initializing the data structures and variables.
- Failing to update the
parent,component_size, orsetsdata structures after merging sets. - Miscalculating the cut value between two sets during the main algorithm steps.
- Implementing an inefficient union-find data structure, such as using a simple loop instead of a disjoint set forest or compression tree.
- Not handling the case where there is only one set remaining after merging (i.e., the graph consists of a single connected component).
- Failing to compute the total weight of all edges in the graph before finding the minimum cut value.
- Not sorting edge weights in non-decreasing order, which can lead to incorrect results during the main algorithm steps.
Practice Questions
- Implement the Minimum Cut - Stoer-Wagner algorithm for directed graphs with positive edge weights.
- Modify the Minimum Cut - Stoer-Wagner algorithm to handle negative edge weights.
- Write a Python implementation of the Kruskal's algorithm for finding the minimum spanning tree (MST) of an undirected graph, and compare its time complexity with that of the Minimum Cut - Stoer-Wagner algorithm.
- Implement the Ford-Fulkerson's algorithm to solve the Maximum Flow problem in a given graph.
- Write a Python implementation of the Edmonds-Karp algorithm for finding the minimum cut in an undirected graph with non-negative edge weights.
- Compare the time complexity and space complexity of the Minimum Cut - Stoer-Wagner algorithm, Kruskal's algorithm, and Edmonds-Karp algorithm.
- Implement a variant of the Minimum Cut - Stoer-Wagner algorithm that uses partition refinement to improve its time complexity.
- Write a Python implementation of the Hungarian algorithm for finding the maximum weight bipartite matching, and discuss how it can be used to solve the Minimum Cut problem in complete graphs.
FAQ
Q: Can the Minimum Cut - Stoer-Wagner algorithm handle graphs with self-loops or multiple edges between two vertices?
A: No, the algorithm assumes that the input graph is simple (i.e., it does not contain self-loops or multiple edges between two vertices).
Q: How does the Minimum Cut - Stoer-Wagner algorithm compare to other minimum cut algorithms like Edmonds-Karp's algorithm and Ford-Fulkerson's algorithm?
A: The Minimum Cut - Stoer-Wagner algorithm has a better time complexity of O(n^3 log n) compared to Edmonds-Karp's algorithm (O(n^4)) and Ford-Fulkerson's algorithm (O(nm)), making it more efficient for large graphs. However, the Minimum Cut - Stoer-Wagner algorithm requires more memory due to its dynamic programming approach.
Q: Can I use the Minimum Cut - Stoer-Wagner algorithm to solve the Maximum Flow problem directly?
A: No, the Minimum Cut - Stoer-Wagner algorithm solves the Minimum Cut problem, but it can be used as a subroutine in algorithms like Ford-Fulkerson's algorithm to find the maximum flow in a graph.
Q: Is there any way to improve the time complexity of the Minimum Cut - Stoer-Wagner algorithm?
A: Yes, there are variants of the algorithm that achieve better time complexities by using different data structures or techniques like partition refinement and network flow-based methods. However, these variants may be more complex and less straightforward than the original algorithm.
Q: Can I use the Minimum Cut - Stoer-Wagner algorithm to solve the Maximum Weight Independent Set problem?
A: No, the Minimum Cut - Stoer-Wagner algorithm solves the Minimum Cut problem, not the Maximum Weight Independent Set problem. However, there are algorithms like the greedy algorithm and the Kernighan-Lin algorithm that can be used to solve the Maximum Weight Independent Set problem.
Q: Is it possible to use the Minimum Cut - Stoer-Wagner algorithm to find the minimum cut in a directed graph with negative edge weights?
A: No, the Minimum Cut - Stoer-Wagner algorithm assumes that the input graph has non-negative edge weights. To handle directed graphs with negative edge weights, you can use algorithms like the Ford-Fulkerson's algorithm or the Edmonds-Karp algorithm, which are designed to handle both positive and negative edge weights.