给定: 整数数组 值K,M
问题: 找到我们可以从给定数组的所有K个元素子集中获得的最大总和,使得和小于值M?
是否有针对此问题的非动态编程解决方案? 或者如果它只是dp [i] [j] [k]只能解决这类问题! 你能解释一下这个算法吗?
答案 0 :(得分:7)
这是Knapsack或子集问题的变体,其中就时间而言(随着输入大小的增长,以指数增长空间需求为代价),动态编程是正确解决此问题的最有效方法。有关您的类似问题,请参阅Is this variant of the subset sum problem easier to solve?。
但是,由于你的问题并不完全相同,我还是会提供一个解释。如果有dp[i][j]
总和true
的子集,则i
= j
,如果没有则false
和dp[][]
。我们的想法是j <= M
将为每个可能的长度编码所有可能子集的总和。然后,我们可以找到最大dp[K][j]
,true
为dp[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
。
(如果X
为N-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();
}
}