计算php数组中可能的解决方案:如何改进我的算法?

时间:2016-07-23 18:12:34

标签: php arrays algorithm sorting permutation

序言

这个问题即将学习PHP,也不是我计划在高效环境中使用的代码。我只是想看到并学习更好的方法来完成这项工作,正如我在我的方法中所做的那样。因此,请仅纠正我的代码或向我展示更好,更快或更短的解决方案。问题本身已经解决了。谢谢!

问题:

前几天用户asked a question在这里上了SO。他的问题引起了我的注意,因为我想找到一种方法来解决他的需求。

他希望获得PHP array的所有可能的组合,其中值的总和 100,或者尽可能接近可能为100 。他给了我们一个示例数组,我将用于我的例子:

$array = array(25, 30, 50, 15, 20, 30);

例如,一个结果应为[2, 4, 5],因为50 + 20 + 30100

$sum = $array[2] + $array[4] + $array[5]; // = 100

我认为基本的想法应该是明确的。现在让我们来看看我的工作......

我的方法:

所以这个问题引起了我作为开发人员的注意。起初,我认为这很简单。只需做一些添加并检查结果。但后来我注意到有一点需要牢记......

有很多组合需要测试。对于示例数组,最多可存在720(6! = 1*2*3*4*5*6 = 720)个排列。为了获得所有可能的组合,我想首先得到阵列的所有可能的排列。

但那只是半真半假。因为数组中可能有双值(,如示例中的30 ),我们无法获得数组值的所有可能排列,我们必须得到所有可能的排列。而是数组键。

所以我使用了php cookbookpc_permut功能并根据我的需要对其进行了修改。它将返回一组键中所有可能的排列。

/**
 * gets all possible permutations of $array
 * @param array $array
 * @param array $permutations
 * @return array
 */
function permutations($array, $permutations = array()) {
    if( !empty($array) ) {
        $result = array();

        for( $i = count($array) - 1; $i >= 0; --$i ) {
            $newItems = $array;
            $newPerms = $permutations;
            list($values) = array_splice($newItems, $i, 1);
            array_unshift($newPerms, $values);
            $result = array_merge($result, permutations($newItems, $newPerms));
        }
    }
    else {
        $result = array($permutations);
    }

    return $result;
}

此函数的结果是一个多维数组,包含有序键数组中的所有排列。

