大小为K的子集的最大和,其总和小于M.

时间:2013-08-05 19:56:59

标签: algorithm data-structures dynamic-programming subset-sum clrs

给定: 整数数组 值K,M

问题: 找到我们可以从给定数组的所有K个元素子集中获得的最大总和,使得和小于值M?

是否有针对此问题的非动态编程解决方案? 或者如果它只是dp [i] [j] [k]只能解决这类问题! 你能解释一下这个算法吗?

3 个答案:

答案 0 :(得分:7)

这是Knapsack或子集问题的变体,其中就时间而言(随着输入大小的增长,以指数增长空间需求为代价),动态编程是正确解决此问题的最有效方法。有关您的类似问题,请参阅Is this variant of the subset sum problem easier to solve?

但是,由于你的问题并不完全相同,我还是会提供一个解释。如果有dp[i][j]总和true的子集,则i = j,如果没有则falsedp[][]。我们的想法是j <= M将为每个可能的长度编码所有可能子集的总和。然后,我们可以找到最大dp[K][j]truedp[0][0] = true。我们的基本情况dp[][]因为我们总是可以通过选择0之一来创建一个总和为0的子集。

复发也相当简单。假设我们使用数组的第一个n值计算了n+1的值。要查找数组的第一个n+1值的所有可能子集,我们可以简单地取initialize dp[0..K][0..M to false dp[0][0] = true for i = 0 to N: for s = 0 to K - 1: for j = M to 0: if dp[s][j] && A[i] + j < M: dp[s + 1][j + A[i]] = true for j = M to 0: if dp[K][j]: print j break _ th值并将其添加到我们之前看到的所有子集中。更具体地说,我们有以下代码:

{{1}}

答案 1 :(得分:1)

我们正在寻找K元素的子集,其元素总和最大但小于M

我们可以将边界[X, Y]放在子集中的最大元素上,如下所示。

首先我们对(N)个整数values[0] ... values[N-1]进行排序,其中元素values[0]是最小的。

下限X

的最大整数

values[X] + values[X-1] + .... + values[X-(K-1)] < M

(如果XN-1,那么我们就找到了答案。)

上限Y是小于N的最大整数

values[0] + values[1] + ... + values[K-2] + values[Y] < M

通过这种观察,我们现在可以为最高项Z的每个值绑定第二高的项,其中

X <= Z <= Y

我们可以使用完全相同的方法,因为问题的形式完全相同。减少的问题是从K-1中找到values[0] ... values[Z-1]个元素的子集,其中元素的总和是最大值,但小于M - values[Z]

一旦我们以相同的方式绑定 值,我们就可以为两个最高值中的每一对设置第三大值。等等。

这为我们提供了一种搜索树形结构,希望搜索的组合比N选择K要少得多。

答案 2 :(得分:0)

Felix是正确的,这是背包问题的一个特例。他的动态编程算法采用 O (K * M)大小和 O (K * K * M)时间量。我相信他对变量N的使用确实应该是K.

有两本专门讨论背包问题的书。 Kellerer,Pferschy和Pisinger [2004,Springer-Verlag,ISBN 3-540-40286-1]的最新版本在其第76页(图4.2)中提供了改进的动态编程算法,该算法采用 O ( K + M)空间和 O (KM)时间,与Felix给出的动态编程算法相比,这是一个巨大的减少。请注意,本书算法的最后一行有一个拼写错误,它应该是c-bar:= c-bar - w_(r(c-bar))。

我的C#实现如下。我不能说我已经对它进行了广泛的测试,我欢迎对此提出反馈。我使用BitArray来实现书中算法中给出的集合的概念。在我的代码中,c是容量(在原始帖子中称为M),我使用w代替A作为保存权重的数组。

其使用的一个例子是:

int[] optimal_indexes_for_ssp = new SubsetSumProblem(12, new List<int> { 1, 3, 5, 6 }).SolveSubsetSumProblem();

数组optimal_indexes_for_ssp包含对应于元素1,5,6的[0,2,3]。

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;

public class SubsetSumProblem
{
    private int[] w;
    private int c;

    public SubsetSumProblem(int c, IEnumerable<int> w)
    {
      if (c < 0) throw new ArgumentOutOfRangeException("Capacity for subset sum problem must be at least 0, but input was: " + c.ToString());
      int n = w.Count();
      this.w = new int[n];
      this.c = c;
      IEnumerator<int> pwi = w.GetEnumerator();
      pwi.MoveNext();
      for (int i = 0; i < n; i++, pwi.MoveNext())
        this.w[i] = pwi.Current;
    }

    public int[] SolveSubsetSumProblem()
    {
      int n = w.Length;
      int[] r = new int[c+1];
      BitArray R = new BitArray(c+1);
      R[0] = true;
      BitArray Rp = new BitArray(c+1);
      for (int d =0; d<=c ; d++) r[d] = 0;
      for (int j = 0; j < n; j++)
      {
        Rp.SetAll(false);
        for (int k = 0; k <= c; k++)
          if (R[k] && k + w[j] <= c) Rp[k + w[j]] = true;
        for (int k = w[j]; k <= c; k++) // since Rp[k]=false for k<w[j]
          if (Rp[k])
          {
            if (!R[k]) r[k] = j;
            R[k] = true;
          }
      }
      int capacity_used= 0;
      for(int d=c; d>=0; d--)
        if (R[d])
        {
          capacity_used = d;
          break;
        }
      List<int> result = new List<int>();
      while (capacity_used > 0)
      {
        result.Add(r[capacity_used]);
        capacity_used -= w[r[capacity_used]];
      } ;
      if (capacity_used < 0) throw new Exception("Subset sum program has an internal logic error");
      return result.ToArray();
    }
}