什么是均匀填充未分类的不同大小的“桶”列表的最有效方法

时间:2013-01-08 16:44:25

标签: c++ algorithm

假设我有bucket未排序列表。 (每个存储桶都有size属性。)假设我有一个数量Q,我必须尽可能均匀地分布在存储桶列表中(最小化最大值)。

如果存储桶排序的尺寸越来越大,那么解决方案就很明显了:完全填充每个存储桶,比如buckets[i],直到Q/(buckets.length-i) <= buckets[i]->size,然后填充其余的具有相同数量的存储桶Q/(buckets.length-i),如图所示:

Filling buckets.

如果存储桶排序,最有效的解决方法是什么?

我只能想到像这样迭代(伪代码):

while Q > 0
    for i in 0..buckets.length-1
        q = Q/(buckets.length-i)
        if q > buckets[i]->size
            q = buckets[i]->size
        buckets[i]->fill(q)
        Q -= q

但我不确定是否有更好的方法,或者排序列表会更有效。

(我面临的实际问题还有更多,例如这个“未排序”的列表实际上是按一个单独的属性“rank”排序的,它确定哪些桶可以获得额外的填充量因此,例如,使用 sort-then-fill 方法,我会根据桶大小和排名对列表进行排序。但是知道答案可以帮助我弄清楚剩下的。)

6 个答案:

答案 0 :(得分:3)

在许多情况下,如果数据被排序,解决方案“如此简单”或“如此有效”,但如果数据不是很复杂或无效,那么最好的解决方案通常是先对数据进行排序然后寻求简单有效的解决方案。即使这意味着您将首先要有对数据进行排序的开销,但是有很多非常好的排序算法可用于几乎任何目的,并且在许多情况下“首先对数据进行排序然后应用简单有效的算法”的总开销对它“仍然低于”没有对数据进行排序并对其应用非常复杂,无效的算法“。

您需要按不同键排序的数据这一事实对我来说意味着您需要两个列表,每个列表按不同的标准排序。除非我们在这里讨论数千个桶,否则第二个列表的内存开销很可能不是问题(毕竟两个列表只包含指向桶对象的指针,这意味着每个指针有4/8个字节,具体取决于事实,如果你有32或64位代码)。一个列表具有按大小排序的存储桶,另一个列表具有按“排名”排序的存储桶,当按照您的问题中所述添加新项目时,使用“按大小排序”列表,同时使用“按排序排序”列表就像你现在使用它一样。

答案 1 :(得分:2)

我认为在线性时间内可能有可能,但是我在某个时刻陷入困境。也许你可以解决问题,也许这种方式无法解决。

考虑以下算法。

基于二进制搜索,我们希望找到最小的未完全填充的存储桶。在线性时间内找到桶中的这样一个桶可能是可能的,但正如我所说,我被困在这里。一旦我们找到了这个桶,其余部分变得微不足道,因为对于所有较小的桶,我们总结它们的大小,从要放置的物品总数中减去它,除以大于或等于我们刚刚找到的桶的桶的数量

以下是尝试解决问题:什么是最小的未完全填充的存储桶?该算法受QuickSelect驱动。

选择一个枢轴桶。看它是否比我们正在寻找的水桶更小或更大。 (这一步很简单。)

  • 如果它更小,则将所有桶的大小总和小于或等于此值,从项目总数中减去此总和,并继续搜索包含所有较大桶的集合。

  • 如果它更大,我们将不得不做类似的事情,但现在减去放置在大于这个桶的所有桶中的项目数。 我们不知道要放在这些桶中的物品数量。这就是问题... 但如果我们知道,我们会继续搜索包含所有较小存储桶的集合。

如果此算法有效,它将在随机数据元素的预期线性时间中运行(请参阅QuickSelect)。

答案 2 :(得分:2)

如果你可以确定q,那么填充每个桶的适当最小级别使得总数为Q,而不是线性解决方案是明确的:

for (bucket b : buckets)
{
    int f = max(b.capacity(), q);
    b.fill(f);
}

所以问题在于确定水平q。

您可以二进制搜索q。我们知道q是min(b.capacity)max(b.capacity)之间的整数。即:

  1. 从候选q'开始,介于最小(容量)和最大(容量)
  2. 之间
  3. 通过计算使用Q'产生的总金额q'
  4. if(Q' > Q)而不是重复q'减少一半
  5. 如果(Q' < Q)比重复q'增加一半
  6. return q = q'
  7. 第2步的每次传递都是O(N),并且会有L = max(capacity) - min(capacity)

    的log(L)传递

    这比在L << N

    时排序更有效

    足够的统计数据是将桶减少为直方图:

    unordered_set<int,int> bucket_capacity;
    
    for (bucket b : buckets)
        bucket_capacity[b.capacity]++;
    

    这仍然是线性的,但是在最坏的情况下不会让我们太多,因为桶可能有不同的大小,但是它将通过限制为L所以效率现在是O(min(L,N) * logL)

    L << N效率变为O(LlogL)

    时,这也很有效

    我怀疑以下情况属实,但不是100%:在L >> N的情况下,可以显示没有线性解决方案。首先,我们假设我们有线性解决方案。然后,我们使用此解决方案作为工具在线性时间内进行比较排序。已经表明在线性时间内不可能进行比较排序,因此我们的假设必须是假的,并且没有线性解。

答案 3 :(得分:1)

另一种想法如下。确定每个桶的平均项数。然后尝试使用该数字填充所有桶(通常,并非所有桶都可以容纳该数量的项目。)

之后,您将有许多剩余项目放在存储桶中(因为并非所有项目都适合上一次迭代)以及可以容纳更多项目的存储桶列表(在前一次迭代中计算) )。

再次,根据要分配的剩余项目数计算剩余存储桶上的平均分配数量。

重复,直到放置所有项目。

我预计O(n * log n)的运行时间,但没有分析它。它与 sort-then-fill 方法的运行时间相同,但是,如果您的桶只有有限数量的不同大小,预计会更低,例如:有些很小,有些很大,有些是巨大的。

答案 4 :(得分:1)

在一个步骤中,您从n个有限容量的未分类桶,k个无限桶(您存储k,而不是那些列表,并在第一次迭代k = 0)和一定量的水w开始。在O(n)时间内,我们将使用n',k',w'将问题减少到另一个实例,其中n'&lt; c * n表示常数c < 1.迭代这个过程将解决问题(一旦n是常数,你可以在恒定时间内解决它)在线性时间内:n + c * n + c ^ 2 * n + ... = O(n)。

在所有n个有限容量中,选择中值(即选择一个使得一半容量更高而一半更低)。这可以在O(n)时间(选择算法)中完成。计算1)较低容量和2)中位数乘以较高容量(包括无限容量)桶数的总和。

如果小于w,你知道你需要更高的水桶,所以特别是所有较低容量的水桶都会被填满。删除它们,从w中删除它们的容量总和,你就完成了这次迭代,n'= n / 2.

另一方面,如果总和大于w,则您知道没有桶将填充到中等容量或更高。因此可以移除所有更高容量的桶,并且将它们的数量添加到无限桶的数量。 w保持不变。再次,n'= n / 2,我们就完成了。

跳过一些简单的细节(特别是如何处理许多桶具有完全相同容量的情况)以保持简短。一旦你知道了正确的水位,你也需要在最后进行一些清理,为每个“无限”(即非满)桶设置它。

答案 5 :(得分:-1)

为什么需要对存储桶列表进行排序? 只需遍历桶两次。

第一次计算所有尺寸。从那里你可以说,“我想在每个桶里有K个物品”

第二次,填满了桶。