从一组可用包装中包装物品

时间:2015-06-11 20:41:48

标签: c# algorithm

假设客户正在订购商品 - 在这种情况下,结果是他们订购了商品的176(totalNeeded)。< / p>

数据库有5条与此项目关联的记录,此项目可以存储在:

{5 pack, 8 pack, 10 pack, 25 pack, 50 pack}

包装的一个粗略方法是:

 Sort the array from biggest to smallest.

 While (totalPacked < totalNeeded) // 176
      {
         1. Maintain an <int, int> dictionary which contains Keys of pack id's, 
         and values of how many needed

         2. Add the largest pack, which is not larger than the amount remaining to pack, 
         increment totalPacked by the pack size

         3. If any remainder is left over after the above, add the smallest pack to reduce 
         waste
               e.g., 4 needed, smallest size is 5, so add one 5; one extra item packed
      }

基于上述逻辑,结果将是:

您需要:3 x 50包,1 x 25包,1 x 5包

总项目:180

超额 = 4项; 180 - 176

上面的代码并不太难,我让它在本地工作。但是,它并不是真正包装此物品的最佳方式。注意:&#34;最好&#34;意味着,最小的过剩量。

因此......我们有8个装,我们需要176. 176/8 = 22.送客户22 x 8包,他们将得到他们所需要的。再次,这比我写的伪代码更简单...看看所需的总数是否可以被数组中的任何包整除 - 如果是这样,&#34;至少&#34;我们知道我们可以准确地回到22 x 8包装上。

如果数字不能被数组值整除,我试图确定可以组合数组值以至少达到我们需要的数量的可能方式(176),然后通过以下方式对不同的组合进行评分需要的包总数。

如果有人可以对此主题进行一些阅读,或者有任何建议让我入门,我们将不胜感激。

谢谢

3 个答案:

答案 0 :(得分:3)

这是Subset Sum Problem(优化版本)

的变体

虽然问题是NP-Complete,但是通过遵循递归公式,它有一个非常有效的伪多项式时间Dynamic Programming解决方案:

D(x,i) = false   x<0
D(0,i) = true
D(x,0) = false   x != 0
D(x,i) = D(x,i-1) OR D(x-arr[i],i

动态编程解决方案将构建一个表,其中元素D[x][i]==true如果您可以使用第一个i种包来建立总和x
毋庸置疑D[x][n] == true如果有一个解决方案,所有可用包总和为x。 (其中n是您拥有的包的总数)。

要获得“最接近的较高数字”,您只需要创建一个大小为W+pack[0]-1的表格(pack[0]是最小的可用包,W是您要查找的总和),并选择产生最接近true的{​​{1}}的值。

如果您希望为不同的包类型赋予不同的值,这将变为Knapsack Problem,这非常相似 - 但使用值而不是简单的真/假。

获取之后选择的实际“项目”(包)是通过返回表格并回溯您的步骤来完成的。 This threadthis thread详细说明如何通过更多细节来实现它。

答案 1 :(得分:2)

如果这个示例问题真正代表了您正在解决的实际问题,那么它足够小,可以使用递归尝试每次使用暴力的组合。例如,我找到了6,681个本地最大化的独特包装,总共205个,总共有176个项目。具有最小数量的包的(唯一)解决方案是6,即{2-8,1-10,3-50}。算法的总运行时间为8毫秒。

public static List<int[]> GeneratePackings(int[] packSizes, int totalNeeded)
{
    var packings = GeneratePackingsInternal(packSizes, 0, new int[packSizes.Length], totalNeeded);
    return packings;
}

private static List<int[]> GeneratePackingsInternal(int[] packSizes, int packSizeIndex, int[] packCounts, int totalNeeded)
{
    if (packSizeIndex >= packSizes.Length) return new List<int[]>();

    var currentPackSize = packSizes[packSizeIndex];
    var currentPacks = new List<int[]>();

    if (packSizeIndex + 1 == packSizes.Length) {
        var lastOptimal = totalNeeded / currentPackSize;
        packCounts[packSizeIndex] = lastOptimal;
        return new List<int[]> { packCounts };
    }

    for (var i = 0; i * currentPackSize <= totalNeeded; i++) {
        packCounts[packSizeIndex] = i;
        currentPacks.AddRange(GeneratePackingsInternal(packSizes, packSizeIndex + 1, (int[])packCounts.Clone(), totalNeeded - i * currentPackSize));
    }

    return currentPacks;
}

该算法非常简单

  1. 遍历5个包的每个组合。
  2. 在扣除指定数量的5件装后,从剩余金额中循环通过每组8件装的组合。
  3. 等50包。对于50包计数,直接划分余数。
  4. 递归收集所有组合(因此它会动态处理任何一组包大小)。
  5. 最后,一旦找到所有组合,很容易找到所有包装,浪费最少,包装数量最少:

    var packSizes = new int[] { 5, 8, 10, 25, 50 };
    var totalNeeded = 176;
    
    var result = GeneratePackings(packSizes, totalNeeded);
    Console.WriteLine(result.Count());
    var maximal = result.Where (r => r.Zip(packSizes, (a, b) => a * b).Sum() == totalNeeded).ToList();
    var min = maximal.Min(m => m.Sum());
    var minPacks = maximal.Where (m => m.Sum() == min).ToList();
    
    foreach (var m in minPacks) {
        Console.WriteLine("{ " + string.Join(", ", m) + " }");
    }
    

    以下是一个工作示例:https://ideone.com/zkCUYZ

答案 2 :(得分:0)

此部分解决方案专门针对您的包装尺寸5, 8, 10, 25, 50。仅限于订单尺寸至少40大。在较小尺寸上存在一些间隙,您必须以另一种方式填充(特别是在6, 7, 22, 27之类的值等。)

显然,获得任何数字不是5的倍数的唯一方法是使用8包。

  1. 确定模块化算术所需的8个包的数量。自8 % 5 == 3起,每个8包将在此周期中处理5的不同余数:0, 2, 4, 1, 3。像
  2. 这样的东西
    public static int GetNumberOf8Packs(int orderCount) {
        int remainder = (orderCount % 5);
        return ((remainder % 3) * 5 + remainder) / 3;
    }
    

    在你176的例子中。176 % 5 == 1这意味着你需要2个8包。

    1. 减去8包的值,得到你需要填充的5的倍数。此时您仍需要提供176 - 16 == 160

    2. 通过整数除法填写所有50个装。跟踪剩菜。

    3. 现在只需根据需要调整5, 10, 25包。显然首先使用较大的值。

    4. 您的代码可能看起来像这样:

      public static Order MakeOrder(int orderSize)
      {
          if (orderSize < 40)
          {
              throw new NotImplementedException("You'll have to write this part, since the modular arithmetic for 8-packs starts working at 40.");
          }
      
          var order = new Order();
          order.num8 = GetNumberOf8Packs(orderSize);
          int multipleOf5 = orderSize - (order.num8 * 8);
          order.num50 = multipleOf5 / 50;
          int remainderFrom50 = multipleOf5 % 50;
          while (remainderFrom50 > 0)
          {
              if (remainderFrom50 >= 25)
              {
                  order.num25++;
                  remainderFrom50 -= 25;
              }
              else if (remainderFrom50 >= 10)
              {
                  order.num10++;
                  remainderFrom50 -= 10;
              }
              else if (remainderFrom50 >= 5)
              {
                  order.num5++;
                  remainderFrom50 -= 5;
              }
          }
      
          return order;
      }
      

      A DotNetFiddle