You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求稳健数字列表拆分方法:满足和占比与数量均衡条件

数字列表双约束子集拆分问题

需求

将数字列表拆分为两个子集,需同时满足:

  • 子集总和占原列表总和的比例落在指定区间(例如45%-55%)
  • 两个子集的元素数量差异不超过总数量的5%(即数量大致相等)

现有方案局限

当前实现了一种基于正态分布概率的随机分配方法,仅在添加循环控制数量条件后适用于小型列表。该方案需要数百次迭代才能大概率找到符合要求的拆分,缺乏稳健性,需要无需反复试错的可靠方案。

现有实现代码

import numpy as np
from scipy.stats import norm

def split_into_normal_distributions(numbers, target_percentage):
    # 计算两个正态分布的均值和标准差
    mean_1 = np.mean(numbers) * target_percentage
    mean_2 = np.mean(numbers) * (1 - target_percentage)
    std = np.std(numbers)

    # 初始化子集
    subset_1 = []
    subset_2 = []

    for num in numbers:
        # 计算当前数字在两个分布中的概率密度
        pdf_1 = norm.pdf(num, loc=mean_1, scale=std)
        pdf_2 = norm.pdf(num, loc=mean_2, scale=std)

        # 计算分配概率比例
        ratio = pdf_1 / (pdf_1 + pdf_2)

        # 根据概率随机分配到子集
        if np.random.rand() < ratio:
            subset_1.append(num)
        else:
            subset_2.append(num)

    return subset_1, subset_2

# 示例数字列表
numbers = [10, 20, 30, 40, 50, 60, 70, 80,10,20,25,20,21,26,65,95,84,65,2,3,6,198,16,651,984,651,35,61,651,16,56,651,651,651,2,32,615,651,984,615,351,651,651,3,5]

# 按目标比例40%/60%拆分
subset_1, subset_2 = split_into_normal_distributions(numbers, 0.4)

print("子集1(目标占比40%):", subset_1, "实际占比:", sum(subset_1)/sum(numbers), "元素数量:", len(subset_1))
print("子集2(目标占比60%):", subset_2, "实际占比:", sum(subset_2)/sum(numbers), "元素数量:", len(subset_2))
print("总元素数:", len(numbers))

稳健解决方案

1. 整数规划法(精确解)

将问题建模为整数线性规划,能精确满足所有约束,适合中等规模的列表:

from pulp import LpProblem, LpVariable, LpMinimize, lpSum, value

def split_with_integer_programming(numbers, target_low, target_high):
    total_elements = len(numbers)
    total_sum = sum(numbers)
    # 数量约束:每个子集元素数在总数量的47.5%-52.5%之间(差异≤5%)
    min_subset_size = int(total_elements * 0.475)
    max_subset_size = int(total_elements * 0.525)

    # 创建规划问题
    prob = LpProblem("SubsetSplit", LpMinimize)
    # 定义二进制变量:x[i]=1表示第i个元素分到子集1,0表示分到子集2
    x = [LpVariable(f"x_{i}", cat="Binary") for i in range(total_elements)]

    # 目标函数:最小化子集1总和与目标中间值的偏差
    target_mid_sum = total_sum * (target_low + target_high) / 2
    prob += lpSum([x[i] * numbers[i] for i in range(total_elements)]) - target_mid_sum

    # 总和比例约束
    prob += lpSum([x[i] * numbers[i] for i in range(total_elements)]) >= total_sum * target_low
    prob += lpSum([x[i] * numbers[i] for i in range(total_elements)]) <= total_sum * target_high

    # 元素数量约束
    prob += lpSum(x) >= min_subset_size
    prob += lpSum(x) <= max_subset_size

    # 求解
    prob.solve()

    # 提取结果
    subset_1 = [numbers[i] for i in range(total_elements) if value(x[i]) == 1]
    subset_2 = [numbers[i] for i in range(total_elements) if value(x[i]) == 0]
    return subset_1, subset_2

# 使用示例
subset_1, subset_2 = split_with_integer_programming(numbers, 0.4, 0.6)
print("整数规划结果:")
print(f"子集1:总和占比{sum(subset_1)/sum(numbers):.2%},元素数{len(subset_1)}")
print(f"子集2:总和占比{sum(subset_2)/sum(numbers):.2%},元素数{len(subset_2)}")

2. 贪心+局部调整法(高效近似解)

适合大规模列表,通过贪心分配+局部元素交换快速满足约束:

def split_with_greedy_adjustment(numbers, target_low, target_high):
    sorted_nums = sorted(numbers, reverse=True)
    subset_1, subset_2 = [], []
    total_sum = sum(numbers)
    total_elements = len(numbers)
    min_size = int(total_elements * 0.475)
    max_size = int(total_elements * 0.525)

    # 第一步:交替分配大元素,优先平衡数量
    for num in sorted_nums:
        if len(subset_1) <= len(subset_2):
            subset_1.append(num)
        else:
            subset_2.append(num)

    # 调整数量到约束范围内
    while len(subset_1) < min_size:
        move_num = min(subset_2)
        subset_2.remove(move_num)
        subset_1.append(move_num)
    while len(subset_1) > max_size:
        move_num = min(subset_1)
        subset_1.remove(move_num)
        subset_2.append(move_num)

    # 第二步:调整总和到目标区间
    current_ratio = sum(subset_1) / total_sum
    while not (target_low <= current_ratio <= target_high):
        if current_ratio < target_low:
            # 交换子集2的最大元素和子集1的最小元素,提升子集1总和
            s1_min, s2_max = min(subset_1), max(subset_2)
            if s2_max <= s1_min:
                break  # 无法再调整
            subset_1.remove(s1_min)
            subset_2.remove(s2_max)
            subset_1.append(s2_max)
            subset_2.append(s1_min)
        else:
            # 交换子集1的最大元素和子集2的最小元素,降低子集1总和
            s1_max, s2_min = max(subset_1), min(subset_2)
            if s1_max <= s2_min:
                break
            subset_1.remove(s1_max)
            subset_2.remove(s2_min)
            subset_1.append(s2_min)
            subset_2.append(s1_max)
        current_ratio = sum(subset_1) / total_sum

    return subset_1, subset_2

# 使用示例
subset_1, subset_2 = split_with_greedy_adjustment(numbers, 0.4, 0.6)
print("\n贪心调整结果:")
print(f"子集1:总和占比{sum(subset_1)/sum(numbers):.2%},元素数{len(subset_1)}")
print(f"子集2:总和占比{sum(subset_2)/sum(numbers):.2%},元素数{len(subset_2)}")

内容的提问来源于stack exchange,提问作者M. Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 17:40:56