Python中使用最优比率生成树算法解决最小乘积生成树问题

2023-04-11 00:00:00 生成 比率 乘积

最优比率生成树问题是指,在加权无向图中找到一棵生成树,使得树上边的权重比之和最小。

算法步骤如下:

  1. 对每条边计算一个比率 $r_i=\frac{w_i}{c_i}$,其中 $w_i$ 表示边权,$c_i$ 表示边上的费用。
  2. 对所有边按照比率 $r$ 从小到大排序。
  3. 依次遍历每条边,如果加入该边不会形成环,则将该边加入生成树中。

代码演示:

假设有如下无向带权图:

   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)

相关文章