Алгоритм вычисления биномиального коэффициента

Мне нужен способ вычисления комбинаций без запуска из памяти. Вот что у меня пока есть.

public static long combination(long n, long k) // nCk
{
    return (divideFactorials(factorial(n), ((factorial(k) * factorial((n - k))))));
}

public static long factorial(long n)
{
    long result;
    if (n <= 1) return 1;
    result = factorial(n - 1) * n;
    return result;
}

public static long divideFactorials(long numerator, long denominator)
{
    return factorial(Math.Abs((numerator - denominator)));
}

я пометил его как C#, но решение в идеале должно быть независимым от языка.

5 ответов


public static long combination(long n, long k)
    {
        double sum=0;
        for(long i=0;i<k;i++)
        {
            sum+=Math.log10(n-i);
            sum-=Math.log10(i+1);
        }
        return (long)Math.pow(10, sum);
    }

одним из лучших методов вычисления биномиального коэффициента, который я видел, является Марк Доминус. Гораздо менее вероятно переполнение с большими значениями для N и K, чем некоторые другие методы.

public static long GetBinCoeff(long N, long K)
{
   // This function gets the total number of unique combinations based upon N and K.
   // N is the total number of items.
   // K is the size of the group.
   // Total number of unique combinations = N! / ( K! (N - K)! ).
   // This function is less efficient, but is more likely to not overflow when N and K are large.
   // Taken from:  http://blog.plover.com/math/choose.html
   //
   long r = 1;
   long d;
   if (K > N) return 0;
   for (d = 1; d <= K; d++)
   {
      r *= N--;
      r /= d;
   }
   return r;
}

вот решение, которое очень похоже на Боба байрана, но проверяет еще два предварительных условия для ускорения кода.

    /// <summary>
    /// Calculates the binomial coefficient (nCk) (N items, choose k)
    /// </summary>
    /// <param name="n">the number items</param>
    /// <param name="k">the number to choose</param>
    /// <returns>the binomial coefficient</returns>
    public static long BinomCoefficient(long n, long k)
    {
        if (k > n) { return 0; }
        if (n == k) { return 1; } // only one way to chose when n == k
        if (k > n - k) { k = n - k; } // Everything is symmetric around n-k, so it is quicker to iterate over a smaller k than a larger one.
        long c = 1;
        for (long i = 1; i <= k; i++)
        {
            c *= n--;
            c /= i;
        }
        return c;
    }

глядя на ваш код, неудивительно, что у вас закончится память довольно быстро. Ваш метод divideFactorials вызывает метод factorial и использует в качестве аргумента разность "числитель-знаменатель". Эта разница, скорее всего, будет очень большой в соответствии с вашим кодом, и вы застрянете в очень длинном цикле в своем факториальном методе.

Если это действительно только о поиске nCk (который я предполагаю, потому что ваш комментарий в вашем коде), просто используйте:

public static long GetnCk(long n, long k)
{
    long bufferNum = 1;
    long bufferDenom = 1;

    for(long i = n; i > Math.Abs(n-k); i--)
    {
        bufferNum *= i;
    }

    for(long i = k; i => 1; i--)
    {
        bufferDenom *= i;
    }

    return (long)(bufferNom/bufferDenom);
}

Of конечно, используя этот метод, вы очень быстро выйдете из диапазона, потому что long фактически не поддерживает очень длинные числа, поэтому n и k должны быть меньше 20.

предположим, что вы действительно работаете с очень большими числами, вы можете использовать двойники вместо длинных, поскольку силы становятся все более значимыми.

Edit: Если вы используете большие числа, вы также можете использовать формулу Стирлинга:

как n становится большим ln (n!)- > n * ln(n) - n.

подставляя это в код:

public static double GetnCk(long n, long k)
{
    double buffern = n*Math.Log(n) - n;
    double bufferk = k*Math.Log(k) - k;
    double bufferkn = Math.Abs(n-k)*Math.Log(Math.Abs(n-k)) - Math.Abs(n-k);

    return Math.Exp(buffern)/(Math.Exp(bufferk)*Math.Exp(bufferkn));
}

Я только предлагаю этот ответ, так как вы сказали, что язык независим, код c# просто используется для его демонстрации. Поскольку для этого вам нужно использовать большие числа для n и k, я предлагаю это как общий способ поиска биномиального коэффициента для больших комбинаций.

для случаев были n и k оба меньше, чем около 200-300, вы должны использовать ответ Виктор Mukherjee предложил, как это точный.

Edit2: Отредактировал мой первый код.


просто ради завершения: стандарт C математическая библиотека имеет реализации как Γ, так и lnΓ (называется tgamma и lgamma), где

Γ (n) & равно; (n-1)!

вычисление библиотеки, безусловно, быстрее и точнее, чем суммирование логарифмов. Для получения дополнительной информации см. Википедия и Mathworld.