注意:这不是作业,而是实习项目。
情况:
您有一系列不同大小的n
组,
任何群组都不能包含超过X
个元素
假设您有一个功能merge(G1, G2)
,它将组G2
的所有元素添加到组G1
中,并从组列表中删除G2
。
编辑:组中的每个元素成员对所有组都是唯一的(即,如果组1具有元素a; a在任何其他组中不存在)
问题:
您需要通过合并组合大小小于X
我最初的想法:
使用贪婪算法,其功能如下:
sort the arrays by decreasing order,
Then while array.size > 0:
Pop largest group (lets call it GL) from the main list, and add it to a toBig list
Then loop through the array until you find a group that can be merged with GL
Merge the groups and add the merged group to a toRemove list
Keep going and merging any group that fits
once loop ends, remove all groups in toRemove from the main list
Continue While Loop
你们对这种方法的看法是什么,它会产生最少数量的群体(或接近的东西)?是否有更优雅的方法或更有效的算法?
感谢您输入
P.S。我试图搜索这个问题,但我不知道问题的名称是什么,并在SE和谷歌上搜索它的描述没有产生相关结果。
答案 0 :(得分:0)
在你的问题中,你从不真正关心群体本身,而是他们的尺寸。所以我建议我们首先将问题重写为只有整数的更简单但相当的问题:我们将按大小替换组,并通过添加替换merge
函数。
我将首先给出一个更简单的算法版本(如果我理解正确的话,会做同样的事情),只是因为它忽略了像组本身,实现细节等不相关的东西。我将举一个例子,说明为什么算法不是最优的,最后说明它实际上是在最优解的2倍之内。
Input: the integer x>0 and a list of positive integers
Do
Find two numbers whose sum is less than x
Merge them
Repeat the above until no such two numbers exist
Return the final list
考虑以下输入:
x = 10
list: (3, 3, 3, 3, 4, 4)
在这种情况下,最佳解决方案是添加(“合并”)两个3
和一个4
,两次,给出(3+3+4, 3+3+4)
,即{{1} }。
但是,您的解决方案可能决定将两个(10, 10)
添加到一起,这将生成新列表4
,该列表长于(9, 1, 8)
。
实际上,即使您决定始终添加两个最大数字或两个最小数字,您也会得到相同的结果。
对于我能想到的任何类似方案,我总能想出一个反例。
您的算法将始终生成一个列表,其中包含最多一个元素,该元素小于或等于(10, 10)
。实际上,如果有两个这样的元素,算法会找到它们并添加它们。
如果您的解决方案列表的大小为floor(x/2)
,则表示所有元素的总和至少 m
。拨打此号码m*floor(x/2)
。
但是,任何最佳解决方案必须至少具有S
元素(否则它们不会都小于ceil(S/x)
)。因此:
x
因此,算法在最优解的optimal >= ceil(S/x) >= ceil( m * floor(x/2) / x )
>= m*floor(x/2)/x ~ m/2
因子内。
答案 1 :(得分:0)
首先,就解决方案而言,实际的元素值并不重要。它们只是一个融合的细节。唯一重要的是组中元素的数量。换句话说,创建一个"计数列表"只有计数。
对列表进行排序。从最低到最高或从最高到最低,这并不重要。我们要去" pop"离开高端。让我们调用计数列表ginp
和输出列表gout
。让我们调用弹出元素gbig
。
因此,如果gbig的大小为X,只需将其添加到痛风中即可。继续弹出,直到我们有痛风< X.现在遍历ginp,hi-to-lo,调用此gtry
。如果(gtry + gbig)< = X,则将gtry合并到gbig中。如果gbig命中X,则添加到痛风并重新开始。请注意,由于gbig会越来越小,因此合并越来越容易。
直到ginp筋疲力尽。这是第一次适应"算法。它是一个工作的基线。由于它的种类,它甚至可能是最好的解决方案,但我怀疑你需要一个最合适的"这有点复杂。
考虑另一种策略。通过给定的gbig和给定的ginp,它们形成了当前状态"。假设[hi-to-lo sort],在弹出gbig之后,ginp [0]将适合。在第一次合适中,我们接受了它。
但是,假设我们跳过它[只是为了笑容],并选择ginp [1]而不是gtry并接受了。现在,对于gbig的下一个添加,我们可以选择ginp [2] [如果它适合],或者我们可以跳过。对剩余的ginp(选择或跳过)重复此操作,直到任一gbig击中X或我们已经移过ginp的末端。
最后,现在弹出另一个gbig并重复,仅使用前一轮中未选择的元素。请注意,你只是继续递归,直到ginp最终耗尽。在每一步,我们从剩下的步骤中选择一个拟合。递归地执行此操作形成[二进制]树(基于take / skip),其将枚举所有可能的选择。请注意,某些节点不会占用,因为ginp中的下一个元素太大(例如gbig会溢出X)
在这个递归中,当ginp耗尽时,保持gout中元素数量的最小值(它也只需要一个计数)。根节点的路径为您提供所需的内容:合并操作列表等。随时获得新的最小痛风时保存。
最大树深度为< = n
更新:需要测试数据?这是一个发电机程序:
#!/usr/bin/perl
# grpgen -- generate test data for group reduction problem
#
# arguments:
# "-X" - maximum value for X
# "-n" - maximum value for n
# "-T" - number of tests to generate
# "-O" - output file
# "-f" -- generate full element data
#
# NOTE: with no arguments or missing arguments will prompt
master(@ARGV);
exit(0);
# master -- master control
sub master
{
local(@argv) = @_;
select(STDOUT);
$| = 1;
$Xmax = getstr(2,"-X","maximum for X");
$nmax = getstr(2,"-n","maximum for n");
$tstmax = getstr(2,"-T","number of tests");
$keyf = getstr(1,"-f","generate full element data");
$ofile = getstr(0,"-O","output file name");
open($xfdst,">$ofile") ||
die("master: unable to open '$ofile' -- $!\n");
for ($tstcur = 1; $tstcur <= $tstmax; ++$tstcur) {
gentest();
}
close($xfdst);
}
# getstr -- get a string/number
sub getstr
{
my($numflg,$opt,$prompt) = @_;
my($arg);
my($askflg);
my($val);
{
# search command line for -whatever
foreach $arg (@argv) {
if ($arg =~ /^$opt(.*)$/) {
$val = $1;
$val = 1
if ($numflg && ($val eq ""));
last;
}
}
last if (defined($val));
$askflg = 1;
while (1) {
printf("Enter ")
if ($numflg != 1);
printf("%s",$prompt);
if ($numflg == 1) {
printf(" (0/1)? ");
}
else {
printf(": ");
}
$val = <STDIN>;
chomp($val);
if ($numflg == 0) {
last if ($val ne "");
next;
}
next unless ($val =~ /^\d+$/);
$val += 0;
last if ($val > 0);
last if ($numflg == 1);
}
}
unless ($askflg) {
printf("%s: %s\n",$prompt,$val);
}
$val;
}
# gentest -- generate a test
sub gentest
{
local($lhs);
local($pre);
$Xlim = getrand($Xmax);
$xfmt = fmtof($Xlim);
$nlim = getrand($nmax);
$nfmt = fmtof($nlim);
printf($xfdst "\n");
printf($xfdst "T=%d X=%d n=%d\n",$tstcur,$Xlim,$nlim);
for ($nidx = 1; $nidx <= $nlim; ++$nidx) {
$xcur = getrand($Xmax);
if ($keyf) {
gengrpx();
}
else {
gengrp();
}
}
genout();
}
# gengrp -- generate group (counts only)
sub gengrp
{
my($rhs);
$rhs = sprintf($xfmt,$xcur);
genout($rhs);
}
# gengrpx -- generate group (with element data)
sub gengrpx
{
my($elidx,$rhs);
$pre = sprintf("$nfmt:",$nidx);
# NOTE: this is all irrelevant semi-junk data, so just wing it
for ($elidx = 1; $elidx <= $xcur; ++$elidx) {
$rhs = sprintf($xfmt,$elidx);
genout($rhs);
}
genout();
}
# genout -- output data
sub genout
{
my($rhs) = @_;
{
if (defined($rhs)) {
last if ((length($pre) + length($lhs) + length($rhs)) < 78);
}
last if ($lhs eq "");
print($xfdst $pre,$lhs,"\n");
undef($lhs);
}
$lhs .= $rhs
if (defined($rhs));
}
# getrand -- get random number
sub getrand
{
my($lim) = @_;
my($val);
$val = int(rand($lim));
$val += 1;
$val;
}
# fmtof -- get number format
sub fmtof
{
my($num) = @_;
my($fmt);
$fmt = sprintf("%d",$num);
$fmt = length($fmt);
$fmt = sprintf(" %%%dd",$fmt);
$fmt;
}