计算C中的二项式系数

时间:2014-06-18 20:07:23

标签: c algorithm

我找到了以下用于计算nCr的代码,但不理解它背后的逻辑。为什么这段代码有效?

long long combi(int n,int k)
{
    long long ans=1;
    k=k>n-k?n-k:k;
    int j=1;
    for(;j<=k;j++,n--)
    {
        if(n%j==0)
        {
            ans*=n/j;
        }else
        if(ans%j==0)
        {
            ans=ans/j*n;
        }else
        {
            ans=(ans*n)/j;
        }
    }
    return ans;
}

4 个答案:

答案 0 :(得分:9)

这是一个聪明的代码!

一般来说,它的目的是计算以下公式:

ans = n! / (k!)(n-k)!

等于:

ans = n(n-1)(n-2) ... (n-k)...1 / k(k-1)...1 * (n-k)(n-k-1) ... 1

明显取消后:

ans = n(n-1)(n-2)..(n-k+1) / k!

现在请注意,提名者和分母具有相同数量的元素(k元素)

所以ans的计算方式如下:

ans  = 1 // initially
ans *= n/1
ans *= (n-1)/2
ans *= (n-2)/3
.
.
.
ans *=  (n-k+1)/k

再看一下代码,你会注意到:

  1. ans在每次迭代时乘以n
  2. n在每次迭代(n--
  3. 时减少1
  4. ans在每次迭代时除以j
  5. 这正是发布代码所做的事情。现在让我们看一下循环中不同条件的含义,分母从n开始,分母从1到k,所以变量j被分配给分母吧?

    1)if(n%j==0)

    如果n/j是(可计算的),则在每一步

    所以我们首先在这里计算它而不是乘以整个ans,这种做法将结果保持在最小的可能值。

    2)else if(ans%j==0)

    如果我们无法计算n/j,但实际上可以计算ans/j,那么每一步都是如此,这样可以说:

    ans /= j; //first we divide
    ans *= n; //then we multiply
    

    这总是让我们的整体产量尽可能小,对吗?

    3)last condition

    在每一步,如果在这种情况下我们既不能计算n/j也不能计算ans/j,我们就没有幸运地先划分再乘以(因此保持结果很小)。但是我们需要继续进行 - 尽管我们只剩下一个选择

    ans *= n; // multiply first
    ans /= j; // then divide
    

    ET VOILA!

    示例 考虑案例3C7 我们知道答案是7!/ 3!* 4! 因此:ans = 7*6*5 / 1*2*3

    让我们看看每次迭代会发生什么:

    //1 
    ans = 1
    
    //2 
    n = 7
    j = 1
    ans = ans * n/j 
    first compute 7/1 = 7
    then multiply to ans
    ans = 1*7
    ans = 7
    
    //3
    n = 6
    j = 2
    ans = ans* n/j
    
    evaluate n/j = 6/2 (can be divided)
             n/j = 3
    ans = ans *(n/j)
        = 7 * 3
        = 21
    
    // 4
    n = 5
    j = 3
    
    ans = ans * n/j
    evaluate n/j = 5/3 oppsss!! (first if)
    evaluate ans/j = 21/3 = 7 YES (second if)
    
    ans = (ans/j)*n
        = 7*5
        = 35
    
    // end iterations
    

    请注意,在最后一次迭代中,如果我们直接计算,我们会说:

    ans = ans*n/j
        = 21 * 5 / 3
        = 105 / 3
        = 34 
    

    是的,它确实找到了正确的结果,但同时价值飞到105之后再回到35.现在想象一下计算真实的大数??

    <强>结论 此代码仔细计算二项式系数,试图在每个计算步骤中保持输出尽可能小,它通过检查是否可以除(然后执行)来执行,因此它能够计算一些非常大的int直接编码无法处理(可能发生OverFlow)

答案 1 :(得分:4)

要回答部分问题,请考虑n choose k的条目构成Pascal's triangle这一事实。由于Pascal的三角形是对称的,将参数k移动到左半部分就足够了,这是用

完成的。

k=k>n-k?n-k:k;

语句;看看C的条件运算符的定义。

此外,结果ans在开头初始化为包含1,这是Pascal三角形中每一行的第一个条目,这意味着最初ans实际上是n choose j {{1}}。

答案 2 :(得分:1)

事实是1&lt; = k&lt; = n / 2的nCr与n / 2 + 1&lt; = k&lt; = n中的相同。因此,k的第一个变化使其值为左半部分的值。还有一件事nCk意味着(n *(n-1) ..... (nk))/(k *(k-1)* .... * 2 * 1)所以上面的代码迭代地应用它。

答案 3 :(得分:-2)

是肯定的。 [N选择K]减少了它的阶乘,因为除数和除数共享许多因子相互抵消x / x = 1(对于x> 0) 诀窍是不计算大因子,因为这些大因子需要太多的地址空间(太多位)

第一个技巧是在分割之前减少分数。 第二个技巧是在条件内做模数,为当前迭代选择3个操作之一。这可以不同的方式完成,并且整数模数被选择为快速运算符,跳过一些较慢的整数除法方法。

你迭代地遍历帕斯卡三角形。 你走的每一条路都会成倍增加。

每个迭代步骤有3种可能的分支路径: 3个步骤中的每一个将累加器“ans”与不同的值相乘,表示帕斯卡三角形上2个“位置”之间的因子。 你最终总是进行N次乘法,其中N是迭代次数,最后是二项式系数的值。

N是你想知道的帕斯卡三角形的列#,你累加一个N,乘以某个东西,同时每次迭代减少帕斯卡三角形的列数(和行数)N = N-1

J = 1;

ANS = 0;

//在每次迭代中;

ans=ans*n;

n=n-1;

ans=ans/j;

j=n+1;

整数除法很慢,可以跳过(或者通过使除数变小来加快)至少一次,通常多次(因为在帕斯卡三角形中有很多共享的素因子),这是完成的通过模数条件。

帕斯卡三角形是非常对称的(在总结其域时),因此这是有效的。

帕斯卡三角形的(部分)列之和之间的差异显示了对称性,这对于乘法和除法非常重要。

只是观看一些关于帕斯卡三角形的对称性和身份的YouTube视频。