【问题标题】:How to distribute work equally among workers by total count and total value?如何按总人数和总价值在工人之间平均分配工作?
【发布时间】:2020-07-13 18:45:52
【问题描述】:

我在处理分发问题时遇到了问题。

我有工人和工作案例。每个工作案例都有一个值。我需要分配工作案例,以便所有工作人员获得相同数量的总价值相似的案例(如果可能)。

总病例数和工人数是随机的。

解决这个问题的最佳方法是什么?我完全被卡住了。

我的第一个想法是按价值排序,然后像这样分发:

public class WorkCase
{
    public decimal Value { get; set; }
}

public class Worker
{
    public List<WorkCase> Cases { get; set; }
}

public static void Sort(List<WorkCase> cases, List<Worker> workers)
{
    cases = cases.OrderByDescending(c => c.Value).ToList();

    var wCount = workers.Count;

    int i = 0;

    while (cases.Any())
    {
        workers[i].Cases.Add(cases.First());

        if (i == workers.Count - 1)
           i = 0;
        else
           i++;
    }
}

但这对最后一个工人并不公平。 感谢您的帮助。

【问题讨论】:

标签: c# algorithm mathematical-optimization


【解决方案1】:

正如 Morinator 已经提到的,这是背包问题的一种变体,不存在完美的解决方案(除了纯粹的暴力破解和幸运地拥有完美匹配的数字)。

但是你可以相当接近。需要注意的是,较大的案例不如较小的案例灵活。举一个现实世界的例子,如果我想让你精确填充给定的容器,使用沙子比使用鹅卵石甚至岩石更容易。

这个真实世界的例子实际上在这里很有帮助。如果您想在最大限度地提高岩石/沙子比例(即尽可能多的岩石)的同时包装该容器,请先用岩石填充容器,然后使用沙子填充间隙。

您可以在此处使用您已经尝试过的完全相同的方法:首先分配最大的案例,最后分配最小的案例。但是,您的代码存在错误,因为您重复分配最大的案例而不是继续下一个案例。

因为您有多个工人,所以有一个次要考虑因素:尽可能将大型案例分配给他们。最简单的方法是始终将案例分配给当前工作量最低的工人(如果出现平局,您选择谁并不重要,只需选择第一个被捆绑的工人)。

修复您的代码:

public static void Sort(List<WorkCase> cases, List<Worker> workers)
{
    cases = cases.OrderByDescending(c => c.Value).ToList();

    foreach(var case in cases)
    {
       // Find the worker with the lowest case load

       var workersByCaseLoad = workers.OrderBy(w => w.Cases.Sum(c => c.Value);
       var workerWithLowestCaseLoad = workersByCaseLoad.First();

       // Assign this case to that worker

       workerWithLowestCaseLoad.Cases.Add(case);
    }
}

这并不总是为您提供与案例负载完全匹配的完美解决方案,但它是一个合理的近似值。有一些结果不是最佳的边缘示例,但这种情况很少见。
为了避免这些边缘情况,您的代码的复杂性必须显着增加。在大多数情况下,付出的代价是不值得的。

请注意,这可能不是最高效的解决方案,因为它涉及许多集合迭代。但假设工人数量和案件量合理(假设在一家公司内作为一个被吐槽的边界),考虑到今天的硬件,这应该不是问题。一些优化可以通过手动跟踪每个工人的总案件量来完成,类似于:

var workersByCaseLoad = workers.OrderBy(w => w.TotalCaseLoad);
var workerWithLowestCaseLoad = workersByCaseLoad.First();

workerWithLowestCaseLoad.Cases.Add(case);
workerWithLostCaseLoad.TotalCaseLoad += case.Value;

它不是很干净(它需要您手动处理这些值并始终保持完美同步),但它确实避免了每次都必须遍历每个工作人员分配的案例。


有趣的是,在处理开始时不知道完整案例列表的情况下(这意味着您无法对案例进行排序),该系统也能很好地工作。只要您将下一个案例分配给负载最低的人,它仍然是一个同样公平的游戏。

如果您的最后几个案例过大,您最终可能会得到一个不太完美的解决方案。这样想:你已经让事情保持平衡,然后必须分配一个更大的案例。这总是会导致问题。

但是如果你不能提前知道案例列表,那么你就不能指望对它们进行排序,然后你会得到一个不太完美但仍然合理平衡的结果。

【讨论】:

  • @DavidWaldo:很好看。这是早期版本的修正神器。立即修复。
【解决方案2】:

这个问题听起来可能是 NP 难题。看看Knapsack-Problem,也是类似的。如果您没有限制每个工作人员的案例数量,您可以将workCasesvalue 降序排序,然后始终将下一个workCase 分配给当前负载最低的工作人员。请注意,即使这种算法也不一定会产生最佳结果。

您可以尝试的另一件事是,首先为每个工人分配正确数量的随机作业,然后反复找到负载最低和最高的工人,让他们从低负载的工人和来自大负荷工人的轻松工作。
请注意,此解决方案也只是一种启发式方法,可能不会产生最佳结果。

但同样,这个问题似乎没有快速完美的解决方案,尝试找到一个 NP-hard 问题并将其简化为您的问题,以表明它无法解决(现在对您而言)。

【讨论】:

  • 另外,如果工人数不除案件数,则不存在任何解决方案。我想这已经解决了。
  • 关于你的评论,怎么样? 2个工人,3个案例(需要2小时,3小时,5小时)有一个完美的解决方案,但2不分3。
  • “让所有工人获得同等数量的案件”,我是否误解了这一点?
  • 您对问题的字母是正确的,但考虑到工作案例的权重(Value)和每个案例的“随机”数量(根据问题),似乎很有可能OP 说错了,并打算说“等量的总案件价值”。如果不是,那么权重似乎无关紧要,问题也很简单(简单除法)。
猜你喜欢
  • 2018-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-14
  • 1970-01-01
  • 1970-01-01
  • 2019-10-13
  • 1970-01-01
相关资源
最近更新 更多