HOWTO · Python

Python 中的二項式係數

本教程演示二項式係數以及如何在 Python 中計算它。

本頁內容

二項式係數表示:從較大的集合中選出指定數量的項目,且不考慮順序時,有多少種不同的選法。Python 提供標準函式庫中的直接解法、SciPy 的兩個函式,以及幾種自行實作公式的簡單方式。該選擇取決於你是否需要精確整數結果、SciPy 特有的行為,或可依需求調整的計算方式。

什麼是二項式係數?

二項式係數「n 選 k」寫作 (\binom{n}{k}),表示從 n 個相異項目中選出 k 個,且不考慮順序時的選法數量。其公式如下:

[
\binom{n}{k} = \frac{n!}{k!(n-k)!}
]

其中 ! 表示階乘。例如,從 10 人中挑選 3 人組成團隊,有 (\binom{10}{3}=120) 種可能。無論名單上依序列出 Alice、Ben、Cara,或以其他順序列出,都是同一個團隊。

在一般的組合問題中,n 和 k 都是整數,且 n >= 0、0 <= k <= n。二項式係數的結果是精確整數。如果你要建模的是重複試驗中成功次數的機率,請參閱Python 中的二項分布;該分布會用到二項式係數,但回答的是不同問題。

在 Python 中使用 math.comb() 計算二項式係數

對整數組合而言,math.comb(n, k) 是最簡單的標準函式庫選擇。它會傳回精確整數,不需要安裝額外套件。它也會在內部利用對稱性 (\binom{n}{k}=\binom{n}{n-k}),因此傳入選取項目數的任一側都可以。

執行以下範例:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

import math

print(math.comb(10, 3))

這個範例提供給文章執行環境執行;這份草稿未附上觀察到的輸出。math.comb() 接受整數輸入。在一般的非負整數問題中,k 不得大於 n;引數無效時,函式會拒絕處理。若你要取得無序選取方式的精確數量,請使用這個方法。

使用 SciPy 計算二項式係數

SciPy 提供 scipy.special.binom() 和 scipy.special.comb(),但這兩個函式在所有情況下並不能互換。兩者都需要安裝 SciPy;這項額外依賴在科學計算的 Python 專案中通常很實用。

scipy.special.binom(n, k) 以伽瑪函數定義,並支援實數引數。它會傳回浮點數,因此適合一般化計算,不適合必須使用精確整數運算的情況。尤其是,它不像 math.comb() 那樣,將輸入限定為整數組合的詮釋。

scipy.special.comb(n, k, exact=False) 是為組合輸入設計的。預設會傳回浮點數結果。當 n 和 k 是整數且需要精確整數結果時,可設定 exact=True。這個選項不會讓任意實數輸入變成精確結果。若要處理一般整數組合範圍以外的值,請確認該函式對輸入的限制。

如果專案已使用 SciPy,或需要它更廣泛的數值處理功能,就可以選擇 SciPy。若只需要整數結果,而且不想增加第三方依賴,則優先使用 math.comb()。

使用 math.factorial() 實作公式

可以直接使用 math.factorial() 實作定義公式。這種寫法清楚呈現組合與階乘之間的關係,但會先計算三個階乘值,再進行除法。這是適合教學的實作;日常計算則使用 math.comb() 更直接。

本節中的程式碼以及顯示的標籤或輸出仍為英文。

from math import factorial


def binomial(n, k):
    if n < 0 or k < 0 or k > n:
        raise ValueError("require n >= 0 and 0 <= k <= n")
    return factorial(n) // (factorial(k) * factorial(n - k))

此函式會檢查一般組合問題的輸入範圍,並使用整數除法。在這些假設成立時,除法結果是精確的。階乘會快速增大,因此這種寫法會建立一些表示最終係數並不需要的龐大中間整數。Python 整數支援任意精度,但這不代表對大型輸入而言,多做這些運算會更理想。

使用乘積計算二項式係數

乘積公式可避免計算完整的階乘。對有效的整數輸入,可使用:

[
\binom{n}{k}=\prod_{i=1}^{k}\frac{n-k+i}{i}.
]

每一步都可以精確地使用整數除法,因為乘上每個因子後的部分結果都是整數。operator.mul 可將乘法運算提供給 functools.reduce;或者使用明確的迴圈,讓每一步的更新方式更容易理解。

本節中的程式碼以及顯示的標籤或輸出仍為英文。

import operator
from functools import reduce


def binomial_product(n, k):
    if n < 0 or k < 0 or k > n:
        raise ValueError("require n >= 0 and 0 <= k <= n")
    k = min(k, n - k)
    return reduce(
        operator.mul,
        (n - k + i for i in range(1, k + 1)),
        1,
    ) // reduce(operator.mul, range(1, k + 1), 1)

這個版本分別計算分子與分母的乘積,但仍會建立中間值,並在最後進行除法。逐步更新結果的迴圈可以讓中間值保持較小:

def binomial_product_loop(n, k):
    if n < 0 or k < 0 or k > n:
        raise ValueError("require n >= 0 and 0 <= k <= n")
    k = min(k, n - k)
    result = 1
    for i in range(1, k + 1):
        result = result * (n - k + i) // i
    return result

這兩種自訂實作都假設輸入為整數,並會拒絕一般組合範圍以外的值。除非是為了學習或調整而需要自訂計算方式,否則正式程式碼建議使用 math.comb()。若 SciPy 的數值 API 適合較大的科學計算流程,就可以使用 SciPy;math.factorial() 展示了公式,而乘積法則可避免建立完整的階乘。