目录
前言
学习 Steven Brunton 讲授的概率与统计入门概述视频,本篇文章记录第七讲:二项分布与多项分布,记录下个人学习笔记,和大家一起分享交流😄
video :https://www.youtube.com/playlist?list=PLMrJAkhIeNNR3sNYvfgiKgcStwuPSts9V
1. 引言
我们一直在研究如何计算概率问题,比如:一副扑克牌中能组成同花顺的手牌有多少种可能,或者出现满堂红的手牌组合有多少种,归根结底,这需要计算特定事件在所有可能情况中出现的概率。
在所有扑克牌组合中,需要统计出同花、满堂红等特定牌型的出现次数,这本质上就是统计事件数量的问题 --- 比如计算所有可能的扑克手牌总数,或者统计包含单张 K 的手牌组合数量等。
我们总结出了一些非常实用的计算公式,现在我要写下一个重要公式,这个公式与二项式系数密切相关,之后我们会将其推广到多项式分布,这个公式在计算可能出现的扑克牌组合数量时非常实用,当我们从一副 52 张扑克牌中发出 5 张手牌时,其分布符合二项式分布。
如果我发两副扑克手牌,假设有两个玩家在玩,那么发两副或三副手牌分别由多少种发牌方式?这属于多项式分布的范畴,这套公式极其实用,是计算各类复杂概率问题的必备工具。
因此,我们能收集到的无序样本数量就是无序样本的总数,从总数为 n n n 的物件中抽取样本,那么 n n n 就是 52 张牌,我抽到的样本就是一手 5 张牌,无序样本的数量计算方式为:从总量 n n n 中无放回抽取,即当我发出第一张牌后,牌堆只剩 n − 1 n-1 n−1 张牌,发出第二张牌后,牌堆仅剩 50 张牌,因此,无放回的无序样本数量等于组合数 ( n r ) \binom{n}{r} (rn) ,记住 n C r nC_r nCr 是其简写形式,我们有时称之为 "从 n n n 中选取 r r r 个样本",这个公式的字面含义就是 " n n n 选 r r r ",记住,这个公式的计算方式是:
( n r ) = n ! ( n − r ) ! r ! \binom{n}{r} = \frac{n!}{(n-r)! \, r!} (rn)=(n−r)!r!n!
这就是计算公式,这就是我们计算扑克牌可能发牌组合数的方法,从一副 52 张的扑克牌中,不考虑发牌顺序且无放回抽取,总共可以发出多少种不同的 5 张牌组合?无论是先拿到 7 还是先拿到 9,最终组合结果都是相同的,这就是从 n n n 个元素中无序抽取 r r r 的样本的组合数,记作 ( n r ) \binom{n}{r} (rn) ,这个数值与二项式系数密切相关。
2. 二项式系数
我想展示这个是因为它非常有趣 --- 你可能在帕斯卡三角形和多项式展开系数的计算中见过它,若取 ( x + y ) 2 (x+y)^2 (x+y)2 或者更一般地考虑 ( x + y ) n (x+y)^n (x+y)n ,展开后将得到:
( x + y ) n = ∑ k = 0 n ( n k ) ( x k y n − k ) (x+y)^n = \sum_{k=0}^{n} \binom{n}{k} (x^ky^{n-k}) (x+y)n=k=0∑n(kn)(xkyn−k)
若干形如 x k y n − k x^ky^{n-k} xkyn−k 的项之和,求和符号从 k = 0 k=0 k=0 到 n n n ,这些单项式的系数将是 ( n k ) \binom{n}{k} (kn) ,这些就是二项式系数,之所以称为 "二项式",是因为我们实际上是将两个项相加后求幂,因此这个多项式就是所有这些单项式的总和,其中系数正是二项式系数,要计算这些系数,可以通过帕斯卡三角形来求解,想必大家对此早已熟悉。

现在观察系数,帕斯卡三角形的顶端数字是 1,因此 ( x + y ) 0 (x+y)^0 (x+y)0 的结果就是 1, ( x + y ) 1 (x+y)^1 (x+y)1 展开后, x x x 和 y y y 的系数都是 1,帕斯卡三角形中的这些数字就是对应的系数, ( x + y ) 2 (x+y)^2 (x+y)2 展开后的系数是 1、2、1,展开式是 x 2 + 2 x y + y 2 x^2 + 2xy + y^2 x2+2xy+y2 , ( x + y ) 3 (x+y)^3 (x+y)3 展开后得到 x 3 + 3 x 2 y + 3 x y 2 + y 3 x^3 + 3x^2y + 3xy^2 + y^3 x3+3x2y+3xy2+y3 ,注意这个三角形中,每个新项都是它上方两项之和, ( x + y ) 4 (x+y)^4 (x+y)4 的展开系数为 1、4、6、4、1, ( x + y ) 5 (x+y)^5 (x+y)5 的展开系数为 1、5、10、10、5、1。
因此,我们可以直接从帕斯卡三角形中读出这个多项式展开的系数,构造起来非常简单,这个巧妙思路与概率论中频繁出现的 ( n k ) \binom{n}{k} (kn) 二项式系数密切相关,你会发现这种对称结构非常优美,蕴含着精妙的数学美感,这确实令人着迷。
但你会发现,当 n → ∞ n \rightarrow \infty n→∞ 时,比如计算 ( x + y ) 100 (x+y)^{100} (x+y)100 的展开式,这些系数的分布将逐渐收敛于正态分布,这种趋势在此已初现端倪,可以看出这个分布逐渐趋于正态分布。
事实上,你或许会想在计算机上编写代码实现这个现象,尝试取较大的 n n n 值构建三角形,逐行绘制每一层的分布,最终会收敛到优美的高斯分布(即正态分布)。
Python 示例代码如下:
python
"""
帕斯卡三角形(二项式系数)逐渐趋近正态分布的可视化。
数学关系:
C(n, k) / 2^n = P(X = k), X ~ Binomial(n, 1/2)
当 n 足够大时,将横坐标标准化:
z = (k - n/2) / sqrt(n/4)
标准化后的二项分布逐渐趋近标准正态分布 N(0, 1)。
"""
import argparse
import math
from typing import Iterable
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
def pascal_row(n: int) -> list[int]:
"""返回帕斯卡三角形第 n 行,即 (x+y)^n 的全部二项式系数。"""
if n < 0:
raise ValueError("n 必须是非负整数。")
return [math.comb(n, k) for k in range(n + 1)]
def binomial_probability(n: int) -> tuple[np.ndarray, np.ndarray]:
"""
返回 X ~ Binomial(n, 1/2) 的横坐标 k 和概率 P(X=k)。
使用递推方式计算概率,避免直接把超大的组合数转换为浮点数:
p_0 = 2^(-n)
p_(k+1) = p_k * (n-k)/(k+1)
"""
if n < 0:
raise ValueError("n 必须是非负整数。")
k = np.arange(n + 1, dtype=float)
if n == 0:
return k, np.array([1.0])
probabilities = np.empty(n + 1, dtype=float)
probabilities[0] = math.exp(-n * math.log(2.0))
for i in range(n):
probabilities[i + 1] = probabilities[i] * (n - i) / (i + 1)
# 修正可能出现的微小浮点误差。
probabilities /= probabilities.sum()
return k, probabilities
def standard_normal_pdf(z: np.ndarray) -> np.ndarray:
"""标准正态分布 N(0,1) 的概率密度函数。"""
return np.exp(-0.5 * z**2) / math.sqrt(2.0 * math.pi)
def standardized_binomial(n: int) -> tuple[np.ndarray, np.ndarray]:
"""
将 Binomial(n, 1/2) 标准化,并把离散概率转换为近似密度。
z = (k - μ) / σ
density ≈ P(X=k) / Δz = P(X=k) * σ
"""
k, probability = binomial_probability(n)
if n == 0:
return np.array([0.0]), np.array([1.0])
mean = n / 2.0
std = math.sqrt(n) / 2.0
z = (k - mean) / std
density = probability * std
return z, density
def print_pascal_rows(max_n: int) -> None:
"""在终端打印从第 0 行到第 max_n 行的帕斯卡三角形。"""
if max_n < 0:
raise ValueError("max_n 必须是非负整数。")
rows = [" ".join(map(str, pascal_row(n))) for n in range(max_n + 1)]
width = len(rows[-1])
for n, row in enumerate(rows):
print(f"n={n:>2}: {row.center(width)}")
def plot_selected_rows(rows: Iterable[int]) -> None:
"""绘制若干指定行,并与标准正态分布进行比较。"""
rows = sorted(set(rows))
if not rows or rows[0] < 1:
raise ValueError("用于标准化绘图的 n 必须是正整数。")
figure, axis = plt.subplots(figsize=(11, 7))
z_gaussian = np.linspace(-4.5, 4.5, 1000)
axis.plot(
z_gaussian,
standard_normal_pdf(z_gaussian),
linewidth=3,
label="Standard normal distribution",
)
for n in rows:
z, density = standardized_binomial(n)
axis.plot(
z,
density,
marker="o" if n <= 20 else None,
markersize=4,
linewidth=1.8,
label=f"Pascal row n={n}",
)
axis.set_title("Pascal Triangle Coefficients Converging to a Gaussian Distribution")
axis.set_xlabel(r"Standardized position $z=(k-n/2)/\sqrt{n/4}$")
axis.set_ylabel("Probability density")
axis.set_xlim(-4.5, 4.5)
axis.grid(alpha=0.3)
axis.legend()
figure.tight_layout(rect=[0, 0, 1, 0.94])
plt.show()
def plot_original_distribution(n: int) -> None:
"""以原始横坐标 k 绘制第 n 行系数归一化后的二项分布。"""
k, probability = binomial_probability(n)
figure, axis = plt.subplots(figsize=(11, 6))
axis.bar(k, probability, width=0.85)
mean = n / 2.0
std = math.sqrt(n) / 2.0
x = np.linspace(max(0.0, mean - 4.5 * std), min(float(n), mean + 4.5 * std), 1000)
gaussian_approximation = (
np.exp(-0.5 * ((x - mean) / std) ** 2)
/ (std * math.sqrt(2.0 * math.pi))
)
axis.plot(x, gaussian_approximation, linewidth=3, label="Gaussian approximation")
axis.set_title(
rf"Normalized Coefficients of $(x+y)^{{{n}}}$: "
rf"$P(X=k)=\binom{{{n}}}{{k}}/2^{{{n}}}$"
)
axis.set_xlabel("k")
axis.set_ylabel("Probability")
axis.grid(axis="y", alpha=0.3)
axis.legend()
figure.tight_layout()
plt.show()
def animate_convergence(
max_n: int = 100,
interval_ms: int = 100,
save_path: str | None = None,
) -> None:
"""逐行绘制 n=1,...,max_n,动态展示其向标准正态分布收敛。"""
if max_n < 1:
raise ValueError("max_n 必须至少为 1。")
figure, axis = plt.subplots(figsize=(11, 7))
z_gaussian = np.linspace(-4.5, 4.5, 1000)
axis.plot(
z_gaussian,
standard_normal_pdf(z_gaussian),
linewidth=3,
label="Standard normal distribution",
)
binomial_line, = axis.plot([], [], linewidth=2, marker="o", markersize=3)
title = axis.set_title(
r"Pascal row $n=1$: "
r"$\binom{n}{k}/2^n \longrightarrow N(0,1)$",
pad=12,
)
axis.set_xlabel(r"Standardized position $z=(k-n/2)/\sqrt{n/4}$")
axis.set_ylabel("Probability density")
axis.set_xlim(-4.5, 4.5)
axis.set_ylim(0.0, 0.45)
axis.grid(alpha=0.3)
axis.legend()
def update(n: int):
z, density = standardized_binomial(n)
binomial_line.set_data(z, density)
binomial_line.set_marker("o" if n <= 30 else "")
title.set_text(
rf"Pascal row $n={n}$: "
rf"$\binom{{n}}{{k}}/2^n \longrightarrow N(0,1)$"
)
return binomial_line, title
animation = FuncAnimation(
figure,
update,
frames=range(1, max_n + 1),
interval=interval_ms,
blit=False,
repeat=True,
)
figure.tight_layout()
if save_path:
try:
animation.save(save_path, writer="pillow", fps=max(1, 1000 // interval_ms))
print(f"动画已保存到:{save_path}")
except Exception as exc:
raise RuntimeError(
"保存 GIF 失败,请先安装 Pillow:pip install pillow"
) from exc
else:
plt.show()
def parse_rows(text: str) -> list[int]:
"""把 '2,5,10,30,100' 转换为整数列表。"""
try:
rows = [int(item.strip()) for item in text.split(",") if item.strip()]
except ValueError as exc:
raise argparse.ArgumentTypeError("rows 必须是用逗号分隔的整数。") from exc
if not rows or any(n < 1 for n in rows):
raise argparse.ArgumentTypeError("rows 中的每个 n 都必须是正整数。")
return rows
def main() -> None:
parser = argparse.ArgumentParser(
description="可视化帕斯卡三角形系数向正态分布收敛的现象。"
)
parser.add_argument(
"--mode",
choices=["triangle", "selected", "original", "animate"],
default="selected",
help=(
"triangle: 打印帕斯卡三角形;"
"selected: 比较若干行;"
"original: 绘制某一行的原始分布;"
"animate: 动态逐行绘制。"
),
)
parser.add_argument(
"--n",
type=int,
default=100,
help="用于 triangle、original 或 animate 模式的最大阶数,默认 100。",
)
parser.add_argument(
"--rows",
type=parse_rows,
default=[2, 5, 10, 30, 100],
help="selected 模式中的行号,例如:--rows 2,5,10,30,100",
)
parser.add_argument(
"--interval",
type=int,
default=100,
help="动画每帧间隔,单位毫秒,默认 100。",
)
parser.add_argument(
"--save",
type=str,
default=None,
help="动画保存路径,例如:--save pascal_to_gaussian.gif",
)
args = parser.parse_args()
if args.n < 0:
parser.error("--n 不能为负数。")
if args.interval <= 0:
parser.error("--interval 必须大于 0。")
if args.mode == "triangle":
print_pascal_rows(args.n)
elif args.mode == "selected":
plot_selected_rows(args.rows)
elif args.mode == "original":
if args.n < 1:
parser.error("original 模式要求 --n 至少为 1。")
plot_original_distribution(args.n)
elif args.mode == "animate":
if args.n < 1:
parser.error("animate 模式要求 --n 至少为 1。")
animate_convergence(
max_n=args.n,
interval_ms=args.interval,
save_path=args.save,
)
if __name__ == "__main__":
main()
执行指令如下:
shell
python .\pascal_gaussian.py --mode animate --n 100 --save pascal_to_gaussian.gif
生成的动态图如下所示:

需要注意:并不是帕斯卡三角形中的原始系数直接收敛到正态分布,而是归一化后的系数:
P ( X = k ) = ( n k ) 2 n P(X=k) = \frac{\binom{n}{k}}{2^n} P(X=k)=2n(kn)
构成参数为 n n n 和 p = 1 2 p=\frac{1}{2} p=21 的二项分布。进一步令
z = k − n 2 n 4 , z = \frac{k-\frac{n}{2}}{\sqrt{\frac{n}{4}}}, z=4n k−2n,
标准化后的分布会随着 n n n 增大趋近标准正态分布 N ( 0 , 1 ) N(0,1) N(0,1) 。
还记得《价格猜猜猜》节目里的 Plinko 游戏吗?把球从顶部落下,经过格栅弹跳后最终掉进不同的收集槽里,这种装置有时被称为高尔顿板,德语中称作 Galtonbrett,若将大量珠子撒在布满钉子的格板上,经过弹跳后,它们会逐渐下落并最终形成高斯分布。
帕斯卡三角形正是快速计算多项式展开系数的工具,归根结底,这些系数就是无处不在的二项式系数 --- 组合数 C n k C_n^k Cnk ,我们在概率计算中经常用到它们。
所以如果我想计算从一副 52 张牌中能抽出多少种五张扑克组合,直接用组合数 C 52 5 C_{52}^{5} C525 就行了,对吧?计算起来非常简单。
而多项式分布则是将其推广到更高维分布的方法,适用于更复杂的纸牌游戏场景,假设现在我要发两副扑克牌,我和朋友一起玩,现在有两副五张牌的手牌,这种情况有多少种可能?最终得到的结果就是多项式分布,其计算过程也相当简便。
3. 定义多项分布
下面我们来推导下,那么有 n n n 个物体,划分为 r r r 个类别,其规模分别为 n 1 , n 2 , ... , n r n_1,n_2,\ldots,n_r n1,n2,...,nr ,比如我处理两副牌时,可能会各发五张牌,类似这样的情况,而且顺序不重要。将 n n n 个对象划分为 r r r 个类别,且不考虑顺序,那么样本总数就是下面这种组合数:
( n n 1 n 2 ... n r ) \binom{n}{n_1n_2 \ldots n_r} (n1n2...nrn)
接下来我将说明这个符号的定义, n 1 n 2 ... n r n_1n_2 \ldots n_r n1n2...nr 并非乘积关系,不是 n 1 × n 2 × ... × n r n_1 \times n_2 \times \ldots \times n_r n1×n2×...×nr 的形式,这并非从 n n n 个元素中选取这个大数值的组合,这是一种新的计数记号,明白吗?
其定义方式如下:
( n n 1 n 2 ... n r ) = n ! n 1 ! n 2 ! ⋯ n r ! \binom{n}{n_1n_2 \ldots n_r} = \frac{n!}{n_1 \ ! \ n_2! \ \cdots \ n_r!} (n1n2...nrn)=n1 ! n2! ⋯ nr!n!
还有一点需要补充说明,这里有个前提条件:这些 n i n_i ni 的总和必须等于 n n n ,因此,整副 52 张牌会被分成 r r r 手牌,想象一下你在玩黑桃牌戏:整副牌分给四位玩家后,会形成四手牌,所有手牌相加正好是 52 张,也就是说,这里假设 n 1 + n 2 + ⋯ + n r = n n_1 + n_2 + \cdots + n_r = n n1+n2+⋯+nr=n ,对吧?
这与计算从 52 张牌中发出 5 张手牌的组合数略有不同,但原理相通,同样的原理也可用于计算从 52 张牌中抽取 5 张手牌的组合数,这里假设将整个集合 n n n 划分为这 r r r 个组别,其计算公式如上所示。
4. 公式推导
通过基本原理推导这个公式的过程其实很有帮助,因为这能让你更熟练地掌握这类计算,所以这个公式并非凭空而来,完全可以通过列举所有可能性地方式来推导出这个公式,接下来我们来看看这个公式的推导过程。
这个概率的推导依据是:可能发生的事件总数等于从 n n n 个元素中选取 n 1 , n 2 , ... , n r n_1,n_2,\ldots, n_r n1,n2,...,nr 个元素的组合数。首先分配 n 1 n_1 n1 张牌,有多少种方法可以做到这一点?这就是我们前面提到的 ( n n 1 ) \binom{n}{n_1} (n1n) ,现在我要分配第二组,这就是 ( n − n 1 n 2 ) \binom{n-n_1}{n_2} (n2n−n1) ,现在剩下 n − n 1 − n 2 n-n_1-n_2 n−n1−n2 张牌,我要用这些来分配 n 3 n_3 n3 ,所以是 ( n − n 1 − n 2 n 3 ) \binom{n-n_1-n_2}{n_3} (n3n−n1−n2) ,依次类推,直到最后一项,即:
( n − n 1 − n 2 − ⋯ − n r n r ) \binom{n-n_1-n_2-\cdots -n_r}{n_r} (nrn−n1−n2−⋯−nr)
其中每一项的简答排列都可以计算出来,这些本质上都是二项系数:从总数 n n n 种抽取 n 1 n_1 n1 张牌的组合数,以此类推,我们可以列出所有这些组合数的计算公式,然后将它们相乘,你会发现这些项会以非常巧妙地方式相互抵消,最终得到下面这个简洁的公式:
( n n 1 n 2 ... n r ) = n ! n 1 ! n 2 ! ⋯ n r ! \binom{n}{n_1n_2 \ldots n_r} = \frac{n!}{n_1 \ ! \ n_2! \ \cdots \ n_r!} (n1n2...nrn)=n1 ! n2! ⋯ nr!n!
5. 结语
OK,让我们从整体视角来看,这就是所谓的多项系数,利用这个公式可以计算多种概率问题,在工厂生产零件时,若将合格品与不合格品分为两类,分别记为 n 1 n_1 n1 和 n 2 n_2 n2 ,这种分类情况就符合多项式分布。
通过这个多项式分布,可以计算在采样中出现特定数量不合格品的概率,用这个方法可以计算特定扑克牌型的概率,比如同花顺、四条等组合出现的可能性,能计算的情况非常丰富。
假设总人数为偶数,设为 2 n 2n 2n 人,有多少种方法可以将这些人两两配对?这些人能配成多少对组合?这是道课后习题,你需要针对不同的 n n n 值进行计算。
那如果是委员会决策呢?你正在组建一个分委员会,现有七位成员,需组建三个分委员会:两人组、三人组和两人组,从七人中选出这些小组委员会共有多少种方式?
这类问题可以通过多项式分布来计算,它是二项式分布的推广形式,这些二项式系数实在精妙绝伦,它是多项式展开的系数,源自帕斯卡三角形,当 n n n 足够大时,最终会收敛于正态分布。
OK,以上就是本讲的全部内容。
我们将在后续课程中演示如何从二项分布推导出正态分布,但我想让大家先对这些概念有个直观认识,因为借助基础的 ( n r ) \binom{n}{r} (rn) 组合数及其多项式推广,我们就能计算各种有趣事件的概率 --- 比如纸牌游戏中的各类情形。
结语
本讲从二项式系数 ( n r ) \binom{n}{r} (rn) 出发,串联起帕斯卡三角形、多项式展开与高尔顿板,直观展示了归一化后的二项式系数随 n n n 增大逐渐收敛于高斯分布的过程 --- 这为中心极限定理埋下了伏笔。在此基础上,我们将二项式系数推广为多项分布:
( n n 1 n 2 ... n r ) = n ! n 1 ! n 2 ! ⋯ n r ! \binom{n}{n_1 n_2 \ldots n_r} = \frac{n!}{n_1! \ n_2! \ \cdots \ n_r!} (n1n2...nrn)=n1! n2! ⋯ nr!n!
这一公式是解决 "将 n n n 个对象划分为 r r r 个无序组别" 的通用计数工具,可广泛应用于扑克牌型、委员会分组、质量抽检等场景。掌握从二项到多项的推广逻辑,就掌握了一套处理复杂组合计数问题的核心方法论 🤗。