【问题标题】:Divide times in two boxes and find the minimum difference将时间除以两个盒子并找到最小差异
【发布时间】:2014-02-22 12:15:04
【问题描述】:

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

我有包 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。我相信这是正确的结果,但我几乎可以肯定,如果它是纯粹的变化,因为如果我尝试更大的情况,程序会在一段时间后停止。可能是因为递归树变大了。

有人能指点我一个方向吗?

【问题讨论】:

  • items_numbernumber_of_pizzas 到底是什么?也很高兴知道middle_time 是什么,尽管我认为它是total_time 的一半。看到完整的源代码也很好。
  • 对不起。犯了一个错误。是items_number,即物品的总量。我举的例子中有四个。你是对的。 middle_timetotal_time 的一半

标签: c algorithm recursion


【解决方案1】:

好的,在澄清之后,让我(希望)为您指明一个方向:

假设您知道n 是什么,在n items 中提到。在您的示例中,2n4,使得 n = 2。让我们再选一个n,这次是3,我们的times 应该是:

1.00
2.00
3.00
4.00
5.00
6.00

现在,我们已经可以知道答案是什么了;你所说的都是正确的,最好每个包的n = 3times 总和为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),其计算结果为 (6 * 5 * 4) / (3 * 2 * 1) = 20;除以 2,就是10

我的猜测是,即使n 是 10,计算机也不会造成问题,因此组合的数量高达C(20, 10) / 2 = 92 378。但是,手动写下 9 个嵌套的 for 循环对您来说是个问题...

不管怎样,好消息是,您可以递归地嵌套这些循环。在这里,我将结束我的指导。由于您显然已经在研究递归,所以此时提供解决方案对我来说并不好。我可以向你保证这是可行的。

我自己制作的版本也可以在一秒钟内完成items_number = 22,而无需进行任何优化;只是用蛮力。这使得352 716 组合,而我的机器只是一个简单的Windows 平板电脑......

【讨论】:

  • 首先让我感谢您的解释。我在第二段中遇到了麻烦。你说 2n 是 4 使得 n = 4。n 不是 n=2 吗?
  • 谢谢。现在将专注于递归部分:(
  • @Favolas 没错。抱歉,第一次运行时我写了类似 "n = 2 so 2n = 4";我显然忘记在途中改变它......
  • 另一个问题。由于您正在为 n=2 制作,第二个 for(在第一次迭代时 i = 1)不应该在 013 组结束吗?
  • @Favolas 如果我创建了n = 2,则不会有第二个for,而只有一个for,它将检查010203231312 分别是其余的。然而,从“让我们选择另一个n,...”这句话开始,我已经开始使用n = 3,这需要我使用第二个for循环,嵌套。
【解决方案2】:

您的问题称为Partition Problem。这是 NP 难的,并且在某个时间点之后,将需要 非常 很长时间才能完成:随着要测试的案例数量的增长,树会呈指数级增长。

分区问题在互联网上是众所周知的并且有据可查。存在一些优化的解决方案

【讨论】:

  • 谢谢,但是如果集合可以分成两部分,我会遇到分区问题。如果不可能一分为二,它不会给我最小的差异
  • 在维基百科上它说:“有一个分区问题的优化版本,即将多重集 S 划分为两个子集 S1、S2,使得 S1 中元素之和与S2 中的元素总和最小化。"这听起来像是您要解决的问题:)
  • 谢谢。现在只需要找到算法解释。但尽管如此,我的递归函数并没有按预期工作,我不明白为什么
  • 再次,病例数呈指数级增长。通常,当问题非常小时,它需要一瞬间,然后在某些时候需要几秒钟,然后只添加一个元素将使它永远运行
【解决方案3】:

您的方法不是天真的蛮力方法,它只会遍历项目列表并将其递归地放入包 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}

【讨论】:

  • 感谢您的解释。我停在step == 1,因为我不想要一个将所有物品放在同一个包中的解决方案。我认为这会在包里至少留下一件物品
猜你喜欢
  • 1970-01-01
  • 2021-06-27
  • 2021-03-22
  • 1970-01-01
  • 2022-11-01
  • 1970-01-01
  • 2016-06-19
  • 1970-01-01
  • 2020-12-11
相关资源
最近更新 更多