具有特定标准的笛卡尔积

时间:2016-11-17 04:23:51

标签: php arrays algorithm permutation cartesian-product

我试图找到笛卡尔积并附加特定标准。

我有四个人,每人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

            }
        }
    }    
}      

我还以为我可以找到一种方法来计算总价格/得分比率并将其用于我的优势,但我不知道我错过了什么。

4 个答案:

答案 0 :(得分:2)

正如Barmar所指出的那样,对每个池中的人进行排序可以让您在总价超过限制时提前停止循环,从而减少需要检查的案例数。但是,应用此改进的渐近复杂度仍为O(n 4 )(其中n是池中的人数)。

我将概述一种具有更好渐近复杂度的替代方法,如下所示:

  1. 构建一个池X,其中包含来自池A的一对人和来自池B的另一人。
  2. 构建一个池Y,其中包含来自池C的一对人和来自池D的另一人。
  3. 按总价格对池X中的对进行排序。然后对于任何具有相同价格的货币对,保留分数最高的货币对,并丢弃剩余货币对。
  4. 按总价格对池Y中的对进行排序。然后对于任何具有相同价格的货币对,保留分数最高的货币对,并丢弃剩余货币对。
  5. 使用两个指针执行循环以检查满足价格约束的所有可能组合,其中head指针从池X中的第一个项开始,以及tail指针从池Y中的最后一项开始。下面给出了示例代码,以说明此循环的工作原理:
  6. =============================================== ===========================

    $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)

有关您的方法的一些注意事项。严格地说,从数学的角度来看,你计算出的排列方式多于实际需要的排列方式。

在组合学中,要提出两个重要问题,以便得出产生所有可能组合所需的确切排列数。

  1. 订单有关系吗? (对于你的情况,它没有)
  2. 允许重复吗? (对于你的情况,没有必要重复)
  3. 由于这两个问题的答案都是 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个以上的人,并以极快的处理时间找到最佳结果!