You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用OrTools在Python中改进最大集合装箱(set packing)问题的求解效果

使用OrTools优化集合装箱问题的解决方案

你提到的集合装箱问题确实是NP-hard的,你的贪心算法虽然运行速度快,但解质量容易陷入局部最优。OrTools的CP-SAT求解器非常适合这类组合优化问题,能在合理时间内找到远优于贪心算法的解。下面是基于OrTools的实现,我会详细拆解每一步的思路:

核心思路

集合装箱的目标是用最少的两两不相交集合族覆盖所有输入集合,等价于在干扰图中寻找最小着色数(干扰图中相邻节点不能同色,每个颜色对应一个集合族)。OrTools的CP-SAT求解器通过建模约束和目标函数,能高效搜索最优或近似最优解。

实现步骤与代码

from ortools.sat.python import cp_model
from random import randint, sample, shuffle
import itertools

# 生成问题实例(和你的原代码逻辑一致)
N_SETS = 150_000
LO, HI = 100, 300
DOMAIN = list(range(150_000))
sets = [set(sample(DOMAIN, k=randint(LO, HI))) for _ in range(N_SETS)]

def optimize_set_packing(sets, time_limit_seconds=600):
    # 预处理:快速找出所有有交集的集合对(优化大规模数据的计算效率)
    print("预处理冲突集合对...")
    element_to_sets = {}
    for idx, s in enumerate(sets):
        for elem in s:
            if elem not in element_to_sets:
                element_to_sets[elem] = []
            element_to_sets[elem].append(idx)
    
    # 收集去重后的冲突对
    conflicting_pairs = set()
    for elem in element_to_sets:
        # 共享同一元素的集合两两冲突
        for pair in itertools.combinations(element_to_sets[elem], 2):
            conflicting_pairs.add(tuple(sorted(pair)))
    conflicting_pairs = list(conflicting_pairs)
    print(f"共识别到 {len(conflicting_pairs)} 对冲突集合")

    # 初始化CP-SAT模型
    model = cp_model.CpModel()
    num_sets = len(sets)

    # 定义变量:每个集合对应的组号,初始范围设为0到集合总数(求解器会自动缩小范围)
    group_vars = [model.NewIntVar(0, num_sets - 1, f"set_{i}_group") for i in range(num_sets)]

    # 添加约束:有交集的集合不能分配到同一组
    print("添加约束条件...")
    for (i, j) in conflicting_pairs:
        model.Add(group_vars[i] != group_vars[j])

    # 定义目标函数:最小化使用的组数量(组号从0开始,所以最大组号+1就是总组数)
    max_group = model.NewIntVar(0, num_sets - 1, "max_group")
    model.AddMaxEquality(max_group, group_vars)
    model.Minimize(max_group + 1)

    # 配置求解器参数
    solver = cp_model.CpSolver()
    solver.parameters.max_time_in_seconds = time_limit_seconds  # 设置时间限制,平衡解质量和速度
    solver.parameters.num_search_workers = 8  # 启用并行求解,根据CPU核心数调整
    solver.parameters.log_search_progress = True  # 可选:打印搜索进度

    # 启动求解
    print("开始求解...")
    status = solver.Solve(model)

    # 处理求解结果
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        result_type = "最优解" if status == cp_model.OPTIMAL else "高质量可行解"
        print(f"求解状态: {result_type}")
        print(f"使用的组数量: {solver.Value(max_group) + 1}")
        
        # 整理分组结果
        groups = {}
        for i in range(num_sets):
            group_id = solver.Value(group_vars[i])
            if group_id not in groups:
                groups[group_id] = set()
            groups[group_id].update(sets[i])
        
        # 验证结果正确性
        total_elements = sum(len(s) for s in sets)
        assert sum(len(g) for g in groups.values()) == total_elements
        
        return groups.values()
    else:
        print("未找到可行解")
        return None

# 运行优化(可多次运行,利用求解器的随机性探索更优解)
for _ in range(2):
    shuffle(sets)
    groups = optimize_set_packing(sets, time_limit_seconds=300)
    if groups:
        print(f"最终组数量: {len(groups)}")

关键优化说明

  • 预处理冲突对:通过元素映射的方式避免O(n²)的暴力比较,这是处理15万级大规模集合的核心优化。
  • 并行求解:启用多线程搜索,充分利用CPU多核资源加速求解。
  • 时间限制配置:由于问题是NP-hard,设置合理的时间限制可以在解质量和运行时间之间取得平衡,若时间允许可延长限制以追求更优解。
  • 目标函数设计:通过最小化最大组号间接减少总组数,是CP-SAT处理这类最小化问题的常用技巧。

与原贪心算法的对比

  • 原贪心算法是在线局部决策,按顺序将集合加入第一个可容纳的组,容易陷入局部最优,解质量波动大。
  • OrTools的CP-SAT是全局搜索,会探索多种分配可能性,能找到更接近最优的解,尤其是在时间充足的情况下。

内容的提问来源于stack exchange,提问作者Stop US and Israel

火山引擎 最新活动