给定一组n个数(1 <= n <= 100),其中每个数是1到450之间的整数,我们需要将这些数字集分配到两个集A和B中,以便以下两个案例成立:
有人可以建议一个有效的算法来解决上述问题吗?
谢谢。
答案 0 :(得分:9)
由于数字很小,因此不是NP完全。
要解决此问题,您可以使用动态编程:
制作一张布尔三维表 其中t [s,n,i]处的true表示可以使用索引i下的n个元素的子集来达到和s。 要计算t [s,n,i]的值,请检查t [s,n,i-1]和t [s - a [i],n-1,i-1]。 然后在第二个索引n / 2处查看表格以找到最佳解决方案。
编辑:您实际上不需要一次完整的表格。您可以为每个索引i创建一个二维表t_i [s,n],并从表中为i-1计算i的表,因此您只需要这些二维表中的两个,这样可以节省大量内存。 (感谢Martin Hock。)
答案 1 :(得分:3)
这是Number Partioning Problem的约束版本。通常目标是找到任何2个不相交的子集,以最小化总和的差异。您的问题在某种意义上受到限制,您只考虑1个可能性:2组大小N / 2(或1组N / 2和一组N / 2 + 1,如果总数不均匀)。这大大减少了搜索空间,但我现在无法想出一个好的算法,我会考虑一下。
答案 2 :(得分:2)
如果数字是连续的,那么你只需在A和B之间交替分配它们。
我怀疑他们不是,在这种情况下......
将最大的未分配数分配给具有最低总和的组,除非这些组的大小差异小于或等于未分配数字的数量(在这种情况下将所有剩余数字分配给较小的组)。 / p>
在所有情况下都找不到最佳解决方案,但它非常简单。
答案 3 :(得分:2)
没关系,我认为数字是连续的。这看起来有点像Knapsack Problem,这是NP难的。
这些数字是连续的吗?
证明:
在分配了4个数字的每个数字后,A和B都包含相同数量的项目,并且每个组中的项目总和是相同的,因为
(n) + (n - 3) == (n - 1) + (n - 2)
在最后一次迭代中,我们在上面的步骤1中,我们剩下0,1 1,2 [1,2]或3 [1,2,3]个数字。
在案例0中,我们完成了,并且组的数量和重量相等。
在案例1中,我们将数字1分配给组A.组A还有一个项目和一个权重。在这种情况下,这是公平的。
在案例2中,我们将数字2分配给组A,将数字1分配给组B.现在组具有相同数量的项目,组A具有一个额外权重。再次,这是我们可以得到的公平。
在案例3中,将号码3分配给组A,并将号码2和1分配给组B.现在组具有相同的权重(3 == 2 + 1),组B有一个额外项目。
答案 4 :(得分:2)
首先,在没有第一个约束的情况下找到问题的解决方案(即 - 使得和尽可能接近)。使用DP方法可以解决这个问题(您可以阅读有关DP here的更多信息,第一个问题 - 关于硬币 - 与您的非常相似)。
一旦你可以解决它,你可以再添加一个状态到DP - 已经选择到子集的人数。这给你一个N ^ 3算法。
答案 5 :(得分:2)
我有一个算法。它使用了很多递归和迭代的概念。
假设你有n个Xn,其中1&lt; = n&lt; = 100且1&lt; = Xn&lt; = 450。
如果n&lt; 3然后分配数字并停止算法,
如果n> 2然后按升序对您的号码列表进行排序,
示例:强>
假设:n = 7且数字为10,75,30,45,25,15,20
通过1:
因为n> 2对列表进行排序:10,15,20,25,30,45,75
总和S = 220
A = 220 /((7-1)/ 2)= 73
全部:
10&amp; 75 =&gt; 85
15&amp; 45 =&gt; 60
20&amp; 30 =&gt; 50
剩余数字是&lt; 2所以在总和列表中加25:85(10,75),60(15,45),50(20,30),25(25)
传递2:
n = 4且数字为85,60,50,25
列表计数是&gt; 2所以排序清单:25(25),50(20,30),60(15,45),85(10,75)
总和S仍然相同(S = 220)但A必须重新计算:A = 220 /((4-0)/ 2)= 110
全部:
25&amp; 85 =&gt; 110
50&amp; 60 =&gt; 110
总和名单是:110(25(25),85(10,75)),110(50(20,30),60(15,45))
传递3:
n = 2且数字为110,110
n&lt; 3所以分配数字:
A = 25,10,75
B = 20,30,15,45
这适用于我测试的每个场景。
答案 6 :(得分:1)
您对#2的要求需要澄清,因为: “A中所有数字的总和尽可能与B中所有数字的总和几乎相等”是明确的,但是你的陈述“分配应该公平”使得一切都不清楚。 “公平”究竟意味着什么?该过程是否需要随机元素?
答案 7 :(得分:1)
@ShreevatsaR指出,下面的算法称为贪婪算法。它在某些输入上表现不佳(我尝试了10组不同的随机生成的大小为100的输入集,在所有情况下,总和非常接近,这使我认为对输入的排序足以使该算法成功)。
另请参阅"The Easiest Hard Problem", American Scientist, March-April 2002推荐的ShreevatsaR。
#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw( sum );
my @numbers = generate_list();
print "@numbers\n\n";
my (@A, @B);
my $N = @numbers;
while ( @numbers ) {
my $n = pop @numbers;
printf "Step: %d\n", $N - @numbers;
{
no warnings 'uninitialized';
if ( sum(@A) < sum(@B) ) {
push @A, $n;
}
else {
push @B, $n;
}
printf "A: %s\n\tsum: %d\n\tnum elements: %d\n",
"@A", sum(@A), scalar @A;
printf "B: %s\n\tsum: %d\n\tnum elements: %d\n\n",
"@B", sum(@B), scalar @B;
}
}
sub generate_list { grep { rand > 0.8 } 1 .. 450 }
请注意,generate_list
会按升序返回列表。
答案 8 :(得分:0)
我认为数字不是连续的,你不能重新平衡?
由于约束1,您总是需要每隔一次插入切换桶。因此,每当您没有被迫选择一个桶时,选择一个逻辑桶(添加该号码将使得该总和更接近另一个桶)。如果这个铲斗与你之前的铲斗不同,你可以在另一个转弯处进行转弯。
答案 9 :(得分:0)
任何双背包算法都可以(无论数字的分布如何)。
答案 10 :(得分:0)
Simulated Annealing可以很快找到更好更好的答案。你可以保持1.真实,同时提高2的接近度。
答案 11 :(得分:0)
如果您需要完美的答案,那么您必须生成并循环遍历所有可能的答案集。如果您需要一个非常好的答案,那么模拟退火等技术就是您的选择。下面是一些使用非常原始的冷却计划来找到答案的C代码。
#include <stdio.h>
#include <stdlib.h>
#define MAXPAR 50
#define MAXTRIES 10000000
int data1[] = {192,130,446,328,40,174,218,31,59,234,26,365,253,11,198,98,
279,6,276,72,219,15,192,289,289,191,244,62,443,431,363,10
} ;
int data2[] = { 1,2,3,4,5,6,7,8,9 } ;
// What does the set sum to
int sumSet ( int data[], int len )
{
int result = 0 ;
for ( int i=0; i < len; ++i )
result += data[i] ;
return result ;
}
// Print out a set
void printSet ( int data[], int len )
{
for ( int i=0; i < len; ++i )
printf ( "%d ", data[i] ) ;
printf ( " Sums to %d\n", sumSet ( data,len ) ) ;
}
// Partition the values using simulated annealing
void partition ( int data[], size_t len )
{
int set1[MAXPAR] = {0} ; // Parttition 1
int set2[MAXPAR] = {0} ; // Parttition 2
int set1Pos, set2Pos, dataPos, set1Len, set2Len ; // Data about the partitions
int minDiff ; // The best solution found so far
int sum1, sum2, diff ;
int tries = MAXTRIES ; // Don't loop for ever
set1Len = set2Len = -1 ;
dataPos = 0 ;
// Initialize the two partitions
while ( dataPos < len )
{
set1[++set1Len] = data[dataPos++] ;
if ( dataPos < len )
set2[++set2Len] = data[dataPos++] ;
}
// Very primitive simulated annealing solution
sum1 = sumSet ( set1, set1Len ) ;
sum2 = sumSet ( set2, set2Len ) ;
diff = sum1 - sum2 ; // The initial difference - we want to minimize this
minDiff = sum1 + sum2 ;
printf ( "Initial diff is %d\n", diff ) ;
// Loop until a solution is found or all are tries are exhausted
while ( diff != 0 && tries > 0 )
{
// Look for swaps that improves the difference
int newDiff, newSum1, newSum2 ;
set1Pos = rand() % set1Len ;
set2Pos = rand() % set2Len ;
newSum1 = sum1 - set1[set1Pos] + set2[set2Pos] ;
newSum2 = sum2 + set1[set1Pos] - set2[set2Pos] ;
newDiff = newSum1 - newSum2 ;
if ( abs ( newDiff ) < abs ( diff ) || // Is this a better solution?
tries/100 > rand() % MAXTRIES ) // Or shall we just swap anyway - chance of swap decreases as tries reduces
{
int tmp = set1[set1Pos] ;
set1[set1Pos] = set2[set2Pos] ;
set2[set2Pos] = tmp ;
diff = newDiff ;
sum1 = newSum1 ;
sum2 = newSum2 ;
// Print it out if its the best we have seen so far
if ( abs ( diff ) < abs ( minDiff ) )
{
minDiff = diff ;
printSet ( set1, set1Len ) ;
printSet ( set2, set2Len ) ;
printf ( "diff of %d\n\n", abs ( diff ) ) ;
}
}
--tries ;
}
printf ( "done\n" ) ;
}
int main ( int argc, char **argv )
{
// Change this to init rand from the clock say if you don't want the same
// results repoduced evert time!
srand ( 12345 ) ;
partition ( data1, 31 ) ;
partition ( data2, 9 ) ;
return 0;
}
答案 12 :(得分:-1)
我会尝试使用遗传算法,因为这似乎是应用它们的一个非常好的问题。
编码只是长度为N的二进制字符串,表示0表示第一组,1表示第二组。当每组中的元素数量不同时给出负面适应度,当总和相似时给出正适应性......类似于:
fitness(gen) = (sum(gen)-n/2))^2 + (sum(values[i]*(-1)**gen[i] for i in 0..n))^2
(并尽量减少适应性)
当然,这可以给你一个次优的答案,但对于大的现实世界的问题,这通常就足够了。