【问题标题】:How to split an array into two subsets and keep sum of sub-values of array as equal as possible如何将数组拆分为两个子集并使数组的子值之和尽可能相等
【发布时间】:2012-10-18 04:44:15
【问题描述】:

我真的需要一位算法大师!所以问题是我得到了一个这样的数组:

[
    [870, 23]
    [970, 78]
    [110, 50]
]

我想把它拆分,让它看起来像这样:

// first array
[
    [970, 78]
]
// second array
[
    [870, 23]
    [110, 50]
]

那么现在,为什么我也想让它看起来像这样?

因为我想让子值的总和尽可能相等。所以970 大约是870 + 11078 大约是23 + 50。 所以在这种情况下这很容易,因为如果你只是拆分它们并且只查看第一个子值它已经是正确的但我想检查两者并保持它们尽可能相等,这样它也可以使用一个包含 100 个子数组的数组!所以如果有人能告诉我我可以用什么算法来编程,那就太好了!

天平:

  • ~1000 个元素(子列表)在数组中
  • 元素是最大为 10^9 的整数

我正在寻找“足够接近的解决方案” - 它不一定是确切的最佳解决方案。

【问题讨论】:

  • @FelixKling 不,这不是重复的,他想根据一个总和而不是两个总和将一个数组分成两个子数组,实际上还有更多的问题是问这个。
  • 嗯,我还是会说这基本上是同一个问题,只是更难;)答案链接到en.wikipedia.org/wiki/Partition_problem,这应该为您解决该问题提供了一个良好的开端。
  • @FelixKling 是的,没错,但不仅仅是有点难,解决起来要困难得多。
  • @FelixKling 这不是分区问题,其他答案也是错误的。分区问题是当您需要从数组中获取子集时,在这里您只需要选择“分区点” - 您获取子数组,而不是子集。通过检查每个可能的“分区”点并检查总和,蛮力解决方案是O(n^2)。 (当然,除非它可以是任何子集,而不是子数组 - 然后是分区问题,但问题只是令人困惑)

标签: javascript jquery arrays algorithm


【解决方案1】:

首先,正如已经确定的那样 - 问题是NP-Hard,具有简化形式的分区问题。

Reduction:
给定partition problem 的实例,创建每个大小为 1 的列表。结果就是这个问题。

以上结论
这个问题是NP-Hard,没有已知的多项式解。

第二,由于问题的规模,任何指数和伪多项式解决方案都需要很长时间才能工作。

第三,它留给我们启发式和近似算法。

我建议采用以下方法:

  1. 标准化子列表的比例,因此所有元素都将采用相同的比例(例如,所有元素都将被标准化为范围[-1,1],或者所有元素都将被标准化为标准normal distribution)。
  2. 创建一个新列表,其中每个元素将是规范化列表中匹配子列表的总和。
  3. 使用为子集和/分区问题开发的一些近似或启发式解决方案。

结果不会是最优的,但是这里最优是真的无法企及。

【讨论】:

    【解决方案2】:

    根据我从原始帖子下的讨论中收集到的信息,您不是在寻找单个分裂点,而是希望将所有对分布在两个集合中,这样两个集合中的每个集合中的总和大约为相等。

    既然可以接受足够接近的解决方案,也许您可​​以尝试基于模拟退火的方法? (见http://en.wikipedia.org/wiki/Simulated_annealing

    简而言之,这个想法是您首先将每对随机分配到左集或右集。 接下来,您可以通过

    • a) 将随机选择的对从左侧移动到右侧集合,
    • b) 移动随机选择的对 从右到左的集合,或
    • c) 两者兼而有之。

    接下来,确定这个新状态是否比当前状态更好或更差。如果更好,请使用它。 如果更差,只有在被接受概率函数接受的情况下才取,这是一个函数 最初允许使用更差的状态,但随着时间的推移(或 SA 术语中的“温度降低”)越来越少地支持它们。 经过大量的迭代(比如 100.000 次),你应该会得到一个不错的结果。

    (可选)多次重新运行此算法,因为它可能会陷入局部最优(尽管接受概率函数会尝试解决此问题)。

    这种方法的优点是易于实施,您可以自己决定多长时间 您希望它继续寻找更好的解决方案。

    【讨论】:

      【解决方案3】:

      我假设我们只是在数组中间寻找一个位置,将其分成第一部分和第二部分。

      似乎线性算法可以做到这一点。 JavaScript 中的类似内容。

      arrayLength = 2;
      tolerance = 10;
      
      // Initialize the two sums.
      firstSum = [];
      secondSum = [];
      for (j = 0; j < arrayLength; j++)
      {
         firstSum[j] = 0;
         secondSum[j] = 0;
         for (i = 0; i < arrays.length; i++)
         {
            secondSum += arrays[i][j];
         }
      }
      
      // Try splitting at every place in "arrays".
      // Try to get the sums as close as possible.
      for (i = 0; i < arrays.length; i++)
      {
         goodEnough = true;
         for (j = 0; j < arrayLength; j++)
         {
            if (Math.abs(firstSum[j] - secondSum[j]) > tolerance)
               goodEnough = false;
         }
      
         if (goodEnough)
         {
            alert("split before index " + i);
            break;
         }
      
         // Update the sums for the new position.
         for (j = 0; j < arrayLength; j++)
         {
            firstSum[j] += arrays[i][j];
            secondSum[j] -= arrays[i][j];
         }
      }
      

      【讨论】:

      • 该示例显示了从数组中间的一个元素形成数组 1,而数组的其余部分形成数组 2。所以不要在一个地方拆分。
      【解决方案4】:

      感谢所有答案,蛮力攻击是个好主意,NP-Hard 也与此有关,但事实证明这是一个多重背包问题,可以使用 this pdf document 解决。

      【讨论】:

        猜你喜欢
        • 2020-09-13
        • 2016-07-04
        • 2017-03-09
        • 2012-04-12
        • 2019-11-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多