Graph matching algorithms.
This module provides algorithms for finding matchings in graphs. A matching is a set of edges without common vertices.
Algorithms
| Problem | Algorithm | Function | Complexity |
|---|---|---|---|
| Maximum bipartite matching | Hopcroft-Karp | hopcroft_karp/1 | O(E√V) |
| Weighted bipartite matching | Hungarian (Kuhn-Munkres) | hungarian/2 | O(V³) |
| Maximum general matching | Edmonds' Blossom | blossom_maximum_matching/1 | O(V²E) |
Key Concepts
- Matching: A set of edges with no shared vertices
- Maximum Matching: A matching with the largest possible number of edges
- Perfect Matching: Every vertex is matched (requires equal partitions)
- Assignment Problem: Optimal assignment of workers to jobs with costs/weights
Examples
iex> graph = Yog.from_edges(:undirected, [{:a1, :b1, 1}, {:a1, :b2, 1}, {:a2, :b2, 1}, {:a2, :b3, 1}])
iex> matching = Yog.Matching.hopcroft_karp(graph)
iex> map_size(matching)
4
iex> matching[:a1] in [:b1, :b2]
true
iex> matching[:a2] in [:b2, :b3]
true
iex> matching[:a1] != matching[:a2]
Summary
Functions
Finds a maximum cardinality matching in a general (possibly non-bipartite) graph using Edmonds' Blossom algorithm.
Finds a maximum cardinality matching in a bipartite graph using the Hopcroft-Karp algorithm.
Finds a minimum or maximum weight perfect matching in a bipartite graph using the Hungarian (Kuhn-Munkres) algorithm.
Types
Functions
@spec blossom_maximum_matching(Yog.Graph.t()) :: %{ required(Yog.node_id()) => Yog.node_id() }
Finds a maximum cardinality matching in a general (possibly non-bipartite) graph using Edmonds' Blossom algorithm.
Unlike hopcroft_karp/1, this works on any undirected graph, including those
with odd cycles. The algorithm detects odd cycles (blossoms), contracts them into
super-vertices, and continues the search for augmenting paths.
Returns a bidirectional map where each matched pair appears twice
(u => v and v => u) for easy lookup in either direction.
Examples
# Triangle - odd cycle requires blossom contraction
iex> graph = Yog.from_edges(:undirected, [{1, 2, 1}, {2, 3, 1}, {3, 1, 1}])
iex> matching = Yog.Matching.blossom_maximum_matching(graph)
iex> div(map_size(matching), 2)
1
# Square - even cycle, perfect matching exists
iex> graph = Yog.from_edges(:undirected, [{1, 2, 1}, {2, 3, 1}, {3, 4, 1}, {4, 1, 1}])
iex> matching = Yog.Matching.blossom_maximum_matching(graph)
iex> div(map_size(matching), 2)
2
# Empty graph
iex> matching = Yog.Matching.blossom_maximum_matching(Yog.undirected())
iex> matching == %{}
trueTime Complexity
O(V²E) — practical for graphs up to ~1000 nodes.
References
- Wikipedia: Blossom algorithm
- Edmonds, J. (1965). "Paths, trees, and flowers"
@spec hopcroft_karp(Yog.Graph.t()) :: %{required(Yog.node_id()) => Yog.node_id()}
Finds a maximum cardinality matching in a bipartite graph using the Hopcroft-Karp algorithm.
The algorithm repeatedly finds a maximal set of shortest vertex-disjoint augmenting paths via BFS layering, then augments the matching along each path via DFS. This yields O(E√V) time complexity, significantly faster than the naive O(VE) augmenting-path approach on dense graphs.
Returns a bidirectional map where each matched pair appears twice
(u => v and v => u) for easy lookup in either direction.
Raises ArgumentError if the input graph is not bipartite.
Examples
# Simple path graph
iex> graph = Yog.from_edges(:undirected, [{1, 2, 1}, {2, 3, 1}, {3, 4, 1}])
iex> matching = Yog.Matching.hopcroft_karp(graph)
iex> map_size(matching)
4
# Star graph
iex> graph = Yog.from_edges(:undirected, [{1, 2, 1}, {1, 3, 1}, {1, 4, 1}])
iex> matching = Yog.Matching.hopcroft_karp(graph)
iex> map_size(matching)
2
# Complete bipartite K_{2,2}
iex> graph = Yog.from_edges(:undirected, [{:a1, :b1, 1}, {:a1, :b2, 1}, {:a2, :b1, 1}, {:a2, :b2, 1}])
iex> matching = Yog.Matching.hopcroft_karp(graph)
iex> map_size(matching)
4
# Empty graph
iex> matching = Yog.Matching.hopcroft_karp(Yog.undirected())
iex> matching == %{}
trueTime Complexity
O(E√V)
@spec hungarian(Yog.Graph.t(), optimization()) :: {number(), %{required(Yog.node_id()) => Yog.node_id()}}
Finds a minimum or maximum weight perfect matching in a bipartite graph using the Hungarian (Kuhn-Munkres) algorithm.
The graph must be bipartite. Rectangular partitions (|L| ≠ |R|) are padded with dummy nodes at zero cost so the algorithm can proceed. Missing edges are not supported: the graph must be complete between the two partitions.
Returns {total_cost, matching} where matching is a bidirectional map
(u => v and v => u for every matched pair). Dummy nodes are excluded from
the result.
Examples
iex> graph = Yog.from_edges(:undirected, [
...> {:a, :x, 10}, {:a, :y, 19}, {:a, :z, 8},
...> {:b, :x, 15}, {:b, :y, 17}, {:b, :z, 12},
...> {:c, :x, 8}, {:c, :y, 18}, {:c, :z, 9}
...> ])
iex> {cost, matching} = Yog.Matching.hungarian(graph, :min)
iex> cost
33
iex> matching[:a]
:z