将时间分成两个盒子,找出最小差异

时间:2014-02-22 12:15:05

标签: c algorithm recursion

开始学习递归,我遇到了这个简单的问题。我相信有更多优化方法可以做到这一点,但首先我要尝试学习强力方法。

我有包A和包B,每个都有n items一段时间(一个带有两位小数的浮点数)。这个想法是通过两个袋子分配物品,并获得两个袋子的最小差异。我们的想法是尝试所有可能的结果。

我只想在一个袋子里(比如袋子A),因为另一个袋子将包含所有不在袋子A中的物品,因此差异将是总时间的绝对值总和 - 2 *总和物品时间在包A中。

我正在调用我的递归函数:

min = total_time;
recursive(0, items_number - 1, 0);

该功能的代码是:

void recursive(int index, int step, float sum) {
    sum += items_time[index];
    float difference = fabs(total_time - 2 * sum);

    if (min > difference) {
        min = difference;
    }

    if (!(min == 0.00 || step == 1 || sum > middle_time)) {
        int i;
        for (i = 0; i < items_number; i++) {
            if (i != index) {
                recursive(i, step - 1, sum);
            }
        }
    }
}

想象一下,我有4个时间为1.23, 2.17 , 2.95 , 2.31

的项目

我收到了结果0.30。我相信这是正确的结果,但我几乎可以肯定,如果它是纯粹的改变,因为如果我尝试更大的案例,程序会在一段时间后停止。可能是因为递归树变大了。

有人能指出我的方向吗?

3 个答案:

答案 0 :(得分:1)

您的问题称为Partition Problem。它是NP难的,经过一段时间后,它将需要非常很长的时间才能完成:随着测试案例数量的增加,树会呈指数级变大。

分区问题是众所周知的,并且在互联网上有详细记录。存在一些优化的解决方案

答案 1 :(得分:1)

你的方法不是天真的蛮力方法,它只是遍历项目清单并递归地放入包A和包B中,以最小的差异选择案例,例如:

double recurse(double arr[], int n, double l, double r)
{
    double ll, rr;

    if (n == 0) return fabs(l - r);

    ll = recurse(arr + 1, n - 1, l + *arr, r);
    rr = recurse(arr + 1, n - 1, l, r + *arr);

    if (ll > rr) return rr;
    return ll;
}

(这段代码非常天真 - 它在很明显非最佳情况下并不是很早,并且通过计算每个案例两次并且交换袋A和B也浪费时间。这是蛮力但是。)

最大递归深度是项n的数字,你称之为递归函数2^n - 1次。

在您的代码中,您可以将相同的项目一遍又一遍地放入包中:

    for (i = 0; i < number_of_pizzas; i++) {
        if (i != index) {
            recursive(i, step - 1, sum);
        }
    }

此循环阻止您处理当前项目,但会很乐意处理在先前递归中放入包中的项目第二(或第三)次。如果要使用该方法,则必须保持哪个项目位于哪个包中。

另外,我不理解你的step。从step - 1开始,在step == 1时停止递归。这意味着您正在考虑n - 2项。我知道其他项目在另一个包中,但这是一个奇怪的情况,不会让你找到解决方案,比如{8.0, 2.4, 2.4, 2.8}

答案 2 :(得分:1)

好的,澄清之后,让我(希望)指出你的方向:

我们假设你知道n中提到的n items是什么。在您的示例中,2n4,即n = 2。我们选择另一个n,这次是3,我们的time应该是:

1.00
2.00
3.00
4.00
5.00
6.00

现在,我们已经可以知道答案是什么了;您所说的都是正确的,最佳地说,每个行李的n = 3 time总计为middle_time,在这种情况下为21 / 2 = 10.5。由于整数可能永远不会与包含小数点的数字相加,因此在此示例中可能永远不会实现10.5 : 10.5,但10 : 11可以,106.00 + 3.00 + 1.00(3)元素),所以...是的,答案只是1

你怎么让电脑计算出来?好;回想起我刚才所说的话:

  

让我们假设您知道 n 是什么。

在这种情况下,一个天真的程序员可能会简单地将所有这些放在2或3个嵌套for循环中。 2如果他/她知道另一半将在你选择一半时确定(通过简单地修复我们组中的第一个元素,因为该元素将被包含在其中一个组中),就像你一样也知道; 3如果他/她不知道。让我们用2

...
float difference;
int i;
for ( i = 1; i < items_number; i++ ) {
    sum = items_time[0] + items_time[i];
    int j;
    for ( j = i + 1; j < items_number; j++ ) {
        sum += items_time[j];
        difference = fabs( total_time - 2 * sum );
        if ( min > difference ) {
            min = difference;
        }
    }
}
...

让我对代码进行一些评论以便更快地理解:在第一个循环中,它会加起来第0次,第1次,然后是第2次,如你所见;然后它将执行您所做的相同检查(计算difference并将其与min进行比较)。我们称之为012组。将要检查的下一个组将是013,然后是014,然后是015;然后023,等等......将检查将6分成两个3的每个可能组合。

对于计算机来说,此操作不应该是无聊的。即使使用这种简单的方法,最大尝试次数将是3个组合的数量,其中6个唯一元素除以2.在数学中,人们将其表示为C(6, 3),其评估为{{1} };除以2,所以它是(6 * 5 * 4) / (3 * 2 * 1) = 20

我的猜测是,即使10为10,计算机也不会出现问题,使组合数量高达C(20, 10) / 2 = 92 378。但是,用手写下9个嵌套的n循环会是一个问题......

无论如何,好处是,你可以递归地嵌套这些循环。在这里,我将结束我的指导。由于你显然正在研究递归,所以在这一点上提供解决方案对我来说并不好。我可以向你保证它是可行的。

我在我的版本上制作的版本也可以在一秒钟内完成,最高可达for,而不做任何优化;只是用蛮力。这使得352 716组合,我的机器只是一个简单的Windows平板电脑......