我有一个数学问题,我需要在一个应用程序中,我想知道是否有一种有效的方法来找到最佳解决方案而不是近似。
示例:
Values: -10, -5, -2, 7, 9, 15
Sum: 14
Range: (10, 18)
Eliminate -2 => SUM = 16
Eliminate -5 => SUM = 21
Eliminate 7 => SUM = 14
Eliminate -10 => SUM = 24
Eliminate 9 => SUM = 15
消除15将使SUM = 0,这超出范围。消除了5个值。
然而,如果我从消除15,然后-10,-5,-2开始,我只能消除4个值。
我曾经写过一个算法,它只是尝试了所有可能的组合,但是当你有25个或更多的值时,它的性能会迅速降低。对于100-200个值,我需要十分之一秒的结果。
目前,我在绝对基础上将值从小到大排序,然后逐个消除它们,直到总和不再在范围内。显然,这可能并不总能给出最佳解决方案。
如果这不适合此类问题,并且您可以推荐另一个论坛,那也会有所帮助。
答案 0 :(得分:3)
我很想倒退,我不确定是否允许(见我的评论。)
因此,不要逐个消除值,让我们找到最小的子列表,其总和在范围内!
存在一个问题 - subset sum problem是np-complete,所以这种方法也是如此。 (想象一下你的范围为0的情况,也是同样的问题。)
有一种已知的算法可以解决O(2 N / 2 )中的这个问题。我将模拟一些Python代码,但与此同时,维基百科页面应该会有所帮助。由于你想在一个范围内找到最少的列表,显然需要进行一些修改。
基本上,您将列表拆分为两个长度为N / 2的任意子列表(列表中有N个元素。)然后生成每个列表中的所有子集,并计算它们的总和。 (这里,我将子集及其和存储在字典中,因此您知道您剩下哪些数字。因为您只想找到最小的数字,所以我也会消除所有与较小数量相同的子集。)对这些列表进行排序,然后向前和向后运行,直到找到适合该范围的所有总和。最后,找出哪个包含最少的元素,你很高兴去!
如果您被允许违反规则,只要最终列表在范围内,请查看此question
编辑:这是一些Python。它是:
未测试
Python,所以不是特别快
显然不是最佳的
迫切需要重构
但我认为,作为一般概念,您将能够获得最快的算法。我有兴趣看到一个更快的概念!
>>> from itertools import combinations, chain
>>>
>>> available = [-10, -5, -2, 7, 9, 15]
>>> target = (10, 18)
>>>
>>>
>>>
>>> def powerset(iterable): # from https://stackoverflow.com/questions/374626/how-can-i-find-all-the-subsets-of-a-set-with-exactly-n-elements
... xs = list(iterable)
... # note we return an iterator rather than a list
... return chain.from_iterable(combinations(xs, n) for n in range(len(xs)+1))
...
>>>
>>> def getMinList(available, target):
... middleIndex = len(available)/2
... l1 = available[:middleIndex]
... l2 = available[middleIndex:]
... dict1 = {}
... dict2 = {}
... for subset in powerset(l1): # reverse so only the smallest subsets are used.
... total = sum(subset)
... if total not in dict1:
... dict1[total] = subset
... for subset in powerset(l2):
... total = sum(subset)
... if total not in dict2:
... dict2[total] = subset
... sortedDict1 = sorted(dict1.iteritems())
... sortedDict2 = sorted(dict2.iteritems())
... resultList = ()
... minValues = middleIndex * 2
... for k1, v1 in sortedDict1:
... for k2, v2 in reversed(sortedDict2):
... sumOfSubsets = k1 + k2
... if sumOfSubsets <= target[1] and sumOfSubsets >= target[0]:
... newTuple = v1 + v2
... lenNewTuple = len(newTuple)
... if (lenNewTuple) < minValues:
... resultList = ((sumOfSubsets, newTuple))
... minValues = lenNewTuple
... return resultList
...
>>> getMinList(available, target)
(15, (15,))
>>>
>>> target = (10, 10)
>>>
>>> getMinList(available, target)
(10, (-5, 15))
>>>
>>> target = (19, 22)
>>>
>>> getMinList(available, target)
(22, (7, 15))
答案 1 :(得分:1)
使用动态编程(通过memoization实现),您可以使用以下内容:
class Memoize:
def __init__(self, f):
self.f = f
self.memo = {}
def __call__(self, *args):
if not args in self.memo:
self.memo[args] = self.f(*args)
return self.memo[args]
def maxsubset(values, min_sum, max_sum):
target_range = range(min_sum, max_sum+1)
@Memoize
def maxsubsetsize(target_sum, current_value_index=len(values)-1):
if current_value_index < 0:
if target_sum == 0:
return 0
else:
return float("-inf")
withit = maxsubsetsize(target_sum - values[current_value_index], current_value_index-1) + 1
without = maxsubsetsize(target_sum, current_value_index-1)
return max(withit, without)
result_sum = max(target_range, key=maxsubsetsize)
setsize = maxsubsetsize(result_sum)
result = []
for i in reversed([x-1 for x in xrange(len(values))]):
s = maxsubsetsize(result_sum, i)
if s < setsize:
result.append(values[i+1])
setsize -= 1
result_sum -= values[i+1]
return result
使用方法:
>>> values = [-10, -5, -2, 7, 9, 15]
>>> min_sum = 10
>>> max_sum = 18
>>> xs = maxsubset(values, min_sum-sum(values), max_sum-sum(values))
>>> print xs
[9, 7, -2, -5, -10]
>>> print "sum:", sum(xs)
-1
如果可以获得特定金额,您可以添加额外的检查。所有可用的负值都给出了总和的下限,所有可用的正数给出了上限。
答案 2 :(得分:1)
更糟糕的情况是,你需要检查所有的组合,即O(2 ^ n)。但是如果你开始检查最小的子列表,你可以在找到一个后停止。这是我的c ++写作。它可以改善内存使用,但需要更多的工作。所以取决于你的输入,它可能非常快或慢。
using namespace std;
int compare(int x, int r1, int r2)
{`
if (x < r1) return -1;
if (x > r2) return 1;
return 0;
}
// assume sorted v, binary search
bool hasMemInRange(const vector<int>& v, int r1, int r2)
{
int b, e, c, r;
b=0; e=v.size();
while(e > b) {
c = (b+e)/2;
r = compare(v[c], r1, r2);
if (r < 0) {
b += max(1, (e-b)/2);
} else if (r > 0) {
e -= max(1, (e-b)/2);
} else {
return true;
}
}
return false;
}
struct InputNode {
vector<int> l;
int r1, r2;
};
// assume v is sorted
int maxRemoval(const vector<int>& v, int r1, int r2)
{
if (compare(0, r1, r2) == 0) return v.size();
if (hasMemInRange(v, r1, r2)) return (v.size() - 1);
queue<InputNode> q;
InputNode node;
node.l = v;
node.r1 = r1;
node.r2 = r2;
q.push(node);
while(! q.empty()) {
InputNode& n = q.front();
if (n.l.size() == 1) {
return 0;
}
for (int i=0; i<n.l.size(); ++i) {
vector<int> nv = n.l;
nv.erase(nv.begin() + i);
node.l = nv;
node.r1 = r1-n.l[i];
if (hasMemInRange(nv, node.r1, node.r2)) {
return (nv.size() - 1);
}
q.push(node);
}
q.pop();
}
}
int list_ints[] = {-10, -5, -2, 7, 9, 15 };
int main()
{
vector<int> l(list_ints, list_ints + sizeof(list_ints)/sizeof(int));
for (auto i: list_ints) cout << i << " ";
cout << endl << endl;
cout << maxRemoval(l, 10, 18) << endl;
return 0;
}