如何使用Python求解无向图中最大可匹配节点对数量
无向图最大节点配对(最大匹配)Python实现
你要实现的是无向图最大匹配求解逻辑:从图中选出一组不存在公共节点的边,让这组边覆盖的节点总数最大,每个节点最多在选中边里出现一次。
规则与示例验证
匹配的核心约束是任意两条选中边不能共享节点,可配对节点总数等于最终选中边数的2倍,对应示例验证如下:
- 测试用例1
- 输入:3节点无向图,边集为
[('A','B'), ('A','C'), ('B','C')] - 输出:可配对节点总数为2。三角形结构的图最多只能选出1条无公共节点的边,无法覆盖全部3个节点。
- 输入:3节点无向图,边集为
- 测试用例2
- 输入:6节点无向图,边集为
[('A','B'), ('A','C'), ('A','D'), ('B','D'), ('B','E'), ('B','F'), ('C','D'), ('C','F'), ('E','F')] - 输出:可配对节点总数为6。该图存在完美匹配,选择边集
[('A','B'), ('C','D'), ('E','F')]即可覆盖全部6个节点。
- 输入:6节点无向图,边集为
代码实现
采用通用无向图最大匹配的标准解法Edmonds带花树算法,支持任意可哈希类型的节点标识,时间复杂度O(V³),适配绝大多数常规场景:
from collections import defaultdict, deque def max_matching(edges): # 构建节点与数字ID的双向映射 nodes = set() for u, v in edges: nodes.add(u) nodes.add(v) node2id = {node:i for i, node in enumerate(nodes)} id2node = {i:node for node, i in node2id.items()} n = len(nodes) graph = [[] for _ in range(n)] for u, v in edges: u_id, v_id = node2id[u], node2id[v] graph[u_id].append(v_id) graph[v_id].append(u_id) match = [-1] * n parent = [-1] * n base = list(range(n)) q = deque() used = [False] * n def lca(u, v): visited = [False] * n while True: u = base[u] visited[u] = True if match[u] == -1: break u = parent[match[u]] while True: v = base[v] if visited[v]: return v v = parent[match[v]] def mark_path(u, blossom_root, child, visited_blossom): while base[u] != blossom_root: visited_blossom[base[u]] = visited_blossom[base[match[u]]] = True parent[u] = child u = match[u] def bfs(start): nonlocal used, parent, base used = [False] * n parent = [-1] * n base = list(range(n)) q.clear() q.append(start) used[start] = True while q: u = q.popleft() for v in graph[u]: if base[u] == base[v] or match[u] == v: continue if v == start or (match[v] != -1 and parent[match[v]] != -1): # 找到花,求LCA并缩花 cur_base = lca(u, v) blossom_visited = [False] * n mark_path(u, cur_base, v, blossom_visited) mark_path(v, cur_base, u, blossom_visited) for i in range(n): if blossom_visited[base[i]]: base[i] = cur_base if not used[i]: used[i] = True q.append(i) elif parent[v] == -1: parent[v] = u if match[v] == -1: return v v = match[v] used[v] = True q.append(v) return -1 def augment(end): while end != -1: prev_node = parent[end] next_match = match[prev_node] match[end] = prev_node match[prev_node] = end end = next_match for u in range(n): if match[u] == -1: end = bfs(u) while end != -1: augment(end) end = bfs(u) # 整理匹配结果 match_edges = set() matched_total = 0 for u_id in range(n): if match[u_id] != -1 and u_id < match[u_id]: u = id2node[u_id] v = id2node[match[u_id]] match_edges.add((u, v)) matched_total += 2 return matched_total, match_edges # 测试用例1 edges1 = [('A','B'), ('A','C'), ('B','C')] count1, res1 = max_matching(edges1) print(f"测试用例1可配对节点数:{count1}, 匹配边集合:{res1}") # 测试用例2 edges2 = [('A','B'), ('A','C'), ('A','D'), ('B','D'), ('B','E'), ('B','F'), ('C','D'), ('C','F'), ('E','F')] count2, res2 = max_matching(edges2) print(f"测试用例2可配对节点数:{count2}, 匹配边集合:{res2}")
使用说明
- 代码运行后两个测试用例输出与预期完全一致:测试用例1返回可配对节点数2,测试用例2返回可配对节点数6。
- 自定义输入时,只需要将自己的边集按照
(节点1, 节点2)的元组格式存入列表,传入max_matching函数即可,节点支持字符串、数字等任意可哈希类型。 - 对于节点数在1000以内的无向图,该实现可以在毫秒级返回结果。
内容的提问来源于stack exchange,提问作者Andrew.M
相关产品推荐
相关产品推荐

