使用PHP将大型递归函数保持在最低限度

时间:2015-03-19 09:52:50

标签: php arrays algorithm recursion

我有一个特定的长度,我试图用木板填充。为此,我需要找到木板组合的最佳位置,使用尽可能少的木板和最后木板的最低剩余部分。 为此,我首先创建一个具有所有可能性的树。之后,我将数组平了一点,所以我在一个数组中有所有选项(删除树方面)。最后,我走过数组,得到最低深度和最低剩余的结果。 我通过使用递归函数完成前两个步骤。

问题: 长度为5000毫米,5000毫米,4000毫米和3000毫米的木板有7种可能性。 当我尝试20000mm的长度和2450mm,2750mm,3000mm,3150mm,3600mm,3900mm,4000mm,4600mm,4900mm和5000mm的木板时,有几十种可能性。 使用我当前的代码我用PHP超过了30秒的限制。我试图在谷歌上寻找一个好的算法来解决这个问题,但我找不到可能的解决方案。

有谁知道解决这个问题的算法或解决方案?我试图将计算保持在最低限度,以尽可能快地保持速度。

创建树

public function createPossibilities($lengths, $length, $depth = 1) {
    $lengths = [2450, 2750, 3000, 3150, 3600, 3900, 4000, 4600, 4900, 5000]; // example array of all possible lengths
    $res = []; // array with all possible results

    foreach ($lengths as $l) {
        $rest = $length - $l; // calculates the rest length 
        if ($rest > 0) // check if length is complete
            $children = ['depth' => $depth, 'length' => $l, 'children' => $this->createPossibilities($lengths, $rest, ($depth +1))]; // if length is not complete, do function recursively
        else
            $children = ['depth' => $depth, 'length' => $l, 'leftover' => abs($rest)]; // if length is complete, add the leftover to the array
        $res[] = $children;
    }

    return $res;
}

展平树

public function flattenArray($array, &$possibilities, $str = '') {
    foreach ($array as $element) {
        $temp = $str;
        $temp .= $element['length'] . ', '; // add length to string
        if (array_key_exists('children', $element)) { // check if element has children
            $this->flattenArray($element['children'], $possibilities, $temp); // do  function recursively for the child
        } else { 
            $temp = explode(', ', $temp); // explode the string into an array
            array_pop($temp); // remove last empty element
            $temp['depth'] = count($temp); // add the depth to the array
            $temp['leftover'] = $element['leftover']; // add the leftover to the array
            $possibilities[] = $temp; // add the possibility to the array
        }
    }
}

获得最佳可能性

public function getBestPossibility($options, &$liggersPerBreedte) {
    $minDepth = -1;
    $minLeftover = -1;
    foreach ($options as $option) {
        if ($option['depth'] < $minDepth || $minDepth == -1) { // check if possibility has fewest lengths
            $minDepth = $option['depth']; // put min depth to this option
            unset($option['depth']); // remove depth from option
            $minLeftover = $option['leftover']; // put min leftover to this option
            unset($optie['leftover']); // remove leftover from option
            $bestPosibility = $option; // best possibility is array with lengths
        } else if ($option['depth'] == $minDepth && $option['leftover'] < $minLeftover) { // check if depth is the same as min, but leftover is less
            unset($option['depth']); // remove depth from option
            $minLeftover = $optie['leftover']; // put min leftover to this option
            unset($optie['leftover']); // remove leftover from option
            $bestPosibility = $option; // best possibility is array with lengths
        }
    }
}

示例

$tree = createPossibilities([5000, 4000, 3000], 8000);
$possibilities = [];
$flatten = flattenArray($tree, $possibilities);
$best = getBestPossibilities($possibilities); // result: [5000, 3000]

3 个答案:

答案 0 :(得分:1)

当您以不同的顺序创建可能性时,可以改进它,例如随机顺序,并在某种递归级别停止。或者您可以使用近似值,例如bin-packing。背包是一个有点不同的问题,因为它给了重量和成本。您还可以尝试动态编程:Dynamic programming and memoization: bottom-up vs top-down approaches和memoization。

答案 1 :(得分:0)

您在此处描述的是Knapsack problem的特殊情况:
给定一组项目(在您的案例中为木板),找到一组项目,使总重量(在您的情况下为木板的总长度)小于给定的限制,并且该值尽可能大(此部分不是在你的例子中存在。)

背包问题有很多解决方案(在维基百科上有描述),但要小心,因为它是NP完全的,这意味着你很难解决你将使用的任何策略。

编辑:它实际上是'找到一个项目集合,以便总重量(在您的情况下,木板的总长度)超过给定限制,并且值(木板总数)尽可能小。

答案 2 :(得分:0)

感谢Phpdna的回答,我发现了一种更简单的方法。我没有走过从最小到最大的长度,而是将它们从最大变为最小。第一次到达整个长度时,我将深度设置为最大深度,因此该功能不会循环十亿次。这似乎完美无缺,比我的速度快得多。

    $lengths = [5800, 5150, 4900, 4600, 4300, 3950, 3650, 3050, 2750, 2450];
    $length = 10000;
    $maxDepth = ceil($length / $lengths[count($lengths) - 1]);
    $leftover = $lengths[0];
    $result = '';
    getBestPossibility($length, $lengths, $result, $leftover, $maxDepth);
    echo $result; // "5800, 4300, 100(leftover)"

public function getBestPossibility($length, $lengths, &$result, &$leftover, &$maxDepth, $depth = 1, $path = '') {        
    if ($depth <= $maxDepth) {
        foreach ($lengths as $l) {       
            if ($length - $l <= 0) {          
                $maxDepth = $depth;
                if (abs($length - $l) < $leftover) {
                    $leftover = abs($length - $l);
                    $result = $depth == 1 ? $l . ', ' . $leftover . '(leftover)' : $path . ', ' . $l . ', ' . $leftover . '(leftover)';
                }
            } else {
                $path = $depth == 1 ? $l : $path . ', ' . $l;
                $depth++;
                getBestPossibility(($length - $l), $lengths, $result, $leftover, $maxDepth, $depth, $path);
            }
        }
    }
}