Python/graphs/karger.py

84 lines
2.6 KiB
Python
Raw Normal View History

"""
An implementation of Karger's Algorithm for partitioning a graph.
"""
import random
from typing import Dict, List, Set, Tuple
# Adjacency list representation of this graph:
# https://en.wikipedia.org/wiki/File:Single_run_of_Karger%E2%80%99s_Mincut_algorithm.svg
TEST_GRAPH = {
'1': ['2', '3', '4', '5'],
'2': ['1', '3', '4', '5'],
'3': ['1', '2', '4', '5', '10'],
'4': ['1', '2', '3', '5', '6'],
'5': ['1', '2', '3', '4', '7'],
'6': ['7', '8', '9', '10', '4'],
'7': ['6', '8', '9', '10', '5'],
'8': ['6', '7', '9', '10'],
'9': ['6', '7', '8', '10'],
'10': ['6', '7', '8', '9', '3']
}
def partition_graph(graph: Dict[str, List[str]]) -> Set[Tuple[str, str]]:
"""
Partitions a graph using Karger's Algorithm. Implemented from
pseudocode found here:
https://en.wikipedia.org/wiki/Karger%27s_algorithm.
This function involves random choices, meaning it will not give
consistent outputs.
Args:
graph: A dictionary containing adacency lists for the graph.
Nodes must be strings.
Returns:
The cutset of the cut found by Karger's Algorithm.
>>> graph = {'0':['1'], '1':['0']}
>>> partition_graph(graph)
{('0', '1')}
"""
# Dict that maps contracted nodes to a list of all the nodes it "contains."
contracted_nodes = {node: {node} for node in graph}
graph_copy = {node: graph[node][:] for node in graph}
while len(graph_copy) > 2:
# Choose a random edge.
u = random.choice(list(graph_copy.keys()))
v = random.choice(graph_copy[u])
# Contract edge (u, v) to new node uv
uv = u + v
uv_neighbors = list(set(graph_copy[u] + graph_copy[v]))
uv_neighbors.remove(u)
uv_neighbors.remove(v)
graph_copy[uv] = uv_neighbors
for neighbor in uv_neighbors:
graph_copy[neighbor].append(uv)
contracted_nodes[uv] = {contracted_node for contracted_node in
contracted_nodes[u].union(contracted_nodes[v])}
# Remove nodes u and v.
del graph_copy[u]
del graph_copy[v]
for neighbor in uv_neighbors:
if u in graph_copy[neighbor]:
graph_copy[neighbor].remove(u)
if v in graph_copy[neighbor]:
graph_copy[neighbor].remove(v)
# Find cutset.
groups = [contracted_nodes[node] for node in graph_copy]
return {(node, neighbor) for node in groups[0]
for neighbor in graph[node] if neighbor in groups[1]}
if __name__ == "__main__":
print(partition_graph(TEST_GRAPH))