通过合并它们来最小化组的数量,而不会超出特定的组大小

时间:2015-10-29 06:37:30

标签: algorithm sorting optimization merge

注意:这不是作业,而是实习项目。

情况:

您有一系列不同大小的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和谷歌上搜索它的描述没有产生相关结果。

2 个答案:

答案 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;
}