我想要一种算法(没有特定的语言)来从一组整数中找到一个子集,使得它们的总和在一定的范围内。
例如,如果我有一群人,其权重如下。
var people:{
jane:126,
julia:112,
charles:98,
john:182,
bob:213,
edgar: 237,
jay: 223,
dan: 191,
alex: 210,
david: 196
}
现在,从这些人那里,我想找到一个总重量在818-822磅之间的子集(如果你正在尝试数学...不要打扰,这些数字不在我的我甚至不知道这个数据集是否有解决方案。群体中的人数无关紧要,只是来自较大群体的群体。实际上,任何一个团队都会这样做(尽管在我的情况下随机更好)。
请注意,这只是一个简单的例子......实际上会有数百人,并且可能没有符合此标准的组合。因为实际的数字会比这大得多,所以我很关心n ^ n问题,并且经历了数千次迭代,即使我需要这么快就能运行。
也许那天我在计算机科学课上睡着了,但除了蛮力方法之外,我还没有能够提出任何其他方法。
我已经将其标记为javascript,因为它最接近我的实际实现(并且它更容易阅读)。对其他解决方案持开放态度,只要它们不是基于某些Cthulhu函数的预测。
我知道这是一个很难问的问题,但是我们将不胜感激。
好的,我很难过。 23个小时发布我可以代码改进的奖励 - 我的背景肯定不在这个领域,我很难辨别用于描述问题的符号,更不用说解决方案。
有人想帮助我,并给我一些示例javascript代码,我可以修改到最终项目?我会尽可能地增加一个250pt的赏金......但如果有一个合适的解决方案,我会在时机成熟的时候把它拿出来。
答案 0 :(得分:9)
这类似于0-1 Knapsack problem或Subset sum problem。
如果权重不是非常大的整数,则动态编程解决方案应该是有效的。
这是javascript实现的动态编程算法。如果你想要随机组,只需在应用这个算法之前随机改组人员列表。
var p = {
jane:126,
julia:112,
...
};
function subset(people, min, max)
{
var subsets = [];
subsets[0] = '';
for (var person in people)
{
for (var s = min-1; s >= 0; --s)
{
if (s in subsets)
{
var sum = s + people[person];
if (!(sum in subsets))
{
subsets[sum] = subsets[s] + ' ' + person;
if (sum >= min && sum <= max)
{
return subsets[sum];
}
}
}
}
}
return 'Not found';
}
print(subset(p, 818, 822));
答案 1 :(得分:4)
它看起来像是“二元背包问题”的一种变体,如果最佳拟合仍然在可接受的范围之外,则增加了限制,答案将被拒绝。
您可能需要查看“多项式时间近似值”。
一种方法可以是按重量对集合进行排序。然后你开始从中间向下和向上看:你得到丹,约翰,大卫,亚历克斯和你在779.你加上简,发现自己在905,那太多了87;所以你检查下面的名字,朱莉娅,那是112,看看差异之间最接近的匹配。与朱莉娅交换亚历克斯(210-112)将失去你98,与朱莉娅交换大卫将失去84.泡沫,冲洗,重复。
算法是O(n log n)用于排序,然后取决于设置大小。它有几个缺点(不保证收敛,群体往往是连续的,它会聚集起点附近的人,等等),但如果你只想要“一个群体”,那就足够了。
您也可以递归地实现算法。最糟糕的情况是O(n ^ 3 log n),但如果你真的在和人类一起工作(权重聚集在相当小的范围内,平滑的曲线),收敛可能会非常快。
答案 2 :(得分:3)
这就是我所谓的“排序和支架”问题。解决问题的方法是对数据进行排序,然后围绕目标值或目标范围进行包围。
例如,在这种情况下,排序顺序为:
98
112个
126个
182个
191个
196个
210个
213个
223个
237个
现在你对列表求平均值:178.8。因此起始支架是(126,182)。开始从这个平均值移出:总和(126,182,112,191,98)= 709,太小了。删除98并替换另一侧的值:196,即总和(126,182,112,191,196)= 807,仍然太小。转到高位的下一个值,总和(126,182,112,191,210)= 821。好的,找到了一场比赛。通过继续此过程,您可以找到每场比赛。基本上包围的功能是帮助您只搜索所有可能组合的子集,这样您就不必检查每个组合。您从平均值向外生成组合,而不是从一端或另一端生成组合。
每当您的金额超过/低于该范围时,您将在高/低侧终止组合生成并切换到另一侧。这是解决问题的最佳方案。
实现方法:要实现此算法,您需要获得一个以“词典”顺序工作的组合生成器。然后从n开始,比方说5项,并确定中位数组合,如上所示。然后你得到下一个较低的组合,如果你是低,你切换到下一个更高的组合,依此类推。
-------------- ADDENDUM -------------------
考虑到这一点后,最好使用普通的改变型算法而不是词典组合器。这种类型的算法将生成所有组合,但仅在给定时间切换任何2个元素。基本上你修改这个算法,以便在超出范围时(超出范围或低于范围)切换方向。
答案 3 :(得分:1)
以下是对类似问题Find combination(s) sum of element(s) in array whose sum equal to a given number
的回答<?php
$limit = 12;
$array = array(6,1,10,4,1,3,11,2,15,5,12,10,17);
echo implode(', ',$array).'<br>';
// remove items 15 and 17 because they are great then $limit = 12
$array = array_filter($array, function($var) use ($limit) {
return ($var <= $limit);
});
rsort($array);
echo implode(', ',$array);
// the algorithm is usable if the number of elements is less than 20 (because set_time_limit)
$num = count($array);
//The total number of possible combinations
$total = pow(2, $num);
$out = array();
// algorithm from http://r.je/php-find-every-combination.html
// loop through each possible combination
for ($i = 0; $i < $total; $i++) {
$comb = array();
// for each combination check if each bit is set
for ($j = 0; $j < $num; $j++) {
// is bit $j set in $i?
if (pow(2, $j) & $i){
$comb[] = $array[$j];
}
}
if (array_sum($comb) == $limit)
{
$out[] = $comb;
}
}
array_multisort(array_map('count', $out), SORT_ASC, $out);
$out = array_unique($out, SORT_REGULAR);
foreach($out as $result) echo implode(', ', $result).'<br>';
此代码的输出是组合的列表,其总和仅为$ limit(12)...
12
10, 2
11, 1
5, 4, 3
6, 4, 2
6, 5, 1
10, 1, 1
5, 4, 2, 1
6, 3, 2, 1
6, 4, 1, 1
5, 3, 2, 1, 1