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

基于Google OR-Tools实现分组标准差最小化的代码求助

学生分组优化问题(Google OR-Tools)

我正在使用Google OR-Tools编写程序,将若干学生分配至数量可变的小组中。每位学生可选择一位希望同组的“好友”。
约束条件:

  • 每位学生必须被分配至一个小组;
  • 要求选择同组的学生必须被分配到同一小组。
    我希望通过最小化各组学生数量的标准差来提升分配的公平性,目前已完成基础约束的代码,但卡在了实现最小化目标的环节,请求提供对应的示例代码。

现有代码(C#)

Student student = new Student("123", "Sam", null);
Student student2 = new Student("234", "Jos", student);
Student student3 = new Student("345", "Jef", null);
Student student4 = new Student("456", "Jane", student3);

List<Student> students = new List<Student>()
{
    student, student2, student3, student4
};

const int GROUPS = 2;

CpModel model = new CpModel();

// 创建分配变量:assignments[i,j]为1表示第i个学生分配到第j组
BoolVar[,] assignments = new BoolVar[students.Count,GROUPS];

for(int i = 0; i < students.Count; i++)
{
    for(int j = 0; j < GROUPS; j++)
    {
        assignments[i, j] = model.NewBoolVar($"assingments_{i}_{j}");
    }
}

// 约束:每个学生必须分配到恰好一个小组
for(int i = 0; i < students.Count; i++)
{
    IList<ILiteral> groupsForStudent = new List<ILiteral>();

    for(int j = 0; j < GROUPS; j++)
    {
        groupsForStudent.Add(assignments[i, j]);
    }

    model.AddExactlyOne(groupsForStudent);
}

// 约束:好友必须分配到同一小组
for(int i = 0; i < students.Count; i++)
{
    Student first = students[i];

    if(first.Friend != null)
    {
        int otherIdx = students.IndexOf(first.Friend);

        for(int j = 0; j < GROUPS; j++)
        {
            model.AddAbsEquality(assignments[i, j], assignments[otherIdx, j]);
        }
    }
}

解决方案:添加最小化标准差的目标函数

标准差的优化等价于最小化各组人数与平均值差的平方和(标准差是平方和的平方根,两者优化方向完全一致),这种方式更适配整数规划模型的处理逻辑。以下是完整的补充代码:

// 计算总学生数与理想分组人数(平均值)
int totalStudents = students.Count;
int avgSize = totalStudents / GROUPS;

// 定义每个小组的人数变量
IntVar[] groupSizes = new IntVar[GROUPS];
for (int j = 0; j < GROUPS; j++)
{
    groupSizes[j] = model.NewIntVar(0, totalStudents, $"group_size_{j}");
    // 约束:组人数等于分配到该组的学生数量之和
    model.Add(LinearExpr.Sum(Enumerable.Range(0, students.Count).Select(i => assignments[i, j])) == groupSizes[j]);
}

// 计算组人数与平均值差的平方和
IntVar sumOfSquares = model.NewIntVar(0, totalStudents * totalStudents, "sum_of_squares");
LinearExpr sumExpr = new LinearExpr();

for (int j = 0; j < GROUPS; j++)
{
    // 计算 groupSizes[j]^2
    IntVar sizeSquared = model.NewIntVar(0, totalStudents * totalStudents, $"size_squared_{j}");
    model.AddMultiplicationEquality(sizeSquared, groupSizes[j], groupSizes[j]);
    
    // 累加 (size - avg)^2 = size² - 2*avg*size + avg²
    sumExpr.Add(sizeSquared);
    sumExpr.AddTerm(groupSizes[j], -2 * avgSize);
}
sumExpr.Add(avgSize * avgSize * GROUPS);
model.Add(sumExpr == sumOfSquares);

// 设置目标:最小化平方和(等价于最小化标准差)
model.Minimize(sumOfSquares);

// 求解模型并输出结果
CpSolver solver = new CpSolver();
CpSolverStatus status = solver.Solve(model);

if (status == CpSolverStatus.Optimal || status == CpSolverStatus.Feasible)
{
    Console.WriteLine("最优分配结果:");
    for (int j = 0; j < GROUPS; j++)
    {
        Console.WriteLine($"组 {j+1} 人数:{solver.Value(groupSizes[j])}");
        Console.WriteLine("成员列表:");
        for (int i = 0; i < students.Count; i++)
        {
            if (solver.BooleanValue(assignments[i, j]))
            {
                Console.WriteLine($"  - {students[i].Name} (ID: {students[i].Id})");
            }
        }
        Console.WriteLine();
    }
    Console.WriteLine($"组人数与平均值差的平方和:{solver.Value(sumOfSquares)}");
}
else
{
    Console.WriteLine("未找到可行分配方案");
}

关键说明

  1. 用平方和代替标准差:避免浮点运算,适配OR-Tools的整数变量处理机制,且优化目标完全等价
  2. 变量定义:通过LinearExpr.Sum计算每组的实际人数,建立分配变量与组人数的关联
  3. 平方计算:使用AddMultiplicationEquality实现变量的平方运算,符合CP-Solver的约束定义规则

内容的提问来源于stack exchange,提问作者Sam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 16:47:08