Array (
    [0] => Array (
        [0] => 0
        [1] => 1
        [2] => 2
        [3] => 3
        [4] => 4
        [5] => 5
    )
    [1] => Array (
        [0] => 1
        [1] => 0
        [2] => 2
        [3] => 3
        [4] => 4
        [5] => 5
    )
    [...
)

所以,现在我得到了所有的排列。计算可能的组合并不是那么难。我只是循环遍历排列,增加总和直到它们达到100或更高并返回组合键。

但我发现我错过了一件事。当我得到所有可能的排列时,甚至有一些结果在列表中加倍。为了解释,这两个结果基本相同:

[2, 4, 5]; // 50 + 20 + 30 = 100
[4, 5, 2]; // 20 + 30 + 50 = 100

我在计算后最终对键进行了排序,并将它们用作结果数组中的索引。所以可以肯定的是,每个组合只在结果中存在一次。这是我的combinations函数:

/**
 * gets all possible key combinations of $array with a sum below or equal $maxSum
 * @param array $array
 * @param integer $maxSum
 * @return array
 */
function combinations($array, $maxSum) {
    // get all permutations of the array keys
    $permutations = permutations(array_keys($array));
    $combinations = array();

    // loop all permutations
    foreach( $permutations as $keys ) {
        // create a container for each permutation to store calculation
        $current = array(
            "sum"  => 0,
            "keys" => array()
        );

        // now loop through the permutation keys
        foreach( $keys as $key ) {
            // if the addition is still between or equal $maxSum
            if( $current["sum"] + $array[$key] <= $maxSum ) {
                // increment the sum and add key to result
                $current["sum"] += $array[$key];
                $current["keys"][] = $key;
            }
        }

        // to be sure each combination only exists once in the result
        // order the keys and use them as array index
        sort($current["keys"]);
        $combinations[join("", $current["keys"])] = $current;
    }

    // remove the created key-index from array when finished
    return array_values($combinations);
}

执行很简单:

$array = array(25, 30, 50, 15, 20, 30);
print_r(combinations($array, 100));

结果是一个包含所有组合的数组。对于我们的示例数组,有11种可能的组合。结果如下:

Array (
    [0] => Array (
        [sum] => 90
        [keys] => Array (
            [0] => 0
            [1] => 1
            [2] => 3
            [3] => 4
        )
    )
    [1] => Array (
        [sum] => 90
        [keys] => Array (
            [0] => 0
            [1] => 2
            [2] => 3
        )
    )
    [...

由于我已将此脚本编写为answer of the original question,我会问自己,是否还有其他甚至更好的方法来完成这项工作。也许有一种没有排列的方法,或者从计算或结果数组中排除相同组合的方法。我知道我也可以直接在permutations函数中执行计算,但这基本上是相同的工作流程。

我真的希望得到一些建议,提示或改进。我认为这里有一些改进脚本的潜力,但实际上我不知道怎么做。但我确信它可以更简单直接地完成......

感谢您的时间! :)

1 个答案:

答案 0 :(得分:0)

对数组进行排序会带来一些可能性:这是我想到的那个:

我表示(selectedIndexes)由selectedIndexes的所有元素组成的元素,例如 a({25,30,30})=(25,30,30)

P(n)是索引1到n的所有组合的集合,为清楚起见,我的数组从索引1开始(因此P(2)= {1,2,(1,2)})

我正在使用下面的伪代码中解释的2个中断条件。第一个是aSorted =允许总和的第一个元素。 第二个是与aSorted第一个元素相比总和太小

    selectedIndexes = {}
    sum = 100  
    aSorted = {15, 20, 25, 30, 30, 50} //starting values for the example
                                       //to clarify the following function
    aSum = {15, 35, 60, 90}

function n(aSorted, sum, selectedIndexes){
    compute aSum //precisely search in aSum for the index at which
                 //the elements are bigger than sum, and cut
    answer = (P(count(aSum))) X a(selectedIndexes) // with X being the cartesian product

    for (i=count(aSum)+1; i<=count(aSorted); i++){
        newASorted = splice(aSorted, count(aSum))
        // 1st break condition
        if(newASorted is empty) return answer
        // 2nd break condition the new sum < the first element of aSorted
        if (aSorted(i)<sum && sum-aSorted(i)>=aSorted(1)){
            answer += n(newASorted, sum-aSorted(i), push(selectedIndexes,    
            i))
        }
    }
return answer
}

这个算法的复杂性感觉是二次的(在快速检查之后它更像是n ^ log2(n)的顺序)关于数组中元素的数量

为了使它不那么抽象,让我们开发一个例子(警告我比伪代码更信任这个例子,尽管我自己在伪代码中没有看到不准确):

n({15,20,25,30,30,50},100,{})= P(4)+ n({15,20,25,30,30},50,{6}) + n({15,20,25,30},70,{5})

首先开发方程右侧的第n个函数

n({15,20,25,30,30},50,{5})=(P(2)X {6})+ n({15,20,25,30},20,{ 5,6}} + n({15,20,25},20,{4,6})+ n({15,20},25,{3,6}}

n({15,20,25,30},20,{5,6}} =(P(1)X {(5,6)})// + n({15},0,{ 2,5,6}}(但0 <15)断裂条件2

n({15,20,25},20,{4,6})= P(1)X {(4,6)} //和休息条件2

n({15,20},25,{3,6}} = P(1)X {(3,6)} // + n({15},5,{2,3,6} )(但5 <15)断裂条件2

现在开发方程右侧的第二个n函数

n({15,20,25,30},70,{5})=(P(3)X {5})+ n({15,20,25},40,{4,5} )

n({15,20,25},40,{4,5}} =(P(2)X {(4,5)})+ n({15,20},15,{3, 4,5}}

n({15,20},15,{3,4,5}} = P(1)x {(3,4,5)} // + n({},0,{1,3 ,4,5})新的休息条件aSum为空