编写更快的组合算法

发布于 2024-12-22 20:58:08 字数 653 浏览 2 评论 0原文

我正在尝试编写一个组合算法,以在不重复的情况下从 n 中获取 k 的所有可能组合。

公式为:

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

结果最终存放在一个数组中。我实际上写的是:

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);
}

这是实现这一目标的最快方法吗?有没有办法加快这个速度?也许递归地写?

I'm trying to write a combinatorics algorithm to get all the possible combinations of k out of n without repetitions.

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

漆黑的白昼 2024-12-29 20:58:08

我认为问题是计算 C(n,k) ,这可以在不计算阶乘的情况下完成,技巧是首先要注意,

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

也是为了效率

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

,如果有错误,请随意编辑,因为我已经从 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;
}

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) = (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

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;
}
娇妻 2024-12-29 20:58:08

IMO 不值得优化,除非大量使用,因为浮点限制:170! = 7.257415615308E+306,下一个阶乘(171!)超出了浮点范围。
我想递归会减慢这个过程(但没有测试过)。

IMO it is not worth to optimize that unless HEAVY usage, due to float point limitations: 170! = 7.257415615308E+306, and next factorial (171!) is beyond floating point range.
I guess that recursion WILL slow down the process (but not tested that).

没有你我更好 2024-12-29 20:58:08
function Factorial($x)
{
    if ($x < 1)
    {
        echo "Factorial() Error: Number too small!";
    }

这是错误的,0! = 1 已定义,因此测试应为 $x < 0 。

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

您输入了错误的条件,它必须是 $xx <= $x

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

    return ($ans);
}

这里有两个潜在的问题,

  1. 调用阶乘函数比循环计算组合计数要慢,
  2. 阶乘很快就会变大,因此在不需要的地方可能会出现溢出和不准确的风险

。实际问题取决于您的应用程序。您写道,结果最终以数组形式结束,大概是为了避免重新计算,因此初始计算的速度不太重要。然而,溢出问题很可能是存在的。为了避免这些问题,请根据帕斯卡三角形递归计算数组条目,choose(n+1,k) = choice(n,k) + Choose(n,k-1),其中 choose (n,k) = 0 如果 k 0k > n。或者,您可以从 choose(n,0) = 1choose(n,k) = choice(n,k-1)*(n+1-k) 开始计算每一行)/k 表示 1 <= k <= n。这两种方法都避免了大的中间 n!,因此可以为更广泛的数字提供准确的结果。

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

That's wrong, 0! = 1 is defined, so the test should be $x < 0.

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

You typo'ed the condition, it must be $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
  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. 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. Both methods avoid the large intermediate n! and thus give accurate results for a wider range of numbers.

舟遥客 2024-12-29 20:58:08

这是我获得阶乘循环最快的速度:

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

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

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 ;
}
檐上三寸雪 2024-12-29 20:58:08

您实际上不需要计算完整的分子和分母。例如:

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

也就是说,分母中最大的因数抵消了分子阶乘中最低的部分。因此,例如,如果 k > n/2,您所需要做的就是将 k+1 到 n 的数字相乘,然后除以 (nk)!。这节省了计算全阶乘的大量工作。

这是此方法的草案:

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);
}

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 (n-k)!. 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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文