简体   繁体   English

写一个更快的组合算法

[英]Write a faster combinatorics algorithm

I'm trying to write a combinatorics algorithm to get all the possible combinations of k out of n without repetitions. 我正在尝试编写一个组合算法,以获得nk所有可能组合而不重复。

The formula is: 公式是:

n!/(k!(n-k)!)); 

The results end up in an array. 结果以数组结尾。 What I've actually written is this: 我实际写的是这样的:

function Factorial($x)
{
    if ($x < 1)
    {
        echo "Factorial() Error: Number too small!";
    )

    $ans = 1;
    for ($xx = 2; $xx >= $x; $xx++)
    {
        $ans = $ans * $xx;
    }

    return($ans);
}

function Combination($selectcount,$availablecount)
{
    $ans = Factorial($availablecount) / (
        Factorial($availablecount - $selectcount) * Factorial($selectcount)
    );

    return ($ans);
}

Is this the fastest way to accomplish this? 这是实现这一目标的最快方法吗? Is there a way to speed this up? 有没有办法加快速度? Maybe to write it recursively? 也许以递归方式写出来?

I think the problem is to calculate C(n,k) which can be done without calculating factorial, the trick is to note first that 我认为问题是计算C(n,k),这可以在不计算阶乘的情况下完成,诀窍是首先要注意

C(n,k) = (n*(n-1)...(n-k+1)) / (1*2*...*k) = (n/1)*(n-1/2)*...(n-k+1/k)

Also for efficiency 也为了效率

C(n,k) = C(n,n-k), therefore choose which ever is smaller k or n-k

Feel free to edit if there is a mistake as i have converted it from C and i dont know php 如果有错误,请随意编辑,因为我已经从C转换它,我不知道PHP

function nCk($n, $k)
{
    if( $n-$k<$k )
        $k = $n-$k;
    $ans = 1;
    for( $i=1; $i<=$k; ++$i )
    {
        $ans = ($ans*($n-$i+1))/$i;
    }
    return $ans;
}

IMO it is not worth to optimize that unless HEAVY usage, due to float point limitations: 170! IMO不值得优化,除非由于浮点限制而使用HEAVY:170! = 7.257415615308E+306, and next factorial (171!) is beyond floating point range. = 7.257415615308E + 306,下一个阶乘(171!)超出浮点范围。 I guess that recursion WILL slow down the process (but not tested that). 我猜这个递归会减慢进程(但没有测试过)。

function Factorial($x)
{
    if ($x < 1)
    {
        echo "Factorial() Error: Number too small!";
    }

That's wrong, 0! = 1 那是错的, 0! = 1 0! = 1 is defined, so the test should be $x < 0 . 定义了0! = 1 ,因此测试应该是$x < 0

    $ans = 1;
    for ($xx = 2; $xx >= $x; $xx++)

You typo'ed the condition, it must be $xx <= $x . 你错了条件,它必须是$xx <= $x

function Combination($selectcount,$availablecount)
{
    $ans = Factorial($availablecount) / (
        Factorial($availablecount - $selectcount) * Factorial($selectcount)
    );

    return ($ans);
}

You have two potential problems here, 你有两个潜在的问题,

  1. calling the Factorial function is slower than having the loop calculating the combination count here 调用Factorial函数比在这里计算组合计数的循环要慢
  2. the factorials become large very quickly, so you risk overflow and inaccuracies where you needn't 阶乘变得非常快,因此在不需要的地方冒险溢出和不准确

Whether these are actual problems depends on your application. 这些是否是实际问题取决于您的应用。 You wrote that the results end up in an array, presumably to avoid recalculation, so the speed for the initial calculation is less important. 你写道,结果最终是一个数组,大概是为了避免重新计算,所以初始计算的速度不那么重要。 However, the overflow problems may well be. 但是,溢出问题可能很好。 To avoid those, calculate the array entries recursively per Pascal's triangle, choose(n+1,k) = choose(n,k) + choose(n,k-1) , where choose(n,k) = 0 if k < 0 or k > n . 为了避免这些,按照Pascal三角形递归计算数组条目, choose(n+1,k) = choose(n,k) + choose(n,k-1) ,其中如果k < 0 choose(n,k) = 0 k < 0k > n Alternatively, you can calculate each row starting with choose(n,0) = 1 and choose(n,k) = choose(n,k-1)*(n+1-k)/k for 1 <= k <= n . 或者,您可以计算以choose(n,0) = 1开头的每一行choose(n,0) = 1choose(n,k) = choose(n,k-1)*(n+1-k)/k1 <= k <= n Both methods avoid the large intermediate n! 两种方法都避免了大的中间n! and thus give accurate results for a wider range of numbers. 从而为更广泛的数字提供准确的结果。

This is the fastest I've ever managed to get a factorial loop: 这是我设法获得阶乘循环的最快速度:

function Factorial($factVal) {
    if ($factVal < 0) {
        die("Factorial() Error: Number too small!");
    }

    $factorial = 1;
    while ($factVal > 1) {
        $factorial *= $factVal--;
    }
    return $factorial ;
}

You don't actually need to compute the full numerator and denominator. 您实际上不需要计算完整的分子和分母。 For instance: 例如:

C(7,2) = 7! / (2! * (7-2)!) = 7! / (2! * 5!) = (7 * 6) / (2 * 1)

That is, the largest factor in the denominator cancels the lowest part of the numerator's factorial. 也就是说,分母中的最大因子取消了分子的阶乘的最低部分。 So, for instance, if k > n/2, all you need to do is multiply the numbers from k+1 through n and then divide by (nk)!. 所以,例如,如果k> n / 2,你需要做的就是将数字从k + 1乘以n然后除以(nk)! This saves considerable work over computing the full factorial. 这节省了相当于计算全因子的大量工作。

Here's a draft at this approach: 这是这种方法的草案:

function Combination($selectcount,$availablecount)
{
    $remainder = $availablecount - $selectcount;
    if ($remainder > $selectcount) {
        $tmp = $remainder;
        $remainder = $selectcount;
        $selectcount = $tmp;
    }
    $ans = 1;
    while ($availablecount > $selectcount) {
        $ans *= $availablecount;
        $availablecount--;
    }
    while ($remainder > 1) {
        $ans /= $remainder;
        $remainder--;
    }

    return ($ans);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM