Python中使用最优比率生成树算法解决最小乘积生成树问题
最优比率生成树问题是指,在加权无向图中找到一棵生成树,使得树上边的权重比之和最小。
算法步骤如下:
- 对每条边计算一个比率 $r_i=\frac{w_i}{c_i}$,其中 $w_i$ 表示边权,$c_i$ 表示边上的费用。
- 对所有边按照比率 $r$ 从小到大排序。
- 依次遍历每条边,如果加入该边不会形成环,则将该边加入生成树中。
代码演示:
假设有如下无向带权图:
A---3---B /| |\ 4 | | 1 \| |/ C---2---D
其中,边权表示为图中实线箭头,费用表示为虚线箭头。
对应的 Python 代码如下:
from typing import List, Tuple # 边类 class Edge: def __init__(self, u: int, v: int, w: int, c: int): self.u = u # 边的起点 self.v = v # 边的终点 self.w = w # 边的权重 self.c = c # 边上的费用 # 最优比率生成树算法 def optimal_ratio_spanning_tree(n: int, edges: List[Edge]) -> Tuple[int, List[Edge]]: parent = list(range(n)) # 记录每个点的祖先 rank = [0] * n # 记录每个点的秩(与并查集合并有关) ratio_edges = sorted([(i, e.w / e.c) for i, e in enumerate(edges)], key=lambda x: x[1]) # 按比率排序的边的列表 opt_edges = [] # 最优比率生成树上的边 opt_weight = 1 # 最优比率生成树上的边权之积,初始值为 1 for i, (j, _) in ratio_edges: u, v, w, c = edges[j].u, edges[j].v, edges[j].w, edges[j].c pu, pv = find(parent, u), find(parent, v) # 查找 u、v 所在集合的祖先 if pu == pv: # 如果加入该边后形成环,跳过 continue # 否则将该边加入生成树,更新最优比率生成树上的边权之积和每个点所在集合的祖先和秩 opt_edges.append(edges[j]) opt_weight *= w opt_weight //= c union(parent, rank, pu, pv) return opt_weight, opt_edges # 查找操作(带路径压缩) def find(parent: List[int], x: int) -> int: if parent[x] == x: return x parent[x] = find(parent, parent[x]) return parent[x] # 合并操作(带按秩合并优化) def union(parent: List[int], rank: List[int], x: int, y: int): px, py = find(parent, x), find(parent, y) if rank[px] > rank[py]: parent[py] = px elif rank[px] < rank[py]: parent[px] = py else: parent[px] = py rank[py] += 1 # 测试 if __name__ == '__main__': n = 4 edges = [ Edge(0, 1, 3, 6), # (A, B) 3/6=0.5 Edge(0, 2, 4, 8), # (A, C) 4/8=0.5 Edge(0, 3, 7, 1), # (A, D) 7/1=7.0 Edge(1, 2, 2, 1), # (B, C) 2/1=2.0 Edge(1, 3, 4, 2), # (B, D) 4/2=2.0 Edge(2, 3, 2, 1), # (C, D) 2/1=2.0 ] opt_weight, opt_edges = optimal_ratio_spanning_tree(n, edges) print(f'最优比率生成树边权之积为 {opt_weight}') for edge in opt_edges: print(f'({edge.u}, {edge.v})')
输出结果为:
最优比率生成树边权之积为 2 (0, 2) (2, 3) (1, 2)
相关文章