我试图找到笛卡尔积并附加特定标准。
我有四个人,每人25人。每个人都有分数和价格。每个池中的每个人看起来都是这样。
[0] => array(
"name" => "jacob",
"price" => 15,
"score" => 100
),
[1] => array(
"name" => "daniel",
"price" => 22,
"score" => 200
)
我想找到最好的人群组合,每个人都会挑选一个人。但是,有一个上限价格,没有分组可以超过一定的价格。
我一直在搞乱笛卡儿和排列函数,似乎无法弄清楚如何做到这一点。我知道如何对其进行编码的唯一方法是使用嵌套的foreach
循环,但这非常令人费解。
正如您所看到的,下面的代码非常低效。特别是如果游泳池增加了!
foreach($poolA as $vA) {
foreach($poolb as $vB) {
foreach($poolC as $vC) {
foreach($poolD as $vD) {
// calculate total price and check if valid
// calculate total score and check if greatest
// if so, add to $greatest array
}
}
}
}
我还以为我可以找到一种方法来计算总价格/得分比率并将其用于我的优势,但我不知道我错过了什么。
答案 0 :(得分:2)
正如Barmar所指出的那样,对每个池中的人进行排序可以让您在总价超过限制时提前停止循环,从而减少需要检查的案例数。但是,应用此改进的渐近复杂度仍为O(n 4 )(其中n
是池中的人数)。
我将概述一种具有更好渐近复杂度的替代方法,如下所示:
X
,其中包含来自池A
的一对人和来自池B
的另一人。Y
,其中包含来自池C
的一对人和来自池D
的另一人。X
中的对进行排序。然后对于任何具有相同价格的货币对,保留分数最高的货币对,并丢弃剩余货币对。Y
中的对进行排序。然后对于任何具有相同价格的货币对,保留分数最高的货币对,并丢弃剩余货币对。head
指针从池X
中的第一个项开始,以及tail
指针从池Y
中的最后一项开始。下面给出了示例代码,以说明此循环的工作原理:=============================================== ===========================
$head = 0;
$tail = sizeof($poolY) - 1;
while ($head < sizeof($poolX) && $tail >= 0) {
$total_price = $poolX[$head].price + $poolY[$tail].price;
// Your logic goes here...
if ($total_price > $price_limit) {
$tail--;
} else if ($total_price < $price_limit) {
$head++;
} else {
$head++;
$tail--;
}
}
for ($i = $head; $i < sizeof($poolX); $i++) {
// Your logic goes here...
}
for ($i = $tail; $i >= 0; $i--) {
// Your logic goes here...
}
=============================================== ===========================
步骤1和2的复杂性是O(n 2 ),步骤3和4的复杂性可以在O(n 2 log(n)中完成))使用平衡二叉树。并且步骤5基本上是对n 2 项的线性扫描,因此复杂度也是O(n 2 )。因此,该方法的总体复杂性为O(n 2 log(n))。
答案 1 :(得分:0)
有关您的方法的一些注意事项。严格地说,从数学的角度来看,你计算出的排列方式多于实际需要的排列方式。
在组合学中,要提出两个重要问题,以便得出产生所有可能组合所需的确切排列数。
由于这两个问题的答案都是 no ,因此您只需要进行嵌套循环的一小部分迭代。目前您正在执行pow(25, 4)
排列,即390625
。您实际上只需要n! / r! (n-r)!
或gmp_fact(25) / (gmp_fact(4) * gmp_fact(25 - 4))
,这只需要12650
个排列。
这是一个简单的函数示例,它使用PHP中的生成器(取自this SO answer)生成无重复的组合(和顺序无关紧要)。
function comb($m, $a) {
if (!$m) {
yield [];
return;
}
if (!$a) {
return;
}
$h = $a[0];
$t = array_slice($a, 1);
foreach(comb($m - 1, $t) as $c)
yield array_merge([$h], $c);
foreach(comb($m, $t) as $c)
yield $c;
}
$a = range(1,25); // 25 people in each pool
$n = 4; // 4 pools
foreach(comb($n, $a) as $i => $c) {
echo $i, ": ", array_sum($c), "\n";
}
修改生成器函数以检查价格总和是否满足/超过所需的阈值并且仅从那里返回有效结果(即在需要时放弃)将非常容易。
重复和顺序对您的用例来说并不重要的原因是因为无论您添加$price1 + $price2
还是$price2 + $price1
都无关紧要,两种排列结果无疑都是相同的。因此,您只需要将每个唯一集合相加一次,以确定所有可能的总和。
答案 2 :(得分:0)
与chiwangs解决方案类似,您可以预先消除每个群组成员,其中该群组中的另一个群组成员存在,以较低的价格获得相同或更高的分数。 也许你可以用这种方法消除每组中的许多成员。
然后你可以使用这种技术,构建两对并重复过滤(消除对,其中存在一对,相同或更低的成本得分更高)然后以相同的方式组合对,或者添加一个成员一步一步(一对,三联,四重奏)。
如果有一些会员,他们自己超过了允许的总价,他们可以预先消除。
如果按顺序降序排序4组,并且找到解决方案abcd,其中总价是合法的,那么您找到了给定abc集的最优解。
答案 3 :(得分:0)
这里的反应帮助我找到了做这件事的最好方法。
我还没有对功能进行优化,但基本上我一次循环查看每个结果两个,以找到两个池中每个组合的合并工资/分数。
我存储了合并的工资 - &gt;在新阵列中得分组合,如果薪水已存在,我会比较分数并删除较低分数。
$results = array();
foreach($poolA as $A) {
foreach($poolB as $B) {
$total_salary = $A['Salary'] + $B['Salary'];
$total_score = $A['Score'] + $B['Score'];
$pids = array($A['pid'], $B['pid']);
if(isset($results[$total_salary]) {
if($total_score > $results[$total_salary]['Score']) {
$results[$total_salary]['Score'] => $total_score;
$results[$total_salary]['pid'] => $pids;
} else {
$results[$total_salary]['Score'] = $total_score;
$results[$total_salary]['pid'] = $pids;
}
}
}
在这个循环之后,我有另一个相同的,除了我的foreach循环在$ results和$ poolC之间。
foreach($results as $R) {
foreach($poolC as $C) {
最后,我最后一次为$ poolD做了。
我正在通过将所有四个foreach循环合二为一来优化代码。
感谢大家的帮助,我能够遍历9个列表,每个列表中包含25个以上的人,并以极快的处理时间找到最佳结果!