开始学习递归,我遇到了这个简单的问题。我相信有更多优化方法可以做到这一点,但首先我要尝试学习强力方法。
我有包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
。我相信这是正确的结果,但我几乎可以肯定,如果它是纯粹的改变,因为如果我尝试更大的案例,程序会在一段时间后停止。可能是因为递归树变大了。
有人能指出我的方向吗?
答案 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
是什么。在您的示例中,2n
为4
,即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
可以,10
到6.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平板电脑